diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f85f5cc650b82..73dd695a2e492 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -67,6 +67,19 @@ "problemMatcher": "$msCompile", "group": "build" }, + { + "label": "build Roslyn.sln", + "command": "dotnet", + "type": "shell", + "args": [ + "build", + "-p:RunAnalyzersDuringBuild=false", + "-p:GenerateFullPaths=true", + "Roslyn.sln" + ], + "problemMatcher": "$msCompile", + "group": "build" + }, { "label": "build current project", "type": "shell", @@ -123,6 +136,30 @@ }, "problemMatcher": "$msCompile", "group": "build" + }, + { + "label": "build language server", + "command": "dotnet", + "type": "shell", + "args": [ + "build", + "-c", + "Debug", + "src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj" + ], + "problemMatcher": "$msCompile", + "group": "build" + }, + { + "label": "launch vscode with language server", + "command": "${execPath}", + "type": "process", + "options": { + "env": { + "DOTNET_ROSLYN_SERVER_PATH": "${workspaceRoot}/artifacts/bin/Microsoft.CodeAnalysis.LanguageServer/Debug/net8.0/Microsoft.CodeAnalysis.LanguageServer.dll" + } + }, + "dependsOn": [ "build language server" ] } ] } diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml index 5f681633869f5..731a818a6f6c7 100644 --- a/azure-pipelines-official.yml +++ b/azure-pipelines-official.yml @@ -58,6 +58,7 @@ variables: - name: ArtifactServices.Drop.PAT value: $(dn-bot-devdiv-drop-rw-code-rw) - group: DotNet-Roslyn-Insertion-Variables + - group: AzureDevOps-Artifact-Feeds-Pats - name: BuildConfiguration value: release @@ -149,6 +150,7 @@ extends: dropFolder: 'artifacts\VSSetup\$(BuildConfiguration)\Insertion' dropName: $(VisualStudio.DropName) accessToken: $(_DevDivDropAccessToken) + dropRetentionDays: 90 # Publish insertion packages to CoreXT store. - output: nuget diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index 6a36d6df37b25..43ae84c0cc63f 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -10,17 +10,18 @@ efforts behind them. | Feature | Branch | State | Developer | Reviewer | IDE Buddy | LDM Champ | | ------- | ------ | ----- | --------- | -------- | --------- | --------- | -| [Partial properties](https://github.com/dotnet/csharplang/issues/6420) | [partial-properties](https://github.com/dotnet/roslyn/tree/features/partial-properties) | [In Progress](https://github.com/dotnet/roslyn/issues/73090) | [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | TBD | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | -| Ref/unsafe in iterators/async | [RefInAsync](https://github.com/dotnet/roslyn/tree/features/RefInAsync) | [In Progress](https://github.com/dotnet/roslyn/issues/72662) | [jjonescz](https://github.com/jjonescz) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | [ToddGrun](https://github.com/ToddGrun) | | +| [First-class Span Types](https://github.com/dotnet/csharplang/issues/7905) | [FirstClassSpan](https://github.com/dotnet/roslyn/tree/features/FirstClassSpan) | [In Progress](https://github.com/dotnet/roslyn/issues/73445) | [jjonescz](https://github.com/jjonescz) | [cston](https://github.com/cston), [333fred](https://github.com/333fred) | | [333fred](https://github.com/333fred), [stephentoub](https://github.com/stephentoub) | +| [Partial properties](https://github.com/dotnet/csharplang/issues/6420) | [partial-properties](https://github.com/dotnet/roslyn/tree/features/partial-properties) | [In Progress](https://github.com/dotnet/roslyn/issues/73090) | [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | [Cosifne](https://github.com/Cosifne) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | +| Ref/unsafe in iterators/async | [RefInAsync](https://github.com/dotnet/roslyn/tree/features/RefInAsync) | [In Progress](https://github.com/dotnet/roslyn/issues/72662) | [jjonescz](https://github.com/jjonescz) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | (no IDE impact) | | | [Ref Struct Interfaces](https://github.com/dotnet/csharplang/issues/7608) | [RefStructInterfaces](https://github.com/dotnet/roslyn/tree/features/RefStructInterfaces) | [In Progress](https://github.com/dotnet/roslyn/issues/72124) | [AlekseyTs](https://github.com/AlekseyTs) | [cston](https://github.com/cston), [jjonescz](https://github.com/jjonescz) | [ToddGrun](https://github.com/ToddGrun) | [agocke](https://github.com/agocke), [jaredpar](https://github.com/jaredpar) | -| [Semi-auto-properties](https://github.com/dotnet/csharplang/issues/140) | [semi-auto-props](https://github.com/dotnet/roslyn/tree/features/semi-auto-props) | [In Progress](https://github.com/dotnet/roslyn/issues/57012) | [Youssef1313](https://github.com/Youssef1313) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | +| [Semi-auto-properties](https://github.com/dotnet/csharplang/issues/140) | [semi-auto-props](https://github.com/dotnet/roslyn/tree/features/semi-auto-props) | [In Progress](https://github.com/dotnet/roslyn/issues/57012) | [Youssef1313](https://github.com/Youssef1313) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | | [Default in deconstruction](https://github.com/dotnet/roslyn/pull/25562) | [decon-default](https://github.com/dotnet/roslyn/tree/features/decon-default) | [In Progress](https://github.com/dotnet/roslyn/issues/25559) | [jcouv](https://github.com/jcouv) | [gafter](https://github.com/gafter) | | [jcouv](https://github.com/jcouv) | | [Roles/Extensions](https://github.com/dotnet/csharplang/issues/5497) | [roles](https://github.com/dotnet/roslyn/tree/features/roles) | [In Progress](https://github.com/dotnet/roslyn/issues/66722) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [jjonescz](https://github.com/jjonescz) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [MadsTorgersen](https://github.com/MadsTorgersen) | -| [Escape character](https://github.com/dotnet/csharplang/issues/7400) | N/A | [Merged into 17.9p1](https://github.com/dotnet/roslyn/pull/70497) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jcouv](https://github.com/jcouv), [RikkiGibson](https://github.com/RikkiGibson) | | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | -| [Method group natural type improvements](https://github.com/dotnet/csharplang/blob/main/proposals/method-group-natural-type-improvements.md) | main | [Merged into 17.9p2](https://github.com/dotnet/roslyn/issues/69432) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | | [jcouv](https://github.com/jcouv) | -| [`Lock` object](https://github.com/dotnet/csharplang/issues/7104) | [LockObject](https://github.com/dotnet/roslyn/tree/features/LockObject) | [Merged into 17.10p2](https://github.com/dotnet/roslyn/issues/71888) | [jjonescz](https://github.com/jjonescz) | [cston](https://github.com/cston), [RikkiGibson](https://github.com/RikkiGibson) | | [stephentoub](https://github.com/stephentoub) | -| Implicit indexer access in object initializers | main | [Merged into 17.9p3](https://github.com/dotnet/roslyn/pull/70649) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | | | -| [Params-collections](https://github.com/dotnet/csharplang/issues/7700) | main | [Merged to 17.10p3](https://github.com/dotnet/roslyn/issues/71137) | [AlekseyTs](https://github.com/AlekseyTs) | [RikkiGibson](https://github.com/RikkiGibson), [333fred](https://github.com/333fred) | [akhera99](https://github.com/akhera99) | [MadsTorgersen](https://github.com/MadsTorgersen), [AlekseyTs](https://github.com/AlekseyTs) | +| [Escape character](https://github.com/dotnet/csharplang/issues/7400) | N/A | [Merged into 17.9p1](https://github.com/dotnet/roslyn/pull/70497) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jcouv](https://github.com/jcouv), [RikkiGibson](https://github.com/RikkiGibson) | (no IDE impact) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | +| [Method group natural type improvements](https://github.com/dotnet/csharplang/blob/main/proposals/method-group-natural-type-improvements.md) | main | [Merged into 17.9p2](https://github.com/dotnet/roslyn/issues/69432) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | (no IDE impact) | [jcouv](https://github.com/jcouv) | +| [`Lock` object](https://github.com/dotnet/csharplang/issues/7104) | [LockObject](https://github.com/dotnet/roslyn/tree/features/LockObject) | [Merged into 17.10p2](https://github.com/dotnet/roslyn/issues/71888) | [jjonescz](https://github.com/jjonescz) | [cston](https://github.com/cston), [RikkiGibson](https://github.com/RikkiGibson) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) (needs IDE fixer) | [stephentoub](https://github.com/stephentoub) | +| Implicit indexer access in object initializers | main | [Merged into 17.9p3](https://github.com/dotnet/roslyn/pull/70649) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | (no IDE impact) | | +| [Params-collections](https://github.com/dotnet/csharplang/issues/7700) | main | [Merged to 17.10p3](https://github.com/dotnet/roslyn/issues/71137) | [AlekseyTs](https://github.com/AlekseyTs) | [RikkiGibson](https://github.com/RikkiGibson), [333fred](https://github.com/333fred) | [akhera99](https://github.com/akhera99) (needs IDE fixer) | [MadsTorgersen](https://github.com/MadsTorgersen), [AlekseyTs](https://github.com/AlekseyTs) | # C# 12.0 diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md index 76b8741267d6e..351e636ac80db 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md @@ -28,6 +28,34 @@ public class C } ``` +## Collection expression for type implementing `IEnumerable` must have elements implicitly convertible to `object` + +***Introduced in Visual Studio 2022 version 17.10*** + +*Conversion* of a collection expression to a `struct` or `class` that implements `System.Collections.IEnumerable` and *does not* have a strongly-typed `GetEnumerator()` +requires the elements in the collection expression are implicitly convertible to the `object`. +Previously, the elements of a collection expression targeting an `IEnumerable` implementation were assumed to be convertible to `object`, and converted only when binding to the applicable `Add` method. + +This additional requirement means that collection expression conversions to `IEnumerable` implementations are treated consistently with other target types where the elements in the collection expression must be implicitly convertible to the *iteration type* of the target type. + +This change affects collection expressions targeting `IEnumerable` implementations where the elements rely on target-typing to a strongly-typed `Add` method parameter type. +In the example below, an error is reported that `_ => { }` cannot be implicitly converted to `object`. +```csharp +class Actions : IEnumerable +{ + public void Add(Action action); + // ... +} + +Actions a = [_ => { }]; // error CS8917: The delegate type could not be inferred. +``` + +To resolve the error, the element expression could be explicitly typed. +```csharp +a = [(int _) => { }]; // ok +a = [(Action)(_ => { })]; // ok +``` + ## Collection expression target type must have constructor and `Add` method ***Introduced in Visual Studio 2022 version 17.10*** diff --git a/docs/contributing/Building, Debugging, and Testing on Unix.md b/docs/contributing/Building, Debugging, and Testing on Unix.md index d71853057f74e..0690730630dd6 100644 --- a/docs/contributing/Building, Debugging, and Testing on Unix.md +++ b/docs/contributing/Building, Debugging, and Testing on Unix.md @@ -15,6 +15,7 @@ Particularly for developers who aren't experienced with .NET Core development on 1. Install the [.NET 8.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) which matches the `sdk.version` property in [global.json](../../global.json#L3) 3. You can build from VS Code by running the *Run Build Task* command, then selecting an appropriate task such as *build* or *build current project* (the latter builds the containing project for the current file you're viewing in the editor). 4. You can run tests from VS Code by opening a test class in the editor, then using the *Run Tests in Context* and *Debug Tests in Context* editor commands. You may want to bind these commands to keyboard shortcuts that match their Visual Studio equivalents (**Ctrl+R, T** for *Run Tests in Context* and **Ctrl+R, Ctrl+T** for *Debug Tests in Context*). +5. You can launch a new VS Code instance with the language server from your current code by running the "launch vscode with language server" task. ## Running Tests The unit tests can be executed by running `./build.sh --test`. diff --git a/docs/contributing/Building, Debugging, and Testing on Windows.md b/docs/contributing/Building, Debugging, and Testing on Windows.md index 43408387bddba..0482960a9d56c 100644 --- a/docs/contributing/Building, Debugging, and Testing on Windows.md +++ b/docs/contributing/Building, Debugging, and Testing on Windows.md @@ -86,7 +86,7 @@ give it a try. ### Deploying with command-line (recommended method) You can build and deploy with the following command: -`.\Build.cmd -Configuration Release -deployExtensions -launch`. +`.\Build.cmd -Restore -Configuration Release -deployExtensions -launch`. Then you can launch the `RoslynDev` hive with `devenv /rootSuffix RoslynDev`. diff --git a/docs/wiki/EnC-Supported-Edits.md b/docs/wiki/EnC-Supported-Edits.md index c05d40b1121ce..39e7f453d42c2 100644 --- a/docs/wiki/EnC-Supported-Edits.md +++ b/docs/wiki/EnC-Supported-Edits.md @@ -50,3 +50,24 @@ This document captures the current state. Potential future improvements in this | Edit a member referencing an embedded interop type | - | | Edit a member with On Error or Resume statements | Specific to Visual Basic | | Edit a member containing an Aggregate, Group By, Simple Join, or Group Join LINQ query clause | Specific to Visual Basic | +| Edit in a solution containing projects that specify `*` in `AssemblyVersionAttribute`, e.g. `[assembly: AssemblyVersion("1.0.*")`]. | See [workaround](#projects-with-variable-assembly-versions) below. | + +### Projects with variable assembly versions + +Hot Reload and Edit & Continue are not compatible with using `*` in `AssemblyVersionAttribute` value. Presence of `*` in the version means that the compiler generates a new version +every build based on the current time. The build then produces non-reproducible, non-deterministic outputs (see [Reproducible Builds](https://reproducible-builds.org)). + +It is thus highly recommended to use alternative versioning methods, such as [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning), that derive the assembly version +from the HEAD commit SHA (for git repositories). + +> To enable generating commit SHA based assembly versions using `Nerdbank.GitVersioning` package, specify `{ "assemblyVersion" : {"precision": "revision"} }` setting in `version.json`. + +If you prefer to keep using `*` in `AssemblyVersionAttribute` it is recommended to use conditional compilation directive to only apply such version to Release builds like so: + +```C# +#if DEBUG +[assembly: AssemblyVersion("1.0.0.0")] +#else +[assembly: AssemblyVersion("1.0.*")] +#endif +``` diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index d9970c400f1f0..39a0493e4ff24 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -5,6 +5,7 @@ 8.0.0-preview.23468.1 1.1.2-beta1.24121.1 0.1.187-beta + <_BasicReferenceAssembliesVersion>1.7.2 4.8.0-3.final 17.10.72-preview @@ -293,13 +294,14 @@ - - - - - - - + + + + + + + + - + https://github.com/dotnet/arcade - 188340e12c0a372b1681ad6a5e72c608021efdba + 67d23f4ba1813b315e7e33c71d18b63475f5c5f8 @@ -144,9 +144,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - 188340e12c0a372b1681ad6a5e72c608021efdba + 67d23f4ba1813b315e7e33c71d18b63475f5c5f8 https://github.com/dotnet/roslyn-analyzers diff --git a/eng/Versions.props b/eng/Versions.props index 78eb11bdac83d..46a3bd50e84f1 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -8,7 +8,7 @@ 4 11 0 - 1 + 2 $(MajorVersion).$(MinorVersion).$(PatchVersion) ' ; +xml_c_data_section + : '' + ; + xml_element : xml_element_start_tag xml_node* xml_element_end_tag ; @@ -1360,84 +1384,388 @@ skipped_tokens_trivia : syntax_token* ; -base_argument_list - : argument_list - | bracketed_argument_list +syntax_token + : character_literal_token + | identifier_token + | keyword + | numeric_literal_token + | operator_token + | punctuation_token + | string_literal_token ; -base_cref_parameter_list - : cref_bracketed_parameter_list - | cref_parameter_list +identifier_token + : '@'? identifier_start_character identifier_part_character ; -base_parameter_list - : bracketed_parameter_list - | parameter_list +identifier_start_character + : letter_character + | underscore_character ; -base_parameter - : function_pointer_parameter - | parameter +letter_character + : /* [\p{L}\p{Nl}] category letter, all subcategories; category number, subcategory letter */ + | unicode_escape_sequence /* only escapes for categories L & Nl allowed */ ; -character_literal_token - : /* see lexical specification */ +underscore_character + : '\\u005' /* unicode_escape_sequence for underscore */ + | '_' ; -expression_or_pattern - : expression - | pattern +identifier_part_character + : combining_character + | connecting_character + | decimal_digit_character + | formatting_character + | letter_character ; -identifier_token - : /* see lexical specification */ +combining_character + : /* [\p{Mn}\p{Mc}] category Mark, subcategories non-spacing and spacing combining */ + | unicode_escape_sequence /* only escapes for categories Mn & Mc allowed */ ; -interpolated_multi_line_raw_string_start_token - : /* see lexical specification */ +connecting_character + : /* [\p{Pc}] category Punctuation, subcategory connector */ + | unicode_escape_sequence /* only escapes for category Pc allowed */ ; -interpolated_raw_string_end_token - : /* see lexical specification */ +decimal_digit_character + : /* [\p{Nd}] category number, subcategory decimal digit */ + | unicode_escape_sequence /* only escapes for category Nd allowed */ ; -interpolated_single_line_raw_string_start_token - : /* see lexical specification */ +formatting_character + : /* [\p{Cf}] category Other, subcategory format. */ + | unicode_escape_sequence /* only escapes for category Cf allowed */ ; -interpolated_string_text_token - : /* see lexical specification */ +keyword + : 'as' + | 'base' + | 'bool' + | 'break' + | 'byte' + | 'case' + | 'catch' + | 'char' + | 'checked' + | 'class' + | 'continue' + | 'decimal' + | 'default' + | 'delegate' + | 'do' + | 'double' + | 'else' + | 'enum' + | 'event' + | 'explicit' + | 'false' + | 'finally' + | 'float' + | 'for' + | 'foreach' + | 'goto' + | 'if' + | 'implicit' + | 'in' + | 'int' + | 'interface' + | 'is' + | 'lock' + | 'long' + | 'namespace' + | 'null' + | 'object' + | 'operator' + | 'out' + | 'params' + | 'return' + | 'sbyte' + | 'short' + | 'sizeof' + | 'stackalloc' + | 'string' + | 'struct' + | 'switch' + | 'this' + | 'throw' + | 'true' + | 'try' + | 'typeof' + | 'uint' + | 'ulong' + | 'unchecked' + | 'ushort' + | 'using' + | 'void' + | 'while' + | '__arglist' + | '__makeref' + | '__reftype' + | '__refvalue' + | modifier ; -multi_line_raw_string_literal_token - : /* see lexical specification */ +numeric_literal_token + : integer_literal_token + | real_literal_token ; -numeric_literal_token - : /* see lexical specification */ +integer_literal_token + : decimal_integer_literal_token + | hexadecimal_integer_literal_token ; -single_line_raw_string_literal_token - : /* see lexical specification */ +decimal_integer_literal_token + : decimal_digit+ integer_type_suffix? + ; + +decimal_digit + : '0' + | '1' + | '2' + | '3' + | '4' + | '5' + | '6' + | '7' + | '8' + | '9' + ; + +integer_type_suffix + : 'L' + | 'l' + | 'LU' + | 'lU' + | 'Lu' + | 'lu' + | 'U' + | 'u' + | 'UL' + | 'uL' + | 'Ul' + | 'ul' + ; + +hexadecimal_integer_literal_token + : ('0x' | '0X') hexadecimal_digit+ integer_type_suffix? + ; + +hexadecimal_digit + : 'A' + | 'a' + | 'B' + | 'b' + | 'C' + | 'c' + | 'D' + | 'd' + | 'E' + | 'e' + | 'F' + | 'f' + | decimal_digit + ; + +real_literal_token + : '.' decimal_digit+ exponent_part? real_type_suffix? + | decimal_digit+ '.' decimal_digit+ exponent_part? real_type_suffix? + | decimal_digit+ exponent_part real_type_suffix? + | decimal_digit+ real_type_suffix + ; + +exponent_part + : ('E' | 'e') ('+' | '-')? decimal_digit+ + ; + +real_type_suffix + : 'D' + | 'd' + | 'F' + | 'f' + | 'M' + | 'm' + ; + +character_literal_token + : '\'' character '\'' + ; + +character + : hexadecimal_escape_sequence + | simple_escape_sequence + | single_character + | unicode_escape_sequence + ; + +hexadecimal_escape_sequence + : '\\x' hexadecimal_digit hexadecimal_digit? hexadecimal_digit? hexadecimal_digit? + ; + +simple_escape_sequence + : '\\"' + | '\\0' + | '\\a' + | '\\b' + | '\\f' + | '\\n' + | '\\r' + | '\\t' + | '\\v' + | '\\\'' + | '\\\\' + ; + +single_character + : /* ~['\\\u000D\u000A\u0085\u2028\u2029] anything but ', \\, and new_line_character */ + ; + +unicode_escape_sequence + : '\\u' hexadecimal_digit hexadecimal_digit hexadecimal_digit hexadecimal_digit + | '\\U' hexadecimal_digit hexadecimal_digit hexadecimal_digit hexadecimal_digit hexadecimal_digit hexadecimal_digit hexadecimal_digit hexadecimal_digit ; string_literal_token - : /* see lexical specification */ + : regular_string_literal_token + | verbatim_string_literal_token + ; + +regular_string_literal_token + : '"' regular_string_literal_character* '"' + ; + +regular_string_literal_character + : hexadecimal_escape_sequence + | simple_escape_sequence + | single_regular_string_literal_character + | unicode_escape_sequence + ; + +single_regular_string_literal_character + : /* ~["\\\u000D\u000A\u0085\u2028\u2029] anything but ", \, and new_line_character */ + ; + +verbatim_string_literal_token + : '@"' verbatim_string_literal_character* '"' + ; + +verbatim_string_literal_character + : quote_escape_sequence + | single_verbatim_string_literal_character + ; + +quote_escape_sequence + : '""' + ; + +single_verbatim_string_literal_character + : /* anything but quotation mark (U+0022) */ + ; + +operator_token + : '!' + | '!=' + | '%' + | '%=' + | '&&' + | '&' + | '&=' + | '*' + | '*=' + | '+' + | '++' + | '+=' + | '-' + | '--' + | '-=' + | '/' + | '/=' + | '<' + | '<<' + | '<<=' + | '<=' + | '=' + | '==' + | '>' + | '>=' + | '>>' + | '>>=' + | '>>>' + | '>>>=' + | '??' + | '??=' + | 'as' + | 'is' + | '^' + | '^=' + | '|' + | '|=' + | '||' + | '~' + ; + +punctuation_token + : '"' + | '#' + | '(' + | ')' + | ',' + | '->' + | '.' + | '..' + | '/>' + | ':' + | '::' + | ';' + | '' + | '?' + | '[' + | '\'' + | '\\' + | ']' + | '{' + | '}' ; -syntax_token - : /* see lexical specification */ +base_argument_list + : argument_list + | bracketed_argument_list + ; + +base_cref_parameter_list + : cref_bracketed_parameter_list + | cref_parameter_list + ; + +base_parameter_list + : bracketed_parameter_list + | parameter_list + ; + +base_parameter + : function_pointer_parameter + | parameter ; -utf_8_multi_line_raw_string_literal_token +expression_or_pattern + : expression + | pattern + ; + +interpolated_string_text_token : /* see lexical specification */ ; -utf_8_single_line_raw_string_literal_token +multi_line_raw_string_literal_token : /* see lexical specification */ ; -utf_8_string_literal_token +single_line_raw_string_literal_token : /* see lexical specification */ ; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs index 9fcab11c2c2f6..75c23b3592248 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs @@ -81,49 +81,7 @@ private BoundExpression VisitAssignmentOperator(BoundAssignmentOperator node, bo break; } - var result = MakeStaticAssignmentOperator(node.Syntax, loweredLeft, loweredRight, node.IsRef, used); - - result = ConvertResultOfAssignmentToDynamicIfNecessary(node, left, result, used); - - Debug.Assert(used || result.Type?.IsVoidType() == true || - (left switch { BoundIndexerAccess indexer => indexer.Indexer, BoundPropertyAccess property => property.PropertySymbol, _ => null }) is not PropertySymbol prop || - prop.GetOwnOrInheritedSetMethod() is null); - - Debug.Assert(result.Type?.IsVoidType() == true || TypeSymbol.Equals(result.Type, node.Type, TypeCompareKind.AllIgnoreOptions)); - - return result; - } - - private static bool ShouldConvertResultOfAssignmentToDynamic(TypeSymbol? assignmentResultType, BoundExpression target) - { - if (assignmentResultType?.IsDynamic() == true && target is BoundIndexerAccess { Type.TypeKind: not TypeKind.Dynamic } indexerAccess) - { - Debug.Assert(!indexerAccess.Indexer.Type.IsDynamic()); - Debug.Assert(!indexerAccess.Indexer.ReturnsByRef); - - return true; - } - - return false; - } - - internal static bool ShouldConvertResultOfAssignmentToDynamic(BoundExpression assignment, BoundExpression target) - { - Debug.Assert(assignment is BoundAssignmentOperator or BoundIncrementOperator or BoundCompoundAssignmentOperator or BoundNullCoalescingAssignmentOperator); - return ShouldConvertResultOfAssignmentToDynamic(assignment.Type, target); - } - - private BoundExpression ConvertResultOfAssignmentToDynamicIfNecessary(BoundExpression originalAssignment, BoundExpression originalTarget, BoundExpression result, bool used) - { - Debug.Assert(originalAssignment.Type is not null); - if (used && ShouldConvertResultOfAssignmentToDynamic(originalAssignment, originalTarget)) - { - Debug.Assert(result.Type is not null); - Debug.Assert(!result.Type.IsDynamic()); - result = _factory.Convert(originalAssignment.Type, result); - } - - return result; + return MakeStaticAssignmentOperator(node.Syntax, loweredLeft, loweredRight, node.IsRef, used); } /// diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs index a63910f4e0572..bdddfdbab14ef 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs @@ -415,16 +415,6 @@ BoundExpression visitArgumentsAndFinishRewrite(BoundCall node, BoundExpression? rewrittenCall = Instrumenter.InstrumentCall(node, rewrittenCall); } - if (node.Type.IsDynamic() && !method.ReturnType.IsDynamic()) - { - Debug.Assert(node.Type.IsDynamic()); - Debug.Assert(!method.ReturnsByRef); - Debug.Assert(rewrittenCall.Type is not null); - Debug.Assert(!rewrittenCall.Type.IsDynamic()); - Debug.Assert(!rewrittenCall.Type.IsVoidType()); - rewrittenCall = _factory.Convert(node.Type, rewrittenCall); - } - return rewrittenCall; } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index ef6e145345c23..6cbecefe3f188 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -195,8 +195,7 @@ private BoundExpression VisitArrayOrSpanCollectionExpression(BoundCollectionExpr syntax, elementType, elements, - asReadOnlySpan: collectionTypeKind == CollectionExpressionTypeKind.ReadOnlySpan, - _additionalLocals); + asReadOnlySpan: collectionTypeKind == CollectionExpressionTypeKind.ReadOnlySpan); } Debug.Assert(IsAllocatingRefStructCollectionExpression(node, collectionTypeKind, elementType.Type, _compilation)); @@ -243,7 +242,7 @@ private BoundExpression VisitCollectionInitializerCollectionExpression(BoundColl // Create a temp for the collection. BoundAssignmentOperator assignmentToTemp; - BoundLocal temp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, isKnownToReferToTempIfReferenceType: true); + BoundLocal temp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp); var sideEffects = ArrayBuilder.GetInstance(elements.Length + 1); sideEffects.Add(assignmentToTemp); @@ -429,16 +428,33 @@ private BoundExpression CreateAndPopulateSpanFromInlineArray( SyntaxNode syntax, TypeWithAnnotations elementType, ImmutableArray elements, - bool asReadOnlySpan, - ArrayBuilder locals) + bool asReadOnlySpan) { Debug.Assert(elements.Length > 0); Debug.Assert(elements.All(e => e is BoundExpression)); Debug.Assert(_factory.ModuleBuilderOpt is { }); Debug.Assert(_diagnostics.DiagnosticBag is { }); Debug.Assert(_compilation.Assembly.RuntimeSupportsInlineArrayTypes); + Debug.Assert(_additionalLocals is { }); int arrayLength = elements.Length; + if (arrayLength == 1 + && _factory.WellKnownMember(asReadOnlySpan + ? WellKnownMember.System_ReadOnlySpan_T__ctor_ref_readonly_T + : WellKnownMember.System_Span_T__ctor_ref_T, isOptional: true) is MethodSymbol spanRefConstructor) + { + // Special case: no need to create an InlineArray1 type. Just use a temp of the element type. + var spanType = _factory + .WellKnownType(asReadOnlySpan ? WellKnownType.System_ReadOnlySpan_T : WellKnownType.System_Span_T) + .Construct([elementType]); + var constructor = spanRefConstructor.AsMember(spanType); + var element = VisitExpression((BoundExpression)elements[0]); + var temp = _factory.StoreToTemp(element, out var assignment); + _additionalLocals.Add(temp.LocalSymbol); + var call = _factory.New(constructor, arguments: [temp], argumentRefKinds: [asReadOnlySpan ? RefKindExtensions.StrictIn : RefKind.Ref]); + return _factory.Sequence([assignment], call); + } + var inlineArrayType = _factory.ModuleBuilderOpt.EnsureInlineArrayTypeExists(syntax, _factory, arrayLength, _diagnostics.DiagnosticBag).Construct(ImmutableArray.Create(elementType)); Debug.Assert(inlineArrayType.HasInlineArrayAttribute(out int inlineArrayLength) && inlineArrayLength == arrayLength); @@ -449,10 +465,10 @@ private BoundExpression CreateAndPopulateSpanFromInlineArray( // Create an inline array and assign to a local. // var tmp = new <>y__InlineArrayN(); BoundAssignmentOperator assignmentToTemp; - BoundLocal inlineArrayLocal = _factory.StoreToTemp(new BoundDefaultExpression(syntax, inlineArrayType), out assignmentToTemp, isKnownToReferToTempIfReferenceType: true); + BoundLocal inlineArrayLocal = _factory.StoreToTemp(new BoundDefaultExpression(syntax, inlineArrayType), out assignmentToTemp); var sideEffects = ArrayBuilder.GetInstance(); sideEffects.Add(assignmentToTemp); - locals.Add(inlineArrayLocal.LocalSymbol); + _additionalLocals.Add(inlineArrayLocal.LocalSymbol); // Populate the inline array. // InlineArrayElementRef<<>y__InlineArrayN, ElementType>(ref tmp, 0) = element0; @@ -604,8 +620,7 @@ bool tryGetToArrayMethod(TypeSymbol spreadTypeOriginalDefinition, WellKnownType // int index = 0; BoundLocal indexTemp = _factory.StoreToTemp( _factory.Literal(0), - out assignmentToTemp, - isKnownToReferToTempIfReferenceType: true); + out assignmentToTemp); localsBuilder.Add(indexTemp); sideEffects.Add(assignmentToTemp); @@ -615,8 +630,7 @@ bool tryGetToArrayMethod(TypeSymbol spreadTypeOriginalDefinition, WellKnownType ImmutableArray.Create(GetKnownLengthExpression(elements, numberIncludingLastSpread, localsBuilder)), initializerOpt: null, arrayType), - out assignmentToTemp, - isKnownToReferToTempIfReferenceType: true); + out assignmentToTemp); localsBuilder.Add(arrayTemp); sideEffects.Add(assignmentToTemp); @@ -903,7 +917,7 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty // If we use optimizations, we know the length of the resulting list, and we store it in a temp so we can pass it to List.ctor(int32) and to CollectionsMarshal.SetCount // int knownLengthTemp = N + s1.Length + ...; - knownLengthTemp = _factory.StoreToTemp(knownLengthExpression, out assignmentToTemp, isKnownToReferToTempIfReferenceType: true); + knownLengthTemp = _factory.StoreToTemp(knownLengthExpression, out assignmentToTemp); localsBuilder.Add(knownLengthTemp); sideEffects.Add(assignmentToTemp); @@ -924,7 +938,7 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty } // Create a temp for the list. - BoundLocal listTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, isKnownToReferToTempIfReferenceType: true); + BoundLocal listTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp); localsBuilder.Add(listTemp); sideEffects.Add(assignmentToTemp); @@ -940,7 +954,7 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty sideEffects.Add(_factory.Call(receiver: null, setCount, listTemp, knownLengthTemp)); // var span = CollectionsMarshal.AsSpan @@ -34,12 +34,13 @@ internal sealed partial class LocalRewriter /// - the conversion phase /// - the assignment phase /// - private BoundExpression? RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right, bool isUsed, NamedTypeSymbol assignmentResultTupleType) + private BoundExpression? RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right, bool isUsed) { var lhsTemps = ArrayBuilder.GetInstance(); var lhsEffects = ArrayBuilder.GetInstance(); ArrayBuilder lhsTargets = GetAssignmentTargetsAndSideEffects(left, lhsTemps, lhsEffects); - BoundExpression? result = RewriteDeconstruction(lhsTargets, conversion, right, assignmentResultTupleType, isUsed); + Debug.Assert(left.Type is { }); + BoundExpression? result = RewriteDeconstruction(lhsTargets, conversion, left.Type, right, isUsed); Binder.DeconstructionVariable.FreeDeconstructionVariables(lhsTargets); if (result is null) { @@ -54,8 +55,8 @@ internal sealed partial class LocalRewriter private BoundExpression? RewriteDeconstruction( ArrayBuilder lhsTargets, Conversion conversion, + TypeSymbol leftType, BoundExpression right, - NamedTypeSymbol assignmentResultTupleType, bool isUsed) { if (right.Kind == BoundKind.ConditionalOperator) @@ -65,17 +66,17 @@ internal sealed partial class LocalRewriter return conditional.Update( conditional.IsRef, VisitExpression(conditional.Condition), - RewriteDeconstruction(lhsTargets, conversion, conditional.Consequence, assignmentResultTupleType, isUsed: true)!, - RewriteDeconstruction(lhsTargets, conversion, conditional.Alternative, assignmentResultTupleType, isUsed: true)!, + RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.Consequence, isUsed: true)!, + RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.Alternative, isUsed: true)!, conditional.ConstantValueOpt, - assignmentResultTupleType, + leftType, wasTargetTyped: true, - assignmentResultTupleType); + leftType); } var temps = ArrayBuilder.GetInstance(); var effects = DeconstructionSideEffects.GetInstance(); - BoundExpression? returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, assignmentResultTupleType, isUsed, inInit: true); + BoundExpression? returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, isUsed, inInit: true); reverseAssignmentsToTargetsIfApplicable(); effects.Consolidate(); @@ -212,7 +213,6 @@ static bool canReorderTargetAssignments(ArrayBuilder temps, DeconstructionSideEffects effects, - NamedTypeSymbol assignmentResultTupleType, bool isUsed, bool inInit) { @@ -227,19 +227,14 @@ static bool canReorderTargetAssignments(ArrayBuilder argsToParamsOpt, BitVector defaultArguments, - BoundExpression originalIndexerAccessOrObjectInitializerMember, + BoundIndexerAccess? oldNodeOpt, bool isLeftOfAssignment) { - Debug.Assert(originalIndexerAccessOrObjectInitializerMember is BoundIndexerAccess or BoundObjectInitializerMember); - Debug.Assert(originalIndexerAccessOrObjectInitializerMember.Type is not null); - if (isLeftOfAssignment && indexer.RefKind == RefKind.None) { TypeSymbol type = indexer.Type; - Debug.Assert(originalIndexerAccessOrObjectInitializerMember.Type.Equals(type, TypeCompareKind.ConsiderEverything)); + Debug.Assert(oldNodeOpt?.Type.Equals(type, TypeCompareKind.ConsiderEverything) != false); // This is an indexer set access. We return a BoundIndexerAccess node here. // This node will be rewritten with MakePropertyAssignment when rewriting the enclosing BoundAssignmentOperator. - return originalIndexerAccessOrObjectInitializerMember is BoundIndexerAccess indexerAccess ? - indexerAccess.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, defaultArguments, type) : + return oldNodeOpt != null ? + oldNodeOpt.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, defaultArguments, type) : new BoundIndexerAccess(syntax, rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, defaultArguments, type); } else @@ -149,14 +146,6 @@ private BoundExpression MakeIndexerAccess( BoundExpression call = MakePropertyGetAccess(syntax, rewrittenReceiver, indexer, rewrittenArguments, argumentRefKindsOpt, getMethod); - if (originalIndexerAccessOrObjectInitializerMember.Type.IsDynamic() == true && !indexer.Type.IsDynamic()) - { - Debug.Assert(call.Type is not null); - Debug.Assert(!call.Type.IsDynamic()); - Debug.Assert(!getMethod.ReturnsByRef); - call = _factory.Convert(originalIndexerAccessOrObjectInitializerMember.Type, call); - } - Debug.Assert(call.Type is not null); if (temps.Count == 0) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs index 6b3b62aa0bc01..5fa4abcac8f85 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs @@ -25,12 +25,10 @@ public override BoundNode VisitNullCoalescingAssignmentOperator(BoundNullCoalesc var lhsRead = MakeRValue(transformedLHS); BoundExpression loweredRight = VisitExpression(node.RightOperand); - var result = node.IsNullableValueTypeAssignment ? + return node.IsNullableValueTypeAssignment ? rewriteNullCoalescingAssignmentForValueType() : rewriteNullCoalscingAssignmentStandard(); - return ConvertResultOfAssignmentToDynamicIfNecessary(node, node.LeftOperand, result, used: true); - BoundExpression rewriteNullCoalscingAssignmentStandard() { // Now that LHS is transformed with temporaries, we rewrite this node into a coalesce expression: diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs index fba0eb42d3ad4..bd8b44b7d3132 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs @@ -689,7 +689,7 @@ private BoundExpression MakeObjectInitializerMemberAccess( rewrittenLeft.Expanded, rewrittenLeft.ArgsToParamsOpt, rewrittenLeft.DefaultArguments, - originalIndexerAccessOrObjectInitializerMember: rewrittenLeft, + oldNodeOpt: null, isLeftOfAssignment: !isRhsNestedInitializer); } else diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs index 00d6f2d7854b0..5ebaf25a2cc57 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs @@ -438,8 +438,7 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) BoundExpression transformedLHS = TransformCompoundAssignmentLHS(node.Operand, isRegularCompoundAssignment: true, tempInitializers, tempSymbols, isDynamic); TypeSymbol? operandType = transformedLHS.Type; //type of the variable being incremented Debug.Assert(operandType is { }); - Debug.Assert(TypeSymbol.Equals(operandType, node.Type, TypeCompareKind.ConsiderEverything2) || - ShouldConvertResultOfAssignmentToDynamic(node, node.Operand)); + Debug.Assert(TypeSymbol.Equals(operandType, node.Type, TypeCompareKind.ConsiderEverything2)); LocalSymbol tempSymbol = _factory.SynthesizedLocal(operandType); tempSymbols.Add(tempSymbol); @@ -473,22 +472,15 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) // In a case of the non-byref operand we use a single-sequence strategy as it results in shorter // overall life time of temps and as such more appropriate. (problem of crossed reads does not affect that case) // - BoundExpression result; - if (isIndirectOrInstanceField(transformedLHS)) { - result = rewriteWithRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, boundTemp, newValue); + return rewriteWithRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, boundTemp, newValue); } else { - result = rewriteWithNotRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, boundTemp, newValue); + return rewriteWithNotRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, boundTemp, newValue); } - result = ConvertResultOfAssignmentToDynamicIfNecessary(node, node.Operand, result, used: true); - Debug.Assert(TypeSymbol.Equals(result.Type, node.Type, TypeCompareKind.AllIgnoreOptions)); - - return result; - static bool isIndirectOrInstanceField(BoundExpression expression) { switch (expression.Kind) diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index 4de6b7382d7ce..581b54d8a18c2 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -795,6 +795,21 @@ public BoundObjectCreationExpression New(NamedTypeSymbol type, ImmutableArray args) => new BoundObjectCreationExpression(Syntax, ctor, args) { WasCompilerGenerated = true }; + public BoundObjectCreationExpression New(MethodSymbol constructor, ImmutableArray arguments, ImmutableArray argumentRefKinds) + => new BoundObjectCreationExpression( + Syntax, + constructor, + arguments, + argumentNamesOpt: default, + argumentRefKinds, + expanded: false, + argsToParamsOpt: default, + defaultArguments: default, + constantValueOpt: null, + initializerExpressionOpt: null, + constructor.ContainingType) + { WasCompilerGenerated = true }; + public BoundObjectCreationExpression New(WellKnownMember wm, ImmutableArray args) { var ctor = WellKnownMethod(wm); diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs index a1e9685c90047..2dcdb75056518 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs @@ -881,7 +881,9 @@ private IOperation CreateBoundObjectInitializerMemberOperation(BoundObjectInitia { // In nested member initializers, the property is not actually set. Instead, it is retrieved for a series of Add method calls or nested property setter calls, // so we need to use the getter for this property - MethodSymbol? accessor = isObjectOrCollectionInitializer ? property.GetOwnOrInheritedGetMethod() : property.GetOwnOrInheritedSetMethod(); + MethodSymbol? accessor = isObjectOrCollectionInitializer || property.RefKind != RefKind.None + ? property.GetOwnOrInheritedGetMethod() + : property.GetOwnOrInheritedSetMethod(); if (accessor == null || boundObjectInitializerMember.ResultKind == LookupResultKind.OverloadResolutionFailure || accessor.OriginalDefinition is ErrorMethodSymbol) { var children = CreateFromArray(((IBoundInvalidNode)boundObjectInitializerMember).InvalidNodeChildren); diff --git a/src/Compilers/CSharp/Portable/Parser/Lexer.cs b/src/Compilers/CSharp/Portable/Parser/Lexer.cs index b409d5a3abab0..bcb3d9296d2ad 100644 --- a/src/Compilers/CSharp/Portable/Parser/Lexer.cs +++ b/src/Compilers/CSharp/Portable/Parser/Lexer.cs @@ -411,6 +411,9 @@ private SyntaxToken Create(in TokenInfo info, SyntaxListBuilder? leading, Syntax case SyntaxKind.EndOfFileToken: token = SyntaxFactory.Token(leadingNode, info.Kind, trailingNode); break; + case SyntaxKind.RazorContentToken: + token = SyntaxFactory.Token(leadingNode, info.Kind, info.Text, trailingNode); + break; case SyntaxKind.None: token = SyntaxFactory.BadToken(leadingNode, info.Text, trailingNode); break; @@ -610,6 +613,19 @@ private void ScanSyntaxToken(ref TokenInfo info) !this.ScanIdentifierOrKeyword(ref info)) { Debug.Assert(TextWindow.PeekChar() == '@'); + + if (TextWindow.PeekChar(1) == ':') + { + // Razor HTML transition. For best consumption by razor, we want to simply pretend it's a token and + // consume all the way to the end of the line. + info.Kind = SyntaxKind.RazorContentToken; + this.AddError(TextWindow.Position + 1, width: 1, ErrorCode.ERR_ExpectedVerbatimLiteral); + + this.ScanToEndOfLine(); + info.Text = TextWindow.GetText(false); + break; + } + this.ConsumeAtSignSequence(); info.Text = TextWindow.GetText(intern: true); this.AddError(ErrorCode.ERR_ExpectedVerbatimLiteral); @@ -1945,14 +1961,6 @@ private void LexSyntaxTrivia(bool afterFirstToken, bool isTrailing, ref SyntaxLi onlyWhitespaceOnLine = false; break; } - else if (ch == ':') - { - // Razor HTML transition. We pretend it's a single-line comment for error recovery. - this.AddError(TextWindow.Position, width: 1, ErrorCode.ERR_UnexpectedCharacter, '@'); - lexSingleLineComment(ref triviaList); - onlyWhitespaceOnLine = false; - break; - } else { return; diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index 3fb6e7986cef3..6f01326afcc99 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -7,18 +7,9 @@ Microsoft.CodeAnalysis.CSharp.Conversion.IsCollectionExpression.get -> bool Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.ReadOnlyKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken refKindKeyword, Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax! type) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax! Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.WithReadOnlyKeyword(Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax! -Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser -Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Dispose() -> void -Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.ParseNextToken() -> Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result -Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.ResetTo(Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result result) -> void -Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result -Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result.ContextualKind.get -> Microsoft.CodeAnalysis.CSharp.SyntaxKind -Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result.Result() -> void -Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result.Token.get -> Microsoft.CodeAnalysis.SyntaxToken -Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.SkipForwardTo(int position) -> void +Microsoft.CodeAnalysis.CSharp.SyntaxKind.RazorContentToken = 8523 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetDeclaredSymbol(this Microsoft.CodeAnalysis.SemanticModel? semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax! node, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.IMethodSymbol? static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetElementConversion(this Microsoft.CodeAnalysis.Operations.ISpreadOperation! spread) -> Microsoft.CodeAnalysis.CSharp.Conversion -static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.CreateTokenParser(Microsoft.CodeAnalysis.Text.SourceText! sourceText, Microsoft.CodeAnalysis.CSharp.CSharpParseOptions? options = null) -> Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser! static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.CrefParameter(Microsoft.CodeAnalysis.SyntaxToken refKindKeyword, Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax! type) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax! [RSEXPERIMENTAL001]Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetSemanticModel(Microsoft.CodeAnalysis.SyntaxTree! syntaxTree, Microsoft.CodeAnalysis.SemanticModelOptions options) -> Microsoft.CodeAnalysis.SemanticModel! [RSEXPERIMENTAL002]override abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.Equals(object? obj) -> bool @@ -26,6 +17,18 @@ static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.CrefParameter(Microsoft.CodeA [RSEXPERIMENTAL002]static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetInterceptorMethod(this Microsoft.CodeAnalysis.SemanticModel? semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.InvocationExpressionSyntax! node, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.IMethodSymbol? [RSEXPERIMENTAL002]static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetInterceptableLocation(this Microsoft.CodeAnalysis.SemanticModel? semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.InvocationExpressionSyntax! node, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.CSharp.InterceptableLocation? [RSEXPERIMENTAL002]static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetInterceptsLocationAttributeSyntax(this Microsoft.CodeAnalysis.CSharp.InterceptableLocation! location) -> string! +[RSEXPERIMENTAL003]Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser +[RSEXPERIMENTAL003]Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Dispose() -> void +[RSEXPERIMENTAL003]Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.ParseLeadingTrivia() -> Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result +[RSEXPERIMENTAL003]Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.ParseNextToken() -> Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result +[RSEXPERIMENTAL003]Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.ParseTrailingTrivia() -> Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result +[RSEXPERIMENTAL003]Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.ResetTo(Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result result) -> void +[RSEXPERIMENTAL003]Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result +[RSEXPERIMENTAL003]Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result.ContextualKind.get -> Microsoft.CodeAnalysis.CSharp.SyntaxKind +[RSEXPERIMENTAL003]Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result.Result() -> void +[RSEXPERIMENTAL003]Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result.Token.get -> Microsoft.CodeAnalysis.SyntaxToken +[RSEXPERIMENTAL003]Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.SkipForwardTo(int position) -> void +[RSEXPERIMENTAL003]static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.CreateTokenParser(Microsoft.CodeAnalysis.Text.SourceText! sourceText, Microsoft.CodeAnalysis.CSharp.CSharpParseOptions? options = null) -> Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser! Microsoft.CodeAnalysis.CSharp.SyntaxKind.AllowsConstraintClause = 8879 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind Microsoft.CodeAnalysis.CSharp.SyntaxKind.AllowsKeyword = 8450 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind Microsoft.CodeAnalysis.CSharp.SyntaxKind.RefStructConstraint = 8880 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs index d2705053fda04..913808058a9d8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.cs @@ -33,9 +33,9 @@ public NamedTypeSymbol ConstructAnonymousTypeSymbol(AnonymousTypeDescriptor type return new AnonymousTypePublicSymbol(this, typeDescr); } - public NamedTypeSymbol ConstructAnonymousDelegateSymbol(AnonymousTypeDescriptor typeDescr, bool checkParamsCollectionsFeatureAvailability) + public NamedTypeSymbol ConstructAnonymousDelegateSymbol(AnonymousTypeDescriptor typeDescr) { - return new AnonymousDelegatePublicSymbol(this, typeDescr, checkParamsCollectionsFeatureAvailability); + return new AnonymousDelegatePublicSymbol(this, typeDescr); } /// diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousType.DelegatePublicSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousType.DelegatePublicSymbol.cs index 3c28925b21379..3f1246a22190f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousType.DelegatePublicSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousType.DelegatePublicSymbol.cs @@ -16,20 +16,9 @@ internal sealed class AnonymousDelegatePublicSymbol : AnonymousTypeOrDelegatePub { private ImmutableArray _lazyMembers; - /// - /// This member does not participate in equality because it is not reflecting any semantic aspect of the symbol. - /// It is only used to determine if we need to check for - /// feature availability, which happens if this field is set to 'true'. - /// If in the process of merging equivalent types, the one with 'false' wins over the one with 'true', - /// that is fine, because that means that the feature availability check is performed on a - /// method declared in this compilation. - /// - internal readonly bool CheckParamsCollectionsFeatureAvailability; - - internal AnonymousDelegatePublicSymbol(AnonymousTypeManager manager, AnonymousTypeDescriptor typeDescr, bool checkParamsCollectionsFeatureAvailability) : + internal AnonymousDelegatePublicSymbol(AnonymousTypeManager manager, AnonymousTypeDescriptor typeDescr) : base(manager, typeDescr) { - CheckParamsCollectionsFeatureAvailability = checkParamsCollectionsFeatureAvailability; } internal override NamedTypeSymbol MapToImplementationSymbol() @@ -41,7 +30,7 @@ internal override AnonymousTypeOrDelegatePublicSymbol SubstituteTypes(AbstractTy { var typeDescr = TypeDescriptor.SubstituteTypes(map, out bool changed); return changed ? - new AnonymousDelegatePublicSymbol(Manager, typeDescr, CheckParamsCollectionsFeatureAvailability) : + new AnonymousDelegatePublicSymbol(Manager, typeDescr) : this; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs index 354eb11748807..e5765d5060ace 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs @@ -82,7 +82,7 @@ internal SyntaxReference SyntaxRef } } - internal virtual CSharpSyntaxNode SyntaxNode + internal CSharpSyntaxNode SyntaxNode { get { diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs index c8d16b0633525..bd23d70560c3d 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.ComponentModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading; @@ -1677,6 +1678,7 @@ public static IEnumerable ParseTokens(string text, int offset = 0, /// /// The source to parse tokens from. /// Parse options for the source. + [Experimental(RoslynExperiments.SyntaxTokenParser, UrlFormat = RoslynExperiments.SyntaxTokenParser_Url)] public static SyntaxTokenParser CreateTokenParser(SourceText sourceText, CSharpParseOptions? options = null) { return new SyntaxTokenParser(new InternalSyntax.Lexer(sourceText, options ?? CSharpParseOptions.Default)); diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs index 827716fb68c83..f5c5b69c74910 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs @@ -528,6 +528,7 @@ public enum SyntaxKind : ushort Utf8StringLiteralToken = 8520, Utf8SingleLineRawStringLiteralToken = 8521, Utf8MultiLineRawStringLiteralToken = 8522, + RazorContentToken = 8523, // trivia EndOfLineTrivia = 8539, diff --git a/src/Compilers/CSharp/Portable/Syntax/SourceTextTokenParser.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxTokenParser.cs similarity index 66% rename from src/Compilers/CSharp/Portable/Syntax/SourceTextTokenParser.cs rename to src/Compilers/CSharp/Portable/Syntax/SyntaxTokenParser.cs index 8c701e1797c1d..887b31df9f759 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SourceTextTokenParser.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxTokenParser.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics.CodeAnalysis; using System.Threading; using InternalSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax; @@ -20,6 +21,7 @@ namespace Microsoft.CodeAnalysis.CSharp; /// /// This type is not thread safe. /// +[Experimental(RoslynExperiments.SyntaxTokenParser, UrlFormat = RoslynExperiments.SyntaxTokenParser_Url)] public sealed class SyntaxTokenParser : IDisposable { private InternalSyntax.Lexer _lexer; @@ -54,6 +56,36 @@ public Result ParseNextToken() return new Result(new SyntaxToken(parent: null, token, startingPosition, index: 0), startingDirectiveStack); } + /// + /// Parse the leading trivia of the next token from the input at the current position. This will advance the internal position of the + /// token parser to the end of the leading trivia of the next token. The returned result will have a token with + /// of , set to , and a parent of . The + /// parsed trivia will be set as the of the token. + /// + public Result ParseLeadingTrivia() + { + var startingDirectiveStack = _lexer.Directives; + var startingPosition = _lexer.TextWindow.Position; + var leadingTrivia = _lexer.LexSyntaxLeadingTrivia(); + var containingToken = InternalSyntax.SyntaxFactory.MissingToken(leading: leadingTrivia.Node, SyntaxKind.None, trailing: null); + return new Result(new SyntaxToken(parent: null, containingToken, startingPosition, index: 0), startingDirectiveStack); + } + + /// + /// Parse syntax trivia from the current position, according to the rules of trailing syntax trivia. This will advance the internal position of the + /// token parser to the end of the trailing trivia from the current location. The returned result will have a token with + /// of , set to , and a parent of . The + /// parsed trivia will be set as the of the token. + /// + public Result ParseTrailingTrivia() + { + var startingDirectiveStack = _lexer.Directives; + var startingPosition = _lexer.TextWindow.Position; + var trailingTrivia = _lexer.LexSyntaxTrailingTrivia(); + var containingToken = InternalSyntax.SyntaxFactory.MissingToken(leading: null, SyntaxKind.None, trailing: trailingTrivia.Node); + return new Result(new SyntaxToken(parent: null, containingToken, startingPosition, index: 0), startingDirectiveStack); + } + /// /// Skip forward in the input to the specified position. Current directive state is preserved during the skip. /// diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 33eabba4ec739..6bc888cca671b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -397,11 +397,6 @@ {0} nelze převést na typ {1}, protože návratový typ se neshoduje s návratovým typem delegáta. - - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - Argumenty typu pro metodu {0} nelze odvodit z použití, protože je použit argument s dynamickým typem a metoda má parametr kolekce parametrů mimo pole. Zkuste argumenty typu zadat explicitně. - - __arglist cannot have an argument passed by 'in' or 'out' __arglist nemůže mít argument předávaný pomocí in nebo out @@ -607,6 +602,11 @@ Pro přístupové objekty vlastnosti i indexeru {0} nelze zadat modifikátory readonly. Místo toho zadejte modifikátor readonly jenom pro vlastnost. + + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + + 'else' cannot start a statement. Příkaz nemůže začínat na else. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 43ba021d7d9f3..798fc08ecb8a3 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -397,11 +397,6 @@ {0} kann nicht in den Typ „{1}“ konvertiert werden, da der Rückgabetyp nicht mit dem Rückgabetyp des Delegaten übereinstimmt - - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - Die Typargumente für die Methode "{0}" können nicht aus der Verwendung abgeleitet werden, da ein Argument mit dynamischem Typ verwendet wird und die Methode einen nicht arraybasierten Params-Auflistungsparameter aufweist. Geben Sie die Typargumente explizit an. - - __arglist cannot have an argument passed by 'in' or 'out' "__arglist" darf kein über "in" oder "out" übergebenes Argument umfassen. @@ -607,6 +602,11 @@ readonly-Modifizierer können nicht für beide Accessoren der Eigenschaft oder des Indexers "{0}" angegeben werden. Legen Sie stattdessen einen readonly-Modifizierer für die Eigenschaft selbst fest. + + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + + 'else' cannot start a statement. Eine Anweisung kann nicht mit "else" beginnen. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 9ba988e36d269..2308721ead667 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -397,11 +397,6 @@ No se puede convertir {0} al tipo "{1}" porque el tipo de valor devuelto no coincide con el tipo de valor devuelto delegado - - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - Los argumentos de tipo para el método "{0}" no se pueden inferir del uso porque se usa un argumento con tipo dinámico y el método tiene un parámetro de colección params no matriz. Intente especificar los argumentos de tipo explícitamente. - - __arglist cannot have an argument passed by 'in' or 'out' __arglist no puede tener un argumento que se ha pasado con "in" o "out" @@ -607,6 +602,11 @@ No se pueden especificar modificadores "readonly" en ambos descriptores de acceso de la propiedad o del indizador "{0}". En su lugar, coloque un modificador "readonly" en la propiedad. + + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + + 'else' cannot start a statement. “else” no puede iniciar una instrucción. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 6cea9c5b50ee7..8585fb986186d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -397,11 +397,6 @@ Impossible de convertir {0} en type « {1} », car le type de retour ne correspond pas au type de retour délégué - - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - Désolé... Nous ne pouvons pas déduire les arguments de type pour la méthode « {0} » à partir de l’utilisation, car un argument avec un type dynamique est utilisé et la méthode a un paramètre de collection de params non-tableau. Essayez de spécifier explicitement les arguments de type. - - __arglist cannot have an argument passed by 'in' or 'out' __arglist ne peut pas avoir un argument passé par 'in' ou 'out' @@ -607,6 +602,11 @@ Impossible de spécifier des modificateurs 'readonly' sur les deux accesseurs de la propriété ou de l'indexeur '{0}'. À la place, mettez un modificateur 'readonly' sur la propriété elle-même. + + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + + 'else' cannot start a statement. 'else' ne peut pas démarrer d'instruction. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index ff4abe8306ad7..bb19c5576466c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -397,11 +397,6 @@ Non è possibile convertire {0} nel tipo ' {1}' perché il tipo restituito non corrisponde al tipo restituito del delegato - - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - Impossibile dedurre dall'utilizzo gli argomenti di tipo generico per il metodo '{0}' poiché è utilizzato un argomento con tipo dinamico e il metodo include un parametro di raccolta dei parametri non di matrice. Provare a specificare gli argomenti di tipo generico in modo esplicito. - - __arglist cannot have an argument passed by 'in' or 'out' __arglist non può contenere un argomento passato da 'in' o 'out' @@ -607,6 +602,11 @@ Non è possibile specificare i modificatori 'readonly' in entrambe le funzioni di accesso della proprietà o dell'indicizzatore '{0}'. Inserire invece un modificatore 'readonly' nella proprietà stessa. + + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + + 'else' cannot start a statement. Un'istruzione non può iniziare con 'else'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 6de8b87226dba..a95aa54b87139 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -397,11 +397,6 @@ 戻り値の型がデリゲート戻り値の型と一致しないため、{0} を型 '{1}' に変換できません - - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - メソッド '{0}' の型引数を使用法から推論できません。これは動的型のある引数が使用され、メソッドに配列以外の params コレクション パラメーターがあるためです。型引数を明示的に指定してみてください。 - - __arglist cannot have an argument passed by 'in' or 'out' __arglist では、'in' や 'out' で引数を渡すことができません @@ -607,6 +602,11 @@ プロパティまたはインデクサー '{0}' の両方のアクセサーで 'readonly' 修飾子を指定することはできません。代わりに、プロパティ自体に 'readonly' 修飾子を指定してください。 + + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + + 'else' cannot start a statement. 'else' でステートメントを開始することはできません。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index a68851470f416..3a0a76cc1b697 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -397,11 +397,6 @@ 반환 형식이 대리자 반환 형식과 일치하지 않기 때문에 {0}을(를) '{1}' 형식으로 변환할 수 없습니다. - - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - 동적 형식을 가진 인수가 사용되고 메서드에 배열 매개 변수가 아닌 params 컬렉션 매개 변수가 있으므로 메서드 '{0}' 형식 인수를 사용법에서 유추할 수 없습니다. 형식 인수를 명시적으로 지정해 보세요. - - __arglist cannot have an argument passed by 'in' or 'out' __arglist는 'in' 또는 'out'으로 전달되는 인수를 가질 수 없습니다. @@ -607,6 +602,11 @@ '{0}' 속성 또는 인덱서의 두 접근자에 'readonly' 한정자를 지정할 수 없습니다. 대신 속성 자체에 'readonly' 한정자를 지정하세요. + + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + + 'else' cannot start a statement. 'else'로 문을 시작할 수 없습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 25fae31c82fcb..740896f6f0ab0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -397,11 +397,6 @@ Nie można przekonwertować {0} na typ "{1}", ponieważ zwracany typ jest niezgodny ze zwracanym typem delegowania - - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - Argumenty typu dla metody „{0}” nie mogą być wywnioskowane z użycia, ponieważ używany jest argument typu dynamicznego, a metoda ma parametr kolekcji params inny niż tablica. Spróbuj jawnie określić argumenty typu. - - __arglist cannot have an argument passed by 'in' or 'out' Element __arglist nie może mieć argumentu przekazywanego przez parametr „in” ani „out” @@ -607,6 +602,11 @@ Nie można określić modyfikatorów „readonly” dla obu metod dostępu właściwości lub indeksatora „{0}”. Zamiast tego dodaj modyfikator „readonly” do samej właściwości. + + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + + 'else' cannot start a statement. Instrukcja nie może rozpoczynać się od elementu „else”. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 7655d23ac143e..beac0aa629b3f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -397,11 +397,6 @@ Não é possível converter {0} para o tipo '{1}' porque o tipo de retorno não corresponde ao tipo de retorno delegado - - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - Os argumentos de tipo para o método '{0}' não podem ser inferidos a partir do uso porque um argumento com tipo dinâmico é usado e o método tem um parâmetro de coleção de params que não é uma matriz. Tente especificar explicitamente os argumentos de tipo. - - __arglist cannot have an argument passed by 'in' or 'out' __arglist não pode ter um argumento passado por 'in' ou 'out' @@ -607,6 +602,11 @@ Não é possível especificar modificadores 'readonly' em ambos os acessadores de propriedade ou de indexador '{0}'. Nesse caso, coloque um modificador 'readonly' na própria propriedade. + + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + + 'else' cannot start a statement. 'else' não pode iniciar uma instrução. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 28b7eec21843e..9fe64263348de 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -397,11 +397,6 @@ Невозможно преобразовать {0} в тип "{1}", поскольку возвращаемый тип не совпадает с возвращаемым типом делегата - - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - Аргументы типа для метода "{0}" не могут быть выведены из использования, поскольку используется аргумент с динамическим типом и метод имеет параметр коллекции params, не являющийся массивом. Попробуйте явно указать аргументы типа. - - __arglist cannot have an argument passed by 'in' or 'out' В __arglist невозможно передать аргумент с помощью in или out @@ -607,6 +602,11 @@ Запрещено указывать модификаторы readonly для обоих методов доступа свойства или индексатора "{0}". Вместо этого укажите модификатор readonly для самого свойства. + + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + + 'else' cannot start a statement. "else" не может запускать оператор. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index f8fcb0a6c083b..12d5367482f69 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -397,11 +397,6 @@ Dönüş türü temsilci dönüş türüyle eşleşmediğinden {0}, ' {1} ' türüne dönüştürülemiyor - - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - Dinamik türe sahip bir bağımsız değişken kullanıldığından ve metot dizi olmayan params koleksiyon parametresine sahip olduğundan, '{0}' metodu için tür bağımsız değişkeni kullanımdan çıkarsanamıyor. Tür bağımsız değişkenlerini açıkça belirtmeyi deneyin. - - __arglist cannot have an argument passed by 'in' or 'out' __arglist, 'in' veya 'out' tarafından geçirilen bir bağımsız değişkene sahip olamaz @@ -607,6 +602,11 @@ 'readonly' değiştiricileri, '{0}' özelliğinin veya dizin oluşturucusunun her iki erişimcisinde de belirtilemez. Bunun yerine özelliğin kendisine bir 'readonly' değiştiricisi koyun. + + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + + 'else' cannot start a statement. 'else' bir deyim başlatamaz. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index cf0899b4808aa..8474baffa7512 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -397,11 +397,6 @@ 无法将 {0} 转换为类型“{1}”,因为返回类型与委托返回类型不匹配 - - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - 无法从用法推断出方法“{0}”的类型参数,因为使用了动态类型参数,并且该方法具有非数组 params 集合参数。请尝试显式指定类型参数。 - - __arglist cannot have an argument passed by 'in' or 'out' __arglist 不能有 "in" 或 "out" 传递的参数 @@ -607,6 +602,11 @@ 不能在属性或索引器 "{0}" 的两个访问器上指定 "readonly" 修饰符。而应在属性本身上指定 "readonly" 修饰符。 + + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + + 'else' cannot start a statement. "else" 不能用在语句的开头。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 75632788f87ef..2f85585c6f3b6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -397,11 +397,6 @@ 無法將 {0} 轉換成類型 '{1}',因為傳回型別不符合委派傳回型別 - - The type arguments for method '{0}' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. - 無法從使用方式推斷方法 '{0}' 的型別引數,因為使用了具有動態型別的引述,而且該方法具有非陣列參數集合參數。請嘗試明確指定型別引數。 - - __arglist cannot have an argument passed by 'in' or 'out' __arglist 不得包含 'in' 或 'out' 傳遞的引數 @@ -607,6 +602,11 @@ 在屬性和索引子 '{0}' 的存取子上均無法指定 'readonly' 修飾元。請改在屬性自身上放置 'readonly' 修飾元。 + + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + '{0}' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + + 'else' cannot start a statement. 'else' 無法開始陳述式。 diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTestBase.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTestBase.cs index 50e51c9e3b42d..e6ddad5f5b487 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTestBase.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTestBase.cs @@ -39,7 +39,7 @@ string getSdkDirectory(TempRoot temp) if (ExecutionConditionUtil.IsCoreClr) { var dir = temp.CreateDirectory(); - File.WriteAllBytes(Path.Combine(dir.Path, "mscorlib.dll"), Net461.References.mscorlib.ImageBytes); + File.WriteAllBytes(Path.Combine(dir.Path, "mscorlib.dll"), Net461.ReferenceInfos.mscorlib.ImageBytes); return dir.Path; } diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index 63c7e533907ee..223d10d5847d3 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -210,6 +210,63 @@ class C Assert.Null(cmd.AnalyzerOptions); } + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/72657")] + public void AnalyzerConfig_DoubleSlash(bool doubleSlashAnalyzerConfig, bool doubleSlashSource) + { + var dir = Temp.CreateDirectory(); + var analyzer = new CompilationAnalyzerWithSeverity(DiagnosticSeverity.Warning, configurable: true); + var src = dir.CreateFile("Class1.cs").WriteAllText(""" + public class C + { + public void M() { } + } + """); + + // The analyzer should produce a warning. + var output = VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, analyzers: [analyzer], expectedWarningCount: 1); + AssertEx.Equal("Class1.cs(1,1): warning ID1000:", output.Trim()); + + // But not when this editorconfig is applied. + var editorconfig = dir.CreateFile(".editorconfig").WriteAllText(""" + root = true + + [*.cs] + dotnet_analyzer_diagnostic.severity = none + + generated_code = true + """); + var cmd = CreateCSharpCompiler( + [ + "/nologo", + "/preferreduilang:en", + "/t:library", + "/analyzerconfig:" + modifyPath(editorconfig.Path, doubleSlashAnalyzerConfig), + modifyPath(src.Path, doubleSlashSource), + ], + [analyzer]); + var outWriter = new StringWriter(CultureInfo.InvariantCulture); + var exitCode = cmd.Run(outWriter); + Assert.Equal(0, exitCode); + AssertEx.Equal("", outWriter.ToString()); + + static string modifyPath(string path, bool doubleSlash) + { + if (!doubleSlash) + { + return path; + } + + // Find the second-to-last slash. + char[] separators = ['/', '\\']; + var lastSlashIndex = path.LastIndexOfAny(separators); + lastSlashIndex = path.LastIndexOfAny(separators, lastSlashIndex - 1); + + // Duplicate that slash. + var lastSlash = path[lastSlashIndex]; + return path[0..lastSlashIndex] + lastSlash + path[lastSlashIndex..]; + } + } + [Fact] public void AnalyzerConfigWithOptions() { @@ -11986,7 +12043,7 @@ public sealed class DiagnosticDescriptor var minSystemCollectionsImmutableImage = CSharpCompilation.Create( "System.Collections.Immutable", new[] { SyntaxFactory.ParseSyntaxTree(minSystemCollectionsImmutableSource) }, - new MetadataReference[] { NetStandard13.SystemRuntime }, + new MetadataReference[] { NetStandard13.References.SystemRuntime }, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, cryptoPublicKey: TestResources.TestKeys.PublicKey_b03f5f7f11d50a3a)).EmitToArray(); var minSystemCollectionsImmutableRef = MetadataReference.CreateFromImage(minSystemCollectionsImmutableImage); @@ -11996,7 +12053,7 @@ public sealed class DiagnosticDescriptor new[] { SyntaxFactory.ParseSyntaxTree(minCodeAnalysisSource) }, new MetadataReference[] { - NetStandard13.SystemRuntime, + NetStandard13.References.SystemRuntime, minSystemCollectionsImmutableRef }, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, cryptoPublicKey: TestResources.TestKeys.PublicKey_31bf3856ad364e35)).EmitToArray(); @@ -12087,7 +12144,7 @@ public override void Initialize(AnalysisContext context) minCodeAnalysisRef, minSystemCollectionsImmutableRef }; - references = references.Concat(NetStandard13.All).ToArray(); + references = references.Concat(NetStandard13.References.All).ToArray(); var analyzerImage = CSharpCompilation.Create( analyzerAssemblyName, diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDynamicTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDynamicTests.cs index 03ec7e35918cc..f6560bb991044 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDynamicTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDynamicTests.cs @@ -8985,38 +8985,66 @@ public void M(C1 c1) "; CompileAndVerifyIL(source, "C.M", @" { - // Code size 91 (0x5b) - .maxstack 5 + // Code size 142 (0x8e) + .maxstack 9 .locals init (object V_0, //lo - object V_1, //ld - bool V_2) + object V_1) //ld IL_0000: ldnull IL_0001: stloc.0 - IL_0002: ldarg.1 - IL_0003: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__1.<>p__0"" - IL_0008: brtrue.s IL_002e - IL_000a: ldc.i4.0 - IL_000b: ldtoken ""bool"" - IL_0010: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" - IL_0015: ldtoken ""C"" - IL_001a: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" - IL_001f: call ""System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)"" - IL_0024: call ""System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)"" - IL_0029: stsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__1.<>p__0"" - IL_002e: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__1.<>p__0"" - IL_0033: ldfld ""System.Func System.Runtime.CompilerServices.CallSite>.Target"" - IL_0038: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__1.<>p__0"" - IL_003d: ldarg.0 - IL_003e: ldfld ""dynamic C.d"" - IL_0043: callvirt ""bool System.Func.Invoke(System.Runtime.CompilerServices.CallSite, object)"" - IL_0048: stloc.2 - IL_0049: ldloca.s V_2 - IL_004b: ldarg.0 - IL_004c: ldflda ""dynamic C.d"" - IL_0051: ldloca.s V_0 - IL_0053: ldloca.s V_1 - IL_0055: callvirt ""void C1.f(ref bool, out dynamic, ref dynamic, out object)"" - IL_005a: ret + IL_0002: ldsfld ""System.Runtime.CompilerServices.CallSite<<>A{00009200}> C.<>o__1.<>p__0"" + IL_0007: brtrue.s IL_0068 + IL_0009: ldc.i4 0x100 + IL_000e: ldstr ""f"" + IL_0013: ldnull + IL_0014: ldtoken ""C"" + IL_0019: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" + IL_001e: ldc.i4.5 + IL_001f: newarr ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo"" + IL_0024: dup + IL_0025: ldc.i4.0 + IL_0026: ldc.i4.1 + IL_0027: ldnull + IL_0028: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" + IL_002d: stelem.ref + IL_002e: dup + IL_002f: ldc.i4.1 + IL_0030: ldc.i4.0 + IL_0031: ldnull + IL_0032: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" + IL_0037: stelem.ref + IL_0038: dup + IL_0039: ldc.i4.2 + IL_003a: ldc.i4.s 17 + IL_003c: ldnull + IL_003d: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" + IL_0042: stelem.ref + IL_0043: dup + IL_0044: ldc.i4.3 + IL_0045: ldc.i4.s 9 + IL_0047: ldnull + IL_0048: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" + IL_004d: stelem.ref + IL_004e: dup + IL_004f: ldc.i4.4 + IL_0050: ldc.i4.s 17 + IL_0052: ldnull + IL_0053: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" + IL_0058: stelem.ref + IL_0059: call ""System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, System.Collections.Generic.IEnumerable, System.Type, System.Collections.Generic.IEnumerable)"" + IL_005e: call ""System.Runtime.CompilerServices.CallSite<<>A{00009200}> System.Runtime.CompilerServices.CallSite<<>A{00009200}>.Create(System.Runtime.CompilerServices.CallSiteBinder)"" + IL_0063: stsfld ""System.Runtime.CompilerServices.CallSite<<>A{00009200}> C.<>o__1.<>p__0"" + IL_0068: ldsfld ""System.Runtime.CompilerServices.CallSite<<>A{00009200}> C.<>o__1.<>p__0"" + IL_006d: ldfld ""<>A{00009200} System.Runtime.CompilerServices.CallSite<<>A{00009200}>.Target"" + IL_0072: ldsfld ""System.Runtime.CompilerServices.CallSite<<>A{00009200}> C.<>o__1.<>p__0"" + IL_0077: ldarg.1 + IL_0078: ldarg.0 + IL_0079: ldfld ""dynamic C.d"" + IL_007e: ldarg.0 + IL_007f: ldflda ""dynamic C.d"" + IL_0084: ldloca.s V_0 + IL_0086: ldloca.s V_1 + IL_0088: callvirt ""void <>A{00009200}.Invoke(System.Runtime.CompilerServices.CallSite, C1, object, ref object, ref object, ref object)"" + IL_008d: ret } ").VerifyDiagnostics(); } @@ -10433,57 +10461,51 @@ public dynamic M(F f, dynamic d) }"; CompileAndVerifyIL(source, "C.M", @" { - // Code size 204 (0xcc) - .maxstack 6 - IL_0000: ldarg.1 - IL_0001: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__0"" - IL_0006: brtrue.s IL_002c - IL_0008: ldc.i4.0 - IL_0009: ldtoken ""int"" - IL_000e: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" - IL_0013: ldtoken ""C"" - IL_0018: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" - IL_001d: call ""System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)"" - IL_0022: call ""System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)"" - IL_0027: stsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__0"" - IL_002c: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__0"" - IL_0031: ldfld ""System.Func System.Runtime.CompilerServices.CallSite>.Target"" - IL_0036: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__0"" - IL_003b: ldarg.2 - IL_003c: callvirt ""int System.Func.Invoke(System.Runtime.CompilerServices.CallSite, object)"" - IL_0041: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__1"" - IL_0046: brtrue.s IL_006c - IL_0048: ldc.i4.0 - IL_0049: ldtoken ""bool"" - IL_004e: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" - IL_0053: ldtoken ""C"" - IL_0058: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" - IL_005d: call ""System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)"" - IL_0062: call ""System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)"" - IL_0067: stsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__1"" - IL_006c: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__1"" - IL_0071: ldfld ""System.Func System.Runtime.CompilerServices.CallSite>.Target"" - IL_0076: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__1"" - IL_007b: ldarg.2 - IL_007c: callvirt ""bool System.Func.Invoke(System.Runtime.CompilerServices.CallSite, object)"" - IL_0081: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__2"" - IL_0086: brtrue.s IL_00ac - IL_0088: ldc.i4.0 - IL_0089: ldtoken ""C"" - IL_008e: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" - IL_0093: ldtoken ""C"" - IL_0098: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" - IL_009d: call ""System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)"" - IL_00a2: call ""System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)"" - IL_00a7: stsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__2"" - IL_00ac: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__2"" - IL_00b1: ldfld ""System.Func System.Runtime.CompilerServices.CallSite>.Target"" - IL_00b6: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__2"" - IL_00bb: ldarg.2 - IL_00bc: callvirt ""C System.Func.Invoke(System.Runtime.CompilerServices.CallSite, object)"" - IL_00c1: callvirt ""int F.Invoke(int, bool, C)"" - IL_00c6: box ""int"" - IL_00cb: ret + // Code size 104 (0x68) + .maxstack 7 + IL_0000: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__0"" + IL_0005: brtrue.s IL_004f + IL_0007: ldc.i4.0 + IL_0008: ldtoken ""C"" + IL_000d: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" + IL_0012: ldc.i4.4 + IL_0013: newarr ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo"" + IL_0018: dup + IL_0019: ldc.i4.0 + IL_001a: ldc.i4.1 + IL_001b: ldnull + IL_001c: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" + IL_0021: stelem.ref + IL_0022: dup + IL_0023: ldc.i4.1 + IL_0024: ldc.i4.0 + IL_0025: ldnull + IL_0026: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" + IL_002b: stelem.ref + IL_002c: dup + IL_002d: ldc.i4.2 + IL_002e: ldc.i4.0 + IL_002f: ldnull + IL_0030: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" + IL_0035: stelem.ref + IL_0036: dup + IL_0037: ldc.i4.3 + IL_0038: ldc.i4.0 + IL_0039: ldnull + IL_003a: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" + IL_003f: stelem.ref + IL_0040: call ""System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Invoke(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Collections.Generic.IEnumerable)"" + IL_0045: call ""System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)"" + IL_004a: stsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__0"" + IL_004f: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__0"" + IL_0054: ldfld ""System.Func System.Runtime.CompilerServices.CallSite>.Target"" + IL_0059: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__0"" + IL_005e: ldarg.1 + IL_005f: ldarg.2 + IL_0060: ldarg.2 + IL_0061: ldarg.2 + IL_0062: callvirt ""object System.Func.Invoke(System.Runtime.CompilerServices.CallSite, F, object, object, object)"" + IL_0067: ret } "); } @@ -12222,30 +12244,48 @@ public struct S CompileAndVerifyIL(source, "C.M", @" { - // Code size 81 (0x51) - .maxstack 4 + // Code size 104 (0x68) + .maxstack 7 .locals init (S V_0) //s IL_0000: ldloca.s V_0 IL_0002: initobj ""S"" - IL_0008: ldloca.s V_0 - IL_000a: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__0"" - IL_000f: brtrue.s IL_0035 - IL_0011: ldc.i4.0 - IL_0012: ldtoken ""int"" - IL_0017: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" - IL_001c: ldtoken ""C"" - IL_0021: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" - IL_0026: call ""System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)"" - IL_002b: call ""System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)"" - IL_0030: stsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__0"" - IL_0035: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__0"" - IL_003a: ldfld ""System.Func System.Runtime.CompilerServices.CallSite>.Target"" - IL_003f: ldsfld ""System.Runtime.CompilerServices.CallSite> C.<>o__0.<>p__0"" - IL_0044: ldarg.1 - IL_0045: callvirt ""int System.Func.Invoke(System.Runtime.CompilerServices.CallSite, object)"" - IL_004a: ldc.i4.1 - IL_004b: call ""void S.this[int].set"" - IL_0050: ret + IL_0008: ldsfld ""System.Runtime.CompilerServices.CallSite<<>F{00000008}> C.<>o__0.<>p__0"" + IL_000d: brtrue.s IL_004e + IL_000f: ldc.i4.0 + IL_0010: ldtoken ""C"" + IL_0015: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" + IL_001a: ldc.i4.3 + IL_001b: newarr ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo"" + IL_0020: dup + IL_0021: ldc.i4.0 + IL_0022: ldc.i4.s 9 + IL_0024: ldnull + IL_0025: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" + IL_002a: stelem.ref + IL_002b: dup + IL_002c: ldc.i4.1 + IL_002d: ldc.i4.0 + IL_002e: ldnull + IL_002f: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" + IL_0034: stelem.ref + IL_0035: dup + IL_0036: ldc.i4.2 + IL_0037: ldc.i4.3 + IL_0038: ldnull + IL_0039: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" + IL_003e: stelem.ref + IL_003f: call ""System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.SetIndex(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Collections.Generic.IEnumerable)"" + IL_0044: call ""System.Runtime.CompilerServices.CallSite<<>F{00000008}> System.Runtime.CompilerServices.CallSite<<>F{00000008}>.Create(System.Runtime.CompilerServices.CallSiteBinder)"" + IL_0049: stsfld ""System.Runtime.CompilerServices.CallSite<<>F{00000008}> C.<>o__0.<>p__0"" + IL_004e: ldsfld ""System.Runtime.CompilerServices.CallSite<<>F{00000008}> C.<>o__0.<>p__0"" + IL_0053: ldfld ""<>F{00000008} System.Runtime.CompilerServices.CallSite<<>F{00000008}>.Target"" + IL_0058: ldsfld ""System.Runtime.CompilerServices.CallSite<<>F{00000008}> C.<>o__0.<>p__0"" + IL_005d: ldloca.s V_0 + IL_005f: ldarg.1 + IL_0060: ldc.i4.1 + IL_0061: callvirt ""object <>F{00000008}.Invoke(System.Runtime.CompilerServices.CallSite, ref S, object, int)"" + IL_0066: pop + IL_0067: ret } "); } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs index d427589e7c5dd..190e152c175b4 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs @@ -15740,7 +15740,7 @@ static void Main() } "; - var compilation = CompileAndVerifyWithMscorlib40(source, new[] { SystemCoreRef, CSharpRef }, expectedOutput: "long.long.2"); + var compilation = CompileAndVerifyWithMscorlib40(source, new[] { SystemCoreRef, CSharpRef }, expectedOutput: "long.ex caught"); } [WorkItem(10463, "https://github.com/dotnet/roslyn/issues/10463")] diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs index 68a43d7eda5f7..bd68eefaa1e3e 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs @@ -550,6 +550,115 @@ .maxstack 2 "); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73068")] + public void GotoInLambda_OutOfScope_Backward() + { + var code = """ + x: + System.Action a = () => + { + using System.IDisposable d = null; + goto x; + }; + """; + CreateCompilation(code).VerifyEmitDiagnostics( + // (1,1): warning CS0164: This label has not been referenced + // x: + Diagnostic(ErrorCode.WRN_UnreferencedLabel, "x").WithLocation(1, 1), + // (5,5): error CS0159: No such label 'x' within the scope of the goto statement + // goto x; + Diagnostic(ErrorCode.ERR_LabelNotFound, "goto").WithArguments("x").WithLocation(5, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73068")] + public void GotoInLambda_OutOfScope_Forward() + { + var code = """ + System.Action a = () => + { + using System.IDisposable d = null; + goto x; + }; + x:; + """; + CreateCompilation(code).VerifyEmitDiagnostics( + // (4,5): error CS0159: No such label 'x' within the scope of the goto statement + // goto x; + Diagnostic(ErrorCode.ERR_LabelNotFound, "goto").WithArguments("x").WithLocation(4, 5), + // (6,1): warning CS0164: This label has not been referenced + // x:; + Diagnostic(ErrorCode.WRN_UnreferencedLabel, "x").WithLocation(6, 1)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73068")] + public void GotoInLambda_NonExistent() + { + var code = """ + System.Action a = () => + { + using System.IDisposable d = null; + goto x; + }; + """; + CreateCompilation(code).VerifyEmitDiagnostics( + // (4,10): error CS0159: No such label 'x' within the scope of the goto statement + // goto x; + Diagnostic(ErrorCode.ERR_LabelNotFound, "x").WithArguments("x").WithLocation(4, 10)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73397")] + public void GotoInLocalFunc_OutOfScope_Backward() + { + var code = """ + #pragma warning disable CS8321 // local function unused + x: + void localFunc() + { + using System.IDisposable d = null; + goto x; + } + """; + CreateCompilation(code).VerifyEmitDiagnostics( + // (6,5): error CS0159: No such label 'x' within the scope of the goto statement + // goto x; + Diagnostic(ErrorCode.ERR_LabelNotFound, "goto").WithArguments("x").WithLocation(6, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73397")] + public void GotoInLocalFunc_OutOfScope_Forward() + { + var code = """ + #pragma warning disable CS8321 // local function unused + void localFunc() + { + using System.IDisposable d = null; + goto x; + } + x:; + """; + CreateCompilation(code).VerifyEmitDiagnostics( + // (5,5): error CS0159: No such label 'x' within the scope of the goto statement + // goto x; + Diagnostic(ErrorCode.ERR_LabelNotFound, "goto").WithArguments("x").WithLocation(5, 5)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73397")] + public void GotoInLocalFunc_NonExistent() + { + var code = """ + #pragma warning disable CS8321 // local function unused + void localFunc() + { + using System.IDisposable d = null; + goto x; + } + """; + CreateCompilation(code).VerifyEmitDiagnostics( + // (5,10): error CS0159: No such label 'x' within the scope of the goto statement + // goto x; + Diagnostic(ErrorCode.ERR_LabelNotFound, "x").WithArguments("x").WithLocation(5, 10)); + } + // Definition same label in different lambdas [WorkItem(5991, "DevDiv_Projects/Roslyn")] [Fact] diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 48efeed6dbc2b..f0d02647d79a9 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -47,7 +47,7 @@ .locals init (char V_0) IL_0006: ldarg.1 IL_0007: stloc.0 IL_0008: ldloca.s V_0 - IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" IL_0014: ret } @@ -60,7 +60,7 @@ .locals init (char V_0) IL_0000: ldarg.1 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" @@ -104,7 +104,7 @@ .locals init (char V_0) IL_0007: call "char char.ToLowerInvariant(char)" IL_000c: stloc.0 IL_000d: ldloca.s V_0 - IL_000f: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0014: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" IL_0019: ret } @@ -118,7 +118,7 @@ .locals init (char V_0) IL_0001: call "char char.ToLowerInvariant(char)" IL_0006: stloc.0 IL_0007: ldloca.s V_0 - IL_0009: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000e: ldarg.0 IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0014: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" @@ -179,7 +179,7 @@ .locals init (char V_0) IL_000a: call "char Test.GetCharWithSideEffect()" IL_000f: stloc.0 IL_0010: ldloca.s V_0 - IL_0012: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0012: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0017: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" IL_001c: ret } @@ -192,7 +192,7 @@ .locals init (char V_0) IL_0000: call "char Test.GetCharWithSideEffect()" IL_0005: stloc.0 IL_0006: ldloca.s V_0 - IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0008: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000d: call "string Test.GetStringWithSideEffect()" IL_0012: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0017: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" @@ -240,13 +240,13 @@ .locals init (char V_0, IL_0001: ldfld "char C.c" IL_0006: stloc.0 IL_0007: ldloca.s V_0 - IL_0009: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000e: ldarg.0 IL_000f: call "ref char C.GetC()" IL_0014: ldind.u2 IL_0015: stloc.1 IL_0016: ldloca.s V_1 - IL_0018: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0018: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_001d: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" IL_0022: call "void System.Console.Write(string)" IL_0027: ret @@ -293,7 +293,7 @@ .locals init (char V_0, //c IL_0003: ldloc.0 IL_0004: stloc.1 IL_0005: ldloca.s V_1 - IL_0007: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0007: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000c: ldarg.0 IL_000d: ldloca.s V_0 IL_000f: call "string C.SneakyLocalChange(ref char)" @@ -497,11 +497,11 @@ .locals init (char V_0, IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.1 IL_000a: stloc.1 IL_000b: ldloca.s V_1 - IL_000d: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000d: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0012: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" IL_0017: ret } @@ -943,7 +943,7 @@ .locals init (int V_0, IL_00cd: ldfld "string Test.d__1.<>7__wrap1" IL_00d2: call "System.ReadOnlySpan string.op_Implicit(string)" IL_00d7: ldloca.s V_2 - IL_00d9: newobj "System.ReadOnlySpan..ctor(in char)" + IL_00d9: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_00de: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" IL_00e3: stloc.1 IL_00e4: leave.s IL_00ff @@ -1014,7 +1014,7 @@ .locals init (char V_0) IL_0006: ldarg.1 IL_0007: stloc.0 IL_0008: ldloca.s V_0 - IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" IL_0014: ret } @@ -1027,7 +1027,7 @@ .locals init (char V_0) IL_0000: ldarg.1 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" @@ -1081,7 +1081,7 @@ .locals init (char V_0) IL_0000: ldarg.1 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" IL_000f: ldarg.0 @@ -1100,7 +1100,7 @@ .locals init (char V_0) IL_0006: ldarg.1 IL_0007: stloc.0 IL_0008: ldloca.s V_0 - IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000f: ldarg.0 IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -1119,7 +1119,7 @@ .locals init (char V_0) IL_000c: ldarg.1 IL_000d: stloc.0 IL_000e: ldloca.s V_0 - IL_0010: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0010: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_001a: ret } @@ -1133,13 +1133,13 @@ .locals init (char V_0, IL_0000: ldarg.1 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" IL_000f: ldarg.1 IL_0010: stloc.1 IL_0011: ldloca.s V_1 - IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0013: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0018: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_001d: ret } @@ -1192,7 +1192,7 @@ .locals init (char V_0) IL_0001: call "char char.ToLowerInvariant(char)" IL_0006: stloc.0 IL_0007: ldloca.s V_0 - IL_0009: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000e: ldarg.0 IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0014: ldarg.0 @@ -1212,7 +1212,7 @@ .locals init (char V_0) IL_0007: call "char char.ToLowerInvariant(char)" IL_000c: stloc.0 IL_000d: ldloca.s V_0 - IL_000f: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0014: ldarg.0 IL_0015: call "System.ReadOnlySpan string.op_Implicit(string)" IL_001a: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -1232,7 +1232,7 @@ .locals init (char V_0) IL_000d: call "char char.ToLowerInvariant(char)" IL_0012: stloc.0 IL_0013: ldloca.s V_0 - IL_0015: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0015: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_001a: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_001f: ret } @@ -1247,14 +1247,14 @@ .locals init (char V_0, IL_0001: call "char char.ToLowerInvariant(char)" IL_0006: stloc.0 IL_0007: ldloca.s V_0 - IL_0009: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000e: ldarg.0 IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0014: ldarg.1 IL_0015: call "char char.ToLowerInvariant(char)" IL_001a: stloc.1 IL_001b: ldloca.s V_1 - IL_001d: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001d: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0022: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0027: ret } @@ -1297,7 +1297,7 @@ .locals init (char V_0) IL_0006: ldarg.1 IL_0007: stloc.0 IL_0008: ldloca.s V_0 - IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000f: ldarg.0 IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -1371,7 +1371,7 @@ .locals init (char V_0) IL_0000: call "char Test.GetCharWithSideEffect()" IL_0005: stloc.0 IL_0006: ldloca.s V_0 - IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0008: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000d: call "string Test.GetStringWithSideEffect()" IL_0012: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0017: call "string Test.GetStringWithSideEffect()" @@ -1390,7 +1390,7 @@ .locals init (char V_0) IL_000a: call "char Test.GetCharWithSideEffect()" IL_000f: stloc.0 IL_0010: ldloca.s V_0 - IL_0012: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0012: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0017: call "string Test.GetStringWithSideEffect()" IL_001c: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0021: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -1409,7 +1409,7 @@ .locals init (char V_0) IL_0014: call "char Test.GetCharWithSideEffect()" IL_0019: stloc.0 IL_001a: ldloca.s V_0 - IL_001c: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001c: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0021: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0026: ret } @@ -1423,13 +1423,13 @@ .locals init (char V_0, IL_0000: call "char Test.GetCharWithSideEffect()" IL_0005: stloc.0 IL_0006: ldloca.s V_0 - IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0008: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000d: call "string Test.GetStringWithSideEffect()" IL_0012: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0017: call "char Test.GetCharWithSideEffect()" IL_001c: stloc.1 IL_001d: ldloca.s V_1 - IL_001f: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001f: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0024: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0029: ret } @@ -1484,18 +1484,18 @@ .locals init (char V_0, IL_0000: ldc.i4.s 97 IL_0002: stloc.0 IL_0003: ldloca.s V_0 - IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0005: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000a: ldarg.0 IL_000b: ldfld "char C.c" IL_0010: stloc.1 IL_0011: ldloca.s V_1 - IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0013: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0018: ldarg.0 IL_0019: call "ref char C.GetC()" IL_001e: ldind.u2 IL_001f: stloc.2 IL_0020: ldloca.s V_2 - IL_0022: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0022: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0027: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_002c: call "void System.Console.Write(string)" IL_0031: ret @@ -1553,17 +1553,17 @@ .locals init (char V_0, //c IL_0003: ldc.i4.s 97 IL_0005: stloc.1 IL_0006: ldloca.s V_1 - IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0008: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000d: ldloc.0 IL_000e: stloc.2 IL_000f: ldloca.s V_2 - IL_0011: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0011: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0016: ldarg.0 IL_0017: ldloca.s V_0 IL_0019: call "char C.SneakyLocalChange(ref char)" IL_001e: stloc.3 IL_001f: ldloca.s V_3 - IL_0021: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0021: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0026: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_002b: ret } @@ -1616,7 +1616,7 @@ .locals init (char V_0) IL_0006: ldarg.1 IL_0007: stloc.0 IL_0008: ldloca.s V_0 - IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" IL_0014: ret } @@ -1802,15 +1802,15 @@ .locals init (char V_0, IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.1 IL_000a: stloc.1 IL_000b: ldloca.s V_1 - IL_000d: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000d: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0012: ldarg.2 IL_0013: stloc.2 IL_0014: ldloca.s V_2 - IL_0016: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0016: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0020: ret } @@ -2004,7 +2004,7 @@ .locals init (char V_0) IL_0011: ldarg.2 IL_0012: stloc.0 IL_0013: ldloca.s V_0 - IL_0015: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0015: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_001a: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" IL_001f: ret } @@ -2017,7 +2017,7 @@ .locals init (char V_0) IL_0000: ldarg.2 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: ldarg.1 IL_000b: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" @@ -2530,7 +2530,7 @@ .locals init (int V_0, IL_0142: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0147: ldarg.0 IL_0148: ldflda "char Test.d__1.<>7__wrap1" - IL_014d: newobj "System.ReadOnlySpan..ctor(in char)" + IL_014d: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0152: ldloc.3 IL_0153: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0158: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -2605,7 +2605,7 @@ .locals init (char V_0) IL_0000: ldarg.1 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" IL_000f: ldarg.0 @@ -2624,7 +2624,7 @@ .locals init (char V_0) IL_0006: ldarg.1 IL_0007: stloc.0 IL_0008: ldloca.s V_0 - IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000f: ldarg.0 IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -2643,7 +2643,7 @@ .locals init (char V_0) IL_000c: ldarg.1 IL_000d: stloc.0 IL_000e: ldloca.s V_0 - IL_0010: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0010: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_001a: ret } @@ -2657,13 +2657,13 @@ .locals init (char V_0, IL_0000: ldarg.1 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" IL_000f: ldarg.1 IL_0010: stloc.1 IL_0011: ldloca.s V_1 - IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0013: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0018: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_001d: ret } @@ -2722,7 +2722,7 @@ .locals init (char V_0) IL_0000: ldarg.1 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" IL_000f: ldarg.0 @@ -2743,7 +2743,7 @@ .locals init (char V_0) IL_0006: ldarg.1 IL_0007: stloc.0 IL_0008: ldloca.s V_0 - IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000f: ldarg.0 IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0015: ldarg.0 @@ -2764,7 +2764,7 @@ .locals init (char V_0) IL_000c: ldarg.1 IL_000d: stloc.0 IL_000e: ldloca.s V_0 - IL_0010: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0010: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0015: ldarg.0 IL_0016: call "System.ReadOnlySpan string.op_Implicit(string)" IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -2785,7 +2785,7 @@ .locals init (char V_0) IL_0012: ldarg.1 IL_0013: stloc.0 IL_0014: ldloca.s V_0 - IL_0016: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0016: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0020: ret } @@ -2799,13 +2799,13 @@ .locals init (char V_0, IL_0000: ldarg.1 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" IL_000f: ldarg.1 IL_0010: stloc.1 IL_0011: ldloca.s V_1 - IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0013: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0018: ldarg.0 IL_0019: call "System.ReadOnlySpan string.op_Implicit(string)" IL_001e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -2823,13 +2823,13 @@ .locals init (char V_0, IL_0006: ldarg.1 IL_0007: stloc.0 IL_0008: ldloca.s V_0 - IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000f: ldarg.0 IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0015: ldarg.1 IL_0016: stloc.1 IL_0017: ldloca.s V_1 - IL_0019: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0019: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_001e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0023: ret } @@ -2843,7 +2843,7 @@ .locals init (char V_0, IL_0000: ldarg.1 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" IL_000f: ldarg.0 @@ -2851,7 +2851,7 @@ .locals init (char V_0, IL_0015: ldarg.1 IL_0016: stloc.1 IL_0017: ldloca.s V_1 - IL_0019: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0019: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_001e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0023: ret } @@ -2911,7 +2911,7 @@ .locals init (char V_0) IL_0001: call "char char.ToLowerInvariant(char)" IL_0006: stloc.0 IL_0007: ldloca.s V_0 - IL_0009: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000e: ldarg.0 IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0014: ldarg.0 @@ -2933,7 +2933,7 @@ .locals init (char V_0) IL_0007: call "char char.ToLowerInvariant(char)" IL_000c: stloc.0 IL_000d: ldloca.s V_0 - IL_000f: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0014: ldarg.0 IL_0015: call "System.ReadOnlySpan string.op_Implicit(string)" IL_001a: ldarg.0 @@ -2955,7 +2955,7 @@ .locals init (char V_0) IL_000d: call "char char.ToLowerInvariant(char)" IL_0012: stloc.0 IL_0013: ldloca.s V_0 - IL_0015: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0015: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_001a: ldarg.0 IL_001b: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -2977,7 +2977,7 @@ .locals init (char V_0) IL_0013: call "char char.ToLowerInvariant(char)" IL_0018: stloc.0 IL_0019: ldloca.s V_0 - IL_001b: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001b: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0025: ret } @@ -2992,14 +2992,14 @@ .locals init (char V_0, IL_0001: call "char char.ToLowerInvariant(char)" IL_0006: stloc.0 IL_0007: ldloca.s V_0 - IL_0009: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000e: ldarg.0 IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0014: ldarg.1 IL_0015: call "char char.ToLowerInvariant(char)" IL_001a: stloc.1 IL_001b: ldloca.s V_1 - IL_001d: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001d: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0022: ldarg.0 IL_0023: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0028: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -3018,14 +3018,14 @@ .locals init (char V_0, IL_0007: call "char char.ToLowerInvariant(char)" IL_000c: stloc.0 IL_000d: ldloca.s V_0 - IL_000f: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0014: ldarg.0 IL_0015: call "System.ReadOnlySpan string.op_Implicit(string)" IL_001a: ldarg.1 IL_001b: call "char char.ToLowerInvariant(char)" IL_0020: stloc.1 IL_0021: ldloca.s V_1 - IL_0023: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0023: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0028: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_002d: ret } @@ -3040,7 +3040,7 @@ .locals init (char V_0, IL_0001: call "char char.ToLowerInvariant(char)" IL_0006: stloc.0 IL_0007: ldloca.s V_0 - IL_0009: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000e: ldarg.0 IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0014: ldarg.0 @@ -3049,7 +3049,7 @@ .locals init (char V_0, IL_001b: call "char char.ToLowerInvariant(char)" IL_0020: stloc.1 IL_0021: ldloca.s V_1 - IL_0023: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0023: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0028: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_002d: ret } @@ -3100,7 +3100,7 @@ .locals init (char V_0) IL_0006: ldarg.1 IL_0007: stloc.0 IL_0008: ldloca.s V_0 - IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000f: ldarg.0 IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0015: ldarg.0 @@ -3186,7 +3186,7 @@ .locals init (char V_0) IL_0000: call "char Test.GetCharWithSideEffect()" IL_0005: stloc.0 IL_0006: ldloca.s V_0 - IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0008: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000d: call "string Test.GetStringWithSideEffect()" IL_0012: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0017: call "string Test.GetStringWithSideEffect()" @@ -3207,7 +3207,7 @@ .locals init (char V_0) IL_000a: call "char Test.GetCharWithSideEffect()" IL_000f: stloc.0 IL_0010: ldloca.s V_0 - IL_0012: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0012: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0017: call "string Test.GetStringWithSideEffect()" IL_001c: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0021: call "string Test.GetStringWithSideEffect()" @@ -3228,7 +3228,7 @@ .locals init (char V_0) IL_0014: call "char Test.GetCharWithSideEffect()" IL_0019: stloc.0 IL_001a: ldloca.s V_0 - IL_001c: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001c: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0021: call "string Test.GetStringWithSideEffect()" IL_0026: call "System.ReadOnlySpan string.op_Implicit(string)" IL_002b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -3249,7 +3249,7 @@ .locals init (char V_0) IL_001e: call "char Test.GetCharWithSideEffect()" IL_0023: stloc.0 IL_0024: ldloca.s V_0 - IL_0026: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0026: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_002b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0030: ret } @@ -3263,13 +3263,13 @@ .locals init (char V_0, IL_0000: call "char Test.GetCharWithSideEffect()" IL_0005: stloc.0 IL_0006: ldloca.s V_0 - IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0008: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000d: call "string Test.GetStringWithSideEffect()" IL_0012: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0017: call "char Test.GetCharWithSideEffect()" IL_001c: stloc.1 IL_001d: ldloca.s V_1 - IL_001f: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001f: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0024: call "string Test.GetStringWithSideEffect()" IL_0029: call "System.ReadOnlySpan string.op_Implicit(string)" IL_002e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -3287,13 +3287,13 @@ .locals init (char V_0, IL_000a: call "char Test.GetCharWithSideEffect()" IL_000f: stloc.0 IL_0010: ldloca.s V_0 - IL_0012: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0012: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0017: call "string Test.GetStringWithSideEffect()" IL_001c: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0021: call "char Test.GetCharWithSideEffect()" IL_0026: stloc.1 IL_0027: ldloca.s V_1 - IL_0029: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0029: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_002e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0033: ret } @@ -3307,7 +3307,7 @@ .locals init (char V_0, IL_0000: call "char Test.GetCharWithSideEffect()" IL_0005: stloc.0 IL_0006: ldloca.s V_0 - IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0008: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000d: call "string Test.GetStringWithSideEffect()" IL_0012: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0017: call "string Test.GetStringWithSideEffect()" @@ -3315,7 +3315,7 @@ .locals init (char V_0, IL_0021: call "char Test.GetCharWithSideEffect()" IL_0026: stloc.1 IL_0027: ldloca.s V_1 - IL_0029: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0029: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_002e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0033: ret } @@ -3374,24 +3374,24 @@ .locals init (char V_0, IL_0000: ldc.i4.s 97 IL_0002: stloc.0 IL_0003: ldloca.s V_0 - IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0005: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000a: ldarg.0 IL_000b: ldfld "char C.c" IL_0010: stloc.1 IL_0011: ldloca.s V_1 - IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0013: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0018: ldarg.0 IL_0019: call "ref char C.GetC()" IL_001e: ldind.u2 IL_001f: stloc.2 IL_0020: ldloca.s V_2 - IL_0022: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0022: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0027: ldarg.0 IL_0028: call "ref char C.GetC2()" IL_002d: ldind.u2 IL_002e: stloc.3 IL_002f: ldloca.s V_3 - IL_0031: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0031: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0036: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_003b: call "void System.Console.Write(string)" IL_0040: ret @@ -3453,7 +3453,7 @@ .locals init (char V_0, //c1 IL_0006: ldloc.0 IL_0007: stloc.2 IL_0008: ldloca.s V_2 - IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000f: ldarg.0 IL_0010: ldloca.s V_0 IL_0012: call "string C.SneakyLocalChange(ref char)" @@ -3461,7 +3461,7 @@ .locals init (char V_0, //c1 IL_001c: ldloc.1 IL_001d: stloc.3 IL_001e: ldloca.s V_3 - IL_0020: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0020: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0025: ldarg.0 IL_0026: ldloca.s V_1 IL_0028: call "string C.SneakyLocalChange(ref char)" @@ -3525,7 +3525,7 @@ .locals init (char V_0) IL_0006: ldarg.1 IL_0007: stloc.0 IL_0008: ldloca.s V_0 - IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000f: ldarg.0 IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -3767,19 +3767,19 @@ .locals init (char V_0, IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.1 IL_000a: stloc.1 IL_000b: ldloca.s V_1 - IL_000d: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000d: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0012: ldarg.2 IL_0013: stloc.2 IL_0014: ldloca.s V_2 - IL_0016: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0016: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_001b: ldarg.3 IL_001c: stloc.3 IL_001d: ldloca.s V_3 - IL_001f: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001f: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0024: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0029: ret } @@ -4045,11 +4045,11 @@ .locals init (char V_0, IL_0011: ldarg.2 IL_0012: stloc.0 IL_0013: ldloca.s V_0 - IL_0015: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0015: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_001a: ldarg.2 IL_001b: stloc.1 IL_001c: ldloca.s V_1 - IL_001e: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001e: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0023: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0028: ret } @@ -4063,11 +4063,11 @@ .locals init (char V_0, IL_0000: ldarg.2 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.2 IL_000a: stloc.1 IL_000b: ldloca.s V_1 - IL_000d: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000d: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0012: ldarg.0 IL_0013: ldarg.1 IL_0014: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" @@ -4086,7 +4086,7 @@ .locals init (char V_0, IL_0000: ldarg.2 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: ldarg.1 IL_000b: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" @@ -4095,7 +4095,7 @@ .locals init (char V_0, IL_001a: ldarg.2 IL_001b: stloc.1 IL_001c: ldloca.s V_1 - IL_001e: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001e: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0023: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0028: ret } @@ -4151,7 +4151,7 @@ .locals init (char V_0) IL_0017: ldarg.3 IL_0018: stloc.0 IL_0019: ldloca.s V_0 - IL_001b: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001b: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0025: ret } @@ -4169,7 +4169,7 @@ .locals init (char V_0) IL_0011: ldarg.3 IL_0012: stloc.0 IL_0013: ldloca.s V_0 - IL_0015: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0015: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_001a: ldarg.2 IL_001b: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -4186,7 +4186,7 @@ .locals init (char V_0) IL_0006: ldarg.3 IL_0007: stloc.0 IL_0008: ldloca.s V_0 - IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000f: ldarg.0 IL_0010: ldarg.1 IL_0011: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" @@ -4204,7 +4204,7 @@ .locals init (char V_0) IL_0000: ldarg.3 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.2 IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" IL_000f: ldarg.0 @@ -4231,7 +4231,7 @@ .locals init (char V_0) IL_0017: ldarg.3 IL_0018: stloc.0 IL_0019: ldloca.s V_0 - IL_001b: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001b: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0025: ret } @@ -4244,7 +4244,7 @@ .locals init (char V_0) IL_0000: ldarg.3 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: ldarg.1 IL_000b: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" @@ -4415,7 +4415,7 @@ .locals init (char V_0) IL_0012: ldarg.3 IL_0013: stloc.0 IL_0014: ldloca.s V_0 - IL_0016: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0016: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" IL_0020: ret } @@ -4428,7 +4428,7 @@ .locals init (char V_0) IL_0000: ldarg.3 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: ldarg.1 IL_000b: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" @@ -5229,10 +5229,10 @@ .locals init (int V_0, IL_01af: call "System.ReadOnlySpan string.op_Implicit(string)" IL_01b4: ldarg.0 IL_01b5: ldflda "char Test.d__1.<>7__wrap1" - IL_01ba: newobj "System.ReadOnlySpan..ctor(in char)" + IL_01ba: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_01bf: ldarg.0 IL_01c0: ldflda "char Test.d__1.<>7__wrap2" - IL_01c5: newobj "System.ReadOnlySpan..ctor(in char)" + IL_01c5: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_01ca: ldloc.s V_4 IL_01cc: call "System.ReadOnlySpan string.op_Implicit(string)" IL_01d1: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -5313,7 +5313,7 @@ .locals init (char V_0) IL_0000: ldarg.1 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" IL_000f: ldarg.0 @@ -5334,7 +5334,7 @@ .locals init (char V_0) IL_0006: ldarg.1 IL_0007: stloc.0 IL_0008: ldloca.s V_0 - IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000f: ldarg.0 IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0015: ldarg.0 @@ -5355,7 +5355,7 @@ .locals init (char V_0) IL_000c: ldarg.1 IL_000d: stloc.0 IL_000e: ldloca.s V_0 - IL_0010: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0010: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0015: ldarg.0 IL_0016: call "System.ReadOnlySpan string.op_Implicit(string)" IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -5376,7 +5376,7 @@ .locals init (char V_0) IL_0012: ldarg.1 IL_0013: stloc.0 IL_0014: ldloca.s V_0 - IL_0016: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0016: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0020: ret } @@ -5390,13 +5390,13 @@ .locals init (char V_0, IL_0000: ldarg.1 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" IL_000f: ldarg.1 IL_0010: stloc.1 IL_0011: ldloca.s V_1 - IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0013: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0018: ldarg.0 IL_0019: call "System.ReadOnlySpan string.op_Implicit(string)" IL_001e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" @@ -5414,13 +5414,13 @@ .locals init (char V_0, IL_0006: ldarg.1 IL_0007: stloc.0 IL_0008: ldloca.s V_0 - IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000f: ldarg.0 IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0015: ldarg.1 IL_0016: stloc.1 IL_0017: ldloca.s V_1 - IL_0019: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0019: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_001e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0023: ret } @@ -5434,7 +5434,7 @@ .locals init (char V_0, IL_0000: ldarg.1 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_0009: ldarg.0 IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" IL_000f: ldarg.0 @@ -5442,7 +5442,7 @@ .locals init (char V_0, IL_0015: ldarg.1 IL_0016: stloc.1 IL_0017: ldloca.s V_1 - IL_0019: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0019: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_001e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_0023: ret } diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 1c461350965c7..3a736dd606073 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -9097,18 +9097,66 @@ static void Main() verifier.VerifyIL("Program.F", """ { - // Code size 21 (0x15) - .maxstack 3 - .locals init (System.Collections.Generic.List V_0) - IL_0000: ldarg.0 - IL_0001: stloc.0 - IL_0002: ldloc.0 - IL_0003: callvirt "int System.Collections.Generic.List.Count.get" - IL_0008: newobj "System.Collections.Generic.List..ctor(int)" - IL_000d: dup - IL_000e: ldloc.0 - IL_000f: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" - IL_0014: ret + // Code size 141 (0x8d) + .maxstack 9 + .locals init (System.Collections.Generic.List V_0, + System.Collections.Generic.List.Enumerator V_1, + object V_2) + IL_0000: newobj "System.Collections.Generic.List..ctor()" + IL_0005: stloc.0 + IL_0006: ldarg.0 + IL_0007: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" + IL_000c: stloc.1 + .try + { + IL_000d: br.s IL_0072 + IL_000f: ldloca.s V_1 + IL_0011: call "dynamic System.Collections.Generic.List.Enumerator.Current.get" + IL_0016: stloc.2 + IL_0017: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_001c: brtrue.s IL_005c + IL_001e: ldc.i4 0x100 + IL_0023: ldstr "Add" + IL_0028: ldnull + IL_0029: ldtoken "Program" + IL_002e: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_0033: ldc.i4.2 + IL_0034: newarr "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" + IL_0039: dup + IL_003a: ldc.i4.0 + IL_003b: ldc.i4.1 + IL_003c: ldnull + IL_003d: call "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)" + IL_0042: stelem.ref + IL_0043: dup + IL_0044: ldc.i4.1 + IL_0045: ldc.i4.0 + IL_0046: ldnull + IL_0047: call "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)" + IL_004c: stelem.ref + IL_004d: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, System.Collections.Generic.IEnumerable, System.Type, System.Collections.Generic.IEnumerable)" + IL_0052: call "System.Runtime.CompilerServices.CallSite, dynamic>> System.Runtime.CompilerServices.CallSite, dynamic>>.Create(System.Runtime.CompilerServices.CallSiteBinder)" + IL_0057: stsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_005c: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_0061: ldfld "System.Action, dynamic> System.Runtime.CompilerServices.CallSite, dynamic>>.Target" + IL_0066: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_006b: ldloc.0 + IL_006c: ldloc.2 + IL_006d: callvirt "void System.Action, dynamic>.Invoke(System.Runtime.CompilerServices.CallSite, System.Collections.Generic.List, dynamic)" + IL_0072: ldloca.s V_1 + IL_0074: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" + IL_0079: brtrue.s IL_000f + IL_007b: leave.s IL_008b + } + finally + { + IL_007d: ldloca.s V_1 + IL_007f: constrained. "System.Collections.Generic.List.Enumerator" + IL_0085: callvirt "void System.IDisposable.Dispose()" + IL_008a: endfinally + } + IL_008b: ldloc.0 + IL_008c: ret } """); } @@ -9118,55 +9166,66 @@ .locals init (System.Collections.Generic.List V_0) verifier.VerifyIL("Program.F", """ { - // Code size 126 (0x7e) - .maxstack 4 + // Code size 141 (0x8d) + .maxstack 9 .locals init (System.Collections.Generic.List V_0, System.Collections.Generic.List.Enumerator V_1, object V_2) - IL_0000: ldarg.0 - IL_0001: dup - IL_0002: callvirt "int System.Collections.Generic.List.Count.get" - IL_0007: newobj "System.Collections.Generic.List..ctor(int)" - IL_000c: stloc.0 - IL_000d: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" - IL_0012: stloc.1 + IL_0000: newobj "System.Collections.Generic.List..ctor()" + IL_0005: stloc.0 + IL_0006: ldarg.0 + IL_0007: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" + IL_000c: stloc.1 .try { - IL_0013: br.s IL_0063 - IL_0015: ldloca.s V_1 - IL_0017: call "dynamic System.Collections.Generic.List.Enumerator.Current.get" - IL_001c: stloc.2 - IL_001d: ldloc.0 - IL_001e: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_0023: brtrue.s IL_0049 - IL_0025: ldc.i4.0 - IL_0026: ldtoken "int" - IL_002b: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_0030: ldtoken "Program" - IL_0035: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_003a: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" - IL_003f: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" - IL_0044: stsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_0049: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_004e: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" - IL_0053: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_0058: ldloc.2 - IL_0059: callvirt "int System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" - IL_005e: callvirt "void System.Collections.Generic.List.Add(int)" - IL_0063: ldloca.s V_1 - IL_0065: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" - IL_006a: brtrue.s IL_0015 - IL_006c: leave.s IL_007c + IL_000d: br.s IL_0072 + IL_000f: ldloca.s V_1 + IL_0011: call "dynamic System.Collections.Generic.List.Enumerator.Current.get" + IL_0016: stloc.2 + IL_0017: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_001c: brtrue.s IL_005c + IL_001e: ldc.i4 0x100 + IL_0023: ldstr "Add" + IL_0028: ldnull + IL_0029: ldtoken "Program" + IL_002e: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_0033: ldc.i4.2 + IL_0034: newarr "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" + IL_0039: dup + IL_003a: ldc.i4.0 + IL_003b: ldc.i4.1 + IL_003c: ldnull + IL_003d: call "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)" + IL_0042: stelem.ref + IL_0043: dup + IL_0044: ldc.i4.1 + IL_0045: ldc.i4.0 + IL_0046: ldnull + IL_0047: call "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)" + IL_004c: stelem.ref + IL_004d: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, System.Collections.Generic.IEnumerable, System.Type, System.Collections.Generic.IEnumerable)" + IL_0052: call "System.Runtime.CompilerServices.CallSite, dynamic>> System.Runtime.CompilerServices.CallSite, dynamic>>.Create(System.Runtime.CompilerServices.CallSiteBinder)" + IL_0057: stsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_005c: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_0061: ldfld "System.Action, dynamic> System.Runtime.CompilerServices.CallSite, dynamic>>.Target" + IL_0066: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_006b: ldloc.0 + IL_006c: ldloc.2 + IL_006d: callvirt "void System.Action, dynamic>.Invoke(System.Runtime.CompilerServices.CallSite, System.Collections.Generic.List, dynamic)" + IL_0072: ldloca.s V_1 + IL_0074: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" + IL_0079: brtrue.s IL_000f + IL_007b: leave.s IL_008b } finally { - IL_006e: ldloca.s V_1 - IL_0070: constrained. "System.Collections.Generic.List.Enumerator" - IL_0076: callvirt "void System.IDisposable.Dispose()" - IL_007b: endfinally + IL_007d: ldloca.s V_1 + IL_007f: constrained. "System.Collections.Generic.List.Enumerator" + IL_0085: callvirt "void System.IDisposable.Dispose()" + IL_008a: endfinally } - IL_007c: ldloc.0 - IL_007d: ret + IL_008b: ldloc.0 + IL_008c: ret } """); } @@ -13908,7 +13967,7 @@ static void M2() comp, symbolValidator: module => { - AssertEx.Equal(new[] { "<>y__InlineArray1", "<>y__InlineArray3" }, getInlineArrayTypeNames(module)); + AssertEx.Equal(new[] { "<>y__InlineArray3" }, getInlineArrayTypeNames(module)); }, verify: Verification.Skipped); @@ -17994,6 +18053,8 @@ public class MyCollectionBuilder var comp = CreateCompilation(sourceA, targetFramework: TargetFramework.Net80); var refA = AsReference(comp, useCompilationReference); + // https://github.com/dotnet/roslyn/issues/73085 + // Test hits an assertion failure when collection-expr with a single element is used here string sourceB = """ #pragma warning disable 219 class Program @@ -18001,7 +18062,7 @@ class Program static void Main() { MyCollection x = []; - MyCollection y = ["2"]; + MyCollection y = ["2", "3"]; MyCollection z = new(); } } @@ -18010,14 +18071,17 @@ static void Main() comp.MakeTypeMissing(SpecialType.System_Int32); comp.VerifyEmitDiagnostics( // (7,34): error CS0518: Predefined type 'System.Int32' is not defined or imported - // MyCollection y = ["2"]; - Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, @"[""2""]").WithArguments("System.Int32").WithLocation(7, 34), + // MyCollection y = ["2", "3"]; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, @"[""2"", ""3""]").WithArguments("System.Int32").WithLocation(7, 34), + // (7,34): error CS0518: Predefined type 'System.Int32' is not defined or imported + // MyCollection y = ["2", "3"]; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, @"[""2"", ""3""]").WithArguments("System.Int32").WithLocation(7, 34), // (7,34): error CS0518: Predefined type 'System.Int32' is not defined or imported - // MyCollection y = ["2"]; - Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, @"[""2""]").WithArguments("System.Int32").WithLocation(7, 34), + // MyCollection y = ["2", "3"]; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, @"[""2"", ""3""]").WithArguments("System.Int32").WithLocation(7, 34), // (7,34): error CS0518: Predefined type 'System.Int32' is not defined or imported - // MyCollection y = ["2"]; - Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, @"[""2""]").WithArguments("System.Int32").WithLocation(7, 34)); + // MyCollection y = ["2", "3"]; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, @"[""2"", ""3""]").WithArguments("System.Int32").WithLocation(7, 34)); } [Fact] @@ -19772,118 +19836,132 @@ static void Main() verifier.VerifyIL("Program.F1", """ { - // Code size 72 (0x48) - .maxstack 4 - .locals init (int V_0, - System.Collections.Generic.List V_1, - System.Span V_2, - int V_3, - System.Span V_4) - IL_0000: ldarg.0 - IL_0001: dup - IL_0002: callvirt "int System.Collections.Generic.List.Count.get" - IL_0007: stloc.0 - IL_0008: ldloc.0 - IL_0009: newobj "System.Collections.Generic.List..ctor(int)" - IL_000e: stloc.1 - IL_000f: ldloc.1 - IL_0010: ldloc.0 - IL_0011: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" - IL_0016: ldloc.1 - IL_0017: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" - IL_001c: stloc.2 - IL_001d: ldc.i4.0 - IL_001e: stloc.3 - IL_001f: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" - IL_0024: stloc.s V_4 - IL_0026: ldloca.s V_4 - IL_0028: ldloca.s V_2 - IL_002a: ldloc.3 - IL_002b: ldloca.s V_4 - IL_002d: call "int System.Span.Length.get" - IL_0032: call "System.Span System.Span.Slice(int, int)" - IL_0037: call "void System.Span.CopyTo(System.Span)" - IL_003c: ldloc.3 - IL_003d: ldloca.s V_4 - IL_003f: call "int System.Span.Length.get" - IL_0044: add - IL_0045: stloc.3 - IL_0046: ldloc.1 - IL_0047: ret - } + // Code size 141 (0x8d) + .maxstack 9 + .locals init (System.Collections.Generic.List V_0, + System.Collections.Generic.List.Enumerator V_1, + object V_2) + IL_0000: newobj "System.Collections.Generic.List..ctor()" + IL_0005: stloc.0 + IL_0006: ldarg.0 + IL_0007: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" + IL_000c: stloc.1 + .try + { + IL_000d: br.s IL_0072 + IL_000f: ldloca.s V_1 + IL_0011: call "dynamic System.Collections.Generic.List.Enumerator.Current.get" + IL_0016: stloc.2 + IL_0017: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_001c: brtrue.s IL_005c + IL_001e: ldc.i4 0x100 + IL_0023: ldstr "Add" + IL_0028: ldnull + IL_0029: ldtoken "Program" + IL_002e: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_0033: ldc.i4.2 + IL_0034: newarr "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" + IL_0039: dup + IL_003a: ldc.i4.0 + IL_003b: ldc.i4.1 + IL_003c: ldnull + IL_003d: call "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)" + IL_0042: stelem.ref + IL_0043: dup + IL_0044: ldc.i4.1 + IL_0045: ldc.i4.0 + IL_0046: ldnull + IL_0047: call "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)" + IL_004c: stelem.ref + IL_004d: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, System.Collections.Generic.IEnumerable, System.Type, System.Collections.Generic.IEnumerable)" + IL_0052: call "System.Runtime.CompilerServices.CallSite, dynamic>> System.Runtime.CompilerServices.CallSite, dynamic>>.Create(System.Runtime.CompilerServices.CallSiteBinder)" + IL_0057: stsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_005c: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_0061: ldfld "System.Action, dynamic> System.Runtime.CompilerServices.CallSite, dynamic>>.Target" + IL_0066: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_006b: ldloc.0 + IL_006c: ldloc.2 + IL_006d: callvirt "void System.Action, dynamic>.Invoke(System.Runtime.CompilerServices.CallSite, System.Collections.Generic.List, dynamic)" + IL_0072: ldloca.s V_1 + IL_0074: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" + IL_0079: brtrue.s IL_000f + IL_007b: leave.s IL_008b + } + finally + { + IL_007d: ldloca.s V_1 + IL_007f: constrained. "System.Collections.Generic.List.Enumerator" + IL_0085: callvirt "void System.IDisposable.Dispose()" + IL_008a: endfinally + } + IL_008b: ldloc.0 + IL_008c: ret + } """); verifier.VerifyIL("Program.F2", """ { - // Code size 154 (0x9a) - .maxstack 4 - .locals init (int V_0, - System.Collections.Generic.List V_1, - System.Span V_2, - int V_3, - System.Collections.Generic.List.Enumerator V_4, - object V_5) - IL_0000: ldarg.0 - IL_0001: dup - IL_0002: callvirt "int System.Collections.Generic.List.Count.get" - IL_0007: stloc.0 - IL_0008: ldloc.0 - IL_0009: newobj "System.Collections.Generic.List..ctor(int)" - IL_000e: stloc.1 - IL_000f: ldloc.1 - IL_0010: ldloc.0 - IL_0011: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" - IL_0016: ldloc.1 - IL_0017: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" - IL_001c: stloc.2 - IL_001d: ldc.i4.0 - IL_001e: stloc.3 - IL_001f: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" - IL_0024: stloc.s V_4 + // Code size 141 (0x8d) + .maxstack 9 + .locals init (System.Collections.Generic.List V_0, + System.Collections.Generic.List.Enumerator V_1, + object V_2) + IL_0000: newobj "System.Collections.Generic.List..ctor()" + IL_0005: stloc.0 + IL_0006: ldarg.0 + IL_0007: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" + IL_000c: stloc.1 .try { - IL_0026: br.s IL_007f - IL_0028: ldloca.s V_4 - IL_002a: call "dynamic System.Collections.Generic.List.Enumerator.Current.get" - IL_002f: stloc.s V_5 - IL_0031: ldloca.s V_2 - IL_0033: ldloc.3 - IL_0034: call "ref int System.Span.this[int].get" - IL_0039: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__1.<>p__0" - IL_003e: brtrue.s IL_0064 - IL_0040: ldc.i4.0 - IL_0041: ldtoken "int" - IL_0046: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_004b: ldtoken "Program" - IL_0050: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_0055: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" - IL_005a: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" - IL_005f: stsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__1.<>p__0" - IL_0064: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__1.<>p__0" - IL_0069: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" - IL_006e: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__1.<>p__0" - IL_0073: ldloc.s V_5 - IL_0075: callvirt "int System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" - IL_007a: stind.i4 - IL_007b: ldloc.3 - IL_007c: ldc.i4.1 - IL_007d: add - IL_007e: stloc.3 - IL_007f: ldloca.s V_4 - IL_0081: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" - IL_0086: brtrue.s IL_0028 - IL_0088: leave.s IL_0098 + IL_000d: br.s IL_0072 + IL_000f: ldloca.s V_1 + IL_0011: call "dynamic System.Collections.Generic.List.Enumerator.Current.get" + IL_0016: stloc.2 + IL_0017: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__1.<>p__0" + IL_001c: brtrue.s IL_005c + IL_001e: ldc.i4 0x100 + IL_0023: ldstr "Add" + IL_0028: ldnull + IL_0029: ldtoken "Program" + IL_002e: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_0033: ldc.i4.2 + IL_0034: newarr "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" + IL_0039: dup + IL_003a: ldc.i4.0 + IL_003b: ldc.i4.1 + IL_003c: ldnull + IL_003d: call "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)" + IL_0042: stelem.ref + IL_0043: dup + IL_0044: ldc.i4.1 + IL_0045: ldc.i4.0 + IL_0046: ldnull + IL_0047: call "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)" + IL_004c: stelem.ref + IL_004d: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, System.Collections.Generic.IEnumerable, System.Type, System.Collections.Generic.IEnumerable)" + IL_0052: call "System.Runtime.CompilerServices.CallSite, dynamic>> System.Runtime.CompilerServices.CallSite, dynamic>>.Create(System.Runtime.CompilerServices.CallSiteBinder)" + IL_0057: stsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__1.<>p__0" + IL_005c: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__1.<>p__0" + IL_0061: ldfld "System.Action, dynamic> System.Runtime.CompilerServices.CallSite, dynamic>>.Target" + IL_0066: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__1.<>p__0" + IL_006b: ldloc.0 + IL_006c: ldloc.2 + IL_006d: callvirt "void System.Action, dynamic>.Invoke(System.Runtime.CompilerServices.CallSite, System.Collections.Generic.List, dynamic)" + IL_0072: ldloca.s V_1 + IL_0074: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" + IL_0079: brtrue.s IL_000f + IL_007b: leave.s IL_008b } finally { - IL_008a: ldloca.s V_4 - IL_008c: constrained. "System.Collections.Generic.List.Enumerator" - IL_0092: callvirt "void System.IDisposable.Dispose()" - IL_0097: endfinally + IL_007d: ldloca.s V_1 + IL_007f: constrained. "System.Collections.Generic.List.Enumerator" + IL_0085: callvirt "void System.IDisposable.Dispose()" + IL_008a: endfinally } - IL_0098: ldloc.1 - IL_0099: ret - } + IL_008b: ldloc.0 + IL_008c: ret + } """); } @@ -20132,79 +20210,85 @@ static void Main() verifier.VerifyIL("Program.F1", """ { - // Code size 39 (0x27) - .maxstack 3 - .locals init (int V_0, - System.Span V_1, - int V_2) - IL_0000: ldc.i4.1 - IL_0001: stloc.0 - IL_0002: ldloc.0 - IL_0003: newobj "System.Collections.Generic.List..ctor(int)" - IL_0008: dup - IL_0009: ldloc.0 - IL_000a: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" - IL_000f: dup - IL_0010: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" - IL_0015: stloc.1 - IL_0016: ldc.i4.0 - IL_0017: stloc.2 - IL_0018: ldloca.s V_1 - IL_001a: ldloc.2 - IL_001b: call "ref object System.Span.this[int].get" - IL_0020: ldarg.0 - IL_0021: stind.ref - IL_0022: ldloc.2 - IL_0023: ldc.i4.1 - IL_0024: add - IL_0025: stloc.2 - IL_0026: ret + // Code size 99 (0x63) + .maxstack 9 + .locals init (System.Collections.Generic.List V_0) + IL_0000: newobj "System.Collections.Generic.List..ctor()" + IL_0005: stloc.0 + IL_0006: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_000b: brtrue.s IL_004b + IL_000d: ldc.i4 0x100 + IL_0012: ldstr "Add" + IL_0017: ldnull + IL_0018: ldtoken "Program" + IL_001d: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_0022: ldc.i4.2 + IL_0023: newarr "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" + IL_0028: dup + IL_0029: ldc.i4.0 + IL_002a: ldc.i4.1 + IL_002b: ldnull + IL_002c: call "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)" + IL_0031: stelem.ref + IL_0032: dup + IL_0033: ldc.i4.1 + IL_0034: ldc.i4.0 + IL_0035: ldnull + IL_0036: call "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)" + IL_003b: stelem.ref + IL_003c: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, System.Collections.Generic.IEnumerable, System.Type, System.Collections.Generic.IEnumerable)" + IL_0041: call "System.Runtime.CompilerServices.CallSite, dynamic>> System.Runtime.CompilerServices.CallSite, dynamic>>.Create(System.Runtime.CompilerServices.CallSiteBinder)" + IL_0046: stsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_004b: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_0050: ldfld "System.Action, dynamic> System.Runtime.CompilerServices.CallSite, dynamic>>.Target" + IL_0055: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__0.<>p__0" + IL_005a: ldloc.0 + IL_005b: ldarg.0 + IL_005c: callvirt "void System.Action, dynamic>.Invoke(System.Runtime.CompilerServices.CallSite, System.Collections.Generic.List, dynamic)" + IL_0061: ldloc.0 + IL_0062: ret } """); verifier.VerifyIL("Program.F2", """ { - // Code size 102 (0x66) - .maxstack 5 - .locals init (int V_0, - System.Span V_1, - int V_2) - IL_0000: ldc.i4.1 - IL_0001: stloc.0 - IL_0002: ldloc.0 - IL_0003: newobj "System.Collections.Generic.List..ctor(int)" - IL_0008: dup - IL_0009: ldloc.0 - IL_000a: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" - IL_000f: dup - IL_0010: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" - IL_0015: stloc.1 - IL_0016: ldc.i4.0 - IL_0017: stloc.2 - IL_0018: ldloca.s V_1 - IL_001a: ldloc.2 - IL_001b: call "ref int System.Span.this[int].get" - IL_0020: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__1.<>p__0" - IL_0025: brtrue.s IL_004b - IL_0027: ldc.i4.0 - IL_0028: ldtoken "int" - IL_002d: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_0032: ldtoken "Program" - IL_0037: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_003c: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" - IL_0041: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" - IL_0046: stsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__1.<>p__0" - IL_004b: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__1.<>p__0" - IL_0050: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" - IL_0055: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__1.<>p__0" - IL_005a: ldarg.0 - IL_005b: callvirt "int System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" - IL_0060: stind.i4 - IL_0061: ldloc.2 - IL_0062: ldc.i4.1 - IL_0063: add - IL_0064: stloc.2 - IL_0065: ret + // Code size 99 (0x63) + .maxstack 9 + .locals init (System.Collections.Generic.List V_0) + IL_0000: newobj "System.Collections.Generic.List..ctor()" + IL_0005: stloc.0 + IL_0006: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__1.<>p__0" + IL_000b: brtrue.s IL_004b + IL_000d: ldc.i4 0x100 + IL_0012: ldstr "Add" + IL_0017: ldnull + IL_0018: ldtoken "Program" + IL_001d: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_0022: ldc.i4.2 + IL_0023: newarr "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" + IL_0028: dup + IL_0029: ldc.i4.0 + IL_002a: ldc.i4.1 + IL_002b: ldnull + IL_002c: call "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)" + IL_0031: stelem.ref + IL_0032: dup + IL_0033: ldc.i4.1 + IL_0034: ldc.i4.0 + IL_0035: ldnull + IL_0036: call "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)" + IL_003b: stelem.ref + IL_003c: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, System.Collections.Generic.IEnumerable, System.Type, System.Collections.Generic.IEnumerable)" + IL_0041: call "System.Runtime.CompilerServices.CallSite, dynamic>> System.Runtime.CompilerServices.CallSite, dynamic>>.Create(System.Runtime.CompilerServices.CallSiteBinder)" + IL_0046: stsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__1.<>p__0" + IL_004b: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__1.<>p__0" + IL_0050: ldfld "System.Action, dynamic> System.Runtime.CompilerServices.CallSite, dynamic>>.Target" + IL_0055: ldsfld "System.Runtime.CompilerServices.CallSite, dynamic>> Program.<>o__1.<>p__0" + IL_005a: ldloc.0 + IL_005b: ldarg.0 + IL_005c: callvirt "void System.Action, dynamic>.Invoke(System.Runtime.CompilerServices.CallSite, System.Collections.Generic.List, dynamic)" + IL_0061: ldloc.0 + IL_0062: ret } """); } @@ -20829,6 +20913,397 @@ static void Report(MyCollection c) Diagnostic(ErrorCode.ERR_CollectionExpressionEscape, "[x, y, z]").WithArguments("MyCollection").WithLocation(12, 60)); } + [Fact] + public void Span_SingleElement() + { + var source = """ + using System; + + class Program + { + static void Main() => M(1); + + static void M(int x) + { + Span y = [x]; + x++; + Console.Write(y[0]); + Console.Write(x); + } + } + """; + + var verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net80, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("12")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 36 (0x24) + .maxstack 2 + .locals init (System.Span V_0, //y + int V_1) + IL_0000: ldarg.0 + IL_0001: stloc.1 + IL_0002: ldloca.s V_1 + IL_0004: newobj "System.Span..ctor(ref int)" + IL_0009: stloc.0 + IL_000a: ldarg.0 + IL_000b: ldc.i4.1 + IL_000c: add + IL_000d: starg.s V_0 + IL_000f: ldloca.s V_0 + IL_0011: ldc.i4.0 + IL_0012: call "ref int System.Span.this[int].get" + IL_0017: ldind.i4 + IL_0018: call "void System.Console.Write(int)" + IL_001d: ldarg.0 + IL_001e: call "void System.Console.Write(int)" + IL_0023: ret + } + """); + + verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net70, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("12")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 43 (0x2b) + .maxstack 5 + .locals init (System.Span V_0) //y + IL_0000: ldloca.s V_0 + IL_0002: ldc.i4.1 + IL_0003: newarr "int" + IL_0008: dup + IL_0009: ldc.i4.0 + IL_000a: ldarg.0 + IL_000b: stelem.i4 + IL_000c: call "System.Span..ctor(int[])" + IL_0011: ldarg.0 + IL_0012: ldc.i4.1 + IL_0013: add + IL_0014: starg.s V_0 + IL_0016: ldloca.s V_0 + IL_0018: ldc.i4.0 + IL_0019: call "ref int System.Span.this[int].get" + IL_001e: ldind.i4 + IL_001f: call "void System.Console.Write(int)" + IL_0024: ldarg.0 + IL_0025: call "void System.Console.Write(int)" + IL_002a: ret + } + """); + } + + [Fact] + public void Span_SingleElement_TempsAreNotReused() + { + var source = """ + using System; + + class Program + { + static void Main() => M(1); + + static void M(int x) + { + { + Span y = [x]; + Console.Write(y[0]); + y[0]++; + } + { + Span y = [x]; + Console.Write(y[0]); + } + } + } + """; + + var verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("11")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 62 (0x3e) + .maxstack 3 + .locals init (int V_0, + int V_1, + System.Span V_2, //y + System.Span V_3) //y + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.Span..ctor(ref int)" + IL_0009: stloc.2 + IL_000a: ldloca.s V_2 + IL_000c: ldc.i4.0 + IL_000d: call "ref int System.Span.this[int].get" + IL_0012: ldind.i4 + IL_0013: call "void System.Console.Write(int)" + IL_0018: ldloca.s V_2 + IL_001a: ldc.i4.0 + IL_001b: call "ref int System.Span.this[int].get" + IL_0020: dup + IL_0021: ldind.i4 + IL_0022: ldc.i4.1 + IL_0023: add + IL_0024: stind.i4 + IL_0025: ldarg.0 + IL_0026: stloc.1 + IL_0027: ldloca.s V_1 + IL_0029: newobj "System.Span..ctor(ref int)" + IL_002e: stloc.3 + IL_002f: ldloca.s V_3 + IL_0031: ldc.i4.0 + IL_0032: call "ref int System.Span.this[int].get" + IL_0037: ldind.i4 + IL_0038: call "void System.Console.Write(int)" + IL_003d: ret + } + """); + + verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net70, options: TestOptions.ReleaseExe, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("11")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 76 (0x4c) + .maxstack 5 + .locals init (System.Span V_0, //y + System.Span V_1) //y + IL_0000: ldloca.s V_0 + IL_0002: ldc.i4.1 + IL_0003: newarr "int" + IL_0008: dup + IL_0009: ldc.i4.0 + IL_000a: ldarg.0 + IL_000b: stelem.i4 + IL_000c: call "System.Span..ctor(int[])" + IL_0011: ldloca.s V_0 + IL_0013: ldc.i4.0 + IL_0014: call "ref int System.Span.this[int].get" + IL_0019: ldind.i4 + IL_001a: call "void System.Console.Write(int)" + IL_001f: ldloca.s V_0 + IL_0021: ldc.i4.0 + IL_0022: call "ref int System.Span.this[int].get" + IL_0027: dup + IL_0028: ldind.i4 + IL_0029: ldc.i4.1 + IL_002a: add + IL_002b: stind.i4 + IL_002c: ldloca.s V_1 + IL_002e: ldc.i4.1 + IL_002f: newarr "int" + IL_0034: dup + IL_0035: ldc.i4.0 + IL_0036: ldarg.0 + IL_0037: stelem.i4 + IL_0038: call "System.Span..ctor(int[])" + IL_003d: ldloca.s V_1 + IL_003f: ldc.i4.0 + IL_0040: call "ref int System.Span.this[int].get" + IL_0045: ldind.i4 + IL_0046: call "void System.Console.Write(int)" + IL_004b: ret + } + """); + } + + [Fact] + public void Span_SingleElement_TempsAreNotReused_SameBlock() + { + var source = """ + using System; + + class Program + { + static void Main() => M(1); + + static void M(int x) + { + Span y = [x]; + Console.Write(y[0]); + y[0]++; + + Span z = [x]; + Console.Write(z[0]); + } + } + """; + + var verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("11")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 62 (0x3e) + .maxstack 3 + .locals init (System.Span V_0, //y + System.Span V_1, //z + int V_2, + int V_3) + IL_0000: ldarg.0 + IL_0001: stloc.2 + IL_0002: ldloca.s V_2 + IL_0004: newobj "System.Span..ctor(ref int)" + IL_0009: stloc.0 + IL_000a: ldloca.s V_0 + IL_000c: ldc.i4.0 + IL_000d: call "ref int System.Span.this[int].get" + IL_0012: ldind.i4 + IL_0013: call "void System.Console.Write(int)" + IL_0018: ldloca.s V_0 + IL_001a: ldc.i4.0 + IL_001b: call "ref int System.Span.this[int].get" + IL_0020: dup + IL_0021: ldind.i4 + IL_0022: ldc.i4.1 + IL_0023: add + IL_0024: stind.i4 + IL_0025: ldarg.0 + IL_0026: stloc.3 + IL_0027: ldloca.s V_3 + IL_0029: newobj "System.Span..ctor(ref int)" + IL_002e: stloc.1 + IL_002f: ldloca.s V_1 + IL_0031: ldc.i4.0 + IL_0032: call "ref int System.Span.this[int].get" + IL_0037: ldind.i4 + IL_0038: call "void System.Console.Write(int)" + IL_003d: ret + } + """); + + verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net70, options: TestOptions.ReleaseExe, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("11")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 76 (0x4c) + .maxstack 5 + .locals init (System.Span V_0, //y + System.Span V_1) //z + IL_0000: ldloca.s V_0 + IL_0002: ldc.i4.1 + IL_0003: newarr "int" + IL_0008: dup + IL_0009: ldc.i4.0 + IL_000a: ldarg.0 + IL_000b: stelem.i4 + IL_000c: call "System.Span..ctor(int[])" + IL_0011: ldloca.s V_0 + IL_0013: ldc.i4.0 + IL_0014: call "ref int System.Span.this[int].get" + IL_0019: ldind.i4 + IL_001a: call "void System.Console.Write(int)" + IL_001f: ldloca.s V_0 + IL_0021: ldc.i4.0 + IL_0022: call "ref int System.Span.this[int].get" + IL_0027: dup + IL_0028: ldind.i4 + IL_0029: ldc.i4.1 + IL_002a: add + IL_002b: stind.i4 + IL_002c: ldloca.s V_1 + IL_002e: ldc.i4.1 + IL_002f: newarr "int" + IL_0034: dup + IL_0035: ldc.i4.0 + IL_0036: ldarg.0 + IL_0037: stelem.i4 + IL_0038: call "System.Span..ctor(int[])" + IL_003d: ldloca.s V_1 + IL_003f: ldc.i4.0 + IL_0040: call "ref int System.Span.this[int].get" + IL_0045: ldind.i4 + IL_0046: call "void System.Console.Write(int)" + IL_004b: ret + } + """); + } + + [Fact] + public void ReadOnlySpan_SingleElement() + { + var source = """ + using System; + + class Program + { + static void Main() => M(1); + + static void M(int x) + { + ReadOnlySpan y = [x]; + x++; + Console.Write(y[0]); + Console.Write(x); + } + } + """; + + var verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net80, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("12")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 36 (0x24) + .maxstack 2 + .locals init (System.ReadOnlySpan V_0, //y + int V_1) + IL_0000: ldarg.0 + IL_0001: stloc.1 + IL_0002: ldloca.s V_1 + IL_0004: newobj "System.ReadOnlySpan..ctor(ref readonly int)" + IL_0009: stloc.0 + IL_000a: ldarg.0 + IL_000b: ldc.i4.1 + IL_000c: add + IL_000d: starg.s V_0 + IL_000f: ldloca.s V_0 + IL_0011: ldc.i4.0 + IL_0012: call "ref readonly int System.ReadOnlySpan.this[int].get" + IL_0017: ldind.i4 + IL_0018: call "void System.Console.Write(int)" + IL_001d: ldarg.0 + IL_001e: call "void System.Console.Write(int)" + IL_0023: ret + } + """); + + verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net70, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("12")); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.M", """ + { + // Code size 43 (0x2b) + .maxstack 5 + .locals init (System.ReadOnlySpan V_0) //y + IL_0000: ldloca.s V_0 + IL_0002: ldc.i4.1 + IL_0003: newarr "int" + IL_0008: dup + IL_0009: ldc.i4.0 + IL_000a: ldarg.0 + IL_000b: stelem.i4 + IL_000c: call "System.ReadOnlySpan..ctor(int[])" + IL_0011: ldarg.0 + IL_0012: ldc.i4.1 + IL_0013: add + IL_0014: starg.s V_0 + IL_0016: ldloca.s V_0 + IL_0018: ldc.i4.0 + IL_0019: call "ref readonly int System.ReadOnlySpan.this[int].get" + IL_001e: ldind.i4 + IL_001f: call "void System.Console.Write(int)" + IL_0024: ldarg.0 + IL_0025: call "void System.Console.Write(int)" + IL_002a: ret + } + """); + } + [CombinatorialData] [Theory] public void SpanArgument_01([CombinatorialValues(TargetFramework.Net70, TargetFramework.Net80)] TargetFramework targetFramework) @@ -20854,81 +21329,48 @@ static void Main() new[] { source, s_collectionExtensionsWithSpan }, targetFramework: targetFramework, verify: Verification.Skipped, - symbolValidator: module => - { - if (targetFramework == TargetFramework.Net80) - { - var synthesizedType = module.GlobalNamespace.GetTypeMember("<>y__InlineArray1"); - Assert.Equal("<>y__InlineArray1", synthesizedType.ToTestDisplayString()); - Assert.Equal("<>y__InlineArray1`1", synthesizedType.MetadataName); - } - }, expectedOutput: IncludeExpectedOutput("[1], [2], [3], [4], ")); if (targetFramework == TargetFramework.Net80) { verifier.VerifyIL("Program.Main", """ { - // Code size 161 (0xa1) + // Code size 87 (0x57) .maxstack 2 - .locals init (<>y__InlineArray1 V_0, - <>y__InlineArray1 V_1, - <>y__InlineArray1 V_2, - <>y__InlineArray1 V_3, + .locals init (object V_0, + int? V_1, + int? V_2, + object V_3, System.Span V_4, System.ReadOnlySpan V_5) - IL_0000: ldloca.s V_0 - IL_0002: initobj "<>y__InlineArray1" - IL_0008: ldloca.s V_0 - IL_000a: ldc.i4.0 - IL_000b: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0010: ldc.i4.1 - IL_0011: box "int" - IL_0016: stind.ref - IL_0017: ldloca.s V_0 - IL_0019: ldc.i4.1 - IL_001a: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_001f: call "void Program.F1(System.Span)" - IL_0024: ldloca.s V_1 - IL_0026: initobj "<>y__InlineArray1" - IL_002c: ldloca.s V_1 - IL_002e: ldc.i4.0 - IL_002f: call "ref int? .InlineArrayElementRef<<>y__InlineArray1, int?>(ref <>y__InlineArray1, int)" - IL_0034: ldc.i4.2 - IL_0035: newobj "int?..ctor(int)" - IL_003a: stobj "int?" - IL_003f: ldloca.s V_1 - IL_0041: ldc.i4.1 - IL_0042: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, int?>(in <>y__InlineArray1, int)" - IL_0047: call "void Program.F2(System.ReadOnlySpan)" - IL_004c: ldloca.s V_2 - IL_004e: initobj "<>y__InlineArray1" - IL_0054: ldloca.s V_2 - IL_0056: ldc.i4.0 - IL_0057: call "ref int? .InlineArrayElementRef<<>y__InlineArray1, int?>(ref <>y__InlineArray1, int)" - IL_005c: ldc.i4.3 - IL_005d: newobj "int?..ctor(int)" - IL_0062: stobj "int?" - IL_0067: ldloca.s V_2 - IL_0069: ldc.i4.1 - IL_006a: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, int?>(ref <>y__InlineArray1, int)" - IL_006f: stloc.s V_4 - IL_0071: ldloca.s V_4 - IL_0073: call "void Program.F3(in System.Span)" - IL_0078: ldloca.s V_3 - IL_007a: initobj "<>y__InlineArray1" - IL_0080: ldloca.s V_3 - IL_0082: ldc.i4.0 - IL_0083: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0088: ldc.i4.4 - IL_0089: box "int" - IL_008e: stind.ref - IL_008f: ldloca.s V_3 - IL_0091: ldc.i4.1 - IL_0092: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, object>(in <>y__InlineArray1, int)" - IL_0097: stloc.s V_5 - IL_0099: ldloca.s V_5 - IL_009b: call "void Program.F4(in System.ReadOnlySpan)" - IL_00a0: ret + IL_0000: ldc.i4.1 + IL_0001: box "int" + IL_0006: stloc.0 + IL_0007: ldloca.s V_0 + IL_0009: newobj "System.Span..ctor(ref object)" + IL_000e: call "void Program.F1(System.Span)" + IL_0013: ldloca.s V_1 + IL_0015: ldc.i4.2 + IL_0016: call "int?..ctor(int)" + IL_001b: ldloca.s V_1 + IL_001d: newobj "System.ReadOnlySpan..ctor(ref readonly int?)" + IL_0022: call "void Program.F2(System.ReadOnlySpan)" + IL_0027: ldloca.s V_2 + IL_0029: ldc.i4.3 + IL_002a: call "int?..ctor(int)" + IL_002f: ldloca.s V_2 + IL_0031: newobj "System.Span..ctor(ref int?)" + IL_0036: stloc.s V_4 + IL_0038: ldloca.s V_4 + IL_003a: call "void Program.F3(in System.Span)" + IL_003f: ldc.i4.4 + IL_0040: box "int" + IL_0045: stloc.3 + IL_0046: ldloca.s V_3 + IL_0048: newobj "System.ReadOnlySpan..ctor(ref readonly object)" + IL_004d: stloc.s V_5 + IL_004f: ldloca.s V_5 + IL_0051: call "void Program.F4(in System.ReadOnlySpan)" + IL_0056: ret } """); } @@ -21016,65 +21458,41 @@ static void Main() expectedOutput: IncludeExpectedOutput("[1], [2], [3], [4], ")); verifier.VerifyIL("Program.Main", """ { - // Code size 149 (0x95) - .maxstack 2 - .locals init (<>y__InlineArray1 V_0, - <>y__InlineArray1 V_1, - <>y__InlineArray1 V_2, - <>y__InlineArray1 V_3) - IL_0000: ldloca.s V_0 - IL_0002: initobj "<>y__InlineArray1" - IL_0008: ldloca.s V_0 - IL_000a: ldc.i4.0 - IL_000b: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0010: ldc.i4.1 - IL_0011: box "int" - IL_0016: stind.ref - IL_0017: ldloca.s V_0 - IL_0019: ldc.i4.1 - IL_001a: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_001f: call "S Program.ReturnsStruct(System.Span)" - IL_0024: pop - IL_0025: ldloca.s V_1 - IL_0027: initobj "<>y__InlineArray1" - IL_002d: ldloca.s V_1 - IL_002f: ldc.i4.0 - IL_0030: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0035: ldc.i4.2 - IL_0036: box "int" - IL_003b: stind.ref - IL_003c: ldloca.s V_1 - IL_003e: ldc.i4.1 - IL_003f: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0044: call "R Program.ReturnsRefStruct(System.Span)" - IL_0049: pop - IL_004a: ldloca.s V_2 - IL_004c: initobj "<>y__InlineArray1" - IL_0052: ldloca.s V_2 - IL_0054: ldc.i4.0 - IL_0055: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_005a: ldc.i4.3 - IL_005b: box "int" - IL_0060: stind.ref - IL_0061: ldloca.s V_2 - IL_0063: ldc.i4.1 - IL_0064: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0069: call "ref int Program.ReturnsRef(System.Span)" - IL_006e: pop - IL_006f: ldloca.s V_3 - IL_0071: initobj "<>y__InlineArray1" - IL_0077: ldloca.s V_3 - IL_0079: ldc.i4.0 - IL_007a: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_007f: ldc.i4.4 - IL_0080: box "int" - IL_0085: stind.ref - IL_0086: ldloca.s V_3 - IL_0088: ldc.i4.1 - IL_0089: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_008e: call "ref readonly int Program.ReturnsRefReadOnly(System.Span)" - IL_0093: pop - IL_0094: ret + // Code size 81 (0x51) + .maxstack 1 + .locals init (object V_0, + object V_1, + object V_2, + object V_3) + IL_0000: ldc.i4.1 + IL_0001: box "int" + IL_0006: stloc.0 + IL_0007: ldloca.s V_0 + IL_0009: newobj "System.Span..ctor(ref object)" + IL_000e: call "S Program.ReturnsStruct(System.Span)" + IL_0013: pop + IL_0014: ldc.i4.2 + IL_0015: box "int" + IL_001a: stloc.1 + IL_001b: ldloca.s V_1 + IL_001d: newobj "System.Span..ctor(ref object)" + IL_0022: call "R Program.ReturnsRefStruct(System.Span)" + IL_0027: pop + IL_0028: ldc.i4.3 + IL_0029: box "int" + IL_002e: stloc.2 + IL_002f: ldloca.s V_2 + IL_0031: newobj "System.Span..ctor(ref object)" + IL_0036: call "ref int Program.ReturnsRef(System.Span)" + IL_003b: pop + IL_003c: ldc.i4.4 + IL_003d: box "int" + IL_0042: stloc.3 + IL_0043: ldloca.s V_3 + IL_0045: newobj "System.Span..ctor(ref object)" + IL_004a: call "ref readonly int Program.ReturnsRefReadOnly(System.Span)" + IL_004f: pop + IL_0050: ret } """); } @@ -21107,51 +21525,33 @@ static void Main() expectedOutput: IncludeExpectedOutput("[2], [3], [4], ")); verifier.VerifyIL("Program.Main", """ { - // Code size 112 (0x70) - .maxstack 2 - .locals init (<>y__InlineArray1 V_0, - <>y__InlineArray1 V_1, - <>y__InlineArray1 V_2) - IL_0000: ldloca.s V_0 - IL_0002: initobj "<>y__InlineArray1" - IL_0008: ldloca.s V_0 - IL_000a: ldc.i4.0 - IL_000b: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0010: ldc.i4.2 - IL_0011: box "int" - IL_0016: stind.ref - IL_0017: ldloca.s V_0 - IL_0019: ldc.i4.1 - IL_001a: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_001f: call "R Program.ReturnsRefStruct(scoped System.Span)" - IL_0024: pop - IL_0025: ldloca.s V_1 - IL_0027: initobj "<>y__InlineArray1" - IL_002d: ldloca.s V_1 - IL_002f: ldc.i4.0 - IL_0030: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0035: ldc.i4.3 - IL_0036: box "int" - IL_003b: stind.ref - IL_003c: ldloca.s V_1 - IL_003e: ldc.i4.1 - IL_003f: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0044: call "ref int Program.ReturnsRef(scoped System.Span)" - IL_0049: pop - IL_004a: ldloca.s V_2 - IL_004c: initobj "<>y__InlineArray1" - IL_0052: ldloca.s V_2 - IL_0054: ldc.i4.0 - IL_0055: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_005a: ldc.i4.4 - IL_005b: box "int" - IL_0060: stind.ref - IL_0061: ldloca.s V_2 - IL_0063: ldc.i4.1 - IL_0064: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0069: call "ref readonly int Program.ReturnsRefReadOnly(scoped System.Span)" - IL_006e: pop - IL_006f: ret + // Code size 61 (0x3d) + .maxstack 1 + .locals init (object V_0, + object V_1, + object V_2) + IL_0000: ldc.i4.2 + IL_0001: box "int" + IL_0006: stloc.0 + IL_0007: ldloca.s V_0 + IL_0009: newobj "System.Span..ctor(ref object)" + IL_000e: call "R Program.ReturnsRefStruct(scoped System.Span)" + IL_0013: pop + IL_0014: ldc.i4.3 + IL_0015: box "int" + IL_001a: stloc.1 + IL_001b: ldloca.s V_1 + IL_001d: newobj "System.Span..ctor(ref object)" + IL_0022: call "ref int Program.ReturnsRef(scoped System.Span)" + IL_0027: pop + IL_0028: ldc.i4.4 + IL_0029: box "int" + IL_002e: stloc.2 + IL_002f: ldloca.s V_2 + IL_0031: newobj "System.Span..ctor(ref object)" + IL_0036: call "ref readonly int Program.ReturnsRefReadOnly(scoped System.Span)" + IL_003b: pop + IL_003c: ret } """); } @@ -21236,105 +21636,69 @@ static void Main() expectedOutput: IncludeExpectedOutput("[1], [2], [3], [4], [5], [6], ")); verifier.VerifyIL("Program.Main", """ { - // Code size 280 (0x118) + // Code size 160 (0xa0) .maxstack 3 .locals init (S V_0, //s R1 V_1, //r1 R2 V_2, //r2 - <>y__InlineArray1 V_3, - <>y__InlineArray1 V_4, - <>y__InlineArray1 V_5, - <>y__InlineArray1 V_6, - <>y__InlineArray1 V_7, - <>y__InlineArray1 V_8) + int? V_3, + int? V_4, + int? V_5, + int? V_6, + int? V_7, + int? V_8) IL_0000: ldloca.s V_0 IL_0002: initobj "S" IL_0008: ldloca.s V_0 IL_000a: ldloca.s V_3 - IL_000c: initobj "<>y__InlineArray1" + IL_000c: ldc.i4.1 + IL_000d: call "int?..ctor(int)" IL_0012: ldloca.s V_3 - IL_0014: ldc.i4.0 - IL_0015: call "ref int? .InlineArrayElementRef<<>y__InlineArray1, int?>(ref <>y__InlineArray1, int)" - IL_001a: ldc.i4.1 - IL_001b: newobj "int?..ctor(int)" - IL_0020: stobj "int?" - IL_0025: ldloca.s V_3 - IL_0027: ldc.i4.1 - IL_0028: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, int?>(in <>y__InlineArray1, int)" - IL_002d: call "void S.M(System.ReadOnlySpan)" - IL_0032: ldloca.s V_0 - IL_0034: ldloca.s V_4 - IL_0036: initobj "<>y__InlineArray1" - IL_003c: ldloca.s V_4 - IL_003e: ldc.i4.0 - IL_003f: call "ref int? .InlineArrayElementRef<<>y__InlineArray1, int?>(ref <>y__InlineArray1, int)" - IL_0044: ldc.i4.2 - IL_0045: newobj "int?..ctor(int)" - IL_004a: stobj "int?" - IL_004f: ldloca.s V_4 - IL_0051: ldc.i4.1 - IL_0052: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, int?>(in <>y__InlineArray1, int)" - IL_0057: ldnull - IL_0058: call "void S.this[System.ReadOnlySpan].set" - IL_005d: ldloca.s V_1 - IL_005f: initobj "R1" - IL_0065: ldloca.s V_1 - IL_0067: ldloca.s V_5 - IL_0069: initobj "<>y__InlineArray1" - IL_006f: ldloca.s V_5 - IL_0071: ldc.i4.0 - IL_0072: call "ref int? .InlineArrayElementRef<<>y__InlineArray1, int?>(ref <>y__InlineArray1, int)" - IL_0077: ldc.i4.3 - IL_0078: newobj "int?..ctor(int)" - IL_007d: stobj "int?" - IL_0082: ldloca.s V_5 - IL_0084: ldc.i4.1 - IL_0085: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, int?>(in <>y__InlineArray1, int)" - IL_008a: call "void R1.M(System.ReadOnlySpan)" - IL_008f: ldloca.s V_1 - IL_0091: ldloca.s V_6 - IL_0093: initobj "<>y__InlineArray1" - IL_0099: ldloca.s V_6 - IL_009b: ldc.i4.0 - IL_009c: call "ref int? .InlineArrayElementRef<<>y__InlineArray1, int?>(ref <>y__InlineArray1, int)" - IL_00a1: ldc.i4.4 - IL_00a2: newobj "int?..ctor(int)" - IL_00a7: stobj "int?" - IL_00ac: ldloca.s V_6 - IL_00ae: ldc.i4.1 - IL_00af: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, int?>(in <>y__InlineArray1, int)" - IL_00b4: ldnull - IL_00b5: call "void R1.this[System.ReadOnlySpan].set" - IL_00ba: ldloca.s V_2 - IL_00bc: initobj "R2" - IL_00c2: ldloca.s V_2 - IL_00c4: ldloca.s V_7 - IL_00c6: initobj "<>y__InlineArray1" - IL_00cc: ldloca.s V_7 - IL_00ce: ldc.i4.0 - IL_00cf: call "ref int? .InlineArrayElementRef<<>y__InlineArray1, int?>(ref <>y__InlineArray1, int)" - IL_00d4: ldc.i4.5 - IL_00d5: newobj "int?..ctor(int)" - IL_00da: stobj "int?" - IL_00df: ldloca.s V_7 - IL_00e1: ldc.i4.1 - IL_00e2: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, int?>(in <>y__InlineArray1, int)" - IL_00e7: call "void R2.M(scoped System.ReadOnlySpan)" - IL_00ec: ldloca.s V_2 - IL_00ee: ldloca.s V_8 - IL_00f0: initobj "<>y__InlineArray1" - IL_00f6: ldloca.s V_8 - IL_00f8: ldc.i4.0 - IL_00f9: call "ref int? .InlineArrayElementRef<<>y__InlineArray1, int?>(ref <>y__InlineArray1, int)" - IL_00fe: ldc.i4.6 - IL_00ff: newobj "int?..ctor(int)" - IL_0104: stobj "int?" - IL_0109: ldloca.s V_8 - IL_010b: ldc.i4.1 - IL_010c: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, int?>(in <>y__InlineArray1, int)" - IL_0111: ldnull - IL_0112: call "void R2.this[scoped System.ReadOnlySpan].set" - IL_0117: ret + IL_0014: newobj "System.ReadOnlySpan..ctor(ref readonly int?)" + IL_0019: call "void S.M(System.ReadOnlySpan)" + IL_001e: ldloca.s V_0 + IL_0020: ldloca.s V_4 + IL_0022: ldc.i4.2 + IL_0023: call "int?..ctor(int)" + IL_0028: ldloca.s V_4 + IL_002a: newobj "System.ReadOnlySpan..ctor(ref readonly int?)" + IL_002f: ldnull + IL_0030: call "void S.this[System.ReadOnlySpan].set" + IL_0035: ldloca.s V_1 + IL_0037: initobj "R1" + IL_003d: ldloca.s V_1 + IL_003f: ldloca.s V_5 + IL_0041: ldc.i4.3 + IL_0042: call "int?..ctor(int)" + IL_0047: ldloca.s V_5 + IL_0049: newobj "System.ReadOnlySpan..ctor(ref readonly int?)" + IL_004e: call "void R1.M(System.ReadOnlySpan)" + IL_0053: ldloca.s V_1 + IL_0055: ldloca.s V_6 + IL_0057: ldc.i4.4 + IL_0058: call "int?..ctor(int)" + IL_005d: ldloca.s V_6 + IL_005f: newobj "System.ReadOnlySpan..ctor(ref readonly int?)" + IL_0064: ldnull + IL_0065: call "void R1.this[System.ReadOnlySpan].set" + IL_006a: ldloca.s V_2 + IL_006c: initobj "R2" + IL_0072: ldloca.s V_2 + IL_0074: ldloca.s V_7 + IL_0076: ldc.i4.5 + IL_0077: call "int?..ctor(int)" + IL_007c: ldloca.s V_7 + IL_007e: newobj "System.ReadOnlySpan..ctor(ref readonly int?)" + IL_0083: call "void R2.M(scoped System.ReadOnlySpan)" + IL_0088: ldloca.s V_2 + IL_008a: ldloca.s V_8 + IL_008c: ldc.i4.6 + IL_008d: call "int?..ctor(int)" + IL_0092: ldloca.s V_8 + IL_0094: newobj "System.ReadOnlySpan..ctor(ref readonly int?)" + IL_0099: ldnull + IL_009a: call "void R2.this[scoped System.ReadOnlySpan].set" + IL_009f: ret } """); } @@ -21374,73 +21738,49 @@ static void Main() expectedOutput: IncludeExpectedOutput("[3], [4], [5], [6], ")); verifier.VerifyIL("Program.Main", """ { - // Code size 187 (0xbb) + // Code size 107 (0x6b) .maxstack 3 .locals init (R1 V_0, //r1 R2 V_1, //r2 - <>y__InlineArray1 V_2, - <>y__InlineArray1 V_3, - <>y__InlineArray1 V_4, - <>y__InlineArray1 V_5) + int? V_2, + int? V_3, + int? V_4, + int? V_5) IL_0000: ldloca.s V_0 IL_0002: initobj "R1" IL_0008: ldloca.s V_0 IL_000a: ldloca.s V_2 - IL_000c: initobj "<>y__InlineArray1" + IL_000c: ldc.i4.3 + IL_000d: call "int?..ctor(int)" IL_0012: ldloca.s V_2 - IL_0014: ldc.i4.0 - IL_0015: call "ref int? .InlineArrayElementRef<<>y__InlineArray1, int?>(ref <>y__InlineArray1, int)" - IL_001a: ldc.i4.3 - IL_001b: newobj "int?..ctor(int)" - IL_0020: stobj "int?" - IL_0025: ldloca.s V_2 - IL_0027: ldc.i4.1 - IL_0028: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, int?>(in <>y__InlineArray1, int)" - IL_002d: call "void R1.M(System.ReadOnlySpan)" - IL_0032: ldloca.s V_0 - IL_0034: ldloca.s V_3 - IL_0036: initobj "<>y__InlineArray1" - IL_003c: ldloca.s V_3 - IL_003e: ldc.i4.0 - IL_003f: call "ref int? .InlineArrayElementRef<<>y__InlineArray1, int?>(ref <>y__InlineArray1, int)" - IL_0044: ldc.i4.4 - IL_0045: newobj "int?..ctor(int)" - IL_004a: stobj "int?" - IL_004f: ldloca.s V_3 - IL_0051: ldc.i4.1 - IL_0052: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, int?>(in <>y__InlineArray1, int)" - IL_0057: call "object R1.this[System.ReadOnlySpan].get" - IL_005c: pop - IL_005d: ldloca.s V_1 - IL_005f: initobj "R2" - IL_0065: ldloca.s V_1 - IL_0067: ldloca.s V_4 - IL_0069: initobj "<>y__InlineArray1" - IL_006f: ldloca.s V_4 - IL_0071: ldc.i4.0 - IL_0072: call "ref int? .InlineArrayElementRef<<>y__InlineArray1, int?>(ref <>y__InlineArray1, int)" - IL_0077: ldc.i4.5 - IL_0078: newobj "int?..ctor(int)" - IL_007d: stobj "int?" - IL_0082: ldloca.s V_4 - IL_0084: ldc.i4.1 - IL_0085: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, int?>(in <>y__InlineArray1, int)" - IL_008a: call "readonly void R2.M(System.ReadOnlySpan)" - IL_008f: ldloca.s V_1 - IL_0091: ldloca.s V_5 - IL_0093: initobj "<>y__InlineArray1" - IL_0099: ldloca.s V_5 - IL_009b: ldc.i4.0 - IL_009c: call "ref int? .InlineArrayElementRef<<>y__InlineArray1, int?>(ref <>y__InlineArray1, int)" - IL_00a1: ldc.i4.6 - IL_00a2: newobj "int?..ctor(int)" - IL_00a7: stobj "int?" - IL_00ac: ldloca.s V_5 - IL_00ae: ldc.i4.1 - IL_00af: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, int?>(in <>y__InlineArray1, int)" - IL_00b4: call "readonly object R2.this[System.ReadOnlySpan].get" - IL_00b9: pop - IL_00ba: ret + IL_0014: newobj "System.ReadOnlySpan..ctor(ref readonly int?)" + IL_0019: call "void R1.M(System.ReadOnlySpan)" + IL_001e: ldloca.s V_0 + IL_0020: ldloca.s V_3 + IL_0022: ldc.i4.4 + IL_0023: call "int?..ctor(int)" + IL_0028: ldloca.s V_3 + IL_002a: newobj "System.ReadOnlySpan..ctor(ref readonly int?)" + IL_002f: call "object R1.this[System.ReadOnlySpan].get" + IL_0034: pop + IL_0035: ldloca.s V_1 + IL_0037: initobj "R2" + IL_003d: ldloca.s V_1 + IL_003f: ldloca.s V_4 + IL_0041: ldc.i4.5 + IL_0042: call "int?..ctor(int)" + IL_0047: ldloca.s V_4 + IL_0049: newobj "System.ReadOnlySpan..ctor(ref readonly int?)" + IL_004e: call "readonly void R2.M(System.ReadOnlySpan)" + IL_0053: ldloca.s V_1 + IL_0055: ldloca.s V_5 + IL_0057: ldc.i4.6 + IL_0058: call "int?..ctor(int)" + IL_005d: ldloca.s V_5 + IL_005f: newobj "System.ReadOnlySpan..ctor(ref readonly int?)" + IL_0064: call "readonly object R2.this[System.ReadOnlySpan].get" + IL_0069: pop + IL_006a: ret } """); } @@ -21468,52 +21808,34 @@ static void Main() expectedOutput: IncludeExpectedOutput("[1], [3], [2], [4], ")); verifier.VerifyIL("Program.Main", """ { - // Code size 113 (0x71) - .maxstack 3 - .locals init (<>y__InlineArray1 V_0, - <>y__InlineArray1 V_1, - <>y__InlineArray1 V_2) - IL_0000: ldloca.s V_0 - IL_0002: initobj "<>y__InlineArray1" - IL_0008: ldloca.s V_0 - IL_000a: ldc.i4.0 - IL_000b: call "ref int .InlineArrayElementRef<<>y__InlineArray1, int>(ref <>y__InlineArray1, int)" - IL_0010: ldloca.s V_1 - IL_0012: initobj "<>y__InlineArray1" - IL_0018: ldloca.s V_1 - IL_001a: ldc.i4.0 - IL_001b: call "ref int .InlineArrayElementRef<<>y__InlineArray1, int>(ref <>y__InlineArray1, int)" - IL_0020: ldc.i4.1 - IL_0021: stind.i4 - IL_0022: ldloca.s V_1 - IL_0024: ldc.i4.1 - IL_0025: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, int>(ref <>y__InlineArray1, int)" - IL_002a: call "int Program.F1(System.Span)" - IL_002f: ldc.i4.2 - IL_0030: add - IL_0031: stind.i4 - IL_0032: ldloca.s V_0 - IL_0034: ldc.i4.1 - IL_0035: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, int>(ref <>y__InlineArray1, int)" - IL_003a: call "int Program.F1(System.Span)" - IL_003f: pop - IL_0040: ldloca.s V_2 - IL_0042: initobj "<>y__InlineArray1" - IL_0048: ldloca.s V_2 - IL_004a: ldc.i4.0 - IL_004b: call "ref int .InlineArrayElementRef<<>y__InlineArray1, int>(ref <>y__InlineArray1, int)" - IL_0050: ldtoken ".__StaticArrayInitTypeSize=4_Align=4 .26B25D457597A7B0463F9620F666DD10AA2C4373A505967C7C8D70922A2D6ECE4" - IL_0055: call "System.ReadOnlySpan System.Runtime.CompilerServices.RuntimeHelpers.CreateSpan(System.RuntimeFieldHandle)" - IL_005a: call "int Program.F2(System.ReadOnlySpan)" - IL_005f: ldc.i4.2 - IL_0060: add - IL_0061: stind.i4 - IL_0062: ldloca.s V_2 - IL_0064: ldc.i4.1 - IL_0065: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, int>(in <>y__InlineArray1, int)" - IL_006a: call "int Program.F2(System.ReadOnlySpan)" - IL_006f: pop - IL_0070: ret + // Code size 62 (0x3e) + .maxstack 2 + .locals init (int V_0, + int V_1, + int V_2) + IL_0000: ldc.i4.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.Span..ctor(ref int)" + IL_0009: call "int Program.F1(System.Span)" + IL_000e: ldc.i4.2 + IL_000f: add + IL_0010: stloc.1 + IL_0011: ldloca.s V_1 + IL_0013: newobj "System.Span..ctor(ref int)" + IL_0018: call "int Program.F1(System.Span)" + IL_001d: pop + IL_001e: ldtoken ".__StaticArrayInitTypeSize=4_Align=4 .26B25D457597A7B0463F9620F666DD10AA2C4373A505967C7C8D70922A2D6ECE4" + IL_0023: call "System.ReadOnlySpan System.Runtime.CompilerServices.RuntimeHelpers.CreateSpan(System.RuntimeFieldHandle)" + IL_0028: call "int Program.F2(System.ReadOnlySpan)" + IL_002d: ldc.i4.2 + IL_002e: add + IL_002f: stloc.2 + IL_0030: ldloca.s V_2 + IL_0032: newobj "System.ReadOnlySpan..ctor(ref readonly int)" + IL_0037: call "int Program.F2(System.ReadOnlySpan)" + IL_003c: pop + IL_003d: ret } """); } @@ -21551,67 +21873,43 @@ static ReadOnlySpan F2(scoped ReadOnlySpan x, ReadOnlySpan y) expectedOutput: IncludeExpectedOutput("[2], [1], [4], [3], ")); verifier.VerifyIL("Program.Main", """ { - // Code size 145 (0x91) + // Code size 77 (0x4d) .maxstack 2 - .locals init (<>y__InlineArray1 V_0, - <>y__InlineArray1 V_1, - <>y__InlineArray1 V_2, - <>y__InlineArray1 V_3, + .locals init (object V_0, + object V_1, + object V_2, + object V_3, System.Span V_4, System.ReadOnlySpan V_5) - IL_0000: ldloca.s V_0 - IL_0002: initobj "<>y__InlineArray1" - IL_0008: ldloca.s V_0 - IL_000a: ldc.i4.0 - IL_000b: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0010: ldc.i4.1 + IL_0000: ldc.i4.1 + IL_0001: box "int" + IL_0006: stloc.0 + IL_0007: ldloca.s V_0 + IL_0009: newobj "System.Span..ctor(ref object)" + IL_000e: stloc.s V_4 + IL_0010: ldc.i4.2 IL_0011: box "int" - IL_0016: stind.ref - IL_0017: ldloca.s V_0 - IL_0019: ldc.i4.1 - IL_001a: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_001f: stloc.s V_4 - IL_0021: ldloca.s V_1 - IL_0023: initobj "<>y__InlineArray1" - IL_0029: ldloca.s V_1 - IL_002b: ldc.i4.0 - IL_002c: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0031: ldc.i4.2 - IL_0032: box "int" - IL_0037: stind.ref - IL_0038: ldloca.s V_1 - IL_003a: ldc.i4.1 - IL_003b: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0040: ldloc.s V_4 - IL_0042: call "System.Span Program.F1(System.Span, scoped System.Span)" - IL_0047: pop - IL_0048: ldloca.s V_2 - IL_004a: initobj "<>y__InlineArray1" - IL_0050: ldloca.s V_2 - IL_0052: ldc.i4.0 - IL_0053: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0058: ldc.i4.3 - IL_0059: box "int" - IL_005e: stind.ref - IL_005f: ldloca.s V_2 - IL_0061: ldc.i4.1 - IL_0062: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, object>(in <>y__InlineArray1, int)" - IL_0067: stloc.s V_5 - IL_0069: ldloca.s V_3 - IL_006b: initobj "<>y__InlineArray1" - IL_0071: ldloca.s V_3 - IL_0073: ldc.i4.0 - IL_0074: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0079: ldc.i4.4 - IL_007a: box "int" - IL_007f: stind.ref - IL_0080: ldloca.s V_3 - IL_0082: ldc.i4.1 - IL_0083: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, object>(in <>y__InlineArray1, int)" - IL_0088: ldloc.s V_5 - IL_008a: call "System.ReadOnlySpan Program.F2(scoped System.ReadOnlySpan, System.ReadOnlySpan)" - IL_008f: pop - IL_0090: ret + IL_0016: stloc.1 + IL_0017: ldloca.s V_1 + IL_0019: newobj "System.Span..ctor(ref object)" + IL_001e: ldloc.s V_4 + IL_0020: call "System.Span Program.F1(System.Span, scoped System.Span)" + IL_0025: pop + IL_0026: ldc.i4.3 + IL_0027: box "int" + IL_002c: stloc.2 + IL_002d: ldloca.s V_2 + IL_002f: newobj "System.ReadOnlySpan..ctor(ref readonly object)" + IL_0034: stloc.s V_5 + IL_0036: ldc.i4.4 + IL_0037: box "int" + IL_003c: stloc.3 + IL_003d: ldloca.s V_3 + IL_003f: newobj "System.ReadOnlySpan..ctor(ref readonly object)" + IL_0044: ldloc.s V_5 + IL_0046: call "System.ReadOnlySpan Program.F2(scoped System.ReadOnlySpan, System.ReadOnlySpan)" + IL_004b: pop + IL_004c: ret } """); } @@ -21865,48 +22163,36 @@ static object[] F2() expectedOutput: IncludeExpectedOutput("[1], [2], ")); verifier.VerifyIL("Program.F1", """ { - // Code size 40 (0x28) - .maxstack 2 - .locals init (System.Span V_0, //s1 - <>y__InlineArray1 V_1) - IL_0000: ldloca.s V_1 - IL_0002: initobj "<>y__InlineArray1" - IL_0008: ldloca.s V_1 - IL_000a: ldc.i4.0 - IL_000b: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0010: ldc.i4.1 - IL_0011: box "int" - IL_0016: stind.ref - IL_0017: ldloca.s V_1 - IL_0019: ldc.i4.1 - IL_001a: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_001f: stloc.0 - IL_0020: ldloca.s V_0 - IL_0022: call "object[] System.Span.ToArray()" - IL_0027: ret + // Code size 23 (0x17) + .maxstack 1 + .locals init (System.Span V_0, //s1 + object V_1) + IL_0000: ldc.i4.1 + IL_0001: box "int" + IL_0006: stloc.1 + IL_0007: ldloca.s V_1 + IL_0009: newobj "System.Span..ctor(ref object)" + IL_000e: stloc.0 + IL_000f: ldloca.s V_0 + IL_0011: call "object[] System.Span.ToArray()" + IL_0016: ret } """); verifier.VerifyIL("Program.F2", """ { - // Code size 40 (0x28) - .maxstack 2 - .locals init (System.ReadOnlySpan V_0, //s2 - <>y__InlineArray1 V_1) - IL_0000: ldloca.s V_1 - IL_0002: initobj "<>y__InlineArray1" - IL_0008: ldloca.s V_1 - IL_000a: ldc.i4.0 - IL_000b: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0010: ldc.i4.2 - IL_0011: box "int" - IL_0016: stind.ref - IL_0017: ldloca.s V_1 - IL_0019: ldc.i4.1 - IL_001a: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, object>(in <>y__InlineArray1, int)" - IL_001f: stloc.0 - IL_0020: ldloca.s V_0 - IL_0022: call "object[] System.ReadOnlySpan.ToArray()" - IL_0027: ret + // Code size 23 (0x17) + .maxstack 1 + .locals init (System.ReadOnlySpan V_0, //s2 + object V_1) + IL_0000: ldc.i4.2 + IL_0001: box "int" + IL_0006: stloc.1 + IL_0007: ldloca.s V_1 + IL_0009: newobj "System.ReadOnlySpan..ctor(ref readonly object)" + IL_000e: stloc.0 + IL_000f: ldloca.s V_0 + IL_0011: call "object[] System.ReadOnlySpan.ToArray()" + IL_0016: ret } """); } @@ -21939,41 +22225,29 @@ static void Main() { verifier.VerifyIL("Program.Main", """ { - // Code size 79 (0x4f) - .maxstack 2 + // Code size 45 (0x2d) + .maxstack 1 .locals init (System.Span V_0, //x System.ReadOnlySpan V_1, //y - <>y__InlineArray1 V_2, - <>y__InlineArray1 V_3) - IL_0000: ldloca.s V_2 - IL_0002: initobj "<>y__InlineArray1" - IL_0008: ldloca.s V_2 - IL_000a: ldc.i4.0 - IL_000b: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0010: ldc.i4.1 - IL_0011: box "int" - IL_0016: stind.ref - IL_0017: ldloca.s V_2 - IL_0019: ldc.i4.1 - IL_001a: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_001f: stloc.0 - IL_0020: ldloca.s V_3 - IL_0022: initobj "<>y__InlineArray1" - IL_0028: ldloca.s V_3 - IL_002a: ldc.i4.0 - IL_002b: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0030: ldc.i4.2 - IL_0031: box "int" - IL_0036: stind.ref - IL_0037: ldloca.s V_3 - IL_0039: ldc.i4.1 - IL_003a: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, object>(in <>y__InlineArray1, int)" - IL_003f: stloc.1 - IL_0040: ldloca.s V_0 - IL_0042: call "void CollectionExtensions.Report(in System.Span)" - IL_0047: ldloca.s V_1 - IL_0049: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" - IL_004e: ret + object V_2, + object V_3) + IL_0000: ldc.i4.1 + IL_0001: box "int" + IL_0006: stloc.2 + IL_0007: ldloca.s V_2 + IL_0009: newobj "System.Span..ctor(ref object)" + IL_000e: stloc.0 + IL_000f: ldc.i4.2 + IL_0010: box "int" + IL_0015: stloc.3 + IL_0016: ldloca.s V_3 + IL_0018: newobj "System.ReadOnlySpan..ctor(ref readonly object)" + IL_001d: stloc.1 + IL_001e: ldloca.s V_0 + IL_0020: call "void CollectionExtensions.Report(in System.Span)" + IL_0025: ldloca.s V_1 + IL_0027: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_002c: ret } """); } @@ -22067,49 +22341,37 @@ static void Main() expectedOutput: IncludeExpectedOutput("[1], [2], ")); verifier.VerifyIL("Program.Main", """ { - // Code size 117 (0x75) - .maxstack 3 + // Code size 83 (0x53) + .maxstack 2 .locals init (R V_0, //x R V_1, //y - <>y__InlineArray1 V_2, - <>y__InlineArray1 V_3) + object V_2, + object V_3) IL_0000: ldloca.s V_0 IL_0002: initobj "R" IL_0008: ldloca.s V_1 IL_000a: initobj "R" IL_0010: ldloca.s V_0 - IL_0012: ldloca.s V_2 - IL_0014: initobj "<>y__InlineArray1" - IL_001a: ldloca.s V_2 - IL_001c: ldc.i4.0 - IL_001d: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0022: ldc.i4.1 - IL_0023: box "int" - IL_0028: stind.ref - IL_0029: ldloca.s V_2 - IL_002b: ldc.i4.1 - IL_002c: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, object>(in <>y__InlineArray1, int)" - IL_0031: stfld "System.ReadOnlySpan R.F" - IL_0036: ldloca.s V_1 - IL_0038: ldloca.s V_3 - IL_003a: initobj "<>y__InlineArray1" - IL_0040: ldloca.s V_3 - IL_0042: ldc.i4.0 - IL_0043: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0048: ldc.i4.2 - IL_0049: box "int" - IL_004e: stind.ref - IL_004f: ldloca.s V_3 - IL_0051: ldc.i4.1 - IL_0052: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, object>(in <>y__InlineArray1, int)" - IL_0057: stfld "System.ReadOnlySpan R.F" - IL_005c: ldloca.s V_0 - IL_005e: ldflda "System.ReadOnlySpan R.F" - IL_0063: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" - IL_0068: ldloca.s V_1 - IL_006a: ldflda "System.ReadOnlySpan R.F" - IL_006f: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" - IL_0074: ret + IL_0012: ldc.i4.1 + IL_0013: box "int" + IL_0018: stloc.2 + IL_0019: ldloca.s V_2 + IL_001b: newobj "System.ReadOnlySpan..ctor(ref readonly object)" + IL_0020: stfld "System.ReadOnlySpan R.F" + IL_0025: ldloca.s V_1 + IL_0027: ldc.i4.2 + IL_0028: box "int" + IL_002d: stloc.3 + IL_002e: ldloca.s V_3 + IL_0030: newobj "System.ReadOnlySpan..ctor(ref readonly object)" + IL_0035: stfld "System.ReadOnlySpan R.F" + IL_003a: ldloca.s V_0 + IL_003c: ldflda "System.ReadOnlySpan R.F" + IL_0041: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0046: ldloca.s V_1 + IL_0048: ldflda "System.ReadOnlySpan R.F" + IL_004d: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0052: ret } """); } @@ -22369,64 +22631,40 @@ static void F(bool b) { verifier.VerifyIL("Program.F", """ { - // Code size 134 (0x86) - .maxstack 2 - .locals init (<>y__InlineArray1 V_0, - <>y__InlineArray1 V_1, - <>y__InlineArray1 V_2, - <>y__InlineArray1 V_3) - IL_0000: ldloca.s V_0 - IL_0002: initobj "<>y__InlineArray1" - IL_0008: ldloca.s V_0 - IL_000a: ldc.i4.0 - IL_000b: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0010: ldc.i4.1 - IL_0011: box "int" - IL_0016: stind.ref - IL_0017: ldloca.s V_0 - IL_0019: ldc.i4.1 - IL_001a: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, object>(in <>y__InlineArray1, int)" - IL_001f: pop - IL_0020: ldarg.0 - IL_0021: brfalse.s IL_0045 - IL_0023: ldloca.s V_1 - IL_0025: initobj "<>y__InlineArray1" - IL_002b: ldloca.s V_1 - IL_002d: ldc.i4.0 - IL_002e: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0033: ldc.i4.2 - IL_0034: box "int" - IL_0039: stind.ref - IL_003a: ldloca.s V_1 - IL_003c: ldc.i4.1 - IL_003d: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, object>(in <>y__InlineArray1, int)" - IL_0042: pop - IL_0043: br.s IL_0065 - IL_0045: ldloca.s V_2 - IL_0047: initobj "<>y__InlineArray1" - IL_004d: ldloca.s V_2 - IL_004f: ldc.i4.0 - IL_0050: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0055: ldc.i4.3 - IL_0056: box "int" - IL_005b: stind.ref - IL_005c: ldloca.s V_2 - IL_005e: ldc.i4.1 - IL_005f: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, object>(in <>y__InlineArray1, int)" - IL_0064: pop - IL_0065: ldloca.s V_3 - IL_0067: initobj "<>y__InlineArray1" - IL_006d: ldloca.s V_3 - IL_006f: ldc.i4.0 - IL_0070: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0075: ldc.i4.4 - IL_0076: box "int" - IL_007b: stind.ref - IL_007c: ldloca.s V_3 - IL_007e: ldc.i4.1 - IL_007f: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, object>(in <>y__InlineArray1, int)" - IL_0084: pop - IL_0085: ret + // Code size 66 (0x42) + .maxstack 1 + .locals init (object V_0, + object V_1, + object V_2, + object V_3) + IL_0000: ldc.i4.1 + IL_0001: box "int" + IL_0006: stloc.0 + IL_0007: ldloca.s V_0 + IL_0009: newobj "System.ReadOnlySpan..ctor(ref readonly object)" + IL_000e: pop + IL_000f: ldarg.0 + IL_0010: brfalse.s IL_0023 + IL_0012: ldc.i4.2 + IL_0013: box "int" + IL_0018: stloc.1 + IL_0019: ldloca.s V_1 + IL_001b: newobj "System.ReadOnlySpan..ctor(ref readonly object)" + IL_0020: pop + IL_0021: br.s IL_0032 + IL_0023: ldc.i4.3 + IL_0024: box "int" + IL_0029: stloc.2 + IL_002a: ldloca.s V_2 + IL_002c: newobj "System.ReadOnlySpan..ctor(ref readonly object)" + IL_0031: pop + IL_0032: ldc.i4.4 + IL_0033: box "int" + IL_0038: stloc.3 + IL_0039: ldloca.s V_3 + IL_003b: newobj "System.ReadOnlySpan..ctor(ref readonly object)" + IL_0040: pop + IL_0041: ret } """); } @@ -22684,25 +22922,19 @@ void A2() expectedOutput: IncludeExpectedOutput("[1], [2], [3], [4], [1], ")); verifier.VerifyIL("Program.<>c__DisplayClass1_0.g__A2|1()", """ { - // Code size 44 (0x2c) - .maxstack 2 + // Code size 23 (0x17) + .maxstack 1 .locals init (System.Span V_0, //s3 - <>y__InlineArray1 V_1) - IL_0000: ldloca.s V_1 - IL_0002: initobj "<>y__InlineArray1" - IL_0008: ldloca.s V_1 - IL_000a: ldc.i4.0 - IL_000b: call "ref T .InlineArrayElementRef<<>y__InlineArray1, T>(ref <>y__InlineArray1, int)" - IL_0010: ldarg.0 - IL_0011: ldfld "T Program.<>c__DisplayClass1_0.z" - IL_0016: stobj "T" - IL_001b: ldloca.s V_1 - IL_001d: ldc.i4.1 - IL_001e: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, T>(ref <>y__InlineArray1, int)" - IL_0023: stloc.0 - IL_0024: ldloca.s V_0 - IL_0026: call "void CollectionExtensions.Report(in System.Span)" - IL_002b: ret + T V_1) + IL_0000: ldarg.0 + IL_0001: ldfld "T Program.<>c__DisplayClass1_0.z" + IL_0006: stloc.1 + IL_0007: ldloca.s V_1 + IL_0009: newobj "System.Span..ctor(ref T)" + IL_000e: stloc.0 + IL_000f: ldloca.s V_0 + IL_0011: call "void CollectionExtensions.Report(in System.Span)" + IL_0016: ret } """); } @@ -22744,10 +22976,10 @@ static void Main() expectedOutput: IncludeExpectedOutput("[b], [a], ")); verifier.VerifyIL("C.<>c.<.ctor>b__1_0(T, T)", """ { - // Code size 76 (0x4c) + // Code size 55 (0x37) .maxstack 2 .locals init (System.ReadOnlySpan V_0, //r1 - <>y__InlineArray1 V_1) + T V_1) IL_0000: ldsfld "System.Action C.<>c.<>9__1_1" IL_0005: dup IL_0006: brtrue.s IL_001f @@ -22759,20 +22991,14 @@ .locals init (System.ReadOnlySpan V_0, //r1 IL_001a: stsfld "System.Action C.<>c.<>9__1_1" IL_001f: ldarg.2 IL_0020: callvirt "void System.Action.Invoke(T)" - IL_0025: ldloca.s V_1 - IL_0027: initobj "<>y__InlineArray1" - IL_002d: ldloca.s V_1 - IL_002f: ldc.i4.0 - IL_0030: call "ref T .InlineArrayElementRef<<>y__InlineArray1, T>(ref <>y__InlineArray1, int)" - IL_0035: ldarg.1 - IL_0036: stobj "T" - IL_003b: ldloca.s V_1 - IL_003d: ldc.i4.1 - IL_003e: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, T>(in <>y__InlineArray1, int)" - IL_0043: stloc.0 - IL_0044: ldloca.s V_0 - IL_0046: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" - IL_004b: ret + IL_0025: ldarg.1 + IL_0026: stloc.1 + IL_0027: ldloca.s V_1 + IL_0029: newobj "System.ReadOnlySpan..ctor(ref readonly T)" + IL_002e: stloc.0 + IL_002f: ldloca.s V_0 + IL_0031: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0036: ret } """); } @@ -22801,60 +23027,48 @@ static void Main() var verifier = CompileAndVerify( new[] { source, s_collectionExtensionsWithSpan }, targetFramework: TargetFramework.Net80, - verify: Verification.Fails, + verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("[1], [2], Disposed, ")); verifier.VerifyIL("Program.Main", """ { - // Code size 97 (0x61) - .maxstack 2 + // Code size 64 (0x40) + .maxstack 1 .locals init (System.ReadOnlySpan V_0, //x Disposable V_1, //d System.ReadOnlySpan V_2, //y - <>y__InlineArray1 V_3, - <>y__InlineArray1 V_4) - IL_0000: ldloca.s V_3 - IL_0002: initobj "<>y__InlineArray1" - IL_0008: ldloca.s V_3 - IL_000a: ldc.i4.0 - IL_000b: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0010: ldc.i4.1 - IL_0011: box "int" - IL_0016: stind.ref - IL_0017: ldloca.s V_3 - IL_0019: ldc.i4.1 - IL_001a: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, object>(in <>y__InlineArray1, int)" - IL_001f: stloc.0 - IL_0020: newobj "Disposable..ctor()" - IL_0025: stloc.1 + object V_3, + object V_4) + IL_0000: ldc.i4.1 + IL_0001: box "int" + IL_0006: stloc.3 + IL_0007: ldloca.s V_3 + IL_0009: newobj "System.ReadOnlySpan..ctor(ref readonly object)" + IL_000e: stloc.0 + IL_000f: newobj "Disposable..ctor()" + IL_0014: stloc.1 .try { - IL_0026: ldloca.s V_4 - IL_0028: initobj "<>y__InlineArray1" - IL_002e: ldloca.s V_4 - IL_0030: ldc.i4.0 - IL_0031: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0036: ldc.i4.2 - IL_0037: box "int" - IL_003c: stind.ref - IL_003d: ldloca.s V_4 - IL_003f: ldc.i4.1 - IL_0040: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, object>(in <>y__InlineArray1, int)" - IL_0045: stloc.2 - IL_0046: ldloca.s V_0 - IL_0048: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" - IL_004d: ldloca.s V_2 - IL_004f: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" - IL_0054: leave.s IL_0060 + IL_0015: ldc.i4.2 + IL_0016: box "int" + IL_001b: stloc.s V_4 + IL_001d: ldloca.s V_4 + IL_001f: newobj "System.ReadOnlySpan..ctor(ref readonly object)" + IL_0024: stloc.2 + IL_0025: ldloca.s V_0 + IL_0027: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_002c: ldloca.s V_2 + IL_002e: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0033: leave.s IL_003f } finally { - IL_0056: ldloc.1 - IL_0057: brfalse.s IL_005f - IL_0059: ldloc.1 - IL_005a: callvirt "void System.IDisposable.Dispose()" - IL_005f: endfinally + IL_0035: ldloc.1 + IL_0036: brfalse.s IL_003e + IL_0038: ldloc.1 + IL_0039: callvirt "void System.IDisposable.Dispose()" + IL_003e: endfinally } - IL_0060: ret + IL_003f: ret } """); } @@ -23244,59 +23458,35 @@ static void Report(ReadOnlySpan s) """)); verifier.VerifyIL("Program.Main", """ { - // Code size 135 (0x87) - .maxstack 2 - .locals init (<>y__InlineArray1 V_0, - <>y__InlineArray1 V_1, - <>y__InlineArray1 V_2, - <>y__InlineArray1 V_3) - IL_0000: ldloca.s V_0 - IL_0002: initobj "<>y__InlineArray1" - IL_0008: ldloca.s V_0 - IL_000a: ldc.i4.0 - IL_000b: call "ref object .InlineArrayElementRef<<>y__InlineArray1, object>(ref <>y__InlineArray1, int)" - IL_0010: ldstr "1" - IL_0015: stind.ref - IL_0016: ldloca.s V_0 - IL_0018: ldc.i4.1 - IL_0019: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, object>(in <>y__InlineArray1, int)" - IL_001e: call "void Program.Report(System.ReadOnlySpan)" - IL_0023: ldloca.s V_1 - IL_0025: initobj "<>y__InlineArray1" - IL_002b: ldloca.s V_1 - IL_002d: ldc.i4.0 - IL_002e: call "ref string .InlineArrayElementRef<<>y__InlineArray1, string>(ref <>y__InlineArray1, int)" - IL_0033: ldstr "2" - IL_0038: stind.ref - IL_0039: ldloca.s V_1 - IL_003b: ldc.i4.1 - IL_003c: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, string>(in <>y__InlineArray1, int)" - IL_0041: call "void Program.Report(System.ReadOnlySpan)" - IL_0046: ldloca.s V_2 - IL_0048: initobj "<>y__InlineArray1" - IL_004e: ldloca.s V_2 - IL_0050: ldc.i4.0 - IL_0051: call "ref nint .InlineArrayElementRef<<>y__InlineArray1, nint>(ref <>y__InlineArray1, int)" - IL_0056: ldc.i4.3 - IL_0057: conv.i - IL_0058: stind.i - IL_0059: ldloca.s V_2 - IL_005b: ldc.i4.1 - IL_005c: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, nint>(in <>y__InlineArray1, int)" - IL_0061: call "void Program.Report(System.ReadOnlySpan)" - IL_0066: ldloca.s V_3 - IL_0068: initobj "<>y__InlineArray1" - IL_006e: ldloca.s V_3 - IL_0070: ldc.i4.0 - IL_0071: call "ref nuint .InlineArrayElementRef<<>y__InlineArray1, nuint>(ref <>y__InlineArray1, int)" - IL_0076: ldc.i4.4 - IL_0077: conv.i - IL_0078: stind.i - IL_0079: ldloca.s V_3 - IL_007b: ldc.i4.1 - IL_007c: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, nuint>(in <>y__InlineArray1, int)" - IL_0081: call "void Program.Report(System.ReadOnlySpan)" - IL_0086: ret + // Code size 67 (0x43) + .maxstack 1 + .locals init (object V_0, + string V_1, + nint V_2, + nuint V_3) + IL_0000: ldstr "1" + IL_0005: stloc.0 + IL_0006: ldloca.s V_0 + IL_0008: newobj "System.ReadOnlySpan..ctor(ref readonly object)" + IL_000d: call "void Program.Report(System.ReadOnlySpan)" + IL_0012: ldstr "2" + IL_0017: stloc.1 + IL_0018: ldloca.s V_1 + IL_001a: newobj "System.ReadOnlySpan..ctor(ref readonly string)" + IL_001f: call "void Program.Report(System.ReadOnlySpan)" + IL_0024: ldc.i4.3 + IL_0025: conv.i + IL_0026: stloc.2 + IL_0027: ldloca.s V_2 + IL_0029: newobj "System.ReadOnlySpan..ctor(ref readonly nint)" + IL_002e: call "void Program.Report(System.ReadOnlySpan)" + IL_0033: ldc.i4.4 + IL_0034: conv.i + IL_0035: stloc.3 + IL_0036: ldloca.s V_3 + IL_0038: newobj "System.ReadOnlySpan..ctor(ref readonly nuint)" + IL_003d: call "void Program.Report(System.ReadOnlySpan)" + IL_0042: ret } """); } @@ -28593,29 +28783,23 @@ .maxstack 4 verifier.VerifyDiagnostics(); verifier.VerifyIL("Program.M", """ { - // Code size 44 (0x2c) + // Code size 27 (0x1b) .maxstack 4 - .locals init (<>y__InlineArray1 V_0) - IL_0000: ldloca.s V_0 - IL_0002: initobj "<>y__InlineArray1" - IL_0008: ldloca.s V_0 - IL_000a: ldc.i4.0 - IL_000b: call "ref int .InlineArrayElementRef<<>y__InlineArray1, int>(ref <>y__InlineArray1, int)" - IL_0010: ldc.i4.1 - IL_0011: stind.i4 - IL_0012: ldloca.s V_0 - IL_0014: ldc.i4.1 - IL_0015: call "System.Span .InlineArrayAsSpan<<>y__InlineArray1, int>(ref <>y__InlineArray1, int)" - IL_001a: pop - IL_001b: ldc.i4.1 - IL_001c: newarr "int" - IL_0021: dup - IL_0022: ldc.i4.0 - IL_0023: ldc.i4.1 - IL_0024: stelem.i4 - IL_0025: call "System.Span System.Span.op_Implicit(int[])" - IL_002a: pop - IL_002b: ret + .locals init (int V_0) + IL_0000: ldc.i4.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.Span..ctor(ref int)" + IL_0009: pop + IL_000a: ldc.i4.1 + IL_000b: newarr "int" + IL_0010: dup + IL_0011: ldc.i4.0 + IL_0012: ldc.i4.1 + IL_0013: stelem.i4 + IL_0014: call "System.Span System.Span.op_Implicit(int[])" + IL_0019: pop + IL_001a: ret } """); } @@ -31488,92 +31672,80 @@ class D : C { } verifier.VerifyDiagnostics(); verifier.VerifyIL("C.Main", """ { - // Code size 185 (0xb9) + // Code size 151 (0x97) .maxstack 3 .locals init (System.ReadOnlySpan V_0, //li1 - <>y__InlineArray1 V_1, - <>y__InlineArray1 V_2, + D V_1, + D V_2, System.ReadOnlySpan V_3, System.ReadOnlySpan V_4, int V_5, C[] V_6, System.ReadOnlySpan.Enumerator V_7, D V_8) - IL_0000: ldloca.s V_1 - IL_0002: initobj "<>y__InlineArray1" - IL_0008: ldloca.s V_1 - IL_000a: ldc.i4.0 - IL_000b: call "ref D .InlineArrayElementRef<<>y__InlineArray1, D>(ref <>y__InlineArray1, int)" - IL_0010: newobj "D..ctor()" - IL_0015: stind.ref - IL_0016: ldloca.s V_1 - IL_0018: ldc.i4.1 - IL_0019: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, D>(in <>y__InlineArray1, int)" - IL_001e: stloc.0 - IL_001f: ldloca.s V_2 - IL_0021: initobj "<>y__InlineArray1" - IL_0027: ldloca.s V_2 - IL_0029: ldc.i4.0 - IL_002a: call "ref D .InlineArrayElementRef<<>y__InlineArray1, D>(ref <>y__InlineArray1, int)" - IL_002f: newobj "D..ctor()" - IL_0034: stind.ref - IL_0035: ldloca.s V_2 - IL_0037: ldc.i4.1 - IL_0038: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, D>(in <>y__InlineArray1, int)" - IL_003d: ldloc.0 - IL_003e: stloc.3 - IL_003f: stloc.s V_4 - IL_0041: ldc.i4.0 - IL_0042: stloc.s V_5 - IL_0044: ldloca.s V_3 - IL_0046: call "int System.ReadOnlySpan.Length.get" - IL_004b: ldloca.s V_4 - IL_004d: call "int System.ReadOnlySpan.Length.get" - IL_0052: add - IL_0053: newarr "C" - IL_0058: stloc.s V_6 - IL_005a: ldloca.s V_3 - IL_005c: call "System.ReadOnlySpan.Enumerator System.ReadOnlySpan.GetEnumerator()" - IL_0061: stloc.s V_7 - IL_0063: br.s IL_007c - IL_0065: ldloca.s V_7 - IL_0067: call "ref readonly D System.ReadOnlySpan.Enumerator.Current.get" - IL_006c: ldind.ref - IL_006d: stloc.s V_8 - IL_006f: ldloc.s V_6 - IL_0071: ldloc.s V_5 - IL_0073: ldloc.s V_8 - IL_0075: stelem.ref - IL_0076: ldloc.s V_5 - IL_0078: ldc.i4.1 - IL_0079: add - IL_007a: stloc.s V_5 - IL_007c: ldloca.s V_7 - IL_007e: call "bool System.ReadOnlySpan.Enumerator.MoveNext()" - IL_0083: brtrue.s IL_0065 - IL_0085: ldloca.s V_4 - IL_0087: call "System.ReadOnlySpan.Enumerator System.ReadOnlySpan.GetEnumerator()" - IL_008c: stloc.s V_7 - IL_008e: br.s IL_00a7 - IL_0090: ldloca.s V_7 - IL_0092: call "ref readonly D System.ReadOnlySpan.Enumerator.Current.get" - IL_0097: ldind.ref - IL_0098: stloc.s V_8 - IL_009a: ldloc.s V_6 - IL_009c: ldloc.s V_5 - IL_009e: ldloc.s V_8 - IL_00a0: stelem.ref - IL_00a1: ldloc.s V_5 - IL_00a3: ldc.i4.1 - IL_00a4: add - IL_00a5: stloc.s V_5 - IL_00a7: ldloca.s V_7 - IL_00a9: call "bool System.ReadOnlySpan.Enumerator.MoveNext()" - IL_00ae: brtrue.s IL_0090 - IL_00b0: ldloc.s V_6 - IL_00b2: ldc.i4.0 - IL_00b3: call "void CollectionExtensions.Report(object, bool)" - IL_00b8: ret + IL_0000: newobj "D..ctor()" + IL_0005: stloc.1 + IL_0006: ldloca.s V_1 + IL_0008: newobj "System.ReadOnlySpan..ctor(ref readonly D)" + IL_000d: stloc.0 + IL_000e: newobj "D..ctor()" + IL_0013: stloc.2 + IL_0014: ldloca.s V_2 + IL_0016: newobj "System.ReadOnlySpan..ctor(ref readonly D)" + IL_001b: ldloc.0 + IL_001c: stloc.3 + IL_001d: stloc.s V_4 + IL_001f: ldc.i4.0 + IL_0020: stloc.s V_5 + IL_0022: ldloca.s V_3 + IL_0024: call "int System.ReadOnlySpan.Length.get" + IL_0029: ldloca.s V_4 + IL_002b: call "int System.ReadOnlySpan.Length.get" + IL_0030: add + IL_0031: newarr "C" + IL_0036: stloc.s V_6 + IL_0038: ldloca.s V_3 + IL_003a: call "System.ReadOnlySpan.Enumerator System.ReadOnlySpan.GetEnumerator()" + IL_003f: stloc.s V_7 + IL_0041: br.s IL_005a + IL_0043: ldloca.s V_7 + IL_0045: call "ref readonly D System.ReadOnlySpan.Enumerator.Current.get" + IL_004a: ldind.ref + IL_004b: stloc.s V_8 + IL_004d: ldloc.s V_6 + IL_004f: ldloc.s V_5 + IL_0051: ldloc.s V_8 + IL_0053: stelem.ref + IL_0054: ldloc.s V_5 + IL_0056: ldc.i4.1 + IL_0057: add + IL_0058: stloc.s V_5 + IL_005a: ldloca.s V_7 + IL_005c: call "bool System.ReadOnlySpan.Enumerator.MoveNext()" + IL_0061: brtrue.s IL_0043 + IL_0063: ldloca.s V_4 + IL_0065: call "System.ReadOnlySpan.Enumerator System.ReadOnlySpan.GetEnumerator()" + IL_006a: stloc.s V_7 + IL_006c: br.s IL_0085 + IL_006e: ldloca.s V_7 + IL_0070: call "ref readonly D System.ReadOnlySpan.Enumerator.Current.get" + IL_0075: ldind.ref + IL_0076: stloc.s V_8 + IL_0078: ldloc.s V_6 + IL_007a: ldloc.s V_5 + IL_007c: ldloc.s V_8 + IL_007e: stelem.ref + IL_007f: ldloc.s V_5 + IL_0081: ldc.i4.1 + IL_0082: add + IL_0083: stloc.s V_5 + IL_0085: ldloca.s V_7 + IL_0087: call "bool System.ReadOnlySpan.Enumerator.MoveNext()" + IL_008c: brtrue.s IL_006e + IL_008e: ldloc.s V_6 + IL_0090: ldc.i4.0 + IL_0091: call "void CollectionExtensions.Report(object, bool)" + IL_0096: ret } """); } diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/InlineArrayTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/InlineArrayTests.cs index 24989c66df068..9b88f1fb7d741 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/InlineArrayTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/InlineArrayTests.cs @@ -14543,10 +14543,10 @@ static void M3(in C x) // Code size 18 (0x12) .maxstack 2 IL_0000: ldarg.0 - IL_0001: call ""ref TBuffer System.Runtime.CompilerServices.Unsafe.AsRef(scoped in TBuffer)"" + IL_0001: call ""ref TBuffer System.Runtime.CompilerServices.Unsafe.AsRef(scoped ref readonly TBuffer)"" IL_0006: call ""ref TElement System.Runtime.CompilerServices.Unsafe.As(ref TBuffer)"" IL_000b: ldarg.1 - IL_000c: call ""System.ReadOnlySpan System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan(scoped ref TElement, int)"" + IL_000c: call ""System.ReadOnlySpan System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan(scoped ref readonly TElement, int)"" IL_0011: ret } "); @@ -14640,7 +14640,7 @@ static void M3(in C x) // Code size 18 (0x12) .maxstack 2 IL_0000: ldarg.0 - IL_0001: call ""ref TBuffer System.Runtime.CompilerServices.Unsafe.AsRef(scoped in TBuffer)"" + IL_0001: call ""ref TBuffer System.Runtime.CompilerServices.Unsafe.AsRef(scoped ref readonly TBuffer)"" IL_0006: call ""ref TElement System.Runtime.CompilerServices.Unsafe.As(ref TBuffer)"" IL_000b: ldarg.1 IL_000c: call ""ref TElement System.Runtime.CompilerServices.Unsafe.Add(ref TElement, int)"" @@ -14731,7 +14731,7 @@ static void M3(in C x) // Code size 12 (0xc) .maxstack 1 IL_0000: ldarg.0 - IL_0001: call ""ref TBuffer System.Runtime.CompilerServices.Unsafe.AsRef(scoped in TBuffer)"" + IL_0001: call ""ref TBuffer System.Runtime.CompilerServices.Unsafe.AsRef(scoped ref readonly TBuffer)"" IL_0006: call ""ref TElement System.Runtime.CompilerServices.Unsafe.As(ref TBuffer)"" IL_000b: ret } @@ -18475,7 +18475,7 @@ .locals init (Buffer4& V_0, IL_0019: dup IL_001a: ldind.i4 IL_001b: call ""void System.Console.Write(int)"" - IL_0020: call ""ref int System.Runtime.CompilerServices.Unsafe.AsRef(scoped in int)"" + IL_0020: call ""ref int System.Runtime.CompilerServices.Unsafe.AsRef(scoped ref readonly int)"" IL_0025: dup IL_0026: ldind.i4 IL_0027: ldc.i4.m1 @@ -18702,7 +18702,7 @@ .locals init (Buffer4& V_0, IL_0019: dup IL_001a: ldind.i4 IL_001b: call ""void System.Console.Write(int)"" - IL_0020: call ""ref int System.Runtime.CompilerServices.Unsafe.AsRef(scoped in int)"" + IL_0020: call ""ref int System.Runtime.CompilerServices.Unsafe.AsRef(scoped ref readonly int)"" IL_0025: dup IL_0026: ldind.i4 IL_0027: ldc.i4.m1 diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs index 90cf200e7976c..99c3c6be72c5c 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs @@ -359,6 +359,85 @@ void assertAttributeData(string name) } } + [Fact] + public void Span_SingleElement_TempsAreNotReused() + { + var source = """ + using System; + + class Program + { + static void Main() + { + M(1); + M(2); + } + + static void M(params Span span) + { + Console.Write(span[0]); + Console.Write(span.Length); + } + } + """; + + var verifier = CompileAndVerify( + source, + targetFramework: TargetFramework.Net80, + verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped, + expectedOutput: ExpectedOutput("1121")); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.Main", """ + { + // Code size 29 (0x1d) + .maxstack 1 + .locals init (int V_0, + int V_1) + IL_0000: ldc.i4.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.Span..ctor(ref int)" + IL_0009: call "void Program.M(params System.Span)" + IL_000e: ldc.i4.2 + IL_000f: stloc.1 + IL_0010: ldloca.s V_1 + IL_0012: newobj "System.Span..ctor(ref int)" + IL_0017: call "void Program.M(params System.Span)" + IL_001c: ret + } + """); + + verifier = CompileAndVerify( + source, + targetFramework: TargetFramework.Net70, + verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped, + expectedOutput: ExpectedOutput("1121")); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.Main", """ + { + // Code size 41 (0x29) + .maxstack 4 + IL_0000: ldc.i4.1 + IL_0001: newarr "int" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldc.i4.1 + IL_0009: stelem.i4 + IL_000a: newobj "System.Span..ctor(int[])" + IL_000f: call "void Program.M(params System.Span)" + IL_0014: ldc.i4.1 + IL_0015: newarr "int" + IL_001a: dup + IL_001b: ldc.i4.0 + IL_001c: ldc.i4.2 + IL_001d: stelem.i4 + IL_001e: newobj "System.Span..ctor(int[])" + IL_0023: call "void Program.M(params System.Span)" + IL_0028: ret + } + """); + } + [Fact] public void String() { @@ -3883,12 +3962,12 @@ void verify(MetadataReference comp1Ref) comp2 = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseDll, parseOptions: TestOptions.Regular12); comp2.VerifyDiagnostics( - // (6,9): error CS8652: The feature 'params collections' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // (6,22): error CS1503: Argument 1: cannot convert from 'int' to 'params System.ReadOnlySpan' // Params.Test1(1); - Diagnostic(ErrorCode.ERR_FeatureInPreview, "Params.Test1(1)").WithArguments("params collections").WithLocation(6, 9), - // (9,9): error CS8652: The feature 'params collections' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + Diagnostic(ErrorCode.ERR_BadArgType, "1").WithArguments("1", "int", "params System.ReadOnlySpan").WithLocation(6, 22), + // (9,16): error CS7036: There is no argument given that corresponds to the required parameter 'a' of 'Params.Test1(params ReadOnlySpan)' // Params.Test1(); - Diagnostic(ErrorCode.ERR_FeatureInPreview, "Params.Test1()").WithArguments("params collections").WithLocation(9, 9) + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "Test1").WithArguments("a", "Params.Test1(params System.ReadOnlySpan)").WithLocation(9, 16) ); } } @@ -3950,9 +4029,12 @@ void verify(MetadataReference comp1Ref) comp2 = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseDll, parseOptions: TestOptions.Regular12); comp2.VerifyDiagnostics( - // (6,18): error CS8652: The feature 'params collections' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. - // var x1 = Params.Test1; - Diagnostic(ErrorCode.ERR_FeatureInPreview, "Params.Test1").WithArguments("params collections").WithLocation(6, 18) + // (9,12): error CS1503: Argument 1: cannot convert from 'int' to 'scoped System.ReadOnlySpan' + // x1(1); + Diagnostic(ErrorCode.ERR_BadArgType, "1").WithArguments("1", "int", "scoped System.ReadOnlySpan").WithLocation(9, 12), + // (12,9): error CS7036: There is no argument given that corresponds to the required parameter 'arg' of '' + // x1(); + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "x1").WithArguments("arg", "").WithLocation(12, 9) ); } } @@ -4055,20 +4137,77 @@ void Test2() void verify(MetadataReference comp1Ref) { var comp2 = CreateCompilation(src2, references: [comp1Ref], options: TestOptions.ReleaseDll, parseOptions: TestOptions.RegularPreview); - comp2.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp2, symbolValidator: checkParamsInDelegate1).VerifyDiagnostics(); + + void checkParamsInDelegate1(ModuleSymbol m) + { + Assert.True(m.GlobalNamespace.GetTypeMember("<>f__AnonymousDelegate0").DelegateInvokeMethod.Parameters.Last().IsParams); + } + + var expectedIL = @" +{ + // Code size 65 (0x41) + .maxstack 2 + IL_0000: ldsfld "" Program.<>O.<0>__Test1"" + IL_0005: dup + IL_0006: brtrue.s IL_001b + IL_0008: pop + IL_0009: ldnull + IL_000a: ldftn ""void Params.Test1(params System.Collections.Generic.IEnumerable)"" + IL_0010: newobj ""<>f__AnonymousDelegate0..ctor(object, System.IntPtr)"" + IL_0015: dup + IL_0016: stsfld "" Program.<>O.<0>__Test1"" + IL_001b: call ""void Program.M1<>()"" + IL_0020: ldsfld "" Program.<>O.<0>__Test1"" + IL_0025: dup + IL_0026: brtrue.s IL_003b + IL_0028: pop + IL_0029: ldnull + IL_002a: ldftn ""void Params.Test1(params System.Collections.Generic.IEnumerable)"" + IL_0030: newobj ""<>f__AnonymousDelegate0..ctor(object, System.IntPtr)"" + IL_0035: dup + IL_0036: stsfld "" Program.<>O.<0>__Test1"" + IL_003b: call ""void Program.M1<>()"" + IL_0040: ret +} +"; + verifier.VerifyIL("Program.Test1", expectedIL); comp2 = CreateCompilation(src2, references: [comp1Ref], options: TestOptions.ReleaseDll, parseOptions: TestOptions.RegularNext); - comp2.VerifyDiagnostics(); + verifier = CompileAndVerify(comp2, symbolValidator: checkParamsInDelegate1).VerifyDiagnostics(); + verifier.VerifyIL("Program.Test1", expectedIL); comp2 = CreateCompilation(src2, references: [comp1Ref], options: TestOptions.ReleaseDll, parseOptions: TestOptions.Regular12); - comp2.VerifyDiagnostics( - // (6,17): error CS8652: The feature 'params collections' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. - // var a = Params.Test1; - Diagnostic(ErrorCode.ERR_FeatureInPreview, "Params.Test1").WithArguments("params collections").WithLocation(6, 17), - // (8,12): error CS8652: The feature 'params collections' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. - // M1(Params.Test1); - Diagnostic(ErrorCode.ERR_FeatureInPreview, "Params.Test1").WithArguments("params collections").WithLocation(8, 12) - ); + verifier = CompileAndVerify(comp2, symbolValidator: checkParamsInDelegate1).VerifyDiagnostics(); + + // Note, we are using System.Action. which doesn't have params + verifier.VerifyIL("Program.Test1", @" +{ + // Code size 65 (0x41) + .maxstack 2 + IL_0000: ldsfld ""System.Action> Program.<>O.<0>__Test1"" + IL_0005: dup + IL_0006: brtrue.s IL_001b + IL_0008: pop + IL_0009: ldnull + IL_000a: ldftn ""void Params.Test1(params System.Collections.Generic.IEnumerable)"" + IL_0010: newobj ""System.Action>..ctor(object, System.IntPtr)"" + IL_0015: dup + IL_0016: stsfld ""System.Action> Program.<>O.<0>__Test1"" + IL_001b: call ""void Program.M1>>(System.Action>)"" + IL_0020: ldsfld ""System.Action> Program.<>O.<0>__Test1"" + IL_0025: dup + IL_0026: brtrue.s IL_003b + IL_0028: pop + IL_0029: ldnull + IL_002a: ldftn ""void Params.Test1(params System.Collections.Generic.IEnumerable)"" + IL_0030: newobj ""System.Action>..ctor(object, System.IntPtr)"" + IL_0035: dup + IL_0036: stsfld ""System.Action> Program.<>O.<0>__Test1"" + IL_003b: call ""void Program.M1>>(System.Action>)"" + IL_0040: ret +} +"); } } @@ -4163,6 +4302,51 @@ void verify(MetadataReference comp1Ref) } } + [Fact] + [WorkItem("https://github.com/dotnet/csharplang/issues/8061")] + public void LanguageVersion_07_CallSite() + { + var src1 = @" +public class Params +{ + static public void Test1(params System.ReadOnlySpan a) + { + System.Console.Write(""span""); + } + + static public void Test1(params long[] a) + { + System.Console.Write(""array""); + } +} +"; + var src2 = @" +class Program +{ + static void Main() + { + Params.Test1(1); + } +} +"; + var comp1 = CreateCompilation(src1, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseDll); + + verify(comp1.ToMetadataReference()); + verify(comp1.EmitToImageReference()); + + void verify(MetadataReference comp1Ref) + { + var comp2 = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + CompileAndVerify(comp2, expectedOutput: ExpectedOutput("span"), verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + + comp2 = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + CompileAndVerify(comp2, expectedOutput: ExpectedOutput("span"), verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + + comp2 = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + CompileAndVerify(comp2, expectedOutput: ExpectedOutput("array"), verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + } + } + [Fact] public void DelegateNaturalType_01() { @@ -4610,9 +4794,7 @@ static void Test(params System.Span a) CompileAndVerify( comp, - verify: ExecutionConditionUtil.IsMonoOrCoreClr ? - Verification.FailsILVerify with { ILVerifyMessage = "[InlineArrayAsSpan]: Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. { Offset = 0xc }" } - : Verification.Skipped, + verify: Verification.Skipped, expectedOutput: ExpectedOutput(@" int int")).VerifyDiagnostics(); @@ -4650,9 +4832,7 @@ class C3 : C2 {} CompileAndVerify( comp, - verify: ExecutionConditionUtil.IsMonoOrCoreClr ? - Verification.FailsILVerify with { ILVerifyMessage = "[InlineArrayAsSpan]: Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. { Offset = 0xc }" } - : Verification.Skipped, + verify: Verification.Skipped, expectedOutput: ExpectedOutput(@" C2 C2")).VerifyDiagnostics(); @@ -5866,7 +6046,6 @@ static void Main() Test(d, 2, 3); Test(2, d, 3); Test(2, 3, d); - Test(d, [3, 4]); Test2(d, d); Test2(d, 1); @@ -5880,7 +6059,6 @@ static void Main() Test2(d, 2, 3); Test2(2, d, 3); Test2(2, 3, d); - Test2(d, [3, 4]); } static void Test(int a, params IEnumerable b) @@ -5896,17 +6074,45 @@ static void Test2(int a, params T[] b) """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"CalledCalledCalledCalledCalledCalledCalled2Called2Called2Called2Called2Called2Called2Called2Called2Called2Called2Called2").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,9): error CS9218: 'Program.Test(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // Test(d); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "Test(d)").WithArguments("Program.Test(int, params System.Collections.Generic.IEnumerable)").WithLocation(8, 9), + // (9,9): error CS9218: 'Program.Test(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // Test(d, 1); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "Test(d, 1)").WithArguments("Program.Test(int, params System.Collections.Generic.IEnumerable)").WithLocation(9, 9), + // (10,9): error CS9218: 'Program.Test(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // Test(d, 2, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "Test(d, 2, 3)").WithArguments("Program.Test(int, params System.Collections.Generic.IEnumerable)").WithLocation(10, 9), + // (11,9): error CS9218: 'Program.Test(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // Test(2, d, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "Test(2, d, 3)").WithArguments("Program.Test(int, params System.Collections.Generic.IEnumerable)").WithLocation(11, 9), + // (12,9): error CS9218: 'Program.Test(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // Test(2, 3, d); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "Test(2, 3, d)").WithArguments("Program.Test(int, params System.Collections.Generic.IEnumerable)").WithLocation(12, 9) + ); } [Fact] public void DynamicInvocation_OrdinaryMethod_02_AmbiguousDynamicParamsArgument() { - var src = """ + var src1 = """ using System.Collections.Generic; +public static class Helpers +{ + public static void Test(params IEnumerable b) + { + System.Console.Write("Called"); + } +} +"""; + + var comp1Ref = CreateCompilation(src1).EmitToImageReference(); + + var src2 = """ +using static Helpers; + class Program { static void Main() @@ -5914,28 +6120,57 @@ static void Main() dynamic d = 1; Test(d); } - - static void Test(params IEnumerable b) - { - System.Console.Write("Called"); - } } """; - var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + var comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + + comp.VerifyDiagnostics( + // (8,14): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.Test(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // Test(d); + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Helpers.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 14) + ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); comp.VerifyDiagnostics( // (8,14): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.Test(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. // Test(d); - Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Program.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 14) + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Helpers.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 14) ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + + comp.VerifyEmitDiagnostics(); } [Fact] public void DynamicInvocation_OrdinaryMethod_03_Warning() { - var src = """ + var src1 = """ using System.Collections.Generic; +public static class Helpers +{ + public static void Test1(params IEnumerable b) => System.Console.Write("Called1"); + public static void Test1(System.DateTime b) => System.Console.Write("Called2"); + + public static void Test2(int x, System.DateTime b) => System.Console.Write("Called3"); + public static void Test2(long x, IEnumerable b) => System.Console.Write("Called4"); + public static void Test2(byte x, params IEnumerable b) => System.Console.Write("Called5"); + + public static void Test3(byte x, params IEnumerable b) => System.Console.Write("Called6"); + public static void Test3(byte x, byte y, byte z) => System.Console.Write("Called7"); + + public static void Test4(byte x, params IEnumerable b) => System.Console.Write("Called8"); + public static void Test4(byte x, long y, long z) => System.Console.Write("Called9"); +} +"""; + + var comp1Ref = CreateCompilation(src1).EmitToImageReference(); + + var src2 = """ +using static Helpers; + class Program { static void Main() @@ -5961,28 +6196,12 @@ static void Main() Test4(d3, x, x); // Called9 Test4(d3, d4, d4); // Called9 } - - static void Test1(params IEnumerable b) => System.Console.Write("Called1"); - static void Test1(System.DateTime b) => System.Console.Write("Called2"); - - static void Test2(int x, System.DateTime b) => System.Console.Write("Called3"); - static void Test2(long x, IEnumerable b) => System.Console.Write("Called4"); - static void Test2(byte x, params IEnumerable b) => System.Console.Write("Called5"); - - static void Test3(byte x, params IEnumerable b) => System.Console.Write("Called6"); - static void Test3(byte x, byte y, byte z) => System.Console.Write("Called7"); - - static void Test4(byte x, params IEnumerable b) => System.Console.Write("Called8"); - static void Test4(byte x, long y, long z) => System.Console.Write("Called9"); } """; - var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); + var comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called2Called1Called3Called5Called3Called4Called7Called6Called8Called9Called9"). - VerifyDiagnostics( - // (8,9): warning CS9220: One or more overloads of method 'Test1' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + var expected = new[] { + // (8,9): warning CS9220: One or more overloads of method 'Test1' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // Test1(d1); // Called2 Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionMethod, "Test1(d1)").WithArguments("Test1").WithLocation(8, 9), // (11,9): warning CS9220: One or more overloads of method 'Test1' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. @@ -5997,13 +6216,65 @@ static void Main() // (20,9): warning CS9220: One or more overloads of method 'Test3' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // Test3(d3, 1, 2); // Called7 Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionMethod, "Test3(d3, 1, 2)").WithArguments("Test3").WithLocation(20, 9), + // (21,9): error CS9218: 'Helpers.Test3(byte, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // Test3(d3, x, x); // Called6 + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "Test3(d3, x, x)").WithArguments("Helpers.Test3(byte, params System.Collections.Generic.IEnumerable)").WithLocation(21, 9), // (25,9): warning CS9220: One or more overloads of method 'Test4' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // Test4(d3, x, x); // Called9 Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionMethod, "Test4(d3, x, x)").WithArguments("Test4").WithLocation(25, 9), // (26,9): warning CS9220: One or more overloads of method 'Test4' having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // Test4(d3, d4, d4); // Called9 Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionMethod, "Test4(d3, d4, d4)").WithArguments("Test4").WithLocation(26, 9) + }; + + comp.VerifyDiagnostics(expected); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(expected); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + comp.VerifyDiagnostics( + // (21,19): error CS1503: Argument 2: cannot convert from 'int' to 'byte' + // Test3(d3, x, x); // Called6 + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("2", "int", "byte").WithLocation(21, 19), + // (21,22): error CS1503: Argument 3: cannot convert from 'int' to 'byte' + // Test3(d3, x, x); // Called6 + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("3", "int", "byte").WithLocation(21, 22) ); + + var src3 = """ +using static Helpers; + +class Program +{ + static void Main() + { + dynamic d1 = System.DateTime.Now; + Test1(d1); // Called2 + + dynamic d2 = new[] { 1 }; + Test1(d2); // Called1 + Test2(1, d1); // Called3 + Test2(1, d2); // Called5 + + int x = 1; + Test2(x, d1); // Called3 + Test2(x, d2); // Called4 + + dynamic d3 = (byte)1; + + dynamic d4 = x; + Test4(d3, x, x); // Called9 + Test4(d3, d4, d4); // Called9 + } +} +"""; + + comp = CreateCompilation(src3, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + CompileAndVerify( + comp, + expectedOutput: @"Called2Called1Called3Called5Called3Called4Called9Called9"). + VerifyDiagnostics(); } [Fact] @@ -6032,9 +6303,11 @@ static void Test(int a, System.DateTime b) """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called True").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,9): error CS9218: 'Program.Test(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // Test(d, 2); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "Test(d, 2)").WithArguments("Program.Test(int, params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + ); } [Fact] @@ -6059,37 +6332,69 @@ static void Test(params IEnumerable b) """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,9): error CS9218: 'Program.Test(params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // Test(d, 2, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "Test(d, 2, 3)").WithArguments("Program.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + ); } [Fact] public void DynamicInvocation_OrdinaryMethod_06_TypeArgumentInferenceError() { - var src1 = """ + var src0 = """ using System.Collections.Generic; -class Program +public class Program { - static void Main() + public static void Test(params IEnumerable b) { - dynamic d = 1; - Test(d, 2, 3); } +} +"""; + var comp0Ref = CreateCompilation(src0).EmitToImageReference(); - static void Test(params IEnumerable b) + var src1 = """ +using static Program; + +class P +{ + static void Main() { - System.Console.Write("Called"); + dynamic d = 1; + Test(d, 2, 3); + Test(d); } } """; - var comp1 = CreateCompilation(src1, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); + var comp1 = CreateCompilation(src1, references: [comp0Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); + + comp1.VerifyDiagnostics( + // (8,9): error CS9218: 'Program.Test(params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // Test(d, 2, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "Test(d, 2, 3)").WithArguments("Program.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 9), + // (9,14): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.Test(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // Test(d); + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Program.Test(params System.Collections.Generic.IEnumerable)").WithLocation(9, 14) + ); + + comp1 = CreateCompilation(src1, references: [comp0Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); comp1.VerifyDiagnostics( - // (8,9): error CS9218: The type arguments for method 'Program.Test(params IEnumerable)' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + // (8,9): error CS9218: 'Program.Test(params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // Test(d, 2, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "Test(d, 2, 3)").WithArguments("Program.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 9), + // (9,14): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.Test(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // Test(d); + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Program.Test(params System.Collections.Generic.IEnumerable)").WithLocation(9, 14) + ); + + comp1 = CreateCompilation(src1, references: [comp0Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + + comp1.VerifyEmitDiagnostics( + // (8,9): error CS1501: No overload for method 'Test' takes 3 arguments // Test(d, 2, 3); - Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs_DynamicArgumentWithParamsCollections, "Test(d, 2, 3)").WithArguments("Program.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + Diagnostic(ErrorCode.ERR_BadArgCount, "Test").WithArguments("Test", "3").WithLocation(8, 9) ); var src2 = """ @@ -6111,37 +6416,69 @@ static void Test(params IEnumerable b) """; var comp2 = CreateCompilation(src2, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp2, - expectedOutput: @"Called").VerifyDiagnostics(); + comp2.VerifyDiagnostics( + // (8,9): error CS9218: 'Program.Test(params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // Test(d, 2, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "Test(d, 2, 3)").WithArguments("Program.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + ); } [Fact] public void DynamicInvocation_OrdinaryMethod_07_TypeArgumentInferenceError() { - var src1 = """ + var src0 = """ using System.Collections.Generic; -class Program +public class Program { - static void Main() + public static void Test(T a, params IEnumerable b) { - dynamic d = 1; - Test(0, d, 2, 3); } +} +"""; + var comp0Ref = CreateCompilation(src0).EmitToImageReference(); - static void Test(T a, params IEnumerable b) + var src1 = """ +using static Program; + +class P +{ + static void Main() { - System.Console.Write("Called"); + dynamic d = 1; + Test(0, d, 2, 3); + Test(0, d); } } """; - var comp1 = CreateCompilation(src1, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); + var comp1 = CreateCompilation(src1, references: [comp0Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); + + comp1.VerifyDiagnostics( + // (8,9): error CS9218: 'Program.Test(T, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // Test(0, d, 2, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "Test(0, d, 2, 3)").WithArguments("Program.Test(T, params System.Collections.Generic.IEnumerable)").WithLocation(8, 9), + // (9,17): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.Test(T, params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // Test(0, d); + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Program.Test(T, params System.Collections.Generic.IEnumerable)").WithLocation(9, 17) + ); + + comp1 = CreateCompilation(src1, references: [comp0Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); comp1.VerifyDiagnostics( - // (8,9): error CS9218: The type arguments for method 'Program.Test(T, params IEnumerable)' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + // (8,9): error CS9218: 'Program.Test(T, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. // Test(0, d, 2, 3); - Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs_DynamicArgumentWithParamsCollections, "Test(0, d, 2, 3)").WithArguments("Program.Test(T, params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "Test(0, d, 2, 3)").WithArguments("Program.Test(T, params System.Collections.Generic.IEnumerable)").WithLocation(8, 9), + // (9,17): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.Test(T, params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // Test(0, d); + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Program.Test(T, params System.Collections.Generic.IEnumerable)").WithLocation(9, 17) + ); + + comp1 = CreateCompilation(src1, references: [comp0Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + + comp1.VerifyEmitDiagnostics( + // (8,9): error CS1501: No overload for method 'Test' takes 4 arguments + // Test(0, d, 2, 3); + Diagnostic(ErrorCode.ERR_BadArgCount, "Test").WithArguments("Test", "4").WithLocation(8, 9) ); var src2 = """ @@ -6163,9 +6500,11 @@ static void Test(T a, params IEnumerable b) """; var comp2 = CreateCompilation(src2, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp2, - expectedOutput: @"Called").VerifyDiagnostics(); + comp2.VerifyDiagnostics( + // (8,9): error CS9218: 'Program.Test(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // Test(0, d, 2, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "Test(0, d, 2, 3)").WithArguments("Program.Test(int, params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + ); } [Fact] @@ -6198,9 +6537,11 @@ public override void Test(params IEnumerable b) """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,9): error CS9218: 'C1.Test(params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // new C2().Test(d, 2, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new C2().Test(d, 2, 3)").WithArguments("C1.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + ); } [Fact] @@ -6233,9 +6574,11 @@ class C2 : C1 """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,9): error CS9218: 'C2.Test(params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // new C2().Test(d, 2, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new C2().Test(d, 2, 3)").WithArguments("C2.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + ); } [Fact] @@ -6273,9 +6616,11 @@ class C2 : C1 """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,9): error CS9218: 'C2.Test(params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // new C2().Test(d, 2, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new C2().Test(d, 2, 3)").WithArguments("C2.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + ); } [Fact] @@ -6313,9 +6658,11 @@ public override void Test(params IEnumerable b) """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,9): error CS9218: 'C2.Test(params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // new C3().Test(d, 2, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new C3().Test(d, 2, 3)").WithArguments("C2.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + ); } [Fact] @@ -6540,9 +6887,9 @@ static void Test(params IEnumerable b) var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); comp.VerifyDiagnostics( - // (8,14): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Test(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // (8,14): error CS8108: Cannot pass argument with dynamic type to params parameter 'b' of local function 'Test'. // Test(d); - Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 14) + Diagnostic(ErrorCode.ERR_DynamicLocalFunctionParamsParameter, "d").WithArguments("b", "Test").WithLocation(8, 14) ); } @@ -6569,9 +6916,9 @@ void Test(params IEnumerable b) var comp1 = CreateCompilation(src1, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); comp1.VerifyDiagnostics( - // (8,9): error CS9218: The type arguments for method 'Test(params IEnumerable)' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + // (8,9): error CS8322: Cannot pass argument with dynamic type to generic local function 'Test' with inferred type arguments. // Test(d, 2, 3); - Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs_DynamicArgumentWithParamsCollections, "Test(d, 2, 3)").WithArguments("Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + Diagnostic(ErrorCode.ERR_DynamicLocalFunctionTypeParameter, "Test(d, 2, 3)").WithArguments("Test").WithLocation(8, 9) ); var src2 = """ @@ -6621,9 +6968,9 @@ void Test(T a, params IEnumerable b) var comp1 = CreateCompilation(src1, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); comp1.VerifyDiagnostics( - // (8,9): error CS9218: The type arguments for method 'Test(T, params IEnumerable)' cannot be inferred from the usage because an argument with dynamic type is used and the method has a non-array params collection parameter. Try specifying the type arguments explicitly. + // (8,9): error CS8322: Cannot pass argument with dynamic type to generic local function 'Test' with inferred type arguments. // Test(0, d, 2, 3); - Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs_DynamicArgumentWithParamsCollections, "Test(0, d, 2, 3)").WithArguments("Test(T, params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + Diagnostic(ErrorCode.ERR_DynamicLocalFunctionTypeParameter, "Test(0, d, 2, 3)").WithArguments("Test").WithLocation(8, 9) ); var src2 = """ @@ -6676,7 +7023,6 @@ static void Main() test2(d, 2, 3); test2(2, d, 3); test2(2, 3, d); - test2(d, [3, 4]); void Test(int a, IEnumerable b) { @@ -6694,15 +7040,36 @@ void Test2(int a, int[] b) """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"CalledCalledCalledCalledCalledCalledCalled2Called2Called2Called2Called2Called2Called2").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (9,9): error CS9218: 'D.Invoke(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // test(d); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "test(d)").WithArguments("D.Invoke(int, params System.Collections.Generic.IEnumerable)").WithLocation(9, 9), + // (10,9): error CS9218: 'D.Invoke(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // test(d, 1); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "test(d, 1)").WithArguments("D.Invoke(int, params System.Collections.Generic.IEnumerable)").WithLocation(10, 9), + // (11,9): error CS9218: 'D.Invoke(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // test(d, 2, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "test(d, 2, 3)").WithArguments("D.Invoke(int, params System.Collections.Generic.IEnumerable)").WithLocation(11, 9), + // (12,9): error CS9218: 'D.Invoke(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // test(2, d, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "test(2, d, 3)").WithArguments("D.Invoke(int, params System.Collections.Generic.IEnumerable)").WithLocation(12, 9), + // (13,9): error CS9218: 'D.Invoke(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // test(2, 3, d); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "test(2, 3, d)").WithArguments("D.Invoke(int, params System.Collections.Generic.IEnumerable)").WithLocation(13, 9) + ); } [Fact] public void DynamicInvocation_Delegate_02_AmbiguousDynamicParamsArgument() { - var src = """ + var src1 = """ +using System.Collections.Generic; + +public delegate void D(params IEnumerable b); +"""; + var comp1Ref = CreateCompilation(src1).EmitToImageReference(); + + var src2 = """ using System.Collections.Generic; class Program @@ -6719,16 +7086,26 @@ static void Test(IEnumerable b) } } } - -delegate void D(params IEnumerable b); """; - var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + var comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + + comp.VerifyDiagnostics( + // (9,14): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'D.Invoke(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // test(d); + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("D.Invoke(params System.Collections.Generic.IEnumerable)").WithLocation(9, 14) + ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); comp.VerifyDiagnostics( // (9,14): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'D.Invoke(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. // test(d); Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("D.Invoke(params System.Collections.Generic.IEnumerable)").WithLocation(9, 14) ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + + comp.VerifyEmitDiagnostics(); } [Fact] @@ -6748,7 +7125,6 @@ static void Main() _ = c1[d, 2, 3]; _ = c1[2, d, 3]; _ = c1[2, 3, d]; - _ = c1[d, [3, 4]]; var c2 = new C2(); @@ -6758,7 +7134,6 @@ static void Main() _ = c2[d, 2, 3]; _ = c2[2, d, 3]; _ = c2[2, 3, d]; - _ = c2[d, [3, 4]]; } } @@ -6787,26 +7162,34 @@ class C2 """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"CalledCalledCalledCalledCalledCalledCalled2Called2Called2Called2Called2Called2Called2").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (9,13): error CS9218: 'C1.this[int, params IEnumerable]' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // _ = c1[d]; + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "c1[d]").WithArguments("C1.this[int, params System.Collections.Generic.IEnumerable]").WithLocation(9, 13), + // (10,13): error CS9218: 'C1.this[int, params IEnumerable]' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // _ = c1[d, 1]; + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "c1[d, 1]").WithArguments("C1.this[int, params System.Collections.Generic.IEnumerable]").WithLocation(10, 13), + // (11,13): error CS9218: 'C1.this[int, params IEnumerable]' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // _ = c1[d, 2, 3]; + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "c1[d, 2, 3]").WithArguments("C1.this[int, params System.Collections.Generic.IEnumerable]").WithLocation(11, 13), + // (12,13): error CS9218: 'C1.this[int, params IEnumerable]' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // _ = c1[2, d, 3]; + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "c1[2, d, 3]").WithArguments("C1.this[int, params System.Collections.Generic.IEnumerable]").WithLocation(12, 13), + // (13,13): error CS9218: 'C1.this[int, params IEnumerable]' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // _ = c1[2, 3, d]; + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "c1[2, 3, d]").WithArguments("C1.this[int, params System.Collections.Generic.IEnumerable]").WithLocation(13, 13) + ); } [Fact] public void DynamicInvocation_Indexer_02_AmbiguousDynamicParamsArgument() { - var src = """ + var src1 = """ using System.Collections.Generic; -class Program +public class Program { - static void Main() - { - dynamic d = 1; - _ = new Program()[d]; - } - - int this[params IEnumerable b] + public int this[params IEnumerable b] { get { @@ -6816,21 +7199,70 @@ int this[params IEnumerable b] } } """; - var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + var comp1Ref = CreateCompilation(src1).EmitToImageReference(); + + var src2 = """ +class P +{ + static void Main() + { + dynamic d = 1; + _ = new Program()[d]; + } +} +"""; + var comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); comp.VerifyDiagnostics( - // (8,27): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.this[params IEnumerable]', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // (6,27): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.this[params IEnumerable]', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. // _ = new Program()[d]; - Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Program.this[params System.Collections.Generic.IEnumerable]").WithLocation(8, 27) + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Program.this[params System.Collections.Generic.IEnumerable]").WithLocation(6, 27) ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + + comp.VerifyDiagnostics( + // (6,27): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.this[params IEnumerable]', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // _ = new Program()[d]; + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Program.this[params System.Collections.Generic.IEnumerable]").WithLocation(6, 27) + ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + + comp.VerifyEmitDiagnostics(); } [Fact] public void DynamicInvocation_Indexer_03_Warning() { - var src = """ + var src1 = """ using System.Collections.Generic; +public class Test1 +{ + public int this[params IEnumerable b] { get { System.Console.Write("Called1"); return 0; } } + public int this[System.DateTime b] { get { System.Console.Write("Called2"); return 0; } } +} +public class Test2 +{ + public int this[int x, System.DateTime b] { get { System.Console.Write("Called3"); return 0; } } + public int this[long x, IEnumerable b] { get { System.Console.Write("Called4"); return 0; } } + public int this[byte x, params IEnumerable b] { get { System.Console.Write("Called5"); return 0; } } +} +public class Test3 +{ + public int this[byte x, params IEnumerable b] { get { System.Console.Write("Called6"); return 0; } } + public int this[byte x, byte y, byte z] { get { System.Console.Write("Called7"); return 0; } } +} +public class Test4 +{ + public int this[byte x, params IEnumerable b] { get { System.Console.Write("Called8"); return 0; } } + public int this[byte x, long y, long z] { get { System.Console.Write("Called9"); return 0; } } +} +"""; + var comp1Ref = CreateCompilation(src1).EmitToImageReference(); + + var src2 = """ class Program { static void Main() @@ -6856,58 +7288,83 @@ static void Main() _ = new Test4()[d3, x, x]; // Called9 _ = new Test4()[d3, d4, d4]; // Called9 } +} +"""; + var comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - class Test1 - { - public int this[params IEnumerable b] { get { System.Console.Write("Called1"); return 0; } } - public int this[System.DateTime b] { get { System.Console.Write("Called2"); return 0; } } - } - class Test2 - { - public int this[int x, System.DateTime b] { get { System.Console.Write("Called3"); return 0; } } - public int this[long x, IEnumerable b] { get { System.Console.Write("Called4"); return 0; } } - public int this[byte x, params IEnumerable b] { get { System.Console.Write("Called5"); return 0; } } - } - class Test3 - { - public int this[byte x, params IEnumerable b] { get { System.Console.Write("Called6"); return 0; } } - public int this[byte x, byte y, byte z] { get { System.Console.Write("Called7"); return 0; } } - } - class Test4 + var expected = new[] { + // (6,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + // _ = new Test1()[d1]; // Called2 + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test1()[d1]").WithLocation(6, 13), + // (9,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + // _ = new Test1()[d2]; // Called1 + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test1()[d2]").WithLocation(9, 13), + // (10,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + // _ = new Test2()[1, d1]; // Called3 + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test2()[1, d1]").WithLocation(10, 13), + // (11,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + // _ = new Test2()[1, d2]; // Called5 + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test2()[1, d2]").WithLocation(11, 13), + // (18,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + // _ = new Test3()[d3, 1, 2]; // Called7 + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test3()[d3, 1, 2]").WithLocation(18, 13), + // (19,13): error CS9218: 'Test3.this[byte, params IEnumerable]' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // _ = new Test3()[d3, x, x]; // Called6 + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new Test3()[d3, x, x]").WithArguments("Test3.this[byte, params System.Collections.Generic.IEnumerable]").WithLocation(19, 13), + // (23,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + // _ = new Test4()[d3, x, x]; // Called9 + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test4()[d3, x, x]").WithLocation(23, 13), + // (24,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + // _ = new Test4()[d3, d4, d4]; // Called9 + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test4()[d3, d4, d4]").WithLocation(24, 13) + }; + + comp.VerifyDiagnostics(expected); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(expected); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + comp.VerifyDiagnostics( + // (19,29): error CS1503: Argument 2: cannot convert from 'int' to 'byte' + // _ = new Test3()[d3, x, x]; // Called6 + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("2", "int", "byte").WithLocation(19, 29), + // (19,32): error CS1503: Argument 3: cannot convert from 'int' to 'byte' + // _ = new Test3()[d3, x, x]; // Called6 + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("3", "int", "byte").WithLocation(19, 32) + ); + + var src3 = """ +class Program +{ + static void Main() { - public int this[byte x, params IEnumerable b] { get { System.Console.Write("Called8"); return 0; } } - public int this[byte x, long y, long z] { get { System.Console.Write("Called9"); return 0; } } + dynamic d1 = System.DateTime.Now; + _ = new Test1()[d1]; // Called2 + + dynamic d2 = new[] { 1 }; + _ = new Test1()[d2]; // Called1 + _ = new Test2()[1, d1]; // Called3 + _ = new Test2()[1, d2]; // Called5 + + int x = 1; + _ = new Test2()[x, d1]; // Called3 + _ = new Test2()[x, d2]; // Called4 + + dynamic d3 = (byte)1; + + dynamic d4 = x; + _ = new Test4()[d3, x, x]; // Called9 + _ = new Test4()[d3, d4, d4]; // Called9 } } """; - var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); + comp = CreateCompilation(src3, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); CompileAndVerify( comp, - expectedOutput: @"Called2Called1Called3Called5Called3Called4Called7Called6Called8Called9Called9"). - VerifyDiagnostics( - // (8,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - // _ = new Test1()[d1]; // Called2 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test1()[d1]").WithLocation(8, 13), - // (11,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - // _ = new Test1()[d2]; // Called1 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test1()[d2]").WithLocation(11, 13), - // (12,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - // _ = new Test2()[1, d1]; // Called3 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test2()[1, d1]").WithLocation(12, 13), - // (13,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - // _ = new Test2()[1, d2]; // Called5 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test2()[1, d2]").WithLocation(13, 13), - // (20,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - // _ = new Test3()[d3, 1, 2]; // Called7 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test3()[d3, 1, 2]").WithLocation(20, 13), - // (25,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - // _ = new Test4()[d3, x, x]; // Called9 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test4()[d3, x, x]").WithLocation(25, 13), - // (26,13): warning CS9221: One or more indexer overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. - // _ = new Test4()[d3, d4, d4]; // Called9 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionIndexer, "new Test4()[d3, d4, d4]").WithLocation(26, 13) - ); + expectedOutput: @"Called2Called1Called3Called5Called3Called4Called9Called9"). + VerifyDiagnostics(); } [Fact] @@ -6938,9 +7395,11 @@ static void Main() """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called True").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,13): error CS9218: 'Program.this[int, params IEnumerable]' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // _ = new Program()[d, 2]; + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new Program()[d, 2]").WithArguments("Program.this[int, params System.Collections.Generic.IEnumerable]").WithLocation(8, 13) + ); } [Fact] @@ -6969,9 +7428,11 @@ int this[params IEnumerable b] """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,13): error CS9218: 'Program.this[params IEnumerable]' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // _ = new Program()[d, 2, 3]; + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new Program()[d, 2, 3]").WithArguments("Program.this[params System.Collections.Generic.IEnumerable]").WithLocation(8, 13) + ); } [Fact] @@ -7008,9 +7469,11 @@ public override T this[params IEnumerable b] """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,13): error CS9218: 'C1.this[params IEnumerable]' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // _ = new C2()[d, 2, 3]; + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new C2()[d, 2, 3]").WithArguments("C1.this[params System.Collections.Generic.IEnumerable]").WithLocation(8, 13) + ); } [Fact] @@ -7047,9 +7510,11 @@ class C2 : C1 """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,13): error CS9218: 'C2.this[params IEnumerable]' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // _ = new C2()[d, 2, 3]; + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new C2()[d, 2, 3]").WithArguments("C2.this[params System.Collections.Generic.IEnumerable]").WithLocation(8, 13) + ); } [Fact] @@ -7091,9 +7556,11 @@ class C2 : C1 """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,13): error CS9218: 'C2.this[params IEnumerable]' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // _ = new C2()[d, 2, 3]; + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new C2()[d, 2, 3]").WithArguments("C2.this[params System.Collections.Generic.IEnumerable]").WithLocation(8, 13) + ); } [Fact] @@ -7135,9 +7602,11 @@ public override T this[params IEnumerable b] """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,13): error CS9218: 'C2.this[params IEnumerable]' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // _ = new C3()[d, 2, 3]; + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new C3()[d, 2, 3]").WithArguments("C2.this[params System.Collections.Generic.IEnumerable]").WithLocation(8, 13) + ); } [Fact] @@ -7322,17 +7791,49 @@ public Test2(int a, params int[] b) """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"CalledCalledCalledCalledCalledCalledCalled2Called2Called2Called2Called2Called2Called2").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,9): error CS9218: 'Program.Test.Test(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // new Test(d); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new Test(d)").WithArguments("Program.Test.Test(int, params System.Collections.Generic.IEnumerable)").WithLocation(8, 9), + // (9,9): error CS9218: 'Program.Test.Test(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // new Test(d, 1); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new Test(d, 1)").WithArguments("Program.Test.Test(int, params System.Collections.Generic.IEnumerable)").WithLocation(9, 9), + // (10,9): error CS9218: 'Program.Test.Test(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // new Test(d, 2, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new Test(d, 2, 3)").WithArguments("Program.Test.Test(int, params System.Collections.Generic.IEnumerable)").WithLocation(10, 9), + // (11,9): error CS9218: 'Program.Test.Test(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // new Test(2, d, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new Test(2, d, 3)").WithArguments("Program.Test.Test(int, params System.Collections.Generic.IEnumerable)").WithLocation(11, 9), + // (12,9): error CS9218: 'Program.Test.Test(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // new Test(2, 3, d); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new Test(2, 3, d)").WithArguments("Program.Test.Test(int, params System.Collections.Generic.IEnumerable)").WithLocation(12, 9), + // (13,21): error CS9176: There is no target type for the collection expression. + // new Test(d, [3, 4]); + Diagnostic(ErrorCode.ERR_CollectionExpressionNoTargetType, "[3, 4]").WithLocation(13, 21), + // (21,22): error CS9176: There is no target type for the collection expression. + // new Test2(d, [3, 4]); + Diagnostic(ErrorCode.ERR_CollectionExpressionNoTargetType, "[3, 4]").WithLocation(21, 22) + ); } [Fact] public void DynamicInvocation_Constructor_02_AmbiguousDynamicParamsArgument() { - var src = """ + var src1 = """ using System.Collections.Generic; +public class Test +{ + public Test(params IEnumerable b) + { + System.Console.Write("Called"); + } +} +"""; + + var comp1Ref = CreateCompilation(src1).EmitToImageReference(); + + var src2 = """ class Program { static void Main() @@ -7340,31 +7841,62 @@ static void Main() dynamic d = 1; new Test(d); } - - class Test - { - public Test(params IEnumerable b) - { - System.Console.Write("Called"); - } - } } """; - var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + var comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + + comp.VerifyDiagnostics( + // (6,18): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Test.Test(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // new Test(d); + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Test.Test(params System.Collections.Generic.IEnumerable)").WithLocation(6, 18) + ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); comp.VerifyDiagnostics( - // (8,18): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Program.Test.Test(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. + // (6,18): error CS9219: Ambiguity between expanded and normal forms of non-array params collection parameter of 'Test.Test(params IEnumerable)', the only corresponding argument has the type 'dynamic'. Consider casting the dynamic argument. // new Test(d); - Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Program.Test.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 18) + Diagnostic(ErrorCode.ERR_ParamsCollectionAmbiguousDynamicArgument, "d").WithArguments("Test.Test(params System.Collections.Generic.IEnumerable)").WithLocation(6, 18) ); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + + comp.VerifyEmitDiagnostics(); } [Fact] public void DynamicInvocation_Constructor_03_Warning() { - var src = """ + var src1 = """ using System.Collections.Generic; +public class Test1 +{ + public Test1(params IEnumerable b) => System.Console.Write("Called1"); + public Test1(System.DateTime b) => System.Console.Write("Called2"); +} + +public class Test2 +{ + public Test2(int x, System.DateTime b) => System.Console.Write("Called3"); + public Test2(long x, IEnumerable b) => System.Console.Write("Called4"); + public Test2(byte x, params IEnumerable b) => System.Console.Write("Called5"); +} + +public class Test3 +{ + public Test3(byte x, params IEnumerable b) => System.Console.Write("Called6"); + public Test3(byte x, byte y, byte z) => System.Console.Write("Called7"); +} + +public class Test4 +{ + public Test4(byte x, params IEnumerable b) => System.Console.Write("Called8"); + public Test4(byte x, long y, long z) => System.Console.Write("Called9"); +} +"""; + + var src2 = """ class Program { static void Main() @@ -7390,61 +7922,87 @@ static void Main() new Test4(d3, x, x); // Called9 new Test4(d3, d4, d4); // Called9 } - - class Test1 - { - public Test1(params IEnumerable b) => System.Console.Write("Called1"); - public Test1(System.DateTime b) => System.Console.Write("Called2"); - } - - class Test2 - { - public Test2(int x, System.DateTime b) => System.Console.Write("Called3"); - public Test2(long x, IEnumerable b) => System.Console.Write("Called4"); - public Test2(byte x, params IEnumerable b) => System.Console.Write("Called5"); - } - - class Test3 - { - public Test3(byte x, params IEnumerable b) => System.Console.Write("Called6"); - public Test3(byte x, byte y, byte z) => System.Console.Write("Called7"); - } - - class Test4 - { - public Test4(byte x, params IEnumerable b) => System.Console.Write("Called8"); - public Test4(byte x, long y, long z) => System.Console.Write("Called9"); - } } """; - var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called2Called1Called3Called5Called3Called4Called7Called6Called8Called9Called9"). - VerifyDiagnostics( - // (8,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + var comp1Ref = CreateCompilation(src1).EmitToImageReference(); + + var comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); + + var expected = new[] { + // (6,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // new Test1(d1); // Called2 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test1(d1)").WithLocation(8, 9), - // (11,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test1(d1)").WithLocation(6, 9), + // (9,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // new Test1(d2); // Called1 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test1(d2)").WithLocation(11, 9), - // (12,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test1(d2)").WithLocation(9, 9), + // (10,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // new Test2(1, d1); // Called3 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test2(1, d1)").WithLocation(12, 9), - // (13,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test2(1, d1)").WithLocation(10, 9), + // (11,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // new Test2(1, d2); // Called5 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test2(1, d2)").WithLocation(13, 9), - // (20,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test2(1, d2)").WithLocation(11, 9), + // (18,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // new Test3(d3, 1, 2); // Called7 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test3(d3, 1, 2)").WithLocation(20, 9), - // (25,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test3(d3, 1, 2)").WithLocation(18, 9), + // (19,9): error CS9218: 'Test3.Test3(byte, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // new Test3(d3, x, x); // Called6 + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new Test3(d3, x, x)").WithArguments("Test3.Test3(byte, params System.Collections.Generic.IEnumerable)").WithLocation(19, 9), + // (23,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // new Test4(d3, x, x); // Called9 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test4(d3, x, x)").WithLocation(25, 9), - // (26,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test4(d3, x, x)").WithLocation(23, 9), + // (24,9): warning CS9222: One or more constructor overloads having non-array params collection parameter might be applicable only in expanded form which is not supported during dynamic dispatch. // new Test4(d3, d4, d4); // Called9 - Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test4(d3, d4, d4)").WithLocation(26, 9) + Diagnostic(ErrorCode.WRN_DynamicDispatchToParamsCollectionConstructor, "new Test4(d3, d4, d4)").WithLocation(24, 9) + }; + + comp.VerifyDiagnostics(expected); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(expected); + + comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + comp.VerifyDiagnostics( + // (19,23): error CS1503: Argument 2: cannot convert from 'int' to 'byte' + // new Test3(d3, x, x); // Called6 + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("2", "int", "byte").WithLocation(19, 23), + // (19,26): error CS1503: Argument 3: cannot convert from 'int' to 'byte' + // new Test3(d3, x, x); // Called6 + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("3", "int", "byte").WithLocation(19, 26) ); + + var src3 = """ +class Program +{ + static void Main() + { + dynamic d1 = System.DateTime.Now; + new Test1(d1); // Called2 + + dynamic d2 = new[] { 1 }; + new Test1(d2); // Called1 + new Test2(1, d1); // Called3 + new Test2(1, d2); // Called5 + + int x = 1; + new Test2(x, d1); // Called3 + new Test2(x, d2); // Called4 + + dynamic d3 = (byte)1; + + dynamic d4 = x; + new Test4(d3, x, x); // Called9 + new Test4(d3, d4, d4); // Called9 + } +} +"""; + + comp = CreateCompilation(src3, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular12); + + CompileAndVerify( + comp, + expectedOutput: @"Called2Called1Called3Called5Called3Called4Called9Called9"). + VerifyDiagnostics(); } [Fact] @@ -7476,9 +8034,11 @@ public Test(int a, System.DateTime b) """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called True").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,9): error CS9218: 'Program.Test.Test(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // new Test(d, 2); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new Test(d, 2)").WithArguments("Program.Test.Test(int, params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + ); } [Fact] @@ -7506,9 +8066,11 @@ public Test(params IEnumerable b) """; var comp = CreateCompilation(src, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - CompileAndVerify( - comp, - expectedOutput: @"Called").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,9): error CS9218: 'Program.Test.Test(params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // new Test(d, 2, 3); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new Test(d, 2, 3)").WithArguments("Program.Test.Test(params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) + ); } [Fact] @@ -7576,7 +8138,10 @@ public Test(int a, params IEnumerable b) comp.VerifyDiagnostics( // (8,9): error CS0144: Cannot create an instance of the abstract type or interface 'Program.Test' // new Test(d); - Diagnostic(ErrorCode.ERR_NoNewAbstract, "new Test(d)").WithArguments("Program.Test").WithLocation(8, 9) + Diagnostic(ErrorCode.ERR_NoNewAbstract, "new Test(d)").WithArguments("Program.Test").WithLocation(8, 9), + // (8,9): error CS9218: 'Program.Test.Test(int, params IEnumerable)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // new Test(d); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, "new Test(d)").WithArguments("Program.Test.Test(int, params System.Collections.Generic.IEnumerable)").WithLocation(8, 9) ); } @@ -15137,5 +15702,117 @@ static void Main() Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("1", "int", "params MyCollection").WithLocation(19, 14) ); } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/73346")] + public void ParameterTypeSpecificity_01() + { + string source = """ +using System; + +namespace OverloadResolutionRepro +{ + public class C + { + public void Method(params Func[] projections) => Console.Write(1); + public void Method(params Func>[] projections) => Console.Write(2); + } + + public class Bar + { + public Wrapper WrappedValue { get; set; } = new Wrapper(); + } + + public struct Wrapper + { + } + + public class EntryPoint + { + static void Main() + { + new C().Method(x => x.WrappedValue); + } + } +} +"""; + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/73346")] + public void ParameterTypeSpecificity_02() + { + string source = """ +using System; + +namespace OverloadResolutionRepro +{ + public class C + { + public C(params Func[] projections) => Console.Write(1); + public C(params Func>[] projections) => Console.Write(2); + } + + public class Bar + { + public Wrapper WrappedValue { get; set; } = new Wrapper(); + } + + public struct Wrapper + { + } + + public class EntryPoint + { + static void Main() + { + new C>(x => x.WrappedValue); + } + } +} +"""; + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/73346")] + public void ParameterTypeSpecificity_03() + { + string source = """ +using System; +using System.Collections.Generic; + +namespace OverloadResolutionRepro +{ + public class C + { + public void Method(params IEnumerable> projections) => Console.Write(1); + public void Method(params IEnumerable>> projections) => Console.Write(2); + } + + public class Bar + { + public Wrapper WrappedValue { get; set; } = new Wrapper(); + } + + public struct Wrapper + { + } + + public class EntryPoint + { + static void Main() + { + new C().Method(x => x.WrappedValue); + } + } +} +"""; + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + } } } diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs index 43b3ff8f5f371..a2f84fa634a77 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs @@ -5704,42 +5704,45 @@ static void Main() void M2(dynamic p) => M(p); } """; - var verifier = CompileAndVerify(source, targetFramework: TargetFramework.StandardAndCSharp, expectedOutput: "12"); + var verifier = CompileAndVerify(source, targetFramework: TargetFramework.StandardAndCSharp, expectedOutput: "exception2"); - verifier.VerifyDiagnostics( - // (10,17): warning CS9192: Argument 1 should be passed with 'ref' or 'in' keyword - // c.M(d); - Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "d").WithArguments("1").WithLocation(10, 17), - // (21,29): warning CS9192: Argument 1 should be passed with 'ref' or 'in' keyword - // void M2(dynamic p) => M(p); - Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "p").WithArguments("1").WithLocation(21, 29) - ); + verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M2", """ { - // Code size 74 (0x4a) - .maxstack 4 - .locals init (int V_0) - IL_0000: ldarg.0 - IL_0001: ldsfld "System.Runtime.CompilerServices.CallSite> C.<>o__2.<>p__0" - IL_0006: brtrue.s IL_002c - IL_0008: ldc.i4.0 - IL_0009: ldtoken "int" - IL_000e: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_0013: ldtoken "C" - IL_0018: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_001d: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" - IL_0022: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" - IL_0027: stsfld "System.Runtime.CompilerServices.CallSite> C.<>o__2.<>p__0" - IL_002c: ldsfld "System.Runtime.CompilerServices.CallSite> C.<>o__2.<>p__0" - IL_0031: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" - IL_0036: ldsfld "System.Runtime.CompilerServices.CallSite> C.<>o__2.<>p__0" - IL_003b: ldarg.1 - IL_003c: callvirt "int System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" - IL_0041: stloc.0 - IL_0042: ldloca.s V_0 - IL_0044: call "void C.M(ref readonly int)" - IL_0049: ret + // Code size 92 (0x5c) + .maxstack 9 + IL_0000: ldsfld "System.Runtime.CompilerServices.CallSite> C.<>o__2.<>p__0" + IL_0005: brtrue.s IL_0045 + IL_0007: ldc.i4 0x102 + IL_000c: ldstr "M" + IL_0011: ldnull + IL_0012: ldtoken "C" + IL_0017: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_001c: ldc.i4.2 + IL_001d: newarr "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" + IL_0022: dup + IL_0023: ldc.i4.0 + IL_0024: ldc.i4.1 + IL_0025: ldnull + IL_0026: call "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)" + IL_002b: stelem.ref + IL_002c: dup + IL_002d: ldc.i4.1 + IL_002e: ldc.i4.0 + IL_002f: ldnull + IL_0030: call "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)" + IL_0035: stelem.ref + IL_0036: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, System.Collections.Generic.IEnumerable, System.Type, System.Collections.Generic.IEnumerable)" + IL_003b: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" + IL_0040: stsfld "System.Runtime.CompilerServices.CallSite> C.<>o__2.<>p__0" + IL_0045: ldsfld "System.Runtime.CompilerServices.CallSite> C.<>o__2.<>p__0" + IL_004a: ldfld "System.Action System.Runtime.CompilerServices.CallSite>.Target" + IL_004f: ldsfld "System.Runtime.CompilerServices.CallSite> C.<>o__2.<>p__0" + IL_0054: ldarg.0 + IL_0055: ldarg.1 + IL_0056: callvirt "void System.Action.Invoke(System.Runtime.CompilerServices.CallSite, C, dynamic)" + IL_005b: ret } """); } diff --git a/src/Compilers/CSharp/Test/Emit3/Microsoft.CodeAnalysis.CSharp.Emit3.UnitTests.csproj b/src/Compilers/CSharp/Test/Emit3/Microsoft.CodeAnalysis.CSharp.Emit3.UnitTests.csproj index 3276fae69d6c0..a79819fbe934a 100644 --- a/src/Compilers/CSharp/Test/Emit3/Microsoft.CodeAnalysis.CSharp.Emit3.UnitTests.csproj +++ b/src/Compilers/CSharp/Test/Emit3/Microsoft.CodeAnalysis.CSharp.Emit3.UnitTests.csproj @@ -27,6 +27,7 @@ + diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs index 77f41b32c7bbd..394f3007bd19f 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs @@ -7850,6 +7850,118 @@ Element Values(0) VerifyFlowGraphAndDiagnosticsForTest(source + s_IAsyncEnumerable + IOperationTests_IForEachLoopStatement.s_ValueTask, expectedGraph, expectedDiagnostics); } + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73068")] + public void UsingDeclaration_Flow_25() + { + var code = """ + class C + { + void M() + /**/{ + x: + System.Action a = () => { + using System.IDisposable d = null; + goto x; + }; + }/**/ + + } + """; + var expectedGraph = """ +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [System.Action a] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Action, IsInvalid, IsImplicit) (Syntax: 'a = () => { ... }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Action, IsInvalid, IsImplicit) (Syntax: 'a = () => { ... }') + Right: + IDelegateCreationOperation (OperationKind.DelegateCreation, Type: System.Action, IsInvalid, IsImplicit) (Syntax: '() => { ... }') + Target: + IFlowAnonymousFunctionOperation (Symbol: lambda expression) (OperationKind.FlowAnonymousFunction, Type: null, IsInvalid) (Syntax: '() => { ... }') + { + Block[B0#A0] - Entry + Statements (0) + Next (Regular) Block[B1#A0] + Entering: {R1#A0} + .locals {R1#A0} + { + Locals: [System.IDisposable d] + Block[B1#A0] - Block + Predecessors: [B0#A0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.IDisposable, IsImplicit) (Syntax: 'd = null') + Left: + ILocalReferenceOperation: d (IsDeclaration: True) (OperationKind.LocalReference, Type: System.IDisposable, IsImplicit) (Syntax: 'd = null') + Right: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.IDisposable, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') + Next (Regular) Block[B2#A0] + Entering: {R2#A0} {R3#A0} + .try {R2#A0, R3#A0} + { + Block[B2#A0] - Block + Predecessors: [B1#A0] + Statements (0) + Next (Error) Block[null] + } + .finally {R4#A0} + { + Block[B3#A0] - Block + Predecessors (0) + Statements (0) + Jump if True (Regular) to Block[B5#A0] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'd = null') + Operand: + ILocalReferenceOperation: d (OperationKind.LocalReference, Type: System.IDisposable, IsImplicit) (Syntax: 'd = null') + Next (Regular) Block[B4#A0] + Block[B4#A0] - Block + Predecessors: [B3#A0] + Statements (1) + IInvocationOperation (virtual void System.IDisposable.Dispose()) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'd = null') + Instance Receiver: + ILocalReferenceOperation: d (OperationKind.LocalReference, Type: System.IDisposable, IsImplicit) (Syntax: 'd = null') + Arguments(0) + Next (Regular) Block[B5#A0] + Block[B5#A0] - Block + Predecessors: [B3#A0] [B4#A0] + Statements (0) + Next (StructuredExceptionHandling) Block[null] + } + } + Block[B6#A0] - Exit [UnReachable] + Predecessors (0) + Statements (0) + } + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"""; + var expectedDiagnostics = new[] + { + // (5,9): warning CS0164: This label has not been referenced + // x: + Diagnostic(ErrorCode.WRN_UnreferencedLabel, "x").WithLocation(5, 9), + // (8,13): error CS0159: No such label 'x' within the scope of the goto statement + // goto x; + Diagnostic(ErrorCode.ERR_LabelNotFound, "goto").WithArguments("x").WithLocation(8, 13) + }; + VerifyFlowGraphAndDiagnosticsForTest(code, expectedGraph, expectedDiagnostics); + } + [CompilerTrait(CompilerFeature.IOperation)] [Fact, WorkItem(32100, "https://github.com/dotnet/roslyn/issues/32100")] public void UsingDeclaration_SingleDeclaration() diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs index 66f72c60a5364..b30a1e4fa287e 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs @@ -3054,18 +3054,12 @@ public C1(long x){} // (27,69): error CS1963: An expression tree may not contain a dynamic operation // Expression> e2 = () => new C { D = { X = { Y = 1 }, Z = 1 } }; Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "Z").WithLocation(27, 69), - // (28,46): error CS1963: An expression tree may not contain a dynamic operation - // Expression> e3 = () => new C() { { d }, { d, d, d } }; - Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(28, 46), - // (28,53): error CS1963: An expression tree may not contain a dynamic operation - // Expression> e3 = () => new C() { { d }, { d, d, d } }; - Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(28, 53), - // (28,56): error CS1963: An expression tree may not contain a dynamic operation + // (28,44): error CS1963: An expression tree may not contain a dynamic operation // Expression> e3 = () => new C() { { d }, { d, d, d } }; - Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(28, 56), - // (28,59): error CS1963: An expression tree may not contain a dynamic operation + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "{ d }").WithLocation(28, 44), + // (28,51): error CS1963: An expression tree may not contain a dynamic operation // Expression> e3 = () => new C() { { d }, { d, d, d } }; - Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(28, 59), + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "{ d, d, d }").WithLocation(28, 51), // (29,54): error CS1963: An expression tree may not contain a dynamic operation // Expression> e4 = x => x.goo(); Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x.goo()").WithLocation(29, 54), @@ -3087,6 +3081,15 @@ public C1(long x){} // (33,54): error CS1963: An expression tree may not contain a dynamic operation // Expression> e8 = x => -x; Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "-x").WithLocation(33, 54), + // (34,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e9 = x => f(d); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "f(d)").WithLocation(34, 54), + // (36,55): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e11 = x => f((dynamic)1); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "f((dynamic)1)").WithLocation(36, 55), + // (37,55): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e12 = x => f(d ?? null); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "f(d ?? null)").WithLocation(37, 55), // (38,55): error CS1963: An expression tree may not contain a dynamic operation // Expression> e13 = x => d ? 1 : 2; Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(38, 55), @@ -4472,7 +4475,7 @@ static void M1() [Fact] public void InArgumentDynamicLocalFunction() { - string source = @" + string source1 = @" class C { private static void M1(in dynamic x, int y, in dynamic z) => System.Console.WriteLine(x == y); @@ -4482,25 +4485,24 @@ static void Main() dynamic d = 1; M1(in d, d = 2, in d); - - void M2(in dynamic x, int y, in dynamic z) => System.Console.WriteLine(x == y); - - M2(in d, d = 3, in d); } } "; - var comp = CreateCompilationWithMscorlib45AndCSharp(source, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + var comp1 = CreateCompilationWithMscorlib45AndCSharp(source1, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); - CompileAndVerify(comp, expectedOutput: -@" -True -True -").VerifyDiagnostics(); + comp1.VerifyEmitDiagnostics( + // (10,15): error CS8364: Arguments with 'in' modifier cannot be used in dynamically dispatched expressions. + // M1(in d, d = 2, in d); + Diagnostic(ErrorCode.ERR_InDynamicMethodArg, "d").WithLocation(10, 15), + // (10,28): error CS8364: Arguments with 'in' modifier cannot be used in dynamically dispatched expressions. + // M1(in d, d = 2, in d); + Diagnostic(ErrorCode.ERR_InDynamicMethodArg, "d").WithLocation(10, 28) + ); - comp = CreateCompilationWithMscorlib45AndCSharp(source, parseOptions: TestOptions.Regular7_2, options: TestOptions.DebugExe); + comp1 = CreateCompilationWithMscorlib45AndCSharp(source1, parseOptions: TestOptions.Regular7_2, options: TestOptions.DebugExe); - comp.VerifyEmitDiagnostics( + comp1.VerifyEmitDiagnostics( // (10,15): error CS8364: Arguments with 'in' modifier cannot be used in dynamically dispatched expressions. // M1(in d, d = 2, in d); Diagnostic(ErrorCode.ERR_InDynamicMethodArg, "d").WithLocation(10, 15), @@ -4508,6 +4510,34 @@ static void Main() // M1(in d, d = 2, in d); Diagnostic(ErrorCode.ERR_InDynamicMethodArg, "d").WithLocation(10, 28) ); + + string source2 = @" +class C +{ + static void Main() + { + dynamic d = 1; + + void M2(in dynamic x, int y, in dynamic z) => System.Console.WriteLine(x == y); + + M2(in d, d = 3, in d); + } +} +"; + + var comp2 = CreateCompilationWithMscorlib45AndCSharp(source2, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + + CompileAndVerify(comp2, expectedOutput: +@" +True +").VerifyDiagnostics(); + + comp2 = CreateCompilationWithMscorlib45AndCSharp(source2, parseOptions: TestOptions.Regular7_2, options: TestOptions.DebugExe); + + CompileAndVerify(comp2, expectedOutput: +@" +True +").VerifyDiagnostics(); } [WorkItem(22813, "https://github.com/dotnet/roslyn/issues/22813")] @@ -4687,24 +4717,11 @@ ref struct S } """; - var verifier = CompileAndVerify(code, expectedOutput: "Hello world", targetFramework: TargetFramework.StandardAndCSharp); - verifier.VerifyDiagnostics(); - verifier.VerifyIL("", $$""" - { - // Code size 23 (0x17) - .maxstack 2 - .locals init (S V_0, //s - object V_1) //d - IL_0000: ldloca.s V_0 - IL_0002: initobj "S" - IL_0008: ldstr "Hello world" - IL_000d: stloc.1 - IL_000e: ldloca.s V_0 - IL_0010: ldloc.1 - IL_0011: call "void S.M({{argType}})" - IL_0016: ret - } - """); + CreateCompilation(code, targetFramework: TargetFramework.StandardAndCSharp).VerifyDiagnostics( + // (4,1): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // s.M(d); + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(4, 1) + ); } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] @@ -4731,49 +4748,11 @@ ref struct S } """; - var verifier = CompileAndVerify(code, expectedOutput: argType == "string" ? "Hello world" : "Caught exception", targetFramework: TargetFramework.StandardAndCSharp); - verifier.VerifyDiagnostics(); - verifier.VerifyIL("", $$""" - { - // Code size 101 (0x65) - .maxstack 4 - .locals init (S V_0, //s - object V_1) //d - IL_0000: ldloca.s V_0 - IL_0002: initobj "S" - IL_0008: ldstr "Hello world" - IL_000d: stloc.1 - .try - { - IL_000e: ldloca.s V_0 - IL_0010: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_0015: brtrue.s IL_003b - IL_0017: ldc.i4.0 - IL_0018: ldtoken "{{argType}}" - IL_001d: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_0022: ldtoken "Program" - IL_0027: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_002c: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" - IL_0031: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" - IL_0036: stsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_003b: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_0040: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" - IL_0045: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_004a: ldloc.1 - IL_004b: callvirt "{{argType}} System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" - IL_0050: call "void S.M({{argType}})" - IL_0055: leave.s IL_0064 - } - catch object - { - IL_0057: pop - IL_0058: ldstr "Caught exception" - IL_005d: call "void System.Console.WriteLine(string)" - IL_0062: leave.s IL_0064 - } - IL_0064: ret - } - """); + CreateCompilation(code, targetFramework: TargetFramework.StandardAndCSharp).VerifyDiagnostics( + // (6,5): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // s.M(d); + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(6, 5) + ); } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] @@ -4852,32 +4831,14 @@ public int this[{{argType}} o] } """; - var verifier = CompileAndVerify(code, expectedOutput: """ - Hello world - Hello world - """, targetFramework: TargetFramework.StandardAndCSharp); - verifier.VerifyDiagnostics(); - verifier.VerifyIL("", $$""" - { - // Code size 33 (0x21) - .maxstack 3 - .locals init (S V_0, //s - object V_1) //d - IL_0000: ldloca.s V_0 - IL_0002: initobj "S" - IL_0008: ldstr "Hello world" - IL_000d: stloc.1 - IL_000e: ldloca.s V_0 - IL_0010: ldloc.1 - IL_0011: call "int S.this[{{argType}}].get" - IL_0016: pop - IL_0017: ldloca.s V_0 - IL_0019: ldloc.1 - IL_001a: ldc.i4.1 - IL_001b: call "void S.this[{{argType}}].set" - IL_0020: ret - } - """); + CreateCompilation(code, targetFramework: TargetFramework.StandardAndCSharp).VerifyDiagnostics( + // (4,5): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // _ = s[d]; + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(4, 5), + // (5,1): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // s[d] = 1; + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(5, 1) + ); } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] @@ -4931,98 +4892,14 @@ public int this[{{argType}} o] } """; - var verifier = CompileAndVerify(code, expectedOutput: argType == "string" - ? """ - Hello world - Hello world - """ - : """ - Caught exception - Caught exception - """, targetFramework: TargetFramework.StandardAndCSharp); - verifier.VerifyDiagnostics(); - - verifier.VerifyIL("Program.<
$>g__get|0_0(ref Program.<>c__DisplayClass0_0)", $$""" - { - // Code size 101 (0x65) - .maxstack 4 - .locals init (S V_0) //s - IL_0000: ldloca.s V_0 - IL_0002: initobj "S" - .try - { - IL_0008: ldloca.s V_0 - IL_000a: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_000f: brtrue.s IL_0035 - IL_0011: ldc.i4.0 - IL_0012: ldtoken "{{argType}}" - IL_0017: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_001c: ldtoken "Program" - IL_0021: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_0026: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" - IL_002b: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" - IL_0030: stsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_0035: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_003a: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" - IL_003f: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_0044: ldarg.0 - IL_0045: ldfld "dynamic Program.<>c__DisplayClass0_0.d" - IL_004a: callvirt "{{argType}} System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" - IL_004f: call "int S.this[{{argType}}].get" - IL_0054: pop - IL_0055: leave.s IL_0064 - } - catch object - { - IL_0057: pop - IL_0058: ldstr "Caught exception" - IL_005d: call "void System.Console.WriteLine(string)" - IL_0062: leave.s IL_0064 - } - IL_0064: ret - } - """); - - verifier.VerifyIL("Program.<
$>g__set|0_1(ref Program.<>c__DisplayClass0_0)", $$""" - { - // Code size 101 (0x65) - .maxstack 4 - .locals init (S V_0) //s - IL_0000: ldloca.s V_0 - IL_0002: initobj "S" - .try - { - IL_0008: ldloca.s V_0 - IL_000a: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__1" - IL_000f: brtrue.s IL_0035 - IL_0011: ldc.i4.0 - IL_0012: ldtoken "{{argType}}" - IL_0017: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_001c: ldtoken "Program" - IL_0021: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_0026: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" - IL_002b: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" - IL_0030: stsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__1" - IL_0035: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__1" - IL_003a: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" - IL_003f: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__1" - IL_0044: ldarg.0 - IL_0045: ldfld "dynamic Program.<>c__DisplayClass0_0.d" - IL_004a: callvirt "{{argType}} System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" - IL_004f: ldc.i4.1 - IL_0050: call "void S.this[{{argType}}].set" - IL_0055: leave.s IL_0064 - } - catch object - { - IL_0057: pop - IL_0058: ldstr "Caught exception" - IL_005d: call "void System.Console.WriteLine(string)" - IL_0062: leave.s IL_0064 - } - IL_0064: ret - } - """); + CreateCompilation(code, targetFramework: TargetFramework.StandardAndCSharp).VerifyDiagnostics( + // (11,13): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // _ = s[d]; + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(11, 13), + // (24,9): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // s[d] = 1; + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(24, 9) + ); } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] @@ -5113,8 +4990,7 @@ class JsonSerializer AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var operation = (IInvocationOperation)model.GetOperation(call); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + var operation = (IDynamicInvocationOperation)model.GetOperation(call); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); CompileAndVerify(comp1).VerifyDiagnostics(); @@ -5147,8 +5023,11 @@ public interface I1 "; var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); - CompileAndVerify(comp2, - expectedOutput: @"System.Object (i1, value) => Convert(i1.Test(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + comp2.VerifyDiagnostics( + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((I1 i1, dynamic value) => i1.Test("name", value)); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, @"i1.Test(""name"", value)").WithLocation(9, 60) + ); string source3 = @" #nullable enable @@ -5174,19 +5053,15 @@ class JsonSerializer "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); - comp3.VerifyDiagnostics( - // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. - // return JsonSerializer.Deserialize(result); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) - ); + comp3.VerifyEmitDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); - Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); } [Theory] @@ -5239,8 +5114,7 @@ class JsonSerializer AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var operation = (IInvocationOperation)model.GetOperation(call); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + var operation = (IDynamicInvocationOperation)model.GetOperation(call); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); CompileAndVerify(comp1).VerifyDiagnostics(); @@ -5273,8 +5147,11 @@ public interface I1 "; var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); - CompileAndVerify(comp2, - expectedOutput: @"System.Object (i1, value) => Convert(i1.Test(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + comp2.VerifyDiagnostics( + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((I1 i1, dynamic value) => i1.Test("name", value)); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, @"i1.Test(""name"", value)").WithLocation(9, 60) + ); string source3 = @" #nullable enable @@ -5299,19 +5176,15 @@ class JsonSerializer } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); - comp3.VerifyDiagnostics( - // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. - // return JsonSerializer.Deserialize(result); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); - Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); } [Theory] @@ -5364,8 +5237,7 @@ class JsonSerializer AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var operation = (IInvocationOperation)model.GetOperation(call); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + var operation = (IDynamicInvocationOperation)model.GetOperation(call); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); CompileAndVerify(comp1).VerifyDiagnostics(); @@ -5398,8 +5270,11 @@ public interface I1 "; var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); - CompileAndVerify(comp2, - expectedOutput: @"System.Object (i1, value) => i1.Test(""name"", value)").VerifyDiagnostics(); + comp2.VerifyDiagnostics( + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((I1 i1, dynamic value) => i1.Test("name", value)); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, @"i1.Test(""name"", value)").WithLocation(9, 60) + ); string source3 = @" #nullable enable @@ -5425,19 +5300,15 @@ class JsonSerializer "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); - comp3.VerifyDiagnostics( - // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. - // return JsonSerializer.Deserialize(result); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); - Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); } [Theory] @@ -5470,17 +5341,21 @@ static class Extensions var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); var model = comp1.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("? result", symbolInfo.Symbol.ToTestDisplayString()); var call = tree.GetRoot().DescendantNodes().OfType().First(); AssertEx.Equal(@"new C().Test(""name"", d)", call.ToString()); symbolInfo = model.GetSymbolInfo(call); - AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Null(symbolInfo.Symbol); var typeInfo = model.GetTypeInfo(call); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + Assert.True(typeInfo.Type.IsErrorType()); + Assert.True(typeInfo.ConvertedType.IsErrorType()); - CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + comp1.VerifyDiagnostics( + // (7,22): error CS1973: 'C' has no applicable method named 'Test' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax. + // var result = new C().Test("name", d); + Diagnostic(ErrorCode.ERR_BadArgTypeDynamicExtension, @"new C().Test(""name"", d)").WithArguments("C", "Test").WithLocation(7, 22) + ); } [Fact] @@ -5507,17 +5382,21 @@ static void Main() var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); var model = comp1.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); var call = tree.GetRoot().DescendantNodes().OfType().First(); AssertEx.Equal(@"Test(name: ""name"", d)", call.ToString()); symbolInfo = model.GetSymbolInfo(call); AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(call); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + comp1.VerifyDiagnostics( + // (7,41): error CS8324: Named argument specifications must appear after all fixed arguments have been specified in a dynamic invocation. + // var result = Test(name: "name", d); + Diagnostic(ErrorCode.ERR_NamedArgumentSpecificationBeforeFixedArgumentInDynamicInvocation, "d").WithLocation(7, 41) + ); } [Fact] @@ -5547,17 +5426,21 @@ static void Main() var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); var model = comp1.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); var call = tree.GetRoot().DescendantNodes().OfType().First(); AssertEx.Equal(@"Test(&name, d)", call.ToString()); symbolInfo = model.GetSymbolInfo(call); AssertEx.Equal("System.Int32 C.Test(System.String* name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(call); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - CompileAndVerify(comp1, expectedOutput: "123", verify: Verification.Skipped).VerifyDiagnostics(); + comp1.VerifyDiagnostics( + // (10,27): error CS1978: Cannot use an expression of type 'string*' as an argument to a dynamically dispatched operation. + // var result = Test(&name, d); + Diagnostic(ErrorCode.ERR_BadDynamicMethodArg, "&name").WithArguments("string*").WithLocation(10, 27) + ); } [Theory] @@ -5587,20 +5470,24 @@ static void Main() var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); var model = comp1.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); var call = tree.GetRoot().DescendantNodes().OfType().First(); AssertEx.Equal(@"Test(""name"", d)", call.ToString()); symbolInfo = model.GetSymbolInfo(call); AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value, params System.Collections.Generic.List list)", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(call); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var operation = (IInvocationOperation)model.GetOperation(call); - AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); - CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + comp1.VerifyDiagnostics( + // (7,22): error CS9218: 'C.Test(string, object, params List)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // var result = Test("name", d); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, @"Test(""name"", d)").WithArguments("C.Test(string, object, params System.Collections.Generic.List)").WithLocation(7, 22) + ); } [Fact] @@ -5637,7 +5524,7 @@ static void Main() AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var operation = (IInvocationOperation)model.GetOperation(call); + var operation = (IDynamicInvocationOperation)model.GetOperation(call); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); @@ -5662,11 +5549,7 @@ static void Test1(int x) {} var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp); - comp.VerifyDiagnostics( - // (7,13): error CS0815: Cannot assign void to an implicitly-typed variable - // var a = Test1(d); - Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "a = Test1(d)").WithArguments("void").WithLocation(7, 13) - ); + comp.VerifyEmitDiagnostics(); } [Fact] @@ -5695,9 +5578,13 @@ public static void Main() var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic a", symbolInfo.Symbol.ToTestDisplayString()); - CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (7,9): error CS1059: The operand of an increment or decrement operator must be a variable, property or indexer + // Test1(d)++; + Diagnostic(ErrorCode.ERR_IncrementLvalueExpected, "Test1(d)").WithLocation(7, 9) + ); } [Fact] @@ -5729,9 +5616,16 @@ public static void Main() var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32* a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic a", symbolInfo.Symbol.ToTestDisplayString()); - CompileAndVerify(comp, expectedOutput: "1", verify: Verification.Skipped).VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (10,10): error CS0193: The * or -> operator must be applied to a pointer + // (*Test1(d))++; + Diagnostic(ErrorCode.ERR_PtrExpected, "*Test1(d)").WithLocation(10, 10), + // (12,34): error CS0193: The * or -> operator must be applied to a pointer + // System.Console.WriteLine(*a); + Diagnostic(ErrorCode.ERR_PtrExpected, "*a").WithLocation(12, 34) + ); } [Theory] @@ -5807,8 +5701,7 @@ class JsonSerializer AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var operation = (IInvocationOperation)model.GetOperation(call); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + var operation = (IDynamicInvocationOperation)model.GetOperation(call); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); CompileAndVerify(comp).VerifyDiagnostics(); @@ -5839,17 +5732,21 @@ static void Main() var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); var model = comp1.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); var call = tree.GetRoot().DescendantNodes().OfType().First(); AssertEx.Equal(@"Test(name: ""name"", d)", call.ToString()); symbolInfo = model.GetSymbolInfo(call); AssertEx.Equal(@"System.Int32 C.D.Invoke(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(call); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + comp1.VerifyDiagnostics( + // (7,41): error CS8324: Named argument specifications must appear after all fixed arguments have been specified in a dynamic invocation. + // var result = Test(name: "name", d); + Diagnostic(ErrorCode.ERR_NamedArgumentSpecificationBeforeFixedArgumentInDynamicInvocation, "d").WithLocation(7, 41) + ); } [Fact] @@ -5880,17 +5777,21 @@ static void Main() var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); var model = comp1.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); var call = tree.GetRoot().DescendantNodes().OfType().First(); AssertEx.Equal(@"Test(&name, d)", call.ToString()); symbolInfo = model.GetSymbolInfo(call); AssertEx.Equal("System.Int32 C.D.Invoke(System.String* name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(call); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - CompileAndVerify(comp1, expectedOutput: "123", verify: Verification.Skipped).VerifyDiagnostics(); + comp1.VerifyDiagnostics( + // (10,27): error CS1978: Cannot use an expression of type 'string*' as an argument to a dynamically dispatched operation. + // var result = Test(&name, d); + Diagnostic(ErrorCode.ERR_BadDynamicMethodArg, "&name").WithArguments("string*").WithLocation(10, 27) + ); } [Theory] @@ -5921,20 +5822,24 @@ static void Main() var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); var model = comp1.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); var call = tree.GetRoot().DescendantNodes().OfType().First(); AssertEx.Equal(@"Test(""name"", d)", call.ToString()); symbolInfo = model.GetSymbolInfo(call); AssertEx.Equal(@"System.Int32 C.D.Invoke(System.String name, System.Object value, params System.Collections.Generic.List list)", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(call); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var operation = (IInvocationOperation)model.GetOperation(call); - AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); - CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + comp1.VerifyDiagnostics( + // (7,22): error CS9218: 'C.D.Invoke(string, object, params List)' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // var result = Test("name", d); + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, @"Test(""name"", d)").WithArguments("C.D.Invoke(string, object, params System.Collections.Generic.List)").WithLocation(7, 22) + ); } [Fact] @@ -5972,7 +5877,7 @@ static void Main() AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var operation = (IInvocationOperation)model.GetOperation(call); + var operation = (IDynamicInvocationOperation)model.GetOperation(call); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); @@ -5999,11 +5904,7 @@ public static void Main() var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp); - comp.VerifyDiagnostics( - // (7,13): error CS0815: Cannot assign void to an implicitly-typed variable - // var a = Test1(d); - Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "a = Test1(d)").WithArguments("void").WithLocation(7, 13) - ); + comp.VerifyEmitDiagnostics(); } [Fact] @@ -6034,9 +5935,13 @@ public static void Main() var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic a", symbolInfo.Symbol.ToTestDisplayString()); - CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (7,9): error CS1059: The operand of an increment or decrement operator must be a variable, property or indexer + // Test1(d)++; + Diagnostic(ErrorCode.ERR_IncrementLvalueExpected, "Test1(d)").WithLocation(7, 9) + ); } [Fact] @@ -6069,9 +5974,16 @@ public static void Main() var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32* a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic a", symbolInfo.Symbol.ToTestDisplayString()); - CompileAndVerify(comp, expectedOutput: "1", verify: Verification.Skipped).VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (10,10): error CS0193: The * or -> operator must be applied to a pointer + // (*Test1(d))++; + Diagnostic(ErrorCode.ERR_PtrExpected, "*Test1(d)").WithLocation(10, 10), + // (12,34): error CS0193: The * or -> operator must be applied to a pointer + // System.Console.WriteLine(*a); + Diagnostic(ErrorCode.ERR_PtrExpected, "*a").WithLocation(12, 34) + ); } [Theory] @@ -6123,8 +6035,7 @@ class JsonSerializer AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); CompileAndVerify(comp).VerifyDiagnostics(); @@ -6157,8 +6068,11 @@ public interface I1 "; var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); - CompileAndVerify(comp2, - expectedOutput: @"System.Object (i1, value) => Convert(i1.get_Item(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + comp2.VerifyDiagnostics( + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((I1 i1, dynamic value) => i1["name", value]); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, @"i1[""name"", value]").WithLocation(9, 60) + ); string source3 = @" #nullable enable @@ -6184,19 +6098,15 @@ class JsonSerializer "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); - comp3.VerifyDiagnostics( - // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. - // return JsonSerializer.Deserialize(result); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); - Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); } [Theory] @@ -6248,8 +6158,7 @@ class JsonSerializer AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); CompileAndVerify(comp).VerifyDiagnostics(); @@ -6282,8 +6191,11 @@ public interface I1 "; var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); - CompileAndVerify(comp2, - expectedOutput: @"System.Object (i1, value) => Convert(i1.get_Item(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + comp2.VerifyDiagnostics( + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((I1 i1, dynamic value) => i1["name", value]); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, @"i1[""name"", value]").WithLocation(9, 60) + ); string source3 = @" #nullable enable @@ -6309,19 +6221,15 @@ class JsonSerializer "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); - comp3.VerifyDiagnostics( - // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. - // return JsonSerializer.Deserialize(result); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); - Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); } [Fact] @@ -6370,8 +6278,7 @@ class JsonSerializer AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); CompileAndVerify(comp).VerifyDiagnostics(); @@ -6404,8 +6311,11 @@ public interface I1 "; var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); - CompileAndVerify(comp2, - expectedOutput: @"System.Object (i1, value) => i1.get_Item(""name"", value)").VerifyDiagnostics(); + comp2.VerifyDiagnostics( + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((I1 i1, dynamic value) => i1["name", value]); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, @"i1[""name"", value]").WithLocation(9, 60) + ); string source3 = @" #nullable enable @@ -6431,19 +6341,15 @@ class JsonSerializer "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. - // return JsonSerializer.Deserialize(result); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); - Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); } [Fact] @@ -6480,11 +6386,10 @@ static void Main() AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); - CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + CompileAndVerify(comp1).VerifyDiagnostics(); } [Fact] @@ -6514,20 +6419,23 @@ static void Main() var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); var model = comp1.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32 C.this[System.String* name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); - CompileAndVerify(comp1, expectedOutput: "123", verify: Verification.Skipped).VerifyDiagnostics(); + comp1.VerifyDiagnostics( + // (10,30): error CS1978: Cannot use an expression of type 'string*' as an argument to a dynamically dispatched operation. + // var result = new C()[&name, d]; + Diagnostic(ErrorCode.ERR_BadDynamicMethodArg, "&name").WithArguments("string*").WithLocation(10, 30) + ); } [Theory] @@ -6558,20 +6466,23 @@ static void Main() var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); var model = comp1.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32 C.this[System.String name, System.Object value, params System.Collections.Generic.List list] { get; }", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); - CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + comp1.VerifyDiagnostics( + // (7,22): error CS9218: 'C.this[string, object, params List]' is applicable only with expanded form of non-array params collection which is not supported during dynamic dispatch. + // var result = new C()["name", d]; + Diagnostic(ErrorCode.ERR_DynamicDispatchToParamsCollection, @"new C()[""name"", d]").WithArguments("C.this[string, object, params System.Collections.Generic.List]").WithLocation(7, 22) + ); } [Fact] @@ -6608,8 +6519,7 @@ static void Main() AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); @@ -6642,7 +6552,7 @@ public static void Main() var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic a", symbolInfo.Symbol.ToTestDisplayString()); TypeInfo typeInfo; @@ -6651,20 +6561,19 @@ public static void Main() symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); } var increment = tree.GetRoot().DescendantNodes().OfType().Single(); typeInfo = model.GetTypeInfo(increment); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + CompileAndVerify(comp).VerifyDiagnostics(); } [Fact] @@ -6697,7 +6606,7 @@ public static void Main() var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32* a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic a", symbolInfo.Symbol.ToTestDisplayString()); TypeInfo typeInfo; @@ -6706,15 +6615,21 @@ public static void Main() symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32* C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32*", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32*", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); } - CompileAndVerify(comp, expectedOutput: "1", verify: Verification.Skipped).VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (11,10): error CS0193: The * or -> operator must be applied to a pointer + // (*c[d])++; + Diagnostic(ErrorCode.ERR_PtrExpected, "*c[d]").WithLocation(11, 10), + // (13,34): error CS0193: The * or -> operator must be applied to a pointer + // System.Console.WriteLine(*a); + Diagnostic(ErrorCode.ERR_PtrExpected, "*a").WithLocation(13, 34) + ); } [Fact] @@ -6766,11 +6681,10 @@ static void Print(dynamic b) symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -6779,7 +6693,7 @@ static void Print(dynamic b) AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var operation = (IAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); @@ -6816,11 +6730,7 @@ static void Print(dynamic b) } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); @@ -6883,8 +6793,7 @@ static void Print(dynamic b) AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -6894,13 +6803,13 @@ static void Print(dynamic b) var operation = (IAssignmentOperation)model.GetOperation(assignment); AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); - AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); var right = assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); @@ -6930,11 +6839,7 @@ static void Print(dynamic b) } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); @@ -6995,11 +6900,10 @@ static void Print(dynamic b) symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -7008,14 +6912,14 @@ static void Print(dynamic b) AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var operation = (IAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); var right = assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); @@ -7046,11 +6950,7 @@ static void Print(dynamic b) } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (12,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(12, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); @@ -7097,11 +6997,10 @@ System.IO.Stream this[int x] symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.IO.Stream C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.IO.Stream", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.IO.Stream", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -7110,20 +7009,16 @@ System.IO.Stream this[int x] AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var operation = (IAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("System.IO.Stream", operation.Target.Type.ToTestDisplayString()); - AssertEx.Equal("System.IO.Stream", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Object", operation.Value.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); var right = assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("System.Object", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.IO.Stream", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("System.Object", typeInfo.ConvertedType.ToTestDisplayString()); - comp.VerifyDiagnostics( - // (9,24): error CS0266: Cannot implicitly convert type 'object' to 'System.IO.Stream'. An explicit conversion exists (are you missing a cast?) - // var a = c[d] = o; - Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "o").WithArguments("object", "System.IO.Stream").WithLocation(9, 24) - ); + comp.VerifyEmitDiagnostics(); } [Fact] @@ -7164,34 +7059,33 @@ static void Print(dynamic b) var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var right = assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); - CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + CompileAndVerify(comp).VerifyDiagnostics(); string source3 = @" #nullable enable @@ -7218,18 +7112,14 @@ static void Print(dynamic b) } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); } @@ -7282,11 +7172,10 @@ static void Print(dynamic b) symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -7294,10 +7183,10 @@ static void Print(dynamic b) AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); symbolInfo = model.GetSymbolInfo(assignment); - AssertEx.Equal("System.Int32 System.Int32.op_Addition(System.Int32 left, System.Int32 right)", symbolInfo.Symbol.ToTestDisplayString()); + AssertEx.Equal("dynamic dynamic.op_Addition(dynamic left, System.Int32 right)", symbolInfo.Symbol.ToTestDisplayString()); var operation = (ICompoundAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); Assert.Null(operation.OperatorMethod); @@ -7335,22 +7224,15 @@ static void Print(dynamic b) } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (10,17): warning CS0458: The result of the expression is always 'null' of type 'dynamic' - // var a = c[d] += (int?)null; - Diagnostic(ErrorCode.WRN_AlwaysNull, "c[d] += (int?)null").WithArguments("dynamic").WithLocation(10, 17), - // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); - Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); } [Fact] @@ -7406,8 +7288,7 @@ static void Print(dynamic b) AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -7520,11 +7401,10 @@ static void Print(dynamic b) symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -7532,10 +7412,10 @@ static void Print(dynamic b) AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); symbolInfo = model.GetSymbolInfo(assignment); - AssertEx.Equal("dynamic dynamic.op_Addition(System.Int32 left, dynamic right)", symbolInfo.Symbol.ToTestDisplayString()); + AssertEx.Equal("dynamic dynamic.op_Addition(dynamic left, dynamic right)", symbolInfo.Symbol.ToTestDisplayString()); var operation = (ICompoundAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); Assert.Null(operation.OperatorMethod); @@ -7624,36 +7504,35 @@ class C2 {} var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("? a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic a", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); - Assert.True(typeInfo.Type.IsErrorType()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.False(typeInfo.Type.IsErrorType()); var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); - Assert.True(typeInfo.Type.IsErrorType()); - AssertEx.Equal("?", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.False(typeInfo.Type.IsErrorType()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); symbolInfo = model.GetSymbolInfo(assignment); - Assert.Null(symbolInfo.Symbol); + AssertEx.Equal("dynamic dynamic.op_Addition(dynamic left, C2 right)", symbolInfo.Symbol.ToTestDisplayString()); var operation = (ICompoundAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("C2", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); AssertEx.Equal("C2", operation.Value.Type.ToTestDisplayString()); - AssertEx.Equal("?", operation.Type.ToTestDisplayString()); - Assert.True(operation.Type.IsErrorType()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.False(operation.Type.IsErrorType()); Assert.Null(operation.OperatorMethod); var right = assignment.Right; @@ -7661,11 +7540,7 @@ class C2 {} AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); - comp.VerifyDiagnostics( - // (9,17): error CS0019: Operator '+=' cannot be applied to operands of type 'C2' and 'C2' - // var a = c[d] += right; - Diagnostic(ErrorCode.ERR_BadBinaryOps, "c[d] += right").WithArguments("+=", "C2", "C2").WithLocation(9, 17) - ); + comp.VerifyEmitDiagnostics(); } [Fact] @@ -7706,36 +7581,35 @@ static void Print(dynamic b) var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); symbolInfo = model.GetSymbolInfo(assignment); - AssertEx.Equal("System.Int32 System.Int32.op_Addition(System.Int32 left, System.Int32 right)", symbolInfo.Symbol.ToTestDisplayString()); + AssertEx.Equal("dynamic dynamic.op_Addition(dynamic left, System.Int32 right)", symbolInfo.Symbol.ToTestDisplayString()); var right = assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); - CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + CompileAndVerify(comp).VerifyDiagnostics(); string source3 = @" #nullable enable @@ -7762,22 +7636,15 @@ static void Print(dynamic b) } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (10,17): warning CS0458: The result of the expression is always 'null' of type 'int?' - // var a = c[d] += (int?)null; - Diagnostic(ErrorCode.WRN_AlwaysNull, "c[d] += (int?)null").WithArguments("int?").WithLocation(10, 17), - // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); - Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); } [Fact] @@ -7829,11 +7696,10 @@ static void Print(dynamic b) symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (PostfixUnaryExpressionSyntax)elementAccess.Parent; @@ -7841,10 +7707,10 @@ static void Print(dynamic b) AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); symbolInfo = model.GetSymbolInfo(assignment); - AssertEx.Equal("System.Int32 System.Int32.op_Increment(System.Int32 value)", symbolInfo.Symbol.ToTestDisplayString()); + AssertEx.Equal("dynamic dynamic.op_Increment(dynamic value)", symbolInfo.Symbol.ToTestDisplayString()); var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); - AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); Assert.Null(operation.OperatorMethod); @@ -7876,19 +7742,15 @@ static void Print(dynamic b) } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); - Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); } [Fact] @@ -7943,8 +7805,7 @@ static void Print(dynamic b) AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (PostfixUnaryExpressionSyntax)elementAccess.Parent; @@ -7987,19 +7848,15 @@ static void Print(dynamic b) } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); - Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); } [Fact] @@ -8038,42 +7895,37 @@ class C2 {} var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("? a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic a", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); - Assert.True(typeInfo.Type.IsErrorType()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.False(typeInfo.Type.IsErrorType()); Assert.Equal(CodeAnalysis.NullableFlowState.None, typeInfo.Nullability.FlowState); var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (PostfixUnaryExpressionSyntax)elementAccess.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); - Assert.True(typeInfo.Type.IsErrorType()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.False(typeInfo.Type.IsErrorType()); Assert.Equal(typeInfo.Type, typeInfo.ConvertedType); symbolInfo = model.GetSymbolInfo(assignment); - Assert.Null(symbolInfo.Symbol); + AssertEx.Equal("dynamic dynamic.op_Increment(dynamic value)", symbolInfo.Symbol.ToTestDisplayString()); var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); - AssertEx.Equal("C2", operation.Target.Type.ToTestDisplayString()); - AssertEx.Equal("?", operation.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); Assert.Null(operation.OperatorMethod); - comp.VerifyDiagnostics( - // (8,17): error CS0023: Operator '++' cannot be applied to operand of type 'C2' - // var a = c[d]++; - Diagnostic(ErrorCode.ERR_BadUnaryOp, "c[d]++").WithArguments("++", "C2").WithLocation(8, 17) - ); + comp.VerifyEmitDiagnostics(); } [Fact] @@ -8125,11 +7977,10 @@ static void Print(dynamic b) symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (PrefixUnaryExpressionSyntax)elementAccess.Parent; @@ -8137,10 +7988,10 @@ static void Print(dynamic b) AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); symbolInfo = model.GetSymbolInfo(assignment); - AssertEx.Equal("System.Int32 System.Int32.op_Increment(System.Int32 value)", symbolInfo.Symbol.ToTestDisplayString()); + AssertEx.Equal("dynamic dynamic.op_Increment(dynamic value)", symbolInfo.Symbol.ToTestDisplayString()); var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); - AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); Assert.Null(operation.OperatorMethod); @@ -8172,19 +8023,15 @@ static void Print(dynamic b) } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); - Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); } [Fact] @@ -8239,8 +8086,7 @@ static void Print(dynamic b) AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (PrefixUnaryExpressionSyntax)elementAccess.Parent; @@ -8283,19 +8129,15 @@ static void Print(dynamic b) } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); - Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); } [Fact] @@ -8336,36 +8178,35 @@ static void Print(dynamic b) var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (PrefixUnaryExpressionSyntax)elementAccess.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); symbolInfo = model.GetSymbolInfo(assignment); - AssertEx.Equal("System.Int32 System.Int32.op_Increment(System.Int32 value)", symbolInfo.Symbol.ToTestDisplayString()); + AssertEx.Equal("dynamic dynamic.op_Increment(dynamic value)", symbolInfo.Symbol.ToTestDisplayString()); var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); - AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); Assert.Null(operation.OperatorMethod); - CompileAndVerify(comp, expectedOutput: "3 3").VerifyDiagnostics(); + CompileAndVerify(comp).VerifyDiagnostics(); string source3 = @" #nullable enable @@ -8392,19 +8233,15 @@ static void Print(dynamic b) } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); - Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); } [Fact] @@ -8456,11 +8293,10 @@ static void Print(dynamic b) symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.String C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -8469,14 +8305,14 @@ static void Print(dynamic b) AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var operation = (IAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("System.String", operation.Target.Type.ToTestDisplayString()); - AssertEx.Equal("System.String", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); var right = assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); @@ -8506,11 +8342,7 @@ static void Print(dynamic b) } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); @@ -8570,11 +8402,10 @@ static void Print(dynamic b) symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32? C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32?", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -8583,14 +8414,14 @@ static void Print(dynamic b) AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var operation = (IAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("System.Int32?", operation.Target.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); var right = assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); @@ -8620,11 +8451,7 @@ static void Print(dynamic b) } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); @@ -8687,8 +8514,7 @@ static void Print(dynamic b) AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -8734,11 +8560,7 @@ static void Print(dynamic b) } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); @@ -8799,11 +8621,10 @@ static void Print(dynamic b) symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.String C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -8812,14 +8633,14 @@ static void Print(dynamic b) AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var operation = (IAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("System.String", operation.Target.Type.ToTestDisplayString()); - AssertEx.Equal("System.String", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); var right = assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); @@ -8850,11 +8671,7 @@ static void Print(dynamic b) } "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (12,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(12, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); @@ -8907,43 +8724,34 @@ class C2 {} var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("C2? a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("C2? C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("C2?", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("C2?", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("?", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var operation = (IAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("C2?", operation.Target.Type.ToTestDisplayString()); - AssertEx.Equal("System.String", operation.Value.Type.ToTestDisplayString()); - AssertEx.Equal("?", operation.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); var right = assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - // The unexpected nullability warning is pre-existing condition - https://github.com/dotnet/roslyn/issues/72912 - comp.VerifyDiagnostics( - // (10,17): error CS0019: Operator '??=' cannot be applied to operands of type 'C2' and 'string' - // var a = c[d] ??= "2"; - Diagnostic(ErrorCode.ERR_BadBinaryOps, @"c[d] ??= ""2""").WithArguments("??=", "C2", "string").WithLocation(10, 17), - // (10,26): warning CS8619: Nullability of reference types in value of type 'string' doesn't match target type 'C2'. - // var a = c[d] ??= "2"; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, @"""2""").WithArguments("string", "C2").WithLocation(10, 26) - ); + comp.VerifyEmitDiagnostics(); } [Fact] @@ -8985,39 +8793,34 @@ struct C2 {} var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("? a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic a", symbolInfo.Symbol.ToTestDisplayString()); var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("?", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var operation = (IAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("C2", operation.Target.Type.ToTestDisplayString()); - AssertEx.Equal("C2", operation.Value.Type.ToTestDisplayString()); - AssertEx.Equal("?", operation.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); var right = assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - comp.VerifyDiagnostics( - // (8,17): error CS0019: Operator '??=' cannot be applied to operands of type 'C2' and 'C2' - // var a = c[d] ??= new C2(); - Diagnostic(ErrorCode.ERR_BadBinaryOps, "c[d] ??= new C2()").WithArguments("??=", "C2", "C2").WithLocation(8, 17) - ); + comp.VerifyEmitDiagnostics(); } [Fact] @@ -9058,39 +8861,38 @@ static void Print(dynamic b) var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("ref System.Int32? C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32?", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var operation = (IAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("System.Int32?", operation.Target.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); var right = assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + CompileAndVerify(comp).VerifyDiagnostics(); } [Fact] @@ -9123,29 +8925,28 @@ int this[int x] var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); var symbolInfo = model.GetSymbolInfo(elementAccess); - AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Null(symbolInfo.Symbol); var typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var operation = (IAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); var right = assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); @@ -9180,10 +8981,7 @@ static void Print(Expression> expr) comp2.VerifyDiagnostics( // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); - Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), - // (9,60): error CS1963: An expression tree may not contain a dynamic operation - // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); - Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59) ); } @@ -9217,13 +9015,12 @@ dynamic this[int x] var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); var symbolInfo = model.GetSymbolInfo(elementAccess); - AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Null(symbolInfo.Symbol); var typeInfo = model.GetTypeInfo(elementAccess); AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -9274,10 +9071,7 @@ static void Print(Expression> expr) comp2.VerifyDiagnostics( // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); - Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), - // (9,60): error CS1963: An expression tree may not contain a dynamic operation - // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); - Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59) ); } @@ -9312,29 +9106,28 @@ int this[int x] var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); var symbolInfo = model.GetSymbolInfo(elementAccess); - AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Null(symbolInfo.Symbol); var typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var operation = (IAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); var right = assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); @@ -9369,13 +9162,7 @@ static void Print(Expression> expr) comp2.VerifyDiagnostics( // (9,70): error CS8074: An expression tree lambda may not contain a dictionary initializer. // var expr = GetExpression((dynamic d, dynamic v) => new C() { [d] = v }); - Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 70), - // (9,71): error CS1963: An expression tree may not contain a dynamic operation - // var expr = GetExpression((dynamic d, dynamic v) => new C() { [d] = v }); - Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 71), - // (9,76): error CS1963: An expression tree may not contain a dynamic operation - // var expr = GetExpression((dynamic d, dynamic v) => new C() { [d] = v }); - Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "v").WithLocation(9, 76) + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 70) ); } @@ -9409,35 +9196,30 @@ int this[int x] var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); var symbolInfo = model.GetSymbolInfo(elementAccess); - AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Null(symbolInfo.Symbol); var typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var operation = (IAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); var right = assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - comp.VerifyDiagnostics( - // (7,33): error CS0029: Cannot implicitly convert type 'string' to 'int' - // var c = new C() { [d] = "2" }; - Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""2""").WithArguments("string", "int").WithLocation(7, 33) - ); + comp.VerifyEmitDiagnostics(); } [Fact] @@ -9470,33 +9252,30 @@ ref int this[int x] var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); var symbolInfo = model.GetSymbolInfo(elementAccess); - AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Null(symbolInfo.Symbol); var typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - // IInvalidOperation is pre-existing condition - https://github.com/dotnet/roslyn/issues/72916 - var propertyRef = (IInvalidOperation)model.GetOperation(elementAccess); - //var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - //AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var operation = (IAssignmentOperation)model.GetOperation(assignment); - AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); var right = assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + CompileAndVerify(comp).VerifyDiagnostics(); } [Fact] @@ -9534,13 +9313,12 @@ class C2 var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); var symbolInfo = model.GetSymbolInfo(elementAccess); - AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Null(symbolInfo.Symbol); var typeInfo = model.GetTypeInfo(elementAccess); AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -9590,9 +9368,6 @@ class C2 // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), - // (9,60): error CS1963: An expression tree may not contain a dynamic operation - // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); - Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), // (9,67): error CS1963: An expression tree may not contain a dynamic operation // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "F").WithLocation(9, 67) @@ -9659,13 +9434,12 @@ class C2 var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); var symbolInfo = model.GetSymbolInfo(elementAccess); - AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Null(symbolInfo.Symbol); var typeInfo = model.GetTypeInfo(elementAccess); AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -9715,9 +9489,6 @@ class C2 // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), - // (9,60): error CS1963: An expression tree may not contain a dynamic operation - // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); - Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), // (9,67): error CS1963: An expression tree may not contain a dynamic operation // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "F").WithLocation(9, 67) @@ -9783,24 +9554,23 @@ class C2 var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); var symbolInfo = model.GetSymbolInfo(elementAccess); - AssertEx.Equal("ref C2 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Null(symbolInfo.Symbol); var typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var operation = (IMemberInitializerOperation)model.GetOperation(assignment); - AssertEx.Equal("C2", operation.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); - CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + CompileAndVerify(comp).VerifyDiagnostics(); string source2 = @" using System; @@ -9838,12 +9608,9 @@ class C2 // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), - // (9,59): error CS8153: An expression tree lambda may not contain a call to a method, property, or indexer that returns by reference - // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); - Diagnostic(ErrorCode.ERR_RefReturningCallInExpressionTree, "[d]").WithLocation(9, 59), - // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // (9,67): error CS1963: An expression tree may not contain a dynamic operation // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); - Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "F").WithLocation(9, 67) ); string source3 = @" @@ -9868,11 +9635,7 @@ class C2 "; var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (7,35): error CS0117: 'C2' does not contain a definition for 'F' - // var c = new C() { [d] = { F = 2 } }; - Diagnostic(ErrorCode.ERR_NoSuchMember, "F").WithArguments("C2", "F").WithLocation(7, 35) - ); + comp3.VerifyEmitDiagnostics(); } [Fact] @@ -9905,13 +9668,12 @@ System.Collections.Generic.List this[int x] var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); var symbolInfo = model.GetSymbolInfo(elementAccess); - AssertEx.Equal("System.Collections.Generic.List C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Null(symbolInfo.Symbol); var typeInfo = model.GetTypeInfo(elementAccess); AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -9956,9 +9718,6 @@ static void Print(Expression> expr) // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), - // (9,60): error CS1963: An expression tree may not contain a dynamic operation - // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); - Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), // (9,66): error CS1963: An expression tree may not contain a dynamic operation // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "2").WithLocation(9, 66) @@ -10020,13 +9779,12 @@ dynamic this[int x] var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); var symbolInfo = model.GetSymbolInfo(elementAccess); - AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Null(symbolInfo.Symbol); var typeInfo = model.GetTypeInfo(elementAccess); AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; @@ -10071,9 +9829,6 @@ static void Print(Expression> expr) // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), - // (9,60): error CS1963: An expression tree may not contain a dynamic operation - // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); - Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), // (9,66): error CS1963: An expression tree may not contain a dynamic operation // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "2").WithLocation(9, 66) @@ -10135,24 +9890,23 @@ ref System.Collections.Generic.List this[int x] var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); var symbolInfo = model.GetSymbolInfo(elementAccess); - AssertEx.Equal("ref System.Collections.Generic.List C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Null(symbolInfo.Symbol); var typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Collections.Generic.List", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Collections.Generic.List", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("System.Collections.Generic.List", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Collections.Generic.List", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var operation = (IMemberInitializerOperation)model.GetOperation(assignment); - AssertEx.Equal("System.Collections.Generic.List", operation.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); - CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + CompileAndVerify(comp).VerifyDiagnostics(); string source2 = @" using System; @@ -10185,12 +9939,9 @@ static void Print(Expression> expr) // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), - // (9,59): error CS8153: An expression tree lambda may not contain a call to a method, property, or indexer that returns by reference - // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); - Diagnostic(ErrorCode.ERR_RefReturningCallInExpressionTree, "[d]").WithLocation(9, 59), - // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // (9,66): error CS1963: An expression tree may not contain a dynamic operation // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); - Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "2").WithLocation(9, 66) ); string source3 = @" @@ -10215,11 +9966,7 @@ class C2 "; var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (7,33): error CS1922: Cannot initialize type 'C2' with a collection initializer because it does not implement 'System.Collections.IEnumerable' - // var c = new C() { [d] = {2} }; - Diagnostic(ErrorCode.ERR_CollectionInitRequiresIEnumerable, "{2}").WithArguments("C2").WithLocation(7, 33) - ); + comp3.VerifyEmitDiagnostics(); } [Fact] @@ -10276,17 +10023,16 @@ static void Print(dynamic b) symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; var tupleTypeInfo = model.GetTypeInfo(left); - AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); var assignment = (AssignmentExpressionSyntax)left.Parent; typeInfo = model.GetTypeInfo(assignment); @@ -10303,12 +10049,12 @@ static void Print(dynamic b) var right = (TupleExpressionSyntax)assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); var rightElement = right.Arguments[0].Expression; typeInfo = model.GetTypeInfo(rightElement); AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); @@ -10408,8 +10154,7 @@ static void Print(dynamic b) AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; @@ -10534,17 +10279,16 @@ static void Print(dynamic b) symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; var tupleTypeInfo = model.GetTypeInfo(left); - AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); var assignment = (AssignmentExpressionSyntax)left.Parent; typeInfo = model.GetTypeInfo(assignment); @@ -10561,12 +10305,12 @@ static void Print(dynamic b) var right = (TupleExpressionSyntax)assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); var rightElement = right.Arguments[0].Expression; typeInfo = model.GetTypeInfo(rightElement); AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); @@ -10634,11 +10378,7 @@ int this[int x] var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); - comp.VerifyDiagnostics( - // (8,30): error CS0029: Cannot implicitly convert type 'string' to 'int' - // var a = (c[d], _) = ("2", 123); - Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""2""").WithArguments("string", "int").WithLocation(8, 30) - ); + comp.VerifyEmitDiagnostics(); } [Fact] @@ -10679,36 +10419,35 @@ static void Print(dynamic b) var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("(System.Int32, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 (System.Int32, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; var tupleTypeInfo = model.GetTypeInfo(left); - AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); var assignment = (AssignmentExpressionSyntax)left.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); @@ -10720,14 +10459,14 @@ static void Print(dynamic b) var right = (TupleExpressionSyntax)assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); var rightElement = right.Arguments[0].Expression; typeInfo = model.GetTypeInfo(rightElement); AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + CompileAndVerify(comp).VerifyDiagnostics(); string source3 = @" #nullable enable @@ -10755,19 +10494,15 @@ static void Print(dynamic b) "; var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); - comp3.VerifyDiagnostics( - // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a.Item1); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a.Item1").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) - ); + comp3.VerifyDiagnostics(); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); - Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); } [Fact] @@ -10832,24 +10567,23 @@ public void Deconstruct(out int x, out int y) symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; var tupleTypeInfo = model.GetTypeInfo(left); - AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); var assignment = (AssignmentExpressionSyntax)left.Parent; typeInfo = model.GetTypeInfo(assignment); AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); - Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsBoxing: true }, Nested: [] }, _] }); var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); @@ -10861,7 +10595,11 @@ public void Deconstruct(out int x, out int y) AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); - CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics( + // (10,18): warning CS8624: Argument of type 'dynamic' cannot be used as an output of type 'int' for parameter 'x' in 'void C2.Deconstruct(out int x, out int y)' due to differences in the nullability of reference types. + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgumentForOutput, "c[d]").WithArguments("dynamic", "int", "x", "void C2.Deconstruct(out int x, out int y)").WithLocation(10, 18) + ); string source3 = @" #nullable enable @@ -10899,7 +10637,11 @@ public void Deconstruct(out int? x, out int y) var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 - comp3.VerifyDiagnostics(); + comp3.VerifyDiagnostics( + // (10,18): warning CS8624: Argument of type 'dynamic' cannot be used as an output of type 'int?' for parameter 'x' in 'void C2.Deconstruct(out int? x, out int y)' due to differences in the nullability of reference types. + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgumentForOutput, "c[d]").WithArguments("dynamic", "int?", "x", "void C2.Deconstruct(out int? x, out int y)").WithLocation(10, 18) + ); tree = comp3.SyntaxTrees.Single(); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); @@ -10976,8 +10718,7 @@ public void Deconstruct(out int x, out int y) AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; @@ -11105,12 +10846,7 @@ public void Deconstruct(out dynamic x, out int y) var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); - // The unexpected error is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72914 - comp.VerifyDiagnostics( - // (10,18): error CS0266: Cannot implicitly convert type 'dynamic' to 'int'. An explicit conversion exists (are you missing a cast?) - // var a = (c[d], _) = new C2(); - Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "c[d]").WithArguments("dynamic", "int").WithLocation(10, 18) - ); + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); string source3 = @" #nullable enable @@ -11122,7 +10858,10 @@ public static void Main() dynamic d = 1; var c = new C(); var a = (c[d], _) = new C2(); + System.Console.Write(a); Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); } int? _test1 = 0; @@ -11145,14 +10884,9 @@ public void Deconstruct(out dynamic? x, out int y) } } "; - var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - // The unexpected error is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72914 - comp3.VerifyDiagnostics( - // (10,18): error CS0266: Cannot implicitly convert type 'dynamic' to 'int?'. An explicit conversion exists (are you missing a cast?) - // var a = (c[d], _) = new C2(); - Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "c[d]").WithArguments("dynamic", "int?").WithLocation(10, 18) - ); + CompileAndVerify(comp3, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); } [Fact] @@ -11188,11 +10922,7 @@ public void Deconstruct(out string x, out int y) var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); - comp.VerifyDiagnostics( - // (8,18): error CS0029: Cannot implicitly convert type 'string' to 'int' - // var a = (c[d], _) = new C2(); - Diagnostic(ErrorCode.ERR_NoImplicitConv, "c[d]").WithArguments("string", "int").WithLocation(8, 18) - ); + comp.VerifyEmitDiagnostics(); } [Fact] @@ -11241,38 +10971,37 @@ public void Deconstruct(out int x, out int y) var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); var model = comp.GetSemanticModel(tree); var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("(System.Int32, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.Int32 (System.Int32, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); - var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); - AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + var propertyRef = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); Assert.Equal(typeInfo.Type, propertyRef.Type); var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; var tupleTypeInfo = model.GetTypeInfo(left); - AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); var assignment = (AssignmentExpressionSyntax)left.Parent; typeInfo = model.GetTypeInfo(assignment); - AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); - Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsBoxing: true }, Nested: [] }, _] }); var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); @@ -11284,7 +11013,11 @@ public void Deconstruct(out int x, out int y) AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); - CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + CompileAndVerify(comp).VerifyDiagnostics( + // (10,18): warning CS8624: Argument of type 'dynamic' cannot be used as an output of type 'int' for parameter 'x' in 'void C2.Deconstruct(out int x, out int y)' due to differences in the nullability of reference types. + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgumentForOutput, "c[d]").WithArguments("dynamic", "int", "x", "void C2.Deconstruct(out int x, out int y)").WithLocation(10, 18) + ); string source3 = @" #nullable enable @@ -11321,9 +11054,9 @@ public void Deconstruct(out int? x, out int y) var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); comp3.VerifyDiagnostics( - // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. - // Print(a.Item1); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a.Item1").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + // (10,18): warning CS8624: Argument of type 'dynamic' cannot be used as an output of type 'int?' for parameter 'x' in 'void C2.Deconstruct(out int? x, out int y)' due to differences in the nullability of reference types. + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgumentForOutput, "c[d]").WithArguments("dynamic", "int?", "x", "void C2.Deconstruct(out int? x, out int y)").WithLocation(10, 18) ); tree = comp3.SyntaxTrees.Single(); @@ -11331,8 +11064,8 @@ public void Deconstruct(out int? x, out int y) model = comp3.GetSemanticModel(tree); typeInfo = model.GetTypeInfo(node); - AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); - Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); } [Fact] @@ -11373,18 +11106,18 @@ int this[int x] symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; typeInfo = model.GetTypeInfo(left); - AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); left = (TupleExpressionSyntax)left.Parent.Parent; typeInfo = model.GetTypeInfo(left); - AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); var assignment = (AssignmentExpressionSyntax)left.Parent; typeInfo = model.GetTypeInfo(assignment); @@ -11396,17 +11129,17 @@ int this[int x] var right = (TupleExpressionSyntax)assignment.Right; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); right = (TupleExpressionSyntax)right.Arguments[0].Expression; typeInfo = model.GetTypeInfo(right); AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); var rightElement = right.Arguments[0].Expression; typeInfo = model.GetTypeInfo(rightElement); AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); CompileAndVerify(comp, expectedOutput: "((2, 123), 124) 2").VerifyDiagnostics(); } @@ -11457,25 +11190,25 @@ public void Deconstruct(out int x, out int y) symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; typeInfo = model.GetTypeInfo(left); - AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); left = (TupleExpressionSyntax)left.Parent.Parent; typeInfo = model.GetTypeInfo(left); - AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); var assignment = (AssignmentExpressionSyntax)left.Parent; typeInfo = model.GetTypeInfo(assignment); AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); - Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }, _] }); + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsBoxing: true }, Nested: [] }, _] }, _] }); var right = (TupleExpressionSyntax)assignment.Right; typeInfo = model.GetTypeInfo(right); @@ -11535,13 +11268,13 @@ int this[int x] symbolInfo = model.GetSymbolInfo(elementAccess); AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); var typeInfo = model.GetTypeInfo(elementAccess); - AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; typeInfo = model.GetTypeInfo(left); - AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); - AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); var assignment = (AssignmentExpressionSyntax)left.Parent; typeInfo = model.GetTypeInfo(assignment); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs index 87a9a31973096..36e227ae247df 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs @@ -14139,10 +14139,10 @@ public void AppendFormatted(dynamic d) verifier.VerifyIL("", @" { - // Code size 43 (0x2b) - .maxstack 3 + // Code size 128 (0x80) + .maxstack 9 .locals init (object V_0, //d - CustomHandler V_1) + CustomHandler V_1) IL_0000: ldc.i4.1 IL_0001: box ""int"" IL_0006: stloc.0 @@ -14153,12 +14153,39 @@ .locals init (object V_0, //d IL_0010: ldloca.s V_1 IL_0012: ldstr ""literal"" IL_0017: call ""void CustomHandler.AppendLiteral(dynamic)"" - IL_001c: ldloca.s V_1 - IL_001e: ldloc.0 - IL_001f: call ""void CustomHandler.AppendFormatted(dynamic)"" - IL_0024: ldloc.1 - IL_0025: call ""void Program.<
$>g__M|0_0(CustomHandler)"" - IL_002a: ret + IL_001c: ldsfld ""System.Runtime.CompilerServices.CallSite<<>A{00000008}> Program.<>o__0.<>p__0"" + IL_0021: brtrue.s IL_0062 + IL_0023: ldc.i4 0x100 + IL_0028: ldstr ""AppendFormatted"" + IL_002d: ldnull + IL_002e: ldtoken ""Program"" + IL_0033: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" + IL_0038: ldc.i4.2 + IL_0039: newarr ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo"" + IL_003e: dup + IL_003f: ldc.i4.0 + IL_0040: ldc.i4.s 9 + IL_0042: ldnull + IL_0043: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" + IL_0048: stelem.ref + IL_0049: dup + IL_004a: ldc.i4.1 + IL_004b: ldc.i4.0 + IL_004c: ldnull + IL_004d: call ""Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)"" + IL_0052: stelem.ref + IL_0053: call ""System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, System.Collections.Generic.IEnumerable, System.Type, System.Collections.Generic.IEnumerable)"" + IL_0058: call ""System.Runtime.CompilerServices.CallSite<<>A{00000008}> System.Runtime.CompilerServices.CallSite<<>A{00000008}>.Create(System.Runtime.CompilerServices.CallSiteBinder)"" + IL_005d: stsfld ""System.Runtime.CompilerServices.CallSite<<>A{00000008}> Program.<>o__0.<>p__0"" + IL_0062: ldsfld ""System.Runtime.CompilerServices.CallSite<<>A{00000008}> Program.<>o__0.<>p__0"" + IL_0067: ldfld ""<>A{00000008} System.Runtime.CompilerServices.CallSite<<>A{00000008}>.Target"" + IL_006c: ldsfld ""System.Runtime.CompilerServices.CallSite<<>A{00000008}> Program.<>o__0.<>p__0"" + IL_0071: ldloca.s V_1 + IL_0073: ldloc.0 + IL_0074: callvirt ""void <>A{00000008}.Invoke(System.Runtime.CompilerServices.CallSite, ref CustomHandler, dynamic)"" + IL_0079: ldloc.1 + IL_007a: call ""void Program.<
$>g__M|0_0(CustomHandler)"" + IL_007f: ret } "); } @@ -14422,74 +14449,12 @@ public override string ToString() } """; - var verifier = CompileAndVerify([source, InterpolatedStringHandlerAttribute], expectedOutput: "literalHello world!", targetFramework: TargetFramework.StandardAndCSharp); - verifier.VerifyDiagnostics(); - - verifier.VerifyIL("", type switch - { - "string" => """ - { - // Code size 110 (0x6e) - .maxstack 4 - .locals init (object V_0, //d - CustomInterpolationHandler V_1) - IL_0000: ldstr "Hello world!" - IL_0005: stloc.0 - IL_0006: ldloca.s V_1 - IL_0008: ldc.i4.7 - IL_0009: ldc.i4.1 - IL_000a: call "CustomInterpolationHandler..ctor(int, int)" - IL_000f: ldloca.s V_1 - IL_0011: ldstr "literal" - IL_0016: call "void CustomInterpolationHandler.AppendLiteral(string)" - IL_001b: ldloca.s V_1 - IL_001d: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_0022: brtrue.s IL_0048 - IL_0024: ldc.i4.0 - IL_0025: ldtoken "string" - IL_002a: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_002f: ldtoken "Program" - IL_0034: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" - IL_0039: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" - IL_003e: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" - IL_0043: stsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_0048: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_004d: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" - IL_0052: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" - IL_0057: ldloc.0 - IL_0058: callvirt "string System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" - IL_005d: call "void CustomInterpolationHandler.AppendFormatted(string)" - IL_0062: ldloc.1 - IL_0063: call "string Program.<
$>g__Interpolate|0_0(CustomInterpolationHandler)" - IL_0068: call "void System.Console.WriteLine(string)" - IL_006d: ret - } - """, - _ => $$""" - { - // Code size 47 (0x2f) - .maxstack 3 - .locals init (object V_0, //d - CustomInterpolationHandler V_1) - IL_0000: ldstr "Hello world!" - IL_0005: stloc.0 - IL_0006: ldloca.s V_1 - IL_0008: ldc.i4.7 - IL_0009: ldc.i4.1 - IL_000a: call "CustomInterpolationHandler..ctor(int, int)" - IL_000f: ldloca.s V_1 - IL_0011: ldstr "literal" - IL_0016: call "void CustomInterpolationHandler.AppendLiteral(string)" - IL_001b: ldloca.s V_1 - IL_001d: ldloc.0 - IL_001e: call "void CustomInterpolationHandler.AppendFormatted({{type}})" - IL_0023: ldloc.1 - IL_0024: call "string Program.<
$>g__Interpolate|0_0(CustomInterpolationHandler)" - IL_0029: call "void System.Console.WriteLine(string)" - IL_002e: ret - } - """, - }); + var comp = CreateCompilation([source, InterpolatedStringHandlerAttribute], targetFramework: TargetFramework.StandardAndCSharp); + comp.VerifyDiagnostics( + // (7,31): error CS9230: Cannot perform a dynamic invocation on an expression with type 'CustomInterpolationHandler'. + // Console.WriteLine(Interpolate($"literal" + $"{d}")); + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, expression).WithArguments("CustomInterpolationHandler").WithLocation(7, 31) + ); } [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] @@ -17225,9 +17190,14 @@ public void RefStructHandler_DynamicInHole(string expression) var comp = CreateCompilationWithCSharp(new[] { code, handler }); - // Note: We don't give any errors when mixing dynamic and ref structs today. If that ever changes, we should get an - // error here. This will crash at runtime because of this. - comp.VerifyEmitDiagnostics(); + comp.VerifyDiagnostics( + // 0.cs(4,19): error CS9230: Cannot perform a dynamic invocation on an expression with type 'CustomHandler'. + // CustomHandler c = $"{h1}" + $"{h2}"; + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, expression).WithArguments("CustomHandler").WithLocation(4, 19), + // 0.cs(4,19): error CS9230: Cannot perform a dynamic invocation on an expression with type 'CustomHandler'. + // CustomHandler c = $"{h1}" + $"{h2}"; + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, expression).WithArguments("CustomHandler").WithLocation(4, 19) + ); } [Theory] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NonTrailingNamedArgumentsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NonTrailingNamedArgumentsTests.cs index b9edc6addca35..038f5839f3eee 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NonTrailingNamedArgumentsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NonTrailingNamedArgumentsTests.cs @@ -872,9 +872,6 @@ void M(C c) }"; var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (15,9): error CS0200: Property or indexer 'C.this[int, int]' cannot be assigned to -- it is read only - // c[a: 1, d] = d; - Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "c[a: 1, d]").WithArguments("C.this[int, int]").WithLocation(15, 9) ); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 81d33246c8d68..11f632233060e 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -65962,11 +65962,10 @@ static void G(object? x, dynamic y) } }"; var comp = CreateCompilationWithMscorlib40AndSystemCore(new[] { source }, options: WithNullableEnable()); - comp.VerifyDiagnostics( - // 0.cs(8,11): warning CS8604: Possible null reference argument for parameter 'x' in 'void C.F(object x, object y)'. - // F(x, y); - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "x").WithArguments("x", "void C.F(object x, object y)").WithLocation(8, 11) - ); + // https://github.com/dotnet/roslyn/issues/29893: We should be able to report warnings + // when all applicable methods agree on the nullability of particular parameters. + // (For instance, x in F(x, y) above.) + comp.VerifyDiagnostics(); } [Fact] @@ -158692,5 +158691,45 @@ static void M() where T : struct, I // y.Value.Item.ToString(); Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y.Value.Item").WithLocation(15, 9)); } + + [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2045970")] + public void BadAsyncLambdaInNamedArgument() + { + var source = """ + #nullable enable + using System; + using System.Threading.Tasks; + + class D + { + void M() + { + var c1 = new C(); + var c2 = new C(); + C.M1(f: async () => + { + if (c2 != null) + { + c1. + await c2.M2(); + } + }); + } + } + + class C + { + public static void M1(Func f) { } + public async Task M2() => await Task.Yield(); + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (15,20): error CS1001: Identifier expected + // c1. + Diagnostic(ErrorCode.ERR_IdentifierExpected, "").WithLocation(15, 20), + // (15,20): error CS1002: ; expected + // c1. + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(15, 20)); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs index 154e76c31bcc9..bb9290473490d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs @@ -6,6 +6,8 @@ using System.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Roslyn.Utilities; @@ -4183,5 +4185,113 @@ public void DynamicInvocationOnRefStructs() Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "S").WithArguments("S").WithLocation(5, 11) ); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72916")] + public void RefReturning_Indexer() + { + var source = """ + public class C + { + public static void Main() + { + var c = new C() { [1] = 2 }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + } + """; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72916")] + public void RefReturning_Property() + { + var source = """ + public class C + { + public static void Main() + { + var c = new C() { P = 2 }; + System.Console.WriteLine(c.P); + } + + int _test1 = 0; + ref int P + { + get => ref _test1; + } + } + """; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var propertyAccess = tree.GetRoot().DescendantNodes().OfType().First().Left; + var symbolInfo = model.GetSymbolInfo(propertyAccess); + AssertEx.Equal("ref System.Int32 C.P { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(propertyAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(propertyAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)propertyAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs index ac9c6e9013618..061ff22cd7415 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs @@ -12639,9 +12639,14 @@ public void RefStructHandler_DynamicInHole(string expression) var comp = CreateCompilationWithCSharp(new[] { code, handler }); - // Note: We don't give any errors when mixing dynamic and ref structs today. If that ever changes, we should get an - // error here. This will crash at runtime because of this. - comp.VerifyEmitDiagnostics(); + comp.VerifyDiagnostics( + // 0.cs(4,19): error CS9230: Cannot perform a dynamic invocation on an expression with type 'CustomHandler'. + // CustomHandler c = $"""{h1}""" + $"""{h2}"""; + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, expression).WithArguments("CustomHandler").WithLocation(4, 19), + // 0.cs(4,19): error CS9230: Cannot perform a dynamic invocation on an expression with type 'CustomHandler'. + // CustomHandler c = $"""{h1}""" + $"""{h2}"""; + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, expression).WithArguments("CustomHandler").WithLocation(4, 19) + ); } [Theory] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs index 5fb336b121a87..fc678a39411b1 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs @@ -18436,7 +18436,10 @@ static void Main() } "; var comp = CreateCompilation(text, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe); - CompileAndVerify(comp, expectedOutput: "Called").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (12,9): error CS1971: The call to method 'M' needs to be dynamically dispatched, but cannot be because it is part of a base access expression. Consider casting the dynamic arguments or eliminating the base access. + // base.M(d); + Diagnostic(ErrorCode.ERR_NoDynamicPhantomOnBase, "base.M(d)").WithArguments("M")); } [Fact] @@ -18527,39 +18530,10 @@ public string M(object o) "; var comp = CreateCompilation(text, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe); - var verifier = CompileAndVerify(comp, expectedOutput: "You passed 1").VerifyDiagnostics(); - - verifier.VerifyIL("D.M", -@" -{ - // Code size 78 (0x4e) - .maxstack 4 - .locals init (string V_0) - IL_0000: nop - IL_0001: ldarg.0 - IL_0002: ldsfld ""System.Runtime.CompilerServices.CallSite> D.<>o__1.<>p__0"" - IL_0007: brfalse.s IL_000b - IL_0009: br.s IL_002f - IL_000b: ldc.i4.0 - IL_000c: ldtoken ""int"" - IL_0011: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" - IL_0016: ldtoken ""D"" - IL_001b: call ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)"" - IL_0020: call ""System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)"" - IL_0025: call ""System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)"" - IL_002a: stsfld ""System.Runtime.CompilerServices.CallSite> D.<>o__1.<>p__0"" - IL_002f: ldsfld ""System.Runtime.CompilerServices.CallSite> D.<>o__1.<>p__0"" - IL_0034: ldfld ""System.Func System.Runtime.CompilerServices.CallSite>.Target"" - IL_0039: ldsfld ""System.Runtime.CompilerServices.CallSite> D.<>o__1.<>p__0"" - IL_003e: ldarg.1 - IL_003f: callvirt ""int System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)"" - IL_0044: call ""string B.this[int].get"" - IL_0049: stloc.0 - IL_004a: br.s IL_004c - IL_004c: ldloc.0 - IL_004d: ret -} -"); + comp.VerifyDiagnostics( + // (19,16): error CS1972: The indexer access needs to be dynamically dispatched, but cannot be because it is part of a base access expression. Consider casting the dynamic arguments or eliminating the base access. + // int s = base[(dynamic)o]; + Diagnostic(ErrorCode.ERR_NoDynamicPhantomOnBaseIndexer, "base[(dynamic)o]")); } [Fact] @@ -18582,7 +18556,11 @@ static public class Extension }"; var comp = CreateCompilation(text, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe); - CompileAndVerify(comp, expectedOutput: "Called").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (8,9): error CS1973: 'B' has no applicable method named 'Goo' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax. + // b.Goo(d); + Diagnostic(ErrorCode.ERR_BadArgTypeDynamicExtension, "b.Goo(d)").WithArguments("B", "Goo").WithLocation(8, 9) + ); } [Fact] @@ -22778,7 +22756,10 @@ static void Bar(string x) {} }"; var comp = CreateCompilationWithMscorlib40AndSystemCore(text); - comp.VerifyDiagnostics(); + comp.VerifyDiagnostics( +// (9,9): warning CS1974: The dynamically dispatched call to method 'Goo' may fail at runtime because one or more applicable overloads are conditional methods. +// Goo(d); +Diagnostic(ErrorCode.WRN_DynamicDispatchToConditionalMethod, "Goo(d)").WithArguments("Goo")); } [Fact] diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs index 2e233219a3fc0..203805fa93bd7 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs @@ -85,7 +85,7 @@ public void M(dynamic d) Assert.Equal("C", semanticInfo.Type.Name); Assert.Equal("C..ctor(out dynamic x, dynamic y)", semanticInfo.Symbol.ToTestDisplayString()); - Assert.Equal(CandidateReason.None, semanticInfo.CandidateReason); + Assert.Equal(CandidateReason.LateBound, semanticInfo.CandidateReason); Assert.Equal(0, semanticInfo.CandidateSymbols.Length); Assert.Equal(1, semanticInfo.MethodGroup.Length); Assert.False(semanticInfo.IsCompileTimeConstant); @@ -227,9 +227,9 @@ public void M(dynamic d) Assert.True(semanticInfo.Type.IsDynamic()); Assert.Equal("C C.Create(System.Int32 arg)", semanticInfo.Symbol.ToTestDisplayString()); - Assert.Equal(CandidateReason.None, semanticInfo.CandidateReason); + Assert.Equal(CandidateReason.LateBound, semanticInfo.CandidateReason); Assert.Equal(0, semanticInfo.CandidateSymbols.Length); - Assert.Equal(0, semanticInfo.MethodGroup.Length); + Assert.Equal(1, semanticInfo.MethodGroup.Length); Assert.False(semanticInfo.IsCompileTimeConstant); string sourceCode2 = @" @@ -552,7 +552,7 @@ public int this[int a] Assert.True(semanticInfo.ConvertedType.IsDynamic()); Assert.Equal(ConversionKind.Identity, semanticInfo.ImplicitConversion.Kind); - Assert.Equal(CandidateReason.None, semanticInfo.CandidateReason); + Assert.Equal(CandidateReason.LateBound, semanticInfo.CandidateReason); Assert.Equal(0, semanticInfo.CandidateSymbols.Length); Assert.Equal("System.Int32 C.this[System.Int32 a] { get; set; }", semanticInfo.Symbol.ToTestDisplayString()); diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs index bf23e1d535416..f40628bbb009d 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs @@ -49419,7 +49419,7 @@ static explicit operator byte(I1 x) public void RuntimeFeature_01() { var compilation1 = CreateCompilation("", options: TestOptions.DebugDll, - references: new[] { Net461.mscorlib }, + references: new[] { Net461.References.mscorlib }, targetFramework: TargetFramework.Empty); Assert.False(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation); @@ -49430,7 +49430,7 @@ public void RuntimeFeature_01() public void RuntimeFeature_02() { var compilation1 = CreateCompilation("", options: TestOptions.DebugDll, - references: new[] { Net70.SystemRuntime }, + references: new[] { Net70.References.SystemRuntime }, targetFramework: TargetFramework.Empty); Assert.True(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation); @@ -49730,7 +49730,7 @@ public static class RuntimeFeature "; var compilation1 = CreateCompilation(source, options: TestOptions.DebugDll, - references: new[] { Net461.mscorlib }, + references: new[] { Net461.References.mscorlib }, targetFramework: TargetFramework.Empty); Assert.False(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation); diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs index 132f3bcfa02b7..4ebef05edb5b3 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs @@ -1071,6 +1071,8 @@ public void AllWellKnownTypeMembers() case WellKnownMember.System_Span_T__CopyTo_Span_T: case WellKnownMember.System_ReadOnlySpan_T__CopyTo_Span_T: case WellKnownMember.System_Collections_Immutable_ImmutableArray_T__AsSpan: + case WellKnownMember.System_Span_T__ctor_ref_T: + case WellKnownMember.System_ReadOnlySpan_T__ctor_ref_readonly_T: // Not always available. continue; } diff --git a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalErrorTests.cs b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalErrorTests.cs index 64fc656f659fb..fa74f7a8895d2 100644 --- a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalErrorTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalErrorTests.cs @@ -573,48 +573,48 @@ public static int Main () } [Fact] - public void CS1035AtColonParsedAsComment_01() + public void CS1035AtColonParsedAsBadRazorContent_01() { var test = """ var x = @:; """; ParsingTests.ParseAndValidate(test, - // (1,9): error CS1056: Unexpected character '@' + // (1,9): error CS1525: Invalid expression term '' // var x = @:; - Diagnostic(ErrorCode.ERR_UnexpectedCharacter, "@").WithArguments("@").WithLocation(1, 9), - // (1,12): error CS1733: Expected expression + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "@:;").WithArguments("").WithLocation(1, 9), + // (1,10): error CS1646: Keyword, identifier, or string expected after verbatim specifier: @ // var x = @:; - Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 12), + Diagnostic(ErrorCode.ERR_ExpectedVerbatimLiteral, ":").WithLocation(1, 10), // (1,12): error CS1002: ; expected // var x = @:; Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 12)); } [Fact] - public void CS1035AtColonParsedAsComment_02() + public void CS1035AtColonParsedAsBadRazorContent_02() { var test = """ @:
test
"""; ParsingTests.ParseAndValidate(test, - // (1,1): error CS1056: Unexpected character '@' + // (1,2): error CS1646: Keyword, identifier, or string expected after verbatim specifier: @ // @:
test
- Diagnostic(ErrorCode.ERR_UnexpectedCharacter, "@").WithArguments("@").WithLocation(1, 1)); + Diagnostic(ErrorCode.ERR_ExpectedVerbatimLiteral, ":").WithLocation(1, 2)); } [Fact] - public void CS1035AtColonParsedAsComment_03() + public void CS1035AtColonParsedAsBadRazorContent_03() { var test = """ @: M() {} """; ParsingTests.ParseAndValidate(test, - // (1,1): error CS1056: Unexpected character '@' + // (1,2): error CS1646: Keyword, identifier, or string expected after verbatim specifier: @ // @: M() {} - Diagnostic(ErrorCode.ERR_UnexpectedCharacter, "@").WithArguments("@").WithLocation(1, 1)); + Diagnostic(ErrorCode.ERR_ExpectedVerbatimLiteral, ":").WithLocation(1, 2)); } [Fact, WorkItem(526993, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/526993")] diff --git a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs index 462c92901099f..8c9baa9d69028 100644 --- a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs @@ -441,28 +441,26 @@ public void TestMixedMultiLineCommentTerminators_01(char outsideDelimiter, char } [Fact] - [Trait("Feature", "Comments")] - public void TestAtColonTreatedAsComment_RazorRecovery() + [Trait("Feature", "Razor")] + public void TestAtColonTreatedAsBadRazorContentToken_RazorRecovery() { var text = "@: More text"; var token = LexToken(text); Assert.NotEqual(default, token); - Assert.Equal(SyntaxKind.EndOfFileToken, token.Kind()); + Assert.Equal(SyntaxKind.RazorContentToken, token.Kind()); Assert.Equal(text, token.ToFullString()); var errors = token.Errors(); errors.Verify( - // error CS1056: Unexpected character '@' - TestBase.Diagnostic(ErrorCode.ERR_UnexpectedCharacter).WithArguments("@").WithLocation(1, 1)); - var trivia = token.GetLeadingTrivia().ToArray(); - Assert.Equal(1, trivia.Length); - Assert.NotEqual(default, trivia[0]); - Assert.Equal(SyntaxKind.SingleLineCommentTrivia, trivia[0].Kind()); + // error CS1646: Keyword, identifier, or string expected after verbatim specifier: @ + TestBase.Diagnostic(ErrorCode.ERR_ExpectedVerbatimLiteral).WithLocation(1, 1)); + Assert.Empty(token.LeadingTrivia); + Assert.Empty(token.TrailingTrivia); } [Fact] - [Trait("Feature", "Comments")] - public void TestAtColonTreatedAsCommentAsTrailingTrivia_RazorRecovery() + [Trait("Feature", "Razor")] + public void TestAtColonTreatedAsBadRazorContentTokenEndOfLine_RazorRecovery() { var text = """ Identifier @: More text @@ -474,19 +472,21 @@ public void TestAtColonTreatedAsCommentAsTrailingTrivia_RazorRecovery() Assert.NotEqual(default, token); Assert.Equal(SyntaxKind.IdentifierToken, token.Kind()); + Assert.Empty(token.Errors()); + + token = tokens[1]; + Assert.NotEqual(default, token); + Assert.Equal(SyntaxKind.RazorContentToken, token.Kind()); var errors = token.Errors(); errors.Verify( - // error CS1056: Unexpected character '@' - TestBase.Diagnostic(ErrorCode.ERR_UnexpectedCharacter).WithArguments("@").WithLocation(1, 1)); - var trivia = token.GetLeadingTrivia().ToArray(); - Assert.Equal(0, trivia.Length); - trivia = token.GetTrailingTrivia().ToArray(); - Assert.Equal(3, trivia.Length); - Assert.NotEqual(default, trivia[1]); - Assert.Equal(SyntaxKind.SingleLineCommentTrivia, trivia[1].Kind()); - Assert.Equal("@: More text", trivia[1].ToFullString()); + // error CS1646: Keyword, identifier, or string expected after verbatim specifier: @ + TestBase.Diagnostic(ErrorCode.ERR_ExpectedVerbatimLiteral).WithLocation(1, 1)); + Assert.Empty(token.LeadingTrivia); + Assert.Single(token.TrailingTrivia); + Assert.Equal(SyntaxKind.EndOfLineTrivia, token.TrailingTrivia[0].Kind()); + Assert.Equal("@: More text", token.Text); - token = tokens[1]; + token = tokens[2]; Assert.NotEqual(default, token); Assert.Equal(SyntaxKind.IdentifierToken, token.Kind()); Assert.Equal(""" @@ -496,8 +496,8 @@ public void TestAtColonTreatedAsCommentAsTrailingTrivia_RazorRecovery() } [Fact] - [Trait("Feature", "Comments")] - public void TestAtColonTreatedAsComment_TrailingMultiLine_RazorRecovery() + [Trait("Feature", "Razor")] + public void TestAtColonTreatedAsBadRazorContentToken_TrailingMultiLine_RazorRecovery() { var text = """ @: /* @@ -509,26 +509,24 @@ public void TestAtColonTreatedAsComment_TrailingMultiLine_RazorRecovery() var token = tokens[0]; Assert.NotEqual(default, token); - Assert.Equal(SyntaxKind.IdentifierToken, token.Kind()); - Assert.Equal(""" - @: /* - Identifier - - """, token.ToFullString()); + Assert.Equal(SyntaxKind.RazorContentToken, token.Kind()); + Assert.Equal("@: /*", token.ToString()); var errors = token.Errors(); errors.Verify( - // error CS1056: Unexpected character '@' - TestBase.Diagnostic(ErrorCode.ERR_UnexpectedCharacter).WithArguments("@").WithLocation(1, 1)); - var trivia = token.GetLeadingTrivia().ToArray(); - Assert.Equal(2, trivia.Length); - Assert.NotEqual(default, trivia[0]); - Assert.Equal(SyntaxKind.SingleLineCommentTrivia, trivia[0].Kind()); - Assert.Equal("@: /*", trivia[0].ToFullString()); + // error CS1646: Keyword, identifier, or string expected after verbatim specifier: @ + TestBase.Diagnostic(ErrorCode.ERR_ExpectedVerbatimLiteral).WithLocation(1, 1)); + Assert.Empty(token.LeadingTrivia); + Assert.Single(token.TrailingTrivia); + Assert.Equal(SyntaxKind.EndOfLineTrivia, token.TrailingTrivia[0].Kind()); + + token = tokens[1]; + Assert.Equal(SyntaxKind.IdentifierToken, token.Kind()); + Assert.Equal("Identifier", token.Text); } [Fact] - [Trait("Feature", "Comments")] - public void TestAtColonTreatedAsComment_PreprocessorDisabled_RazorRecovery() + [Trait("Feature", "Razor")] + public void TestAtColonTreatedAsBadRazorContentToken_PreprocessorDisabled_RazorRecovery() { var text = """ #if false @@ -551,8 +549,8 @@ public void TestAtColonTreatedAsComment_PreprocessorDisabled_RazorRecovery() } [Fact] - [Trait("Feature", "Comments")] - public void TestAtColonTreatedAsComment_PreprocessorEnabled_RazorRecovery() + [Trait("Feature", "Razor")] + public void TestAtColonTreatedAsBadRazorContentToken_PreprocessorEnabled_RazorRecovery() { var text = """ #if true @@ -560,21 +558,30 @@ public void TestAtColonTreatedAsComment_PreprocessorEnabled_RazorRecovery() #endif """; - var token = LexToken(text); + var tokens = Lex(text).ToArray(); + Assert.Equal(2, tokens.Length); + var token = tokens[0]; Assert.NotEqual(default, token); - Assert.Equal(SyntaxKind.EndOfFileToken, token.Kind()); - Assert.Equal(text, token.ToFullString()); + Assert.Equal(SyntaxKind.RazorContentToken, token.Kind()); + Assert.Equal(""" + #if true + @: + + """, token.ToFullString()); var errors = token.Errors(); errors.Verify( - // error CS1056: Unexpected character '@' - TestBase.Diagnostic(ErrorCode.ERR_UnexpectedCharacter).WithArguments("@").WithLocation(1, 1)); - var trivia = token.GetLeadingTrivia().ToArray(); - Assert.Equal(4, trivia.Length); - Assert.Equal(SyntaxKind.IfDirectiveTrivia, trivia[0].Kind()); - Assert.Equal(SyntaxKind.SingleLineCommentTrivia, trivia[1].Kind()); - Assert.Equal(SyntaxKind.EndOfLineTrivia, trivia[2].Kind()); - Assert.Equal(SyntaxKind.EndIfDirectiveTrivia, trivia[3].Kind()); + // error CS1646: Keyword, identifier, or string expected after verbatim specifier: @ + TestBase.Diagnostic(ErrorCode.ERR_ExpectedVerbatimLiteral).WithLocation(1, 1)); + Assert.Single(token.GetLeadingTrivia()); + Assert.Equal(SyntaxKind.IfDirectiveTrivia, token.GetLeadingTrivia()[0].Kind()); + + token = tokens[1]; + Assert.Equal(SyntaxKind.EndOfFileToken, token.Kind()); + Assert.Equal("#endif", token.ToFullString()); + Assert.Single(token.GetLeadingTrivia()); + Assert.Equal(SyntaxKind.EndIfDirectiveTrivia, token.GetLeadingTrivia()[0].Kind()); + Assert.Empty(token.GetTrailingTrivia()); } [Fact] diff --git a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/SyntaxTokenParserTests.cs b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/SyntaxTokenParserTests.cs index a943d55c7d1d5..7a3ae5b79fff7 100644 --- a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/SyntaxTokenParserTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/SyntaxTokenParserTests.cs @@ -10,6 +10,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.LexicalAndXml; +#pragma warning disable RSEXPERIMENTAL003 // SyntaxTokenParser public class SyntaxTokenParserTests { [Fact] @@ -265,6 +266,144 @@ public void ResultContextualKind() AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(16, 5), "class", parser.ParseNextToken()); } + [Fact] + public void ParseLeadingTrivia_Empty() + { + var sourceText = SourceText.From("class C { }"); + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + + var result = parser.ParseLeadingTrivia(); + AssertToken(expectedKind: SyntaxKind.None, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 0), "", result); + Assert.Empty(result.Token.LeadingTrivia); + Assert.Empty(result.Token.TrailingTrivia); + + result = parser.ParseTrailingTrivia(); + AssertToken(expectedKind: SyntaxKind.None, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 0), "", result); + Assert.Empty(result.Token.LeadingTrivia); + Assert.Empty(result.Token.TrailingTrivia); + + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 6), "class ", parser.ParseNextToken()); + } + + [Fact] + public void ParseLeadingTrivia_SameLine() + { + var sourceText = SourceText.From("/* test */ class C { }"); + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + + var result = parser.ParseLeadingTrivia(); + AssertToken(expectedKind: SyntaxKind.None, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 11), "/* test */ ", result); + AssertTrivia(result.Token.LeadingTrivia, + (SyntaxKind.MultiLineCommentTrivia, "/* test */"), + (SyntaxKind.WhitespaceTrivia, " ")); + Assert.Empty(result.Token.TrailingTrivia); + + var intermediateResult = parser.ParseLeadingTrivia(); + AssertToken(expectedKind: SyntaxKind.None, expectedContextualKind: SyntaxKind.None, new TextSpan(11, 0), "", intermediateResult); + Assert.Empty(intermediateResult.Token.LeadingTrivia); + Assert.Empty(intermediateResult.Token.TrailingTrivia); + + intermediateResult = parser.ParseTrailingTrivia(); + AssertToken(expectedKind: SyntaxKind.None, expectedContextualKind: SyntaxKind.None, new TextSpan(11, 0), "", intermediateResult); + Assert.Empty(intermediateResult.Token.LeadingTrivia); + Assert.Empty(intermediateResult.Token.TrailingTrivia); + + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(11, 6), "class ", parser.ParseNextToken()); + + parser.ResetTo(result); + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 17), "/* test */ class ", parser.ParseNextToken()); + } + + [Fact] + public void ParseLeadingTrivia_MultiLine() + { + var sourceText = SourceText.From(""" + /* test */ + + class C { } + """.NormalizeLineEndings()); + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + + var result = parser.ParseLeadingTrivia(); + AssertToken(expectedKind: SyntaxKind.None, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 14), $"/* test */\r\n\r\n", result); + AssertTrivia(result.Token.LeadingTrivia, + (SyntaxKind.MultiLineCommentTrivia, "/* test */"), + (SyntaxKind.EndOfLineTrivia, "\r\n"), + (SyntaxKind.EndOfLineTrivia, "\r\n")); + Assert.Empty(result.Token.TrailingTrivia); + + var intermediateResult = parser.ParseLeadingTrivia(); + AssertToken(expectedKind: SyntaxKind.None, expectedContextualKind: SyntaxKind.None, new TextSpan(14, 0), "", intermediateResult); + Assert.Empty(intermediateResult.Token.LeadingTrivia); + Assert.Empty(intermediateResult.Token.TrailingTrivia); + + intermediateResult = parser.ParseTrailingTrivia(); + AssertToken(expectedKind: SyntaxKind.None, expectedContextualKind: SyntaxKind.None, new TextSpan(14, 0), "", intermediateResult); + Assert.Empty(intermediateResult.Token.LeadingTrivia); + Assert.Empty(intermediateResult.Token.TrailingTrivia); + + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(14, 6), "class ", parser.ParseNextToken()); + + parser.ResetTo(result); + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 20), "/* test */\r\n\r\nclass ", parser.ParseNextToken()); + } + + [Fact] + public void ParseTrailingTrivia_Empty() + { + var sourceText = SourceText.From("class C { }"); + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + + var result = parser.ParseTrailingTrivia(); + AssertToken(expectedKind: SyntaxKind.None, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 0), "", result); + Assert.Empty(result.Token.LeadingTrivia); + Assert.Empty(result.Token.TrailingTrivia); + + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 6), "class ", parser.ParseNextToken()); + } + + [Fact] + public void ParseTrailingTrivia_SameLine() + { + var sourceText = SourceText.From("/* test */ class C { }"); + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + + var result = parser.ParseTrailingTrivia(); + AssertToken(expectedKind: SyntaxKind.None, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 11), "/* test */ ", result); + Assert.Empty(result.Token.LeadingTrivia); + AssertTrivia(result.Token.TrailingTrivia, + (SyntaxKind.MultiLineCommentTrivia, "/* test */"), + (SyntaxKind.WhitespaceTrivia, " ")); + + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(11, 6), "class ", parser.ParseNextToken()); + + parser.ResetTo(result); + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 17), "/* test */ class ", parser.ParseNextToken()); + } + + [Fact] + public void ParseTrailingTrivia_MultiLine() + { + var sourceText = SourceText.From(""" + /* test */ + + class C { } + """.NormalizeLineEndings()); + var parser = SyntaxFactory.CreateTokenParser(sourceText, TestOptions.Regular); + + var result = parser.ParseTrailingTrivia(); + AssertToken(expectedKind: SyntaxKind.None, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 12), $"/* test */\r\n", result); + Assert.Empty(result.Token.LeadingTrivia); + AssertTrivia(result.Token.TrailingTrivia, + (SyntaxKind.MultiLineCommentTrivia, "/* test */"), + (SyntaxKind.EndOfLineTrivia, "\r\n")); + + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(12, 8), "\r\nclass ", parser.ParseNextToken()); + + parser.ResetTo(result); + AssertToken(expectedKind: SyntaxKind.ClassKeyword, expectedContextualKind: SyntaxKind.None, new TextSpan(0, 20), "/* test */\r\n\r\nclass ", parser.ParseNextToken()); + } + private static void AssertToken(SyntaxKind expectedKind, SyntaxKind expectedContextualKind, TextSpan expectedFullSpan, string expectedText, SyntaxTokenParser.Result result) { Assert.Equal(expectedKind, result.Token.Kind()); @@ -273,4 +412,15 @@ private static void AssertToken(SyntaxKind expectedKind, SyntaxKind expectedCont Assert.Null(result.Token.Parent); Assert.Equal(expectedFullSpan, result.Token.FullSpan); } + + private static void AssertTrivia(SyntaxTriviaList leadingTrivia, params (SyntaxKind kind, string text)[] expectedTrivia) + { + Assert.Equal(expectedTrivia.Length, leadingTrivia.Count); + for (int i = 0; i < expectedTrivia.Length; i++) + { + var (kind, text) = expectedTrivia[i]; + Assert.Equal(kind, leadingTrivia[i].Kind()); + Assert.Equal(text, leadingTrivia[i].ToFullString()); + } + } } diff --git a/src/Compilers/Core/CodeAnalysisTest/AnalyzerAssemblyLoaderTests.cs b/src/Compilers/Core/CodeAnalysisTest/AnalyzerAssemblyLoaderTests.cs index 5c47171fb8a6f..4f8554738f2b2 100644 --- a/src/Compilers/Core/CodeAnalysisTest/AnalyzerAssemblyLoaderTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/AnalyzerAssemblyLoaderTests.cs @@ -54,7 +54,7 @@ public enum AnalyzerTestKind /// /// Limitation 1: .NET Framework probing path. /// - /// The .NET Framework assembly loader will only call AppDomain.AssemblyResolve when it cannot satifisfy a load + /// The .NET Framework assembly loader will only call AppDomain.AssemblyResolve when it cannot satisfy a load /// request. One of the places the assembly loader will always consider when looking for dependencies of A.dll /// is the directory that A.dll was loading from (it's added to the probing path). That means if B.dll is in the /// same directory then the runtime will silently load it without a way for us to intervene. @@ -95,17 +95,19 @@ public AnalyzerAssemblyLoaderTests(ITestOutputHelper testOutputHelper, AssemblyL #if NETCOREAPP - private void Run(AnalyzerTestKind kind, Action testAction, [CallerMemberName] string? memberName = null) => + private void Run(AnalyzerTestKind kind, Action testAction, IAnalyzerAssemblyResolver[]? externalResolvers = null, [CallerMemberName] string? memberName = null) => Run( kind, static (_, _) => { }, testAction, + externalResolvers, memberName); private void Run( AnalyzerTestKind kind, Action prepLoadContextAction, Action testAction, + IAnalyzerAssemblyResolver[]? externalResolvers = null, [CallerMemberName] string? memberName = null) { var alc = new AssemblyLoadContext($"Test {memberName}", isCollectible: true); @@ -113,7 +115,7 @@ private void Run( { prepLoadContextAction(alc, TestFixture); var util = new InvokeUtil(); - util.Exec(TestOutputHelper, alc, TestFixture, kind, testAction.Method.DeclaringType!.FullName!, testAction.Method.Name); + util.Exec(TestOutputHelper, alc, TestFixture, kind, testAction.Method.DeclaringType!.FullName!, testAction.Method.Name, externalResolvers ?? []); } finally { @@ -126,6 +128,7 @@ private void Run( private void Run( AnalyzerTestKind kind, Action testAction, + IAnalyzerAssemblyResolver[]? externalResolvers = null, [CallerMemberName] string? memberName = null) { AppDomain? appDomain = null; @@ -135,7 +138,7 @@ private void Run( var testOutputHelper = new AppDomainTestOutputHelper(TestOutputHelper); var type = typeof(InvokeUtil); var util = (InvokeUtil)appDomain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName); - util.Exec(testOutputHelper, TestFixture, kind, testAction.Method.DeclaringType.FullName, testAction.Method.Name); + util.Exec(testOutputHelper, TestFixture, kind, testAction.Method.DeclaringType.FullName, testAction.Method.Name, externalResolvers ?? []); } finally { @@ -1421,5 +1424,115 @@ public void AssemblyLoadingInNonDefaultContext_AnalyzerReferencesSystemCollectio }); } #endif + + [Theory] + [CombinatorialData] + public void ExternalResolver_CanIntercept_ReturningNull(AnalyzerTestKind kind) + { + var resolver = new TestAnalyzerAssemblyResolver(n => null); + Run(kind, (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) => + { + loader.AddDependencyLocation(testFixture.Delta1); + Assembly delta = loader.LoadFromPath(testFixture.Delta1); + Assert.NotNull(delta); + VerifyDependencyAssemblies(loader, testFixture.Delta1); + + }, externalResolvers: [resolver]); + Assert.Collection(resolver.CalledFor, (a => Assert.Equal("Delta", a.Name))); + } + + [Theory] + [CombinatorialData] + public void ExternalResolver_CanIntercept_ReturningAssembly(AnalyzerTestKind kind) + { + var resolver = new TestAnalyzerAssemblyResolver(n => GetType().Assembly); + Run(kind, (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) => + { + // net core assembly loader checks that the resolved assembly name is the same as the requested one + // so we use the assembly the tests are contained in as its already be loaded + var thisAssembly = typeof(AnalyzerAssemblyLoaderTests).Assembly; + loader.AddDependencyLocation(thisAssembly.Location); + Assembly loaded = loader.LoadFromPath(thisAssembly.Location); + Assert.Equal(thisAssembly, loaded); + + }, externalResolvers: [resolver]); + Assert.Collection(resolver.CalledFor, (a => Assert.Equal(GetType().Assembly.GetName().Name, a.Name))); + } + + [Theory] + [CombinatorialData] + public void ExternalResolver_CanIntercept_ReturningAssembly_Or_Null(AnalyzerTestKind kind) + { + var thisAssemblyName = GetType().Assembly.GetName(); + var resolver = new TestAnalyzerAssemblyResolver(n => n == thisAssemblyName ? GetType().Assembly : null); + Run(kind, (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) => + { + var thisAssembly = typeof(AnalyzerAssemblyLoaderTests).Assembly; + + loader.AddDependencyLocation(testFixture.Alpha); + Assembly alpha = loader.LoadFromPath(testFixture.Alpha); + Assert.NotNull(alpha); + + loader.AddDependencyLocation(thisAssembly.Location); + Assembly loaded = loader.LoadFromPath(thisAssembly.Location); + Assert.Equal(thisAssembly, loaded); + + loader.AddDependencyLocation(testFixture.Delta1); + Assembly delta = loader.LoadFromPath(testFixture.Delta1); + Assert.NotNull(delta); + + }, externalResolvers: [resolver]); + Assert.Collection(resolver.CalledFor, (a => Assert.Equal("Alpha", a.Name)), a => Assert.Equal(thisAssemblyName.Name, a.Name), a => Assert.Equal("Delta", a.Name)); + } + + [Theory] + [CombinatorialData] + public void ExternalResolver_MultipleResolvers_CanIntercept_ReturningNull(AnalyzerTestKind kind) + { + var resolver1 = new TestAnalyzerAssemblyResolver(n => null); + var resolver2 = new TestAnalyzerAssemblyResolver(n => null); + Run(kind, (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) => + { + loader.AddDependencyLocation(testFixture.Delta1); + Assembly delta = loader.LoadFromPath(testFixture.Delta1); + Assert.NotNull(delta); + VerifyDependencyAssemblies(loader, testFixture.Delta1); + + }, externalResolvers: [resolver1, resolver2]); + Assert.Collection(resolver1.CalledFor, (a => Assert.Equal("Delta", a.Name))); + Assert.Collection(resolver2.CalledFor, (a => Assert.Equal("Delta", a.Name))); + } + + [Theory] + [CombinatorialData] + public void ExternalResolver_MultipleResolvers_ResolutionStops_AfterFirstResolve(AnalyzerTestKind kind) + { + var resolver1 = new TestAnalyzerAssemblyResolver(n => GetType().Assembly); + var resolver2 = new TestAnalyzerAssemblyResolver(n => null); + Run(kind, (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) => + { + var thisAssembly = typeof(AnalyzerAssemblyLoaderTests).Assembly; + loader.AddDependencyLocation(thisAssembly.Location); + Assembly loaded = loader.LoadFromPath(thisAssembly.Location); + Assert.Equal(thisAssembly, loaded); + + }, externalResolvers: [resolver1, resolver2]); + Assert.Collection(resolver1.CalledFor, (a => Assert.Equal(GetType().Assembly.GetName().Name, a.Name))); + Assert.Empty(resolver2.CalledFor); + } + + [Serializable] + private class TestAnalyzerAssemblyResolver(Func func) : MarshalByRefObject, IAnalyzerAssemblyResolver + { + private readonly Func _func = func; + + public List CalledFor { get; } = []; + + public Assembly? ResolveAssembly(AssemblyName assemblyName) + { + CalledFor.Add(assemblyName); + return _func(assemblyName); + } + } } } diff --git a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs index 0fb4efa394de5..1d344104efe9f 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs @@ -932,6 +932,33 @@ public void EditorConfigToDiagnostics() }, options.Select(o => o.TreeOptions).ToArray()); } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72657")] + [InlineData("/", "/")] + [InlineData("/a/b/c/", "/a/b/c/")] + [InlineData("/a/b//c/", "/a/b/c/")] + [InlineData("/a/b/c/", "/a/b//c/")] + [InlineData("/a/b//c/", "/a/b//c/")] + [InlineData("/a/b/c//", "/a/b/c/")] + [InlineData("/a/b/c/", "/a/b/c//")] + [InlineData("/a/b/c//", "/a/b/c//")] + [InlineData("/a/b//c/", "/a/b///c/")] + public void EditorConfigToDiagnostics_DoubleSlash(string prefixEditorConfig, string prefixSource) + { + var configs = ArrayBuilder.GetInstance(); + configs.Add(Parse(""" + [*.cs] + dotnet_diagnostic.cs000.severity = none + """, + prefixEditorConfig + ".editorconfig")); + + var options = GetAnalyzerConfigOptions([prefixSource + "test.cs"], configs); + configs.Free(); + + Assert.Equal([ + CreateImmutableDictionary(("cs000", ReportDiagnostic.Suppress)) + ], options.Select(o => o.TreeOptions).ToArray()); + } + [Fact] public void LaterSectionOverrides() { diff --git a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceAppDomainTests.cs b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceAppDomainTests.cs index 7d679f73ad6e9..f79eaa292b2c4 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceAppDomainTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceAppDomainTests.cs @@ -94,9 +94,9 @@ public class TestAnalyzer : DiagnosticAnalyzer new SyntaxTree[] { CSharp.SyntaxFactory.ParseSyntaxTree(analyzerSource) }, new MetadataReference[] { - NetStandard20.mscorlib, - NetStandard20.netstandard, - NetStandard20.SystemRuntime, + NetStandard20.References.mscorlib, + NetStandard20.References.netstandard, + NetStandard20.References.SystemRuntime, MetadataReference.CreateFromFile(immutable.Path), MetadataReference.CreateFromFile(analyzer.Path) }, diff --git a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceTests.cs b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceTests.cs index db7d81f653b48..ee5884433c796 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceTests.cs @@ -749,5 +749,6 @@ public class TestSourceAndIncrementalGenerator : IIncrementalGenerator, ISourceG public void AddDependencyLocation(string fullPath) { } public bool IsHostAssembly(Assembly assembly) => false; public Assembly LoadFromPath(string fullPath) => throw new Exception(); + public string? GetOriginalDependencyLocation(AssemblyName assembly) => throw new Exception(); } } diff --git a/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs b/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs index 123f0ba58d5d1..fc559b641a539 100644 --- a/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs @@ -420,5 +420,20 @@ public void GetRelativePath_EnsureNo_IndexOutOfRangeException_Unix() var result = PathUtilities.GetRelativePath(@"/A/B/", @"/A/B"); Assert.Equal(expected, result); } + + [Theory] + [InlineData(@"//a/b/c", @"//a/b/c")] + [InlineData(@"/a\b/c/", @"/a/b/c/")] + [InlineData(@"\a\b/c/", @"/a/b/c/")] + [InlineData(@"C:\\a", @"C:/a")] + [InlineData(@"C:\a\b\c\", @"C:/a/b/c/")] + [InlineData(@"/\a", @"//a")] + [InlineData(@"a\\\b", @"a/b")] + [InlineData(@"\\\a\b\c", @"///a/b/c")] + [InlineData(@"\\\\a\b\c", @"///a/b/c")] + public void CollapseWithForwardSlash(string input, string output) + { + AssertEx.Equal(output, PathUtilities.CollapseWithForwardSlash(input.AsSpan())); + } } } diff --git a/src/Compilers/Core/CodeAnalysisTest/InvokeUtil.cs b/src/Compilers/Core/CodeAnalysisTest/InvokeUtil.cs index 3dc9fec60ed45..ef1aae7ebd9d8 100644 --- a/src/Compilers/Core/CodeAnalysisTest/InvokeUtil.cs +++ b/src/Compilers/Core/CodeAnalysisTest/InvokeUtil.cs @@ -35,7 +35,7 @@ namespace Microsoft.CodeAnalysis.UnitTests public sealed class InvokeUtil { - public void Exec(ITestOutputHelper testOutputHelper, AssemblyLoadContext compilerContext, AssemblyLoadTestFixture fixture, AnalyzerTestKind kind, string typeName, string methodName) + internal void Exec(ITestOutputHelper testOutputHelper, AssemblyLoadContext compilerContext, AssemblyLoadTestFixture fixture, AnalyzerTestKind kind, string typeName, string methodName, IAnalyzerAssemblyResolver[] externalResolvers) { // Ensure that the test did not load any of the test fixture assemblies into // the default load context. That should never happen. Assemblies should either @@ -48,9 +48,9 @@ public void Exec(ITestOutputHelper testOutputHelper, AssemblyLoadContext compile using var tempRoot = new TempRoot(); AnalyzerAssemblyLoader loader = kind switch { - AnalyzerTestKind.LoadDirect => new DefaultAnalyzerAssemblyLoader(compilerContext, AnalyzerLoadOption.LoadFromDisk), - AnalyzerTestKind.LoadStream => new DefaultAnalyzerAssemblyLoader(compilerContext, AnalyzerLoadOption.LoadFromStream), - AnalyzerTestKind.ShadowLoad => new ShadowCopyAnalyzerAssemblyLoader(compilerContext, tempRoot.CreateDirectory().Path), + AnalyzerTestKind.LoadDirect => new DefaultAnalyzerAssemblyLoader(compilerContext, AnalyzerLoadOption.LoadFromDisk, externalResolvers.ToImmutableArray()), + AnalyzerTestKind.LoadStream => new DefaultAnalyzerAssemblyLoader(compilerContext, AnalyzerLoadOption.LoadFromStream, externalResolvers.ToImmutableArray()), + AnalyzerTestKind.ShadowLoad => new ShadowCopyAnalyzerAssemblyLoader(compilerContext, tempRoot.CreateDirectory().Path, externalResolvers.ToImmutableArray()), _ => throw ExceptionUtilities.Unreachable() }; @@ -93,13 +93,13 @@ public void Exec(ITestOutputHelper testOutputHelper, AssemblyLoadContext compile public sealed class InvokeUtil : MarshalByRefObject { - public void Exec(ITestOutputHelper testOutputHelper, AssemblyLoadTestFixture fixture, AnalyzerTestKind kind, string typeName, string methodName) + internal void Exec(ITestOutputHelper testOutputHelper, AssemblyLoadTestFixture fixture, AnalyzerTestKind kind, string typeName, string methodName, IAnalyzerAssemblyResolver[] externalResolvers) { using var tempRoot = new TempRoot(); AnalyzerAssemblyLoader loader = kind switch { - AnalyzerTestKind.LoadDirect => new DefaultAnalyzerAssemblyLoader(), - AnalyzerTestKind.ShadowLoad => new ShadowCopyAnalyzerAssemblyLoader(tempRoot.CreateDirectory().Path), + AnalyzerTestKind.LoadDirect => new DefaultAnalyzerAssemblyLoader(externalResolvers.ToImmutableArray()), + AnalyzerTestKind.ShadowLoad => new ShadowCopyAnalyzerAssemblyLoader(tempRoot.CreateDirectory().Path, externalResolvers.ToImmutableArray()), _ => throw ExceptionUtilities.Unreachable() }; diff --git a/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs b/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs index 2296b51ec33ce..a597f86422c39 100644 --- a/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs +++ b/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs @@ -183,7 +183,7 @@ public AnalyzerConfigOptionsResult GetOptionsForSourcePath(string sourcePath) var sectionKey = _sectionKeyPool.Allocate(); - var normalizedPath = PathUtilities.NormalizeWithForwardSlash(sourcePath); + var normalizedPath = PathUtilities.CollapseWithForwardSlash(sourcePath.AsSpan()); normalizedPath = PathUtilities.ExpandAbsolutePathWithRelativeParts(normalizedPath); // If we have a global config, add any sections that match the full path. We can have at most one section since diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs index 5e2a1d99f08dc..c18b863c37250 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs @@ -43,15 +43,16 @@ internal partial class AnalyzerAssemblyLoader internal AssemblyLoadContext CompilerLoadContext => _compilerLoadContext; internal AnalyzerLoadOption AnalyzerLoadOption => _loadOption; - internal AnalyzerAssemblyLoader() - : this(null, AnalyzerLoadOption.LoadFromDisk) + internal AnalyzerAssemblyLoader(ImmutableArray externalResolvers) + : this(null, AnalyzerLoadOption.LoadFromDisk, externalResolvers) { } - internal AnalyzerAssemblyLoader(AssemblyLoadContext? compilerLoadContext, AnalyzerLoadOption loadOption) + internal AnalyzerAssemblyLoader(AssemblyLoadContext? compilerLoadContext, AnalyzerLoadOption loadOption, ImmutableArray externalResolvers) { _loadOption = loadOption; _compilerLoadContext = compilerLoadContext ?? AssemblyLoadContext.GetLoadContext(typeof(AnalyzerAssemblyLoader).GetTypeInfo().Assembly)!; + _externalResolvers = [.. externalResolvers, new CompilerAnalyzerAssemblyResolver(_compilerLoadContext)]; } public bool IsHostAssembly(Assembly assembly) @@ -69,7 +70,7 @@ private partial Assembly Load(AssemblyName assemblyName, string assemblyOriginal { if (!_loadContextByDirectory.TryGetValue(fullDirectoryPath, out loadContext)) { - loadContext = new DirectoryLoadContext(fullDirectoryPath, this, _compilerLoadContext); + loadContext = new DirectoryLoadContext(fullDirectoryPath, this); _loadContextByDirectory[fullDirectoryPath] = loadContext; } } @@ -107,33 +108,23 @@ internal sealed class DirectoryLoadContext : AssemblyLoadContext { internal string Directory { get; } private readonly AnalyzerAssemblyLoader _loader; - private readonly AssemblyLoadContext _compilerLoadContext; - public DirectoryLoadContext(string directory, AnalyzerAssemblyLoader loader, AssemblyLoadContext compilerLoadContext) + public DirectoryLoadContext(string directory, AnalyzerAssemblyLoader loader) : base(isCollectible: true) { Directory = directory; _loader = loader; - _compilerLoadContext = compilerLoadContext; } protected override Assembly? Load(AssemblyName assemblyName) { - var simpleName = assemblyName.Name!; - try + if (_loader.ResolveAssemblyExternally(assemblyName) is { } externallyResolvedAssembly) { - if (_compilerLoadContext.LoadFromAssemblyName(assemblyName) is { } compilerAssembly) - { - return compilerAssembly; - } - } - catch - { - // Expected to happen when the assembly cannot be resolved in the compiler / host - // AssemblyLoadContext. + return externallyResolvedAssembly; } // Prefer registered dependencies in the same directory first. + var simpleName = assemblyName.Name!; var assemblyPath = Path.Combine(Directory, simpleName + ".dll"); if (_loader.IsAnalyzerDependencyPath(assemblyPath)) { @@ -147,7 +138,7 @@ public DirectoryLoadContext(string directory, AnalyzerAssemblyLoader loader, Ass // Note: when loading from disk the .NET runtime has a fallback step that will handle // satellite assembly loading if the call to Load(satelliteAssemblyName) fails. This // loader has a mode where it loads from Stream though and the runtime will not handle - // that automatically. Rather than bifurate our loading behavior between Disk and + // that automatically. Rather than bifurcate our loading behavior between Disk and // Stream both modes just handle satellite loading directly if (assemblyName.CultureInfo is not null && simpleName.EndsWith(".resources", StringComparison.Ordinal)) { @@ -201,6 +192,27 @@ protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) return IntPtr.Zero; } } + + /// + /// A resolver which allows a passed in from the compiler + /// to control assembly resolution. This is important because there are many exchange types + /// that need to unify across the multiple analyzer ALCs. These include common types from + /// Microsoft.CodeAnalysis.dll etc, as well as platform assemblies provided by a + /// host such as visual studio. + /// + /// + /// This resolver essentially forces any assembly that was loaded as a 'core' part of the + /// compiler to be shared across analyzers, and not loaded multiple times into each individual + /// analyzer ALC, even if the analyzer itself shipped a copy of said assembly. + /// + /// The that the core + /// compiler assemblies are already loaded into. + internal sealed class CompilerAnalyzerAssemblyResolver(AssemblyLoadContext compilerContext) : IAnalyzerAssemblyResolver + { + private readonly AssemblyLoadContext _compilerAlc = compilerContext; + + public Assembly? ResolveAssembly(AssemblyName assemblyName) => _compilerAlc.LoadFromAssemblyName(assemblyName); + } } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Desktop.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Desktop.cs index b4d000e335a9d..75d6271100354 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Desktop.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Desktop.cs @@ -5,6 +5,7 @@ #if !NETCOREAPP using System; +using System.Collections.Immutable; using System.Globalization; using System.IO; using System.Reflection; @@ -28,8 +29,9 @@ internal partial class AnalyzerAssemblyLoader { private bool _hookedAssemblyResolve; - internal AnalyzerAssemblyLoader() + internal AnalyzerAssemblyLoader(ImmutableArray externalResolvers) { + _externalResolvers = externalResolvers; } public bool IsHostAssembly(Assembly assembly) @@ -57,6 +59,10 @@ public bool IsHostAssembly(Assembly assembly) private partial Assembly? Load(AssemblyName assemblyName, string assemblyOriginalPath) { EnsureResolvedHooked(); + if (ResolveAssemblyExternally(assemblyName) is { } externallyResolvedAssembly) + { + return externallyResolvedAssembly; + } return AppDomain.CurrentDomain.Load(assemblyName); } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.cs index 6ec884ede8396..9b35df57eee87 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.cs @@ -20,6 +20,13 @@ internal interface IAnalyzerAssemblyLoaderInternal : IAnalyzerAssemblyLoader /// process. Either part of the compiler itself or the process hosting the compiler. /// bool IsHostAssembly(Assembly assembly); + + /// + /// For a given return the location it was originally added + /// from. This will return null for any value that was not directly added through the + /// loader. + /// + string? GetOriginalDependencyLocation(AssemblyName assembly); } /// @@ -61,6 +68,14 @@ internal abstract partial class AnalyzerAssemblyLoader : IAnalyzerAssemblyLoader /// private readonly Dictionary> _knownAssemblyPathsBySimpleName = new(StringComparer.OrdinalIgnoreCase); + /// + /// A collection of s that can be used to override the assembly resolution process. + /// + /// + /// When multiple resolvers are present they are consulted in-order, with the first resolver to return a non-null + /// winning. + private readonly ImmutableArray _externalResolvers; + /// /// The implementation needs to load an with the specified . The /// parameter is the original path. It may be different than @@ -235,6 +250,9 @@ public Assembly LoadFromPath(string originalAnalyzerPath) } } + public string? GetOriginalDependencyLocation(AssemblyName assemblyName) => + GetBestPath(assemblyName).BestOriginalPath; + /// /// Return the best (original, real) path information for loading an assembly with the specified . /// @@ -330,5 +348,33 @@ internal string GetRealAnalyzerLoadPath(string originalFullPath) .ToArray(); } } + + /// + /// Iterates the if any, to see if any of them can resolve + /// the given to an . + /// + /// The name of the assembly to resolve + /// An if one of the resolvers is successful, or + internal Assembly? ResolveAssemblyExternally(AssemblyName assemblyName) + { + if (!_externalResolvers.IsDefaultOrEmpty) + { + foreach (var resolver in _externalResolvers) + { + try + { + if (resolver.ResolveAssembly(assemblyName) is { } resolvedAssembly) + { + return resolvedAssembly; + } + } + catch + { + // Ignore if the external resolver throws + } + } + } + return null; + } } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DefaultAnalyzerAssemblyLoader.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DefaultAnalyzerAssemblyLoader.cs index 624768168d521..b6233d02a6d36 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DefaultAnalyzerAssemblyLoader.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DefaultAnalyzerAssemblyLoader.cs @@ -17,13 +17,19 @@ namespace Microsoft.CodeAnalysis internal sealed class DefaultAnalyzerAssemblyLoader : AnalyzerAssemblyLoader { internal DefaultAnalyzerAssemblyLoader() + : base([]) + { + } + + internal DefaultAnalyzerAssemblyLoader(ImmutableArray externalResolvers) + : base(externalResolvers) { } #if NETCOREAPP - internal DefaultAnalyzerAssemblyLoader(System.Runtime.Loader.AssemblyLoadContext? compilerLoadContext = null, AnalyzerLoadOption loadOption = AnalyzerLoadOption.LoadFromDisk) - : base(compilerLoadContext, loadOption) + internal DefaultAnalyzerAssemblyLoader(System.Runtime.Loader.AssemblyLoadContext? compilerLoadContext = null, AnalyzerLoadOption loadOption = AnalyzerLoadOption.LoadFromDisk, ImmutableArray? externalResolvers = null) + : base(compilerLoadContext, loadOption, externalResolvers ?? []) { } @@ -51,12 +57,12 @@ protected override string PrepareSatelliteAssemblyToLoad(string assemblyFilePath /// /// A shadow copy path will be created on Windows and this value /// will be the base directory where shadow copy assemblies are stored. - internal static IAnalyzerAssemblyLoaderInternal CreateNonLockingLoader(string windowsShadowPath) + internal static IAnalyzerAssemblyLoaderInternal CreateNonLockingLoader(string windowsShadowPath, ImmutableArray? externalResolvers = null) { #if NETCOREAPP if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return new DefaultAnalyzerAssemblyLoader(loadOption: AnalyzerLoadOption.LoadFromStream); + return new DefaultAnalyzerAssemblyLoader(loadOption: AnalyzerLoadOption.LoadFromStream, externalResolvers: externalResolvers); } #endif @@ -68,7 +74,7 @@ internal static IAnalyzerAssemblyLoaderInternal CreateNonLockingLoader(string wi throw new ArgumentException("Must be a full path.", nameof(windowsShadowPath)); } - return new ShadowCopyAnalyzerAssemblyLoader(windowsShadowPath); + return new ShadowCopyAnalyzerAssemblyLoader(windowsShadowPath, externalResolvers); } } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/IAnalyzerAssemblyResolver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/IAnalyzerAssemblyResolver.cs new file mode 100644 index 0000000000000..b6bf2b029de28 --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/IAnalyzerAssemblyResolver.cs @@ -0,0 +1,21 @@ +// 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.Reflection; + +namespace Microsoft.CodeAnalysis +{ + /// + /// Allows a host to override how assembly resolution is performed by the . + /// + internal interface IAnalyzerAssemblyResolver + { + /// + /// Attempts to resolve an assembly by name. + /// + /// The assembly to resolve + /// The resolved assembly, or + Assembly? ResolveAssembly(AssemblyName assemblyName); + } +} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerAssemblyLoader.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerAssemblyLoader.cs index 40592947a8ac4..9cc9ed5b29a33 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerAssemblyLoader.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/ShadowCopyAnalyzerAssemblyLoader.cs @@ -9,6 +9,8 @@ using System.Threading; using System.Threading.Tasks; using Roslyn.Utilities; +using System.Collections.Immutable; +using System.Reflection; #if NETCOREAPP using System.Runtime.Loader; @@ -42,15 +44,16 @@ internal sealed class ShadowCopyAnalyzerAssemblyLoader : AnalyzerAssemblyLoader internal int CopyCount => _mvidPathMap.Count; #if NETCOREAPP - public ShadowCopyAnalyzerAssemblyLoader(string baseDirectory) - : this(null, baseDirectory) + public ShadowCopyAnalyzerAssemblyLoader(string baseDirectory, ImmutableArray? externalResolvers = null) + : this(null, baseDirectory, externalResolvers) { } - public ShadowCopyAnalyzerAssemblyLoader(AssemblyLoadContext? compilerLoadContext, string baseDirectory) - : base(compilerLoadContext, AnalyzerLoadOption.LoadFromDisk) + public ShadowCopyAnalyzerAssemblyLoader(AssemblyLoadContext? compilerLoadContext, string baseDirectory, ImmutableArray? externalResolvers = null) + : base(compilerLoadContext, AnalyzerLoadOption.LoadFromDisk, externalResolvers ?? []) #else - public ShadowCopyAnalyzerAssemblyLoader(string baseDirectory) + public ShadowCopyAnalyzerAssemblyLoader(string baseDirectory, ImmutableArray? externalResolvers = null) + : base(externalResolvers ?? []) #endif { if (baseDirectory is null) diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs index 03aebde7eba92..dfdd6d5956f06 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs @@ -588,7 +588,14 @@ void recurse(ImmutableArray members, DebugId? containingDisplay Debug.Assert(!containingDisplayClassId.HasValue); var displayClass = (INamedTypeSymbolInternal)member; - var displayClassMembers = (synthesizedMemberMap != null) ? synthesizedMemberMap[displayClass] : displayClass.GetMembers(); + + // Synthesized member map, if given, contains all the synthesized members that implement lambdas and closures. + // If not given (for PE symbols) the members are defined on the type symbol directly. + // If the display class doesn't have any synthesized members it won't be present in the map. + // See https://github.com/dotnet/roslyn/issues/73365 + var displayClassMembers = synthesizedMemberMap != null + ? (synthesizedMemberMap.TryGetValue(displayClass, out var m) ? m : []) + : displayClass.GetMembers(); if (displayClass.TypeKind == TypeKind.Struct) { diff --git a/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs b/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs index 570121ad3d3bb..161f93819ddab 100644 --- a/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs +++ b/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs @@ -9,6 +9,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.PooledObjects; @@ -780,6 +781,42 @@ public static bool IsValidFilePath([NotNullWhen(true)] string? fullPath) public static string NormalizeWithForwardSlash(string p) => DirectorySeparatorChar == '/' ? p : p.Replace(DirectorySeparatorChar, '/'); + /// + /// Replaces all sequences of '\' or '/' with a single '/' but preserves UNC prefix '//'. + /// + public static string CollapseWithForwardSlash(ReadOnlySpan path) + { + var sb = new StringBuilder(path.Length); + + int start = 0; + if (path.Length > 1 && IsAnyDirectorySeparator(path[0]) && IsAnyDirectorySeparator(path[1])) + { + // Preserve UNC paths. + sb.Append("//"); + start = 2; + } + + bool wasDirectorySeparator = false; + for (int i = start; i < path.Length; i++) + { + if (IsAnyDirectorySeparator(path[i])) + { + if (!wasDirectorySeparator) + { + sb.Append('/'); + } + wasDirectorySeparator = true; + } + else + { + sb.Append(path[i]); + wasDirectorySeparator = false; + } + } + + return sb.ToString(); + } + /// /// Takes an absolute path and attempts to expand any '..' or '.' into their equivalent representation. /// diff --git a/src/Compilers/Core/Portable/InternalUtilities/RoslynExperiments.cs b/src/Compilers/Core/Portable/InternalUtilities/RoslynExperiments.cs index 75e810ab03512..47827c416f072 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/RoslynExperiments.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/RoslynExperiments.cs @@ -14,4 +14,7 @@ internal static class RoslynExperiments internal const string Interceptors = "RSEXPERIMENTAL002"; internal const string Interceptors_Url = "https://github.com/dotnet/csharplang/issues/7009"; + + internal const string SyntaxTokenParser = "RSEXPERIMENTAL003"; + internal const string SyntaxTokenParser_Url = "https://github.com/dotnet/roslyn/issues/73002"; } diff --git a/src/Compilers/Core/Portable/InternalUtilities/StringTable.cs b/src/Compilers/Core/Portable/InternalUtilities/StringTable.cs index ae51191090a91..6d7bea21b1829 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/StringTable.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/StringTable.cs @@ -6,7 +6,7 @@ using System.Text; using System.Threading; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Collections; #if DEBUG using System.Diagnostics; @@ -63,7 +63,7 @@ private struct Entry // slightly slower than local cache // we read this cache when having a miss in local cache // writes to local cache will update shared cache as well. - private static readonly Entry[] s_sharedTable = new Entry[SharedSize]; + private static readonly SegmentedArray s_sharedTable = new SegmentedArray(SharedSize); // essentially a random number // the usage pattern will randomly use and increment this diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index dbcb737b52e09..1cdddef0d9097 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -479,6 +479,7 @@ internal enum WellKnownMember System_Span_T__ctor_Pointer, System_Span_T__ctor_Array, + System_Span_T__ctor_ref_T, System_Span_T__get_Item, System_Span_T__get_Length, System_Span_T__Slice_Int_Int, @@ -486,6 +487,7 @@ internal enum WellKnownMember System_ReadOnlySpan_T__ctor_Pointer, System_ReadOnlySpan_T__ctor_Array, System_ReadOnlySpan_T__ctor_Array_Start_Length, + System_ReadOnlySpan_T__ctor_ref_readonly_T, System_ReadOnlySpan_T__get_Item, System_ReadOnlySpan_T__get_Length, System_ReadOnlySpan_T__Slice_Int_Int, diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index 351d772afc467..0fd77f299f9c2 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -3333,6 +3333,14 @@ static WellKnownMembers() 1, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type (byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.GenericTypeParameter, 0, + + // System_Span_T__ctor_ref_T + (byte)(MemberFlags.Constructor), // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Span_T - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type + (byte)SignatureTypeCode.ByReference, (byte)SignatureTypeCode.GenericTypeParameter, 0, // System_Span_T__get_Item (byte)(MemberFlags.PropertyGet), // Flags @@ -3388,6 +3396,14 @@ static WellKnownMembers() (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, + // System_ReadOnlySpan_T__ctor_ref_readonly_T + (byte)(MemberFlags.Constructor), // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type + (byte)SignatureTypeCode.ByReference, (byte)SignatureTypeCode.GenericTypeParameter, 0, + // System_ReadOnlySpan_T__get_Item (byte)(MemberFlags.PropertyGet), // Flags (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), // DeclaringTypeId @@ -4722,12 +4738,14 @@ static WellKnownMembers() ".ctor", // System_Runtime_CompilerServices_ObsoleteAttribute__ctor ".ctor", // System_Span_T__ctor_Pointer ".ctor", // System_Span_T__ctor_Array + ".ctor", // System_Span_T__ctor_ref_T "get_Item", // System_Span_T__get_Item "get_Length", // System_Span_T__get_Length "Slice", // System_Span_T__Slice_Int_Int ".ctor", // System_ReadOnlySpan_T__ctor_Pointer ".ctor", // System_ReadOnlySpan_T__ctor_Array ".ctor", // System_ReadOnlySpan_T__ctor_Array_Start_Length + ".ctor", // System_ReadOnlySpan_T__ctor_ref_readonly_T "get_Item", // System_ReadOnlySpan_T__get_Item "get_Length", // System_ReadOnlySpan_T__get_Length "Slice", // System_ReadOnlySpan_T__Slice_Int_Int diff --git a/src/Compilers/Server/VBCSCompiler/AnalyzerConsistencyChecker.cs b/src/Compilers/Server/VBCSCompiler/AnalyzerConsistencyChecker.cs index 5c83c240d529c..2fdf0b39c7355 100644 --- a/src/Compilers/Server/VBCSCompiler/AnalyzerConsistencyChecker.cs +++ b/src/Compilers/Server/VBCSCompiler/AnalyzerConsistencyChecker.cs @@ -128,7 +128,8 @@ private static bool CheckCore( var loadedAssemblyMvid = loadedAssembly.ManifestModule.ModuleVersionId; if (resolvedPathMvid != loadedAssemblyMvid) { - var message = $"analyzer assembly '{resolvedPath}' has MVID '{resolvedPathMvid}' but loaded assembly '{loadedAssembly.Location}' has MVID '{loadedAssemblyMvid}'"; + var loadedAssemblyLocation = loader.GetOriginalDependencyLocation(loadedAssembly.GetName()) ?? loadedAssembly.Location; + var message = $"analyzer assembly '{resolvedPath}' has MVID '{resolvedPathMvid}' but loaded assembly '{loadedAssemblyLocation}' has MVID '{loadedAssemblyMvid}'"; errorMessages ??= new List(); errorMessages.Add(message); logger.LogError(message); diff --git a/src/Compilers/Server/VBCSCompilerTests/AnalyzerConsistencyCheckerTests.cs b/src/Compilers/Server/VBCSCompilerTests/AnalyzerConsistencyCheckerTests.cs index 7cdf7cc9c9287..8bb31b511dfb6 100644 --- a/src/Compilers/Server/VBCSCompilerTests/AnalyzerConsistencyCheckerTests.cs +++ b/src/Compilers/Server/VBCSCompilerTests/AnalyzerConsistencyCheckerTests.cs @@ -65,7 +65,7 @@ private TempFile CreateNetStandardDll(TempDirectory directory, string assemblyNa var comp = CSharpCompilation.Create( assemblyName, sources, - references: NetStandard20.All, + references: NetStandard20.References.All, options: options); var file = directory.CreateFile($"{assemblyName}.dll"); @@ -112,7 +112,7 @@ public void DifferingMvidsDifferentDirectory() var directory = Temp.CreateDirectory(); var assemblyLoader = DefaultAnalyzerAssemblyLoader.CreateNonLockingLoader(directory.CreateDirectory("shadow").Path); - var key = NetStandard20.netstandard.GetAssemblyIdentity().PublicKey; + var key = NetStandard20.References.netstandard.GetAssemblyIdentity().PublicKey; var mvidAlpha1 = CreateNetStandardDll(directory.CreateDirectory("mvid1"), "MvidAlpha", "1.0.0.0", key, "class C { }"); var mvidAlpha2 = CreateNetStandardDll(directory.CreateDirectory("mvid2"), "MvidAlpha", "1.0.0.0", key, "class D { }"); @@ -137,7 +137,7 @@ public void DifferingMvidsSameDirectory() var directory = Temp.CreateDirectory(); var assemblyLoader = DefaultAnalyzerAssemblyLoader.CreateNonLockingLoader(directory.CreateDirectory("shadow").Path); - var key = NetStandard20.netstandard.GetAssemblyIdentity().PublicKey; + var key = NetStandard20.References.netstandard.GetAssemblyIdentity().PublicKey; var mvidAlpha1 = CreateNetStandardDll(directory, "MvidAlpha", "1.0.0.0", key, "class C { }"); var result = AnalyzerConsistencyChecker.Check( @@ -154,8 +154,16 @@ public void DifferingMvidsSameDirectory() directory.Path, ImmutableArray.Create(new CommandLineAnalyzerReference(mvidAlpha2.Path)), assemblyLoader, - Logger); + Logger, + out List? errorMessages); Assert.False(result); + Assert.NotNull(errorMessages); + + // Both the original and failed paths need to appear in the message, not the shadow copy + // paths + var errorMessage = errorMessages!.Single(); + Assert.Contains(mvidAlpha1.Path, errorMessage); + Assert.Contains(mvidAlpha2.Path, errorMessage); } /// @@ -168,7 +176,7 @@ public void DifferingMvidsSameDirectory() public void LoadingLibraryFromCompiler() { var directory = Temp.CreateDirectory(); - _ = CreateNetStandardDll(directory, "System.Memory", "2.0.0.0", NetStandard20.netstandard.GetAssemblyIdentity().PublicKey); + _ = CreateNetStandardDll(directory, "System.Memory", "2.0.0.0", NetStandard20.References.netstandard.GetAssemblyIdentity().PublicKey); // This test must use the DefaultAnalyzerAssemblyLoader as we want assembly binding redirects // to take affect here. @@ -226,7 +234,7 @@ public void AssemblyLoadException() public void LoadingSimpleLibrary() { var directory = Temp.CreateDirectory(); - var key = NetStandard20.netstandard.GetAssemblyIdentity().PublicKey; + var key = NetStandard20.References.netstandard.GetAssemblyIdentity().PublicKey; var compFile = CreateNetStandardDll(directory, "netstandardRef", "1.0.0.0", key); var analyzerReferences = ImmutableArray.Create(new CommandLineAnalyzerReference(compFile.Path)); @@ -242,5 +250,6 @@ public void LoadingSimpleLibrary() public void AddDependencyLocation(string fullPath) { } public bool IsHostAssembly(Assembly assembly) => false; public Assembly LoadFromPath(string fullPath) => throw new Exception(); + public string? GetOriginalDependencyLocation(AssemblyName assembly) => throw new Exception(); } } diff --git a/src/Compilers/Test/Core/AssemblyLoadTestFixture.cs b/src/Compilers/Test/Core/AssemblyLoadTestFixture.cs index 1dc56963a68c2..b86840383740d 100644 --- a/src/Compilers/Test/Core/AssemblyLoadTestFixture.cs +++ b/src/Compilers/Test/Core/AssemblyLoadTestFixture.cs @@ -522,9 +522,9 @@ private static string GenerateDll(string assemblyName, TempDirectory directory, syntaxTrees: new SyntaxTree[] { SyntaxFactory.ParseSyntaxTree(SourceText.From(csSource, encoding: null, SourceHashAlgorithms.Default)) }, references: (new MetadataReference[] { - NetStandard20.mscorlib, - NetStandard20.netstandard, - NetStandard20.SystemRuntime + NetStandard20.References.mscorlib, + NetStandard20.References.netstandard, + NetStandard20.References.SystemRuntime }).Concat(additionalReferences), options: options); diff --git a/src/Compilers/Test/Core/Platform/Desktop/TestHelpers.cs b/src/Compilers/Test/Core/Platform/Desktop/TestHelpers.cs index fc864457cf320..c00cc71683579 100644 --- a/src/Compilers/Test/Core/Platform/Desktop/TestHelpers.cs +++ b/src/Compilers/Test/Core/Platform/Desktop/TestHelpers.cs @@ -83,9 +83,9 @@ public class TestAnalyzer : DiagnosticAnalyzer new SyntaxTree[] { SyntaxFactory.ParseSyntaxTree(SourceText.From(analyzerSource, encoding: null, SourceHashAlgorithms.Default)) }, new MetadataReference[] { - NetStandard20.mscorlib, - NetStandard20.netstandard, - NetStandard20.SystemRuntime, + NetStandard20.References.mscorlib, + NetStandard20.References.netstandard, + NetStandard20.References.SystemRuntime, MetadataReference.CreateFromFile(immutable.Path), MetadataReference.CreateFromFile(analyzer.Path) }, diff --git a/src/Compilers/Test/Core/TargetFrameworkUtil.cs b/src/Compilers/Test/Core/TargetFrameworkUtil.cs index 6f9ea4264263a..592cdb613b90f 100644 --- a/src/Compilers/Test/Core/TargetFrameworkUtil.cs +++ b/src/Compilers/Test/Core/TargetFrameworkUtil.cs @@ -91,7 +91,8 @@ public enum TargetFramework Net50, Net60, Net70, - Net80 + Net80, + Net90, } /// @@ -101,12 +102,12 @@ public enum TargetFramework /// public static class NetCoreApp { - public static ImmutableArray AllReferenceInfos { get; } = ImmutableArray.CreateRange(Net70.References.All); - public static ImmutableArray References { get; } = ImmutableArray.CreateRange(Net70.All); + public static ImmutableArray AllReferenceInfos { get; } = ImmutableArray.CreateRange(Net70.ReferenceInfos.All); + public static ImmutableArray References { get; } = ImmutableArray.CreateRange(Net70.References.All); - public static PortableExecutableReference netstandard { get; } = Net70.netstandard; - public static PortableExecutableReference mscorlib { get; } = Net70.mscorlib; - public static PortableExecutableReference SystemRuntime { get; } = Net70.SystemRuntime; + public static PortableExecutableReference netstandard { get; } = Net70.References.netstandard; + public static PortableExecutableReference mscorlib { get; } = Net70.References.mscorlib; + public static PortableExecutableReference SystemRuntime { get; } = Net70.References.SystemRuntime; } /// @@ -124,7 +125,7 @@ public static class NetFramework /// public static ImmutableArray References { get; } = ImmutableArray - .CreateRange(Net461.All) + .CreateRange(Net461.References.All) .Add(NetFx.ValueTuple.tuplelib); /// @@ -136,19 +137,19 @@ public static class NetFramework /// public static ImmutableArray Standard { get; } = ImmutableArray.Create( - Net461.mscorlib, - Net461.System, - Net461.SystemCore, + Net461.References.mscorlib, + Net461.References.System, + Net461.References.SystemCore, NetFx.ValueTuple.tuplelib, - Net461.SystemRuntime); - - public static PortableExecutableReference mscorlib { get; } = Net461.mscorlib; - public static PortableExecutableReference System { get; } = Net461.System; - public static PortableExecutableReference SystemRuntime { get; } = Net461.SystemRuntime; - public static PortableExecutableReference SystemCore { get; } = Net461.SystemCore; - public static PortableExecutableReference SystemThreadingTasks { get; } = Net461.SystemThreadingTasks; - public static PortableExecutableReference MicrosoftCSharp { get; } = Net461.MicrosoftCSharp; - public static PortableExecutableReference MicrosoftVisualBasic { get; } = Net461.MicrosoftVisualBasic; + Net461.References.SystemRuntime); + + public static PortableExecutableReference mscorlib { get; } = Net461.References.mscorlib; + public static PortableExecutableReference System { get; } = Net461.References.System; + public static PortableExecutableReference SystemRuntime { get; } = Net461.References.SystemRuntime; + public static PortableExecutableReference SystemCore { get; } = Net461.References.SystemCore; + public static PortableExecutableReference SystemThreadingTasks { get; } = Net461.References.SystemThreadingTasks; + public static PortableExecutableReference MicrosoftCSharp { get; } = Net461.References.MicrosoftCSharp; + public static PortableExecutableReference MicrosoftVisualBasic { get; } = Net461.References.MicrosoftVisualBasic; } public static class TargetFrameworkUtil @@ -168,24 +169,108 @@ public static class TargetFrameworkUtil * for a TypeLoadException are missing important information for resolving problems if/when they occur. * https://github.com/dotnet/roslyn/issues/25961 */ + public static ImmutableArray WinRTReferences => + [ + .. TestBase.WinRtRefs + ]; + public static ImmutableArray MinimalReferences => + [ + TestBase.MinCorlibRef + ]; + public static ImmutableArray MinimalAsyncReferences => + [ + TestBase.MinAsyncCorlibRef + ]; + public static ImmutableArray Mscorlib45ExtendedReferences => + [ + Net451.mscorlib, + Net451.System, + Net451.SystemCore, + TestBase.ValueTupleRef, + Net451.SystemRuntime + ]; + public static ImmutableArray Mscorlib46ExtendedReferences => + [ + Net461.References.mscorlib, + Net461.References.System, + Net461.References.SystemCore, + TestBase.ValueTupleRef, + Net461.References.SystemRuntime + ]; + /* + * ⚠ Dev note ⚠: TestBase properties end here. + */ - public static ImmutableArray Mscorlib40References => ImmutableArray.Create(Net40.mscorlib); - public static ImmutableArray Mscorlib40ExtendedReferences => ImmutableArray.Create(Net40.mscorlib, Net40.System, Net40.SystemCore); - public static ImmutableArray Mscorlib40andSystemCoreReferences => ImmutableArray.Create(Net40.mscorlib, Net40.SystemCore); - public static ImmutableArray Mscorlib40andVBRuntimeReferences => ImmutableArray.Create(Net40.mscorlib, Net40.System, Net40.MicrosoftVisualBasic); - public static ImmutableArray Mscorlib45References => ImmutableArray.Create(Net451.mscorlib); - public static ImmutableArray Mscorlib45ExtendedReferences => ImmutableArray.Create(Net451.mscorlib, Net451.System, Net451.SystemCore, TestBase.ValueTupleRef, Net451.SystemRuntime); - public static ImmutableArray Mscorlib45AndCSharpReferences => ImmutableArray.Create(Net451.mscorlib, Net451.SystemCore, Net451.MicrosoftCSharp); - public static ImmutableArray Mscorlib45AndVBRuntimeReferences => ImmutableArray.Create(Net451.mscorlib, Net451.System, Net451.MicrosoftVisualBasic); - public static ImmutableArray Mscorlib46References => ImmutableArray.Create(Net461.mscorlib); - public static ImmutableArray Mscorlib46ExtendedReferences => ImmutableArray.Create(Net461.mscorlib, Net461.System, Net461.SystemCore, TestBase.ValueTupleRef, Net461.SystemRuntime); - public static ImmutableArray Mscorlib461References => ImmutableArray.Create(Net461.mscorlib); - public static ImmutableArray Mscorlib461ExtendedReferences => ImmutableArray.Create(Net461.mscorlib, Net461.System, Net461.SystemCore, NetFx.ValueTuple.tuplelib, Net461.SystemRuntime); - public static ImmutableArray NetStandard20References => ImmutableArray.Create(NetStandard20.netstandard, NetStandard20.mscorlib, NetStandard20.SystemRuntime, NetStandard20.SystemCore, NetStandard20.SystemDynamicRuntime, NetStandard20.SystemLinq, NetStandard20.SystemLinqExpressions); - public static ImmutableArray WinRTReferences => ImmutableArray.Create(TestBase.WinRtRefs); - public static ImmutableArray DefaultVbReferences => ImmutableArray.Create(Net451.mscorlib, Net451.System, Net451.SystemCore, Net451.MicrosoftVisualBasic); - public static ImmutableArray MinimalReferences => ImmutableArray.Create(TestBase.MinCorlibRef); - public static ImmutableArray MinimalAsyncReferences => ImmutableArray.Create(TestBase.MinAsyncCorlibRef); + public static ImmutableArray Mscorlib40References { get; } = + [ + Net40.mscorlib + ]; + public static ImmutableArray Mscorlib40ExtendedReferences { get; } = + [ + Net40.mscorlib, + Net40.System, + Net40.SystemCore + ]; + public static ImmutableArray Mscorlib40andSystemCoreReferences { get; } = + [ + Net40.mscorlib, + Net40.SystemCore + ]; + public static ImmutableArray Mscorlib40andVBRuntimeReferences { get; } = + [ + Net40.mscorlib, + Net40.System, + Net40.MicrosoftVisualBasic + ]; + public static ImmutableArray Mscorlib45References { get; } = + [ + Net451.mscorlib + ]; + public static ImmutableArray Mscorlib45AndCSharpReferences { get; } = + [ + Net451.mscorlib, + Net451.SystemCore, + Net451.MicrosoftCSharp + ]; + public static ImmutableArray Mscorlib45AndVBRuntimeReferences { get; } = + [ + Net451.mscorlib, + Net451.System, + Net451.MicrosoftVisualBasic + ]; + public static ImmutableArray Mscorlib46References { get; } = + [ + Net461.References.mscorlib + ]; + public static ImmutableArray Mscorlib461References { get; } = + [ + Net461.References.mscorlib + ]; + public static ImmutableArray Mscorlib461ExtendedReferences { get; } = + [ + Net461.References.mscorlib, + Net461.References.System, + Net461.References.SystemCore, + NetFx.ValueTuple.tuplelib, + Net461.References.SystemRuntime + ]; + public static ImmutableArray NetStandard20References { get; } = + [ + NetStandard20.References.netstandard, + NetStandard20.References.mscorlib, + NetStandard20.References.SystemRuntime, + NetStandard20.References.SystemCore, + NetStandard20.References.SystemDynamicRuntime, + NetStandard20.References.SystemLinq, + NetStandard20.References.SystemLinqExpressions + ]; + public static ImmutableArray DefaultVbReferences { get; } = + [ + Net451.mscorlib, + Net451.System, + Net451.SystemCore, + Net451.MicrosoftVisualBasic + ]; #if DEBUG @@ -206,8 +291,9 @@ static TargetFrameworkUtil() TargetFramework.NetStandard20 => NetStandard20References, TargetFramework.Net50 => ImmutableArray.CreateRange(LoadDynamicReferences("Net50")), TargetFramework.Net60 => ImmutableArray.CreateRange(LoadDynamicReferences("Net60")), - TargetFramework.NetCoreApp or TargetFramework.Net70 => ImmutableArray.CreateRange(Net70.All), + TargetFramework.NetCoreApp or TargetFramework.Net70 => ImmutableArray.CreateRange(Net70.References.All), TargetFramework.Net80 => ImmutableArray.CreateRange(LoadDynamicReferences("Net80")), + TargetFramework.Net90 => ImmutableArray.CreateRange(LoadDynamicReferences("Net90")), TargetFramework.NetFramework => NetFramework.References, TargetFramework.NetLatest => NetLatest, TargetFramework.Standard => StandardReferences, diff --git a/src/Compilers/Test/Core/TestBase.cs b/src/Compilers/Test/Core/TestBase.cs index d4de9c7e3d829..fe864fc970d5f 100644 --- a/src/Compilers/Test/Core/TestBase.cs +++ b/src/Compilers/Test/Core/TestBase.cs @@ -163,7 +163,7 @@ public virtual void Dispose() public static MetadataReference SystemRuntimeSerializationRef_v4_0_30319_17929 => s_systemRuntimeSerializationRef_v4_0_30319_17929.Value; private static readonly Lazy s_systemCoreRef_v46 = new Lazy( - () => AssemblyMetadata.CreateFromImage(Net461.References.SystemCore.ImageBytes).GetReference(display: "System.Core.v4_6_1038_0.dll"), + () => AssemblyMetadata.CreateFromImage(Net461.ReferenceInfos.SystemCore.ImageBytes).GetReference(display: "System.Core.v4_6_1038_0.dll"), LazyThreadSafetyMode.PublicationOnly); public static MetadataReference SystemCoreRef_v46 => s_systemCoreRef_v4_0_30319_17929.Value; @@ -220,7 +220,7 @@ public virtual void Dispose() public static MetadataReference MscorlibRef_v4_0_30316_17626 => Net451.mscorlib; private static readonly Lazy s_mscorlibRef_v46 = new Lazy( - () => AssemblyMetadata.CreateFromImage(Net461.References.mscorlib.ImageBytes).GetReference(display: "mscorlib.v4_6_1038_0.dll", filePath: @"Z:\FxReferenceAssembliesUri"), + () => AssemblyMetadata.CreateFromImage(Net461.ReferenceInfos.mscorlib.ImageBytes).GetReference(display: "mscorlib.v4_6_1038_0.dll", filePath: @"Z:\FxReferenceAssembliesUri"), LazyThreadSafetyMode.PublicationOnly); public static MetadataReference MscorlibRef_v46 => s_mscorlibRef_v46.Value; @@ -267,7 +267,7 @@ public virtual void Dispose() public static MetadataReference SystemRef => s_systemRef.Value; private static readonly Lazy s_systemRef_v46 = new Lazy( - () => AssemblyMetadata.CreateFromImage(Net461.References.System.ImageBytes).GetReference(display: "System.v4_6_1038_0.dll"), + () => AssemblyMetadata.CreateFromImage(Net461.ReferenceInfos.System.ImageBytes).GetReference(display: "System.v4_6_1038_0.dll"), LazyThreadSafetyMode.PublicationOnly); public static MetadataReference SystemRef_v46 => s_systemRef_v46.Value; diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index 7e37c631b3766..8388968457fb4 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -1204,7 +1204,7 @@ public static CSharpCompilation CreateCompilationWithTasksExtensions( else { allReferences = TargetFrameworkUtil.Mscorlib461ExtendedReferences; - allReferences = allReferences.Concat(new[] { Net461.SystemThreadingTasks, SystemThreadingTasksExtensions.PortableLib }); + allReferences = allReferences.Concat(new[] { Net461.References.SystemThreadingTasks, SystemThreadingTasksExtensions.PortableLib }); } if (references != null) diff --git a/src/Compilers/VisualBasic/Portable/Semantics/CompileTimeCalculations.vb b/src/Compilers/VisualBasic/Portable/Semantics/CompileTimeCalculations.vb index d30639cf12746..79d119f11bf1f 100644 --- a/src/Compilers/VisualBasic/Portable/Semantics/CompileTimeCalculations.vb +++ b/src/Compilers/VisualBasic/Portable/Semantics/CompileTimeCalculations.vb @@ -603,7 +603,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic If sourceValue > &H7000000000000000L Then Dim temporary As Double = (sourceValue - &H7000000000000000L) - If temporary < &H7000000000000000L AndAlso UncheckedCLng(temporary) > &H1000000000000000L Then + If temporary < &H7000000000000000L AndAlso UncheckedCLng(temporary) < &H1000000000000000L Then Return False End If Else diff --git a/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingObjectInitializerTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingObjectInitializerTests.vb index aa9e152b4d414..c008d8dd5f120 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingObjectInitializerTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Binding/BindingObjectInitializerTests.vb @@ -2073,6 +2073,52 @@ Console.writeline( cust2.e.ToString) CompileAndVerify(compilation) End Sub + + Public Sub RefReturningProperty() + Dim cSharpSource = ref _f; + } +}]]>.Value + Dim cSharpCompilation = CreateCSharpCompilation(cSharpSource).VerifyDiagnostics() + Dim cSharpRef = cSharpCompilation.EmitToPortableExecutableReference() + + Dim source = .Value + + Dim expectedOperationTree = .Value + + Dim expectedDiagnostics = String.Empty + + VerifyOperationTreeAndDiagnosticsForTest(Of ObjectCreationExpressionSyntax)(source, expectedOperationTree, expectedDiagnostics, references:={cSharpRef}) + + CompileAndVerify(CreateCompilation(source, {cSharpRef}, TestOptions.ReleaseExe), expectedOutput:=).VerifyDiagnostics() + End Sub + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/Conversions.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/Conversions.vb index d7d6b1d6070b9..25b3ddd15a495 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/Conversions.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/Conversions.vb @@ -605,7 +605,8 @@ End Class New TypeAndValue(uint64Type, CULng(18)), New TypeAndValue(decimalType, CDec(-11.3)), New TypeAndValue(doubleType, CDbl(&HF000000000000000UL)), - New TypeAndValue(doubleType, CDbl(&H70000000000000F0L)), + New TypeAndValue(doubleType, CDbl(&H8000000000000000L)), + New TypeAndValue(doubleType, CDbl(&H7FFFFFFFFFFFFC00L)), New TypeAndValue(typeCodeType, Int32.MinValue), New TypeAndValue(typeCodeType, Int32.MaxValue), New TypeAndValue(typeCodeType, CInt(-3)), @@ -1165,7 +1166,8 @@ End Class New TypeAndValue(uint64Type, CULng(18)), New TypeAndValue(decimalType, CDec(-11.3)), New TypeAndValue(doubleType, CDbl(&HF000000000000000UL)), - New TypeAndValue(doubleType, CDbl(&H70000000000000F0L)), + New TypeAndValue(doubleType, CDbl(&H8000000000000000L)), + New TypeAndValue(doubleType, CDbl(&H7FFFFFFFFFFFFC00L)), New TypeAndValue(typeCodeType, Int32.MinValue), New TypeAndValue(typeCodeType, Int32.MaxValue), New TypeAndValue(typeCodeType, CInt(-3)), @@ -5095,5 +5097,46 @@ End Module ]]>) End Sub + + + Public Sub ConvertLargeDoubleConstantsAndLiteralsToLong() + Dim compilation = CreateCompilationWithMscorlib40AndVBRuntime( + + + , options:=TestOptions.ReleaseExe.WithOverflowChecks(True)) + + Dim expectedOutput = + + CompileAndVerify(compilation, expectedOutput:=expectedOutput).VerifyDiagnostics() + End Sub + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/RequiredMembersTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/RequiredMembersTests.vb index d71521c1d7937..f7abc9ccd3903 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/RequiredMembersTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/RequiredMembersTests.vb @@ -13,7 +13,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests Inherits BasicTestBase Private Function CreateCSharpCompilationWithRequiredMembers(source As String) As CSharpCompilation - Return CreateCSharpCompilation(source, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Return CreateCSharpCompilation(source, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) End Function @@ -424,14 +424,14 @@ End Module" Public Sub EnforcedRequiredMembers_ThroughRetargeting_NoneSet() Dim retargetedCode = GetCDefinition(hasSetsRequiredMembers:=False) - Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim originalBasic = CreateCompilation(" Public Class Base Public Property C As C End Class", {originalC.EmitToImageReference()}) - Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim comp = CreateCompilation(" Module M @@ -455,14 +455,14 @@ BC37321: Required member 'Public Overloads Property Prop As Integer' must be set Public Sub EnforcedRequiredMembers_ThroughRetargeting_AllSet( constructor As String) Dim retargetedCode = GetCDefinition(hasSetsRequiredMembers:=False) - Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim originalBasic = CreateCompilation(" Public Class Base Public Property C As C End Class", {originalC.EmitToImageReference()}) - Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim comp = CreateCompilation(" Module M @@ -480,14 +480,14 @@ End Module", {originalBasic.ToMetadataReference(), retargetedC.EmitToImageRefere Dim retargetedCode = GetCDefinition(hasSetsRequiredMembers:=True) - Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim originalBasic = CreateCompilation(" Public Class Base Public Property C As C End Class", {originalC.EmitToImageReference()}) - Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), retargetedCode, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim comp = CreateCompilation(" Module M @@ -504,14 +504,14 @@ End Module", {originalBasic.ToMetadataReference(), retargetedC.EmitToImageRefere Dim codeWithRequired = GetCDefinition(hasSetsRequiredMembers:=False) Dim codeWithoutRequired = codeWithRequired.Replace("required ", "") - Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), codeWithoutRequired, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim originalC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(1, 0, 0, 0), isRetargetable:=True), codeWithoutRequired, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim originalBasic = CreateCompilation(" Public Class Derived Inherits C End Class", {originalC.EmitToImageReference()}) - Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), codeWithRequired, referencedAssemblies:=Basic.Reference.Assemblies.Net70.All) + Dim retargetedC = CreateCSharpCompilation(New AssemblyIdentity("Ret", New Version(2, 0, 0, 0), isRetargetable:=True), codeWithRequired, referencedAssemblies:=Basic.Reference.Assemblies.Net70.References.All) Dim comp = CreateCompilation(" Module M @@ -543,7 +543,7 @@ End Class", targetFramework:=TargetFramework.Net70) public class Derived : Base { public required int Prop { get; set; } -}", referencedAssemblies:=DirectCast(Basic.Reference.Assemblies.Net70.All, IEnumerable(Of MetadataReference)).Append(originalVbComp.EmitToImageReference())) +}", referencedAssemblies:=DirectCast(Basic.Reference.Assemblies.Net70.References.All, IEnumerable(Of MetadataReference)).Append(originalVbComp.EmitToImageReference())) Dim comp = CreateCompilation($" Module M @@ -1952,7 +1952,7 @@ namespace System } } } -", referencedAssemblies:=Basic.Reference.Assemblies.Net461.All) +", referencedAssemblies:=Basic.Reference.Assemblies.Net461.References.All) Dim csharpCompReference As MetadataReference = csharpComp.EmitToImageReference() ' Using Net461 to get a framework without ValueTuple @@ -2143,7 +2143,7 @@ namespace System } } } -", referencedAssemblies:=Basic.Reference.Assemblies.Net461.All) +", referencedAssemblies:=Basic.Reference.Assemblies.Net461.References.All) ' Using Net461 to get a framework without ValueTuple diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb index 59b991849251c..46f6caf769314 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb @@ -821,7 +821,9 @@ End Namespace WellKnownMember.System_ReadOnlySpan_T__ToArray, WellKnownMember.System_Span_T__CopyTo_Span_T, WellKnownMember.System_ReadOnlySpan_T__CopyTo_Span_T, - WellKnownMember.System_Collections_Immutable_ImmutableArray_T__AsSpan + WellKnownMember.System_Collections_Immutable_ImmutableArray_T__AsSpan, + WellKnownMember.System_Span_T__ctor_ref_T, + WellKnownMember.System_ReadOnlySpan_T__ctor_ref_readonly_T ' Not always available. Continue For End Select @@ -1026,7 +1028,9 @@ End Namespace WellKnownMember.System_ReadOnlySpan_T__ToArray, WellKnownMember.System_Span_T__CopyTo_Span_T, WellKnownMember.System_ReadOnlySpan_T__CopyTo_Span_T, - WellKnownMember.System_Collections_Immutable_ImmutableArray_T__AsSpan + WellKnownMember.System_Collections_Immutable_ImmutableArray_T__AsSpan, + WellKnownMember.System_Span_T__ctor_ref_T, + WellKnownMember.System_ReadOnlySpan_T__ctor_ref_readonly_T ' Not always available. Continue For End Select diff --git a/src/Dependencies/PooledObjects/ArrayBuilder.cs b/src/Dependencies/PooledObjects/ArrayBuilder.cs index b0246977a88b4..62c42b66d1412 100644 --- a/src/Dependencies/PooledObjects/ArrayBuilder.cs +++ b/src/Dependencies/PooledObjects/ArrayBuilder.cs @@ -303,7 +303,12 @@ public void Sort(IComparer comparer) } public void Sort(Comparison compare) - => Sort(Comparer.Create(compare)); + { + if (this.Count <= 1) + return; + + Sort(Comparer.Create(compare)); + } public void Sort(int startIndex, IComparer comparer) { diff --git a/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs b/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs index 7f5a193236ab7..c3fcbfbd57629 100644 --- a/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs +++ b/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs @@ -105,7 +105,7 @@ protected override IList FormatBasedOnEndToken(ParsedDocument docume root, [CommonFormattingHelpers.GetFormattingSpan(root, span.Value)], options, - rules: null, + rules: default, cancellationToken).GetTextChanges(cancellationToken); } diff --git a/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests.cs index c17cda9948689..d3c2a61bced67 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests.cs @@ -6374,8 +6374,7 @@ static void Main(string[] args) /// Note that this test verifies the current end of line sequence in using directives is preserved regardless of /// whether this matches the end_of_line value in .editorconfig or not. /// - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/62976")] public async Task TestAddUsingPreservesNewlines1(TestHost testHost, [CombinatorialValues("\n", "\r\n")] string sourceNewLine, [CombinatorialValues("\n", "\r\n")] string configuredNewLine) { @@ -6418,8 +6417,7 @@ class Class /// Note that this test verifies the current end of line sequence in using directives is preserved regardless of /// whether this matches the end_of_line value in .editorconfig or not. /// - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/62976")] public async Task TestAddUsingPreservesNewlines2(TestHost testHost, [CombinatorialValues("\n", "\r\n")] string sourceNewLine, [CombinatorialValues("\n", "\r\n")] string configuredNewLine) { @@ -6473,8 +6471,7 @@ class Class parameters: new TestParameters(options: Option(FormattingOptions2.NewLine, configuredNewLine), testHost: testHost)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/62976")] public async Task TestAddUsingPreservesNewlines3(TestHost testHost, [CombinatorialValues("\n", "\r\n")] string sourceNewLine, [CombinatorialValues("\n", "\r\n")] string configuredNewLine) { @@ -6528,8 +6525,7 @@ class Class parameters: new TestParameters(options: Option(FormattingOptions2.NewLine, configuredNewLine), testHost: testHost)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/24642")] public async Task TestAddUsingWithMalformedGeneric(TestHost testHost) { @@ -6552,8 +6548,7 @@ class Class parameters: new TestParameters(testHost: testHost)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestOutsideOfMethodWithMalformedGenericParameters(TestHost testHost) { await TestInRegularAndScript1Async( diff --git a/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests_ExtensionMethods.cs b/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests_ExtensionMethods.cs index ce2ca8aeced1d..068ec267af60a 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests_ExtensionMethods.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests_ExtensionMethods.cs @@ -1107,8 +1107,7 @@ public static void Deconstruct(this Program p, out int x, out int y) { } parseOptions: null); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/16547")] public async Task TestAddUsingForAddExtensionMethodWithSameNameAsProperty(TestHost testHost) { @@ -1171,8 +1170,7 @@ public static Foo Self(this Foo foo) }", testHost); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/39155")] public async Task TestExtensionGetAwaiterOverload(TestHost testHost) { @@ -1262,8 +1260,7 @@ public void GetResult() ", testHost); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/39155")] public async Task TestExtensionSelectOverload(TestHost testHost) { diff --git a/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests_Razor.cs b/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests_Razor.cs index 9c7234d79258b..c216fb6cc271c 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests_Razor.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/AddUsing/AddUsingTests_Razor.cs @@ -14,8 +14,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.AddUsing [Trait(Traits.Feature, Traits.Features.CodeActionsAddImport)] public partial class AddUsingTests_Razor : AbstractAddUsingTests { - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAddIntoHiddenRegionWithModernSpanMapper(TestHost host) { await TestAsync( diff --git a/src/EditorFeatures/CSharpTest/CodeActions/InitializeParameter/AddParameterCheckTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/InitializeParameter/AddParameterCheckTests.cs index 33ffe9790ea5f..34d8bbbf7374d 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/InitializeParameter/AddParameterCheckTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/InitializeParameter/AddParameterCheckTests.cs @@ -7,7 +7,6 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.InitializeParameter; -using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; @@ -18,6 +17,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.InitializeParameter { using VerifyCS = CSharpCodeRefactoringVerifier; + [UseExportProvider] [Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] public class AddParameterCheckTests { diff --git a/src/EditorFeatures/CSharpTest/CodeActions/InlineMethod/CSharpInlineMethodTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/InlineMethod/CSharpInlineMethodTests.cs index 0246f4c4468a2..1a0cef13fbdb6 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/InlineMethod/CSharpInlineMethodTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/InlineMethod/CSharpInlineMethodTests.cs @@ -14,6 +14,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.InlineMethod { + [UseExportProvider] [Trait(Traits.Feature, Traits.Features.CodeActionsInlineMethod)] public class CSharpInlineMethodTests { diff --git a/src/EditorFeatures/CSharpTest/Completion/ArgumentProviders/ContextVariableArgumentProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/ArgumentProviders/ContextVariableArgumentProviderTests.cs index acb913736e4e8..13ca5cc67e947 100644 --- a/src/EditorFeatures/CSharpTest/Completion/ArgumentProviders/ContextVariableArgumentProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/ArgumentProviders/ContextVariableArgumentProviderTests.cs @@ -41,8 +41,7 @@ void Target({type} arg) await VerifyDefaultValueAsync(markup, expectedDefaultValue: null, previousDefaultValue: "prior"); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestOutVariable( [CombinatorialValues("string", "bool", "int?")] string type, [CombinatorialValues("out", "ref", "in")] string modifier) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs index 66757d94eb47e..cc83029109a30 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs @@ -22,8 +22,7 @@ namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests [UseExportProvider] public class ActiveStatementTrackingServiceTests { - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TrackingService_GetLatestSpansAsync(bool scheduleInitialTrackingBeforeOpenDoc) { var source1 = "class C { void F() => G(1); void G(int a) => System.Console.WriteLine(1); }"; diff --git a/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs index 03a2dfe0fdc1a..5c3a2f0b11da4 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs @@ -841,7 +841,7 @@ private static string[] GetSupportedDiagnosticIdsForCodeCleanupService(string la var supportedDiagnostics = enabledDiagnostics.Diagnostics.SelectMany(x => x.DiagnosticIds).ToArray(); return supportedDiagnostics; - EditorTestWorkspace GetTestWorkspaceForLanguage(string language) + static EditorTestWorkspace GetTestWorkspaceForLanguage(string language) { if (language == LanguageNames.CSharp) { diff --git a/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs b/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs index ef5d1071b242c..2ee4eedd0421f 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs @@ -441,7 +441,7 @@ public void M() var document = workspace.CurrentSolution.Projects.Single().Documents.Single(); var syntaxRoot = await document.GetRequiredSyntaxRootAsync(CancellationToken.None); var options = CSharpSyntaxFormattingOptions.Default; - var node = Formatter.Format(syntaxRoot, spans, workspace.Services.SolutionServices, options, rules: null, CancellationToken.None); + var node = Formatter.Format(syntaxRoot, spans, workspace.Services.SolutionServices, options, rules: default, CancellationToken.None); Assert.Equal(expected, node.ToFullString()); } diff --git a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs index fca8d0d529356..cf59f9601eb94 100644 --- a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs +++ b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs @@ -30,7 +30,7 @@ private static void SetupSearchProject( Mock searchService, string pattern, bool isFullyLoaded, - INavigateToSearchResult? result) + ImmutableArray results) { if (isFullyLoaded) { @@ -42,7 +42,7 @@ private static void SetupSearchProject( pattern, ImmutableHashSet.Empty, It.IsAny(), - It.IsAny>(), + It.IsAny, Task>>(), It.IsAny>(), It.IsAny())).Callback( (Solution solution, @@ -51,12 +51,12 @@ private static void SetupSearchProject( string pattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) => { - if (result != null) - onResultFound(null!, result); + if (results.Length > 0) + onResultsFound(results); }).Returns(Task.CompletedTask); searchService.Setup(ss => ss.SearchGeneratedDocumentsAsync( @@ -65,7 +65,7 @@ private static void SetupSearchProject( pattern, ImmutableHashSet.Empty, It.IsAny(), - It.IsAny>(), + It.IsAny, Task>>(), It.IsAny>(), It.IsAny())).Callback( (Solution solution, @@ -73,12 +73,12 @@ private static void SetupSearchProject( string pattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) => { - if (result != null) - onResultFound(null!, result); + if (results.Length > 0) + onResultsFound(results); }).Returns(Task.CompletedTask); // Followed by a generated doc search. @@ -92,7 +92,7 @@ private static void SetupSearchProject( pattern, ImmutableHashSet.Empty, It.IsAny(), - It.IsAny>(), + It.IsAny, Task>>(), It.IsAny>(), It.IsAny())).Callback( (Solution solution, @@ -101,12 +101,12 @@ private static void SetupSearchProject( string pattern2, IImmutableSet kinds, Document? activeDocument, - Func onResultFound2, + Func, Task> onResultsFound2, Func onProjectCompleted, CancellationToken cancellationToken) => { - if (result != null) - onResultFound2(null!, result); + if (results.Length > 0) + onResultsFound2(results); }).Returns(Task.CompletedTask); } } @@ -121,10 +121,10 @@ public async Task NotFullyLoadedOnlyMakesOneSearchProjectCallIfValueReturned() var pattern = "irrelevant"; - var result = new TestNavigateToSearchResult(workspace, new TextSpan(0, 0)); + var results = ImmutableArray.Create(new TestNavigateToSearchResult(workspace, new TextSpan(0, 0))); var searchService = new Mock(MockBehavior.Strict); - SetupSearchProject(searchService, pattern, isFullyLoaded: false, result); + SetupSearchProject(searchService, pattern, isFullyLoaded: false, results); // Simulate a host that says the solution isn't fully loaded. var hostMock = new Mock(MockBehavior.Strict); @@ -134,7 +134,7 @@ public async Task NotFullyLoadedOnlyMakesOneSearchProjectCallIfValueReturned() var callbackMock = new Mock(MockBehavior.Strict); callbackMock.Setup(c => c.ReportIncomplete()); callbackMock.Setup(c => c.ReportProgress(It.IsAny(), It.IsAny())); - callbackMock.Setup(c => c.AddItemAsync(It.IsAny(), result, It.IsAny())).Returns(Task.CompletedTask); + callbackMock.Setup(c => c.AddResultsAsync(results, It.IsAny())).Returns(Task.CompletedTask); // Because we returned a result when not fully loaded, we should notify the user that data was not complete. callbackMock.Setup(c => c.Done(false)); @@ -156,14 +156,14 @@ public async Task NotFullyLoadedMakesTwoSearchProjectCallIfValueNotReturned(bool var pattern = "irrelevant"; - var result = new TestNavigateToSearchResult(workspace, new TextSpan(0, 0)); + var results = ImmutableArray.Create(new TestNavigateToSearchResult(workspace, new TextSpan(0, 0))); var searchService = new Mock(MockBehavior.Strict); // First call will pass in that we're not fully loaded. If we return null, we should get // another call with the request to search the fully loaded data. - SetupSearchProject(searchService, pattern, isFullyLoaded: false, result: null); - SetupSearchProject(searchService, pattern, isFullyLoaded: true, result); + SetupSearchProject(searchService, pattern, isFullyLoaded: false, results: []); + SetupSearchProject(searchService, pattern, isFullyLoaded: true, results); // Simulate a host that says the solution isn't fully loaded. var hostMock = new Mock(MockBehavior.Strict); @@ -173,7 +173,7 @@ public async Task NotFullyLoadedMakesTwoSearchProjectCallIfValueNotReturned(bool var callbackMock = new Mock(MockBehavior.Strict); callbackMock.Setup(c => c.ReportIncomplete()); callbackMock.Setup(c => c.ReportProgress(It.IsAny(), It.IsAny())); - callbackMock.Setup(c => c.AddItemAsync(It.IsAny(), result, It.IsAny())) + callbackMock.Setup(c => c.AddResultsAsync(results, It.IsAny())) .Returns(Task.CompletedTask); // Because the remote host wasn't fully loaded, we still notify that our results may be incomplete. @@ -201,8 +201,8 @@ public async Task NotFullyLoadedStillReportsAsNotCompleteIfRemoteHostIsStillHydr // First call will pass in that we're not fully loaded. If we return null, we should get another call with // the request to search the fully loaded data. If we don't report anything the second time, we will still // tell the user the search was complete. - SetupSearchProject(searchService, pattern, isFullyLoaded: false, result: null); - SetupSearchProject(searchService, pattern, isFullyLoaded: true, result: null); + SetupSearchProject(searchService, pattern, isFullyLoaded: false, results: []); + SetupSearchProject(searchService, pattern, isFullyLoaded: true, results: []); // Simulate a host that says the solution isn't fully loaded. var hostMock = new Mock(MockBehavior.Strict); @@ -233,12 +233,12 @@ public async Task FullyLoadedMakesSingleSearchProjectCallIfValueNotReturned() var pattern = "irrelevant"; - var result = new TestNavigateToSearchResult(workspace, new TextSpan(0, 0)); + var results = ImmutableArray.Create(new TestNavigateToSearchResult(workspace, new TextSpan(0, 0))); var searchService = new Mock(MockBehavior.Strict); // First call will pass in that we're fully loaded. If we return null, we should not get another call. - SetupSearchProject(searchService, pattern, isFullyLoaded: true, result: null); + SetupSearchProject(searchService, pattern, isFullyLoaded: true, results: []); // Simulate a host that says the solution is fully loaded. var hostMock = new Mock(MockBehavior.Strict); @@ -247,7 +247,7 @@ public async Task FullyLoadedMakesSingleSearchProjectCallIfValueNotReturned() var callbackMock = new Mock(MockBehavior.Strict); callbackMock.Setup(c => c.ReportProgress(It.IsAny(), It.IsAny())); - callbackMock.Setup(c => c.AddItemAsync(It.IsAny(), result, It.IsAny())) + callbackMock.Setup(c => c.AddResultsAsync(results, It.IsAny())) .Returns(Task.CompletedTask); // Because we did a full search, we should let the user know it was totally accurate. @@ -269,7 +269,7 @@ public async Task DoNotCrashWithoutSearchService() using var workspace = EditorTestWorkspace.CreateCSharp(""); var pattern = "irrelevant"; - var result = new TestNavigateToSearchResult(workspace, new TextSpan(0, 0)); + var results = ImmutableArray.Create(new TestNavigateToSearchResult(workspace, new TextSpan(0, 0))); var hostMock = new Mock(MockBehavior.Strict); hostMock.Setup(h => h.IsFullyLoadedAsync(It.IsAny())).Returns(() => new ValueTask(true)); @@ -280,7 +280,7 @@ public async Task DoNotCrashWithoutSearchService() var callbackMock = new Mock(MockBehavior.Strict); callbackMock.Setup(c => c.ReportIncomplete()); callbackMock.Setup(c => c.ReportProgress(It.IsAny(), It.IsAny())); - callbackMock.Setup(c => c.AddItemAsync(It.IsAny(), result, It.IsAny())).Returns(Task.CompletedTask); + callbackMock.Setup(c => c.AddResultsAsync(results, It.IsAny())).Returns(Task.CompletedTask); callbackMock.Setup(c => c.Done(true)); @@ -317,7 +317,7 @@ public class D """, composition: FirstActiveAndVisibleComposition); var pattern = "irrelevant"; - var result = new TestNavigateToSearchResult(workspace, new TextSpan(0, 0)); + var results = ImmutableArray.Create(new TestNavigateToSearchResult(workspace, new TextSpan(0, 0))); var hostMock = new Mock(MockBehavior.Strict); hostMock.Setup(h => h.IsFullyLoadedAsync(It.IsAny())).Returns(() => new ValueTask(true)); @@ -338,7 +338,7 @@ public class D var callbackMock = new Mock(MockBehavior.Strict); callbackMock.Setup(c => c.ReportIncomplete()); callbackMock.Setup(c => c.ReportProgress(It.IsAny(), It.IsAny())); - callbackMock.Setup(c => c.AddItemAsync(It.IsAny(), result, It.IsAny())).Returns(Task.CompletedTask); + callbackMock.Setup(c => c.AddResultsAsync(results, It.IsAny())).Returns(Task.CompletedTask); callbackMock.Setup(c => c.Done(true)); @@ -365,25 +365,25 @@ private sealed class MockAdvancedNavigateToSearchService : IAdvancedNavigateToSe public Action? OnSearchGeneratedDocumentsAsyncCalled { get; set; } public Action? OnSearchProjectsAsyncCalled { get; set; } - public Task SearchCachedDocumentsAsync(Solution solution, ImmutableArray projects, ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) + public Task SearchCachedDocumentsAsync(Solution solution, ImmutableArray projects, ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { OnSearchCachedDocumentsAsyncCalled?.Invoke(); return Task.CompletedTask; } - public Task SearchDocumentAsync(Document document, string searchPattern, IImmutableSet kinds, Func onResultFound, CancellationToken cancellationToken) + public Task SearchDocumentAsync(Document document, string searchPattern, IImmutableSet kinds, Func, Task> onResultsFound, CancellationToken cancellationToken) { OnSearchDocumentsAsyncCalled?.Invoke(); return Task.CompletedTask; } - public Task SearchGeneratedDocumentsAsync(Solution solution, ImmutableArray projects, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) + public Task SearchGeneratedDocumentsAsync(Solution solution, ImmutableArray projects, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { OnSearchGeneratedDocumentsAsyncCalled?.Invoke(); return Task.CompletedTask; } - public Task SearchProjectsAsync(Solution solution, ImmutableArray projects, ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) + public Task SearchProjectsAsync(Solution solution, ImmutableArray projects, ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { OnSearchProjectsAsyncCalled?.Invoke(); return Task.CompletedTask; diff --git a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToTests.cs b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToTests.cs index 70cbfeaa534c6..14f570ec10efa 100644 --- a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToTests.cs +++ b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToTests.cs @@ -31,8 +31,7 @@ public class NavigateToTests : AbstractNavigateToTests protected override EditorTestWorkspace CreateWorkspace(string content, TestComposition composition) => EditorTestWorkspace.CreateCSharp(content, composition: composition); - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task NoItemsForEmptyFile(TestHost testHost, Composition composition) { await TestAsync(testHost, composition, "", async w => @@ -41,8 +40,7 @@ await TestAsync(testHost, composition, "", async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindClass(TestHost testHost, Composition composition) { await TestAsync( @@ -57,8 +55,7 @@ class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindRecord(TestHost testHost, Composition composition) { await TestAsync( @@ -73,8 +70,7 @@ record Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindRecordClass(TestHost testHost, Composition composition) { await TestAsync( @@ -89,8 +85,7 @@ record class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindRecordStruct(TestHost testHost, Composition composition) { var content = XElement.Parse(""" @@ -111,8 +106,7 @@ await TestAsync(testHost, composition, content, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindClassInFileScopedNamespace(TestHost testHost, Composition composition) { var content = XElement.Parse(""" @@ -132,8 +126,7 @@ await TestAsync(testHost, composition, content, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindVerbatimClass(TestHost testHost, Composition composition) { await TestAsync( @@ -152,8 +145,7 @@ class @static }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindNestedClass(TestHost testHost, Composition composition) { await TestAsync( @@ -174,8 +166,7 @@ internal class DogBed }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindMemberInANestedClass(TestHost testHost, Composition composition) { await TestAsync( @@ -199,8 +190,7 @@ public void Method() }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindGenericClassWithConstraints(TestHost testHost, Composition composition) { await TestAsync( @@ -217,8 +207,7 @@ class Goo where T : IEnumerable }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindGenericMethodWithConstraints(TestHost testHost, Composition composition) { await TestAsync( @@ -238,8 +227,7 @@ public void Bar(T item) where T : IComparable }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindPartialClass(TestHost testHost, Composition composition) { await TestAsync( @@ -264,8 +252,7 @@ partial class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindTypesInMetadata(TestHost testHost, Composition composition) { await TestAsync( @@ -280,8 +267,7 @@ await TestAsync( }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindClassInNamespace(TestHost testHost, Composition composition) { await TestAsync( @@ -299,8 +285,7 @@ class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindStruct(TestHost testHost, Composition composition) { await TestAsync( @@ -315,8 +300,7 @@ struct Bar }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindEnum(TestHost testHost, Composition composition) { await TestAsync( @@ -334,8 +318,7 @@ enum Colors }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindEnumMember(TestHost testHost, Composition composition) { await TestAsync( @@ -353,8 +336,7 @@ enum Colors }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindField1(TestHost testHost, Composition composition) { await TestAsync( @@ -370,8 +352,7 @@ class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindField2(TestHost testHost, Composition composition) { await TestAsync( @@ -387,8 +368,7 @@ class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindField3(TestHost testHost, Composition composition) { await TestAsync( @@ -403,8 +383,7 @@ class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindVerbatimField(TestHost testHost, Composition composition) { await TestAsync( @@ -424,8 +403,7 @@ class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindPtrField1(TestHost testHost, Composition composition) { await TestAsync( @@ -440,8 +418,7 @@ class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindPtrField2(TestHost testHost, Composition composition) { await TestAsync( @@ -457,8 +434,7 @@ class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindConstField(TestHost testHost, Composition composition) { await TestAsync( @@ -474,8 +450,7 @@ class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindIndexer(TestHost testHost, Composition composition) { var program = @"class Goo { int[] arr; public int this[int i] { get { return arr[i]; } set { arr[i] = value; } } }"; @@ -486,8 +461,7 @@ await TestAsync(testHost, composition, program, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindEvent(TestHost testHost, Composition composition) { var program = "class Goo { public event EventHandler ChangedEventHandler; }"; @@ -498,8 +472,7 @@ await TestAsync(testHost, composition, program, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindAutoProperty(TestHost testHost, Composition composition) { await TestAsync( @@ -515,8 +488,7 @@ class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindMethod(TestHost testHost, Composition composition) { await TestAsync( @@ -532,8 +504,7 @@ class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindVerbatimMethod(TestHost testHost, Composition composition) { await TestAsync( @@ -553,8 +524,7 @@ class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindParameterizedMethod(TestHost testHost, Composition composition) { await TestAsync( @@ -572,8 +542,7 @@ void DoSomething(int a, string b) }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindConstructor(TestHost testHost, Composition composition) { await TestAsync( @@ -591,8 +560,7 @@ public Goo() }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindParameterizedConstructor(TestHost testHost, Composition composition) { await TestAsync( @@ -610,8 +578,7 @@ public Goo(int i) }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindStaticConstructor(TestHost testHost, Composition composition) { await TestAsync( @@ -629,8 +596,7 @@ static Goo() }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindPartialMethods(TestHost testHost, Composition composition) { await TestAsync(testHost, composition, "partial class Goo { partial void Bar(); } partial class Goo { partial void Bar() { Console.Write(\"hello\"); } }", async w => @@ -644,8 +610,7 @@ public async Task FindPartialMethods(TestHost testHost, Composition composition) }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindPartialMethodDefinitionOnly(TestHost testHost, Composition composition) { await TestAsync( @@ -661,8 +626,7 @@ partial class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindPartialMethodImplementationOnly(TestHost testHost, Composition composition) { await TestAsync( @@ -680,8 +644,7 @@ partial void Bar() }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindOverriddenMembers(TestHost testHost, Composition composition) { var program = "class Goo { public virtual string Name { get; set; } } class DogBed : Goo { public override string Name { get { return base.Name; } set {} } }"; @@ -710,8 +673,7 @@ await TestAsync(testHost, composition, program, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindInterface(TestHost testHost, Composition composition) { await TestAsync( @@ -726,8 +688,7 @@ public interface IGoo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindTopLevelLocalFunction(TestHost testHost, Composition composition) { await TestAsync( @@ -742,8 +703,7 @@ void Goo() }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindTopLevelLocalFunction_WithParameters(TestHost testHost, Composition composition) { await TestAsync( @@ -758,8 +718,7 @@ void Goo(int i) }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindTopLevelLocalFunction_WithTypeArgumentsAndParameters(TestHost testHost, Composition composition) { await TestAsync( @@ -774,8 +733,7 @@ void Goo(int i) }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindNestedLocalFunctionTopLevelStatements(TestHost testHost, Composition composition) { await TestAsync( @@ -793,8 +751,7 @@ void Bar() }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindLocalFunctionInMethod(TestHost testHost, Composition composition) { await TestAsync( @@ -818,8 +775,7 @@ void Bar() }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindNestedLocalFunctionInMethod(TestHost testHost, Composition composition) { await TestAsync( @@ -843,8 +799,7 @@ void Bar() }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindDelegateInNamespace(TestHost testHost, Composition composition) { await TestAsync( @@ -860,8 +815,7 @@ namespace Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindLambdaExpression(TestHost testHost, Composition composition) { await TestAsync( @@ -879,8 +833,7 @@ class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindArray(TestHost testHost, Composition composition) { await TestAsync( @@ -896,8 +849,7 @@ class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindClassAndMethodWithSameName(TestHost testHost, Composition composition) { await TestAsync( @@ -924,8 +876,7 @@ void Goo() }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FindMethodNestedInGenericTypes(TestHost testHost, Composition composition) { await TestAsync( @@ -949,8 +900,7 @@ void M() }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task OrderingOfConstructorsAndTypes(TestHost testHost, Composition composition) { await TestAsync( @@ -988,8 +938,7 @@ static C2() }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task NavigateToMethodWithNullableParameter(TestHost testHost, Composition composition) { await TestAsync( @@ -1007,8 +956,7 @@ void M(object? o) }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task StartStopSanity(TestHost testHost, Composition composition) { // Verify that multiple calls to start/stop and dispose don't blow up @@ -1032,8 +980,7 @@ public class Goo }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task DescriptionItems(TestHost testHost, Composition composition) { await TestAsync(testHost, composition, """ @@ -1060,8 +1007,7 @@ void assertDescription(string label, string value) }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TermSplittingTest1(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; @@ -1080,8 +1026,7 @@ await TestAsync(testHost, composition, source, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TermSplittingTest2(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; @@ -1097,8 +1042,7 @@ await TestAsync(testHost, composition, source, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TermSplittingTest3(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; @@ -1114,8 +1058,7 @@ await TestAsync(testHost, composition, source, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TermSplittingTest4(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; @@ -1126,8 +1069,7 @@ await TestAsync(testHost, composition, source, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TermSplittingTest5(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; @@ -1138,8 +1080,7 @@ await TestAsync(testHost, composition, source, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TermSplittingTest6(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; @@ -1159,8 +1100,7 @@ await TestAsync(testHost, composition, source, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TermSplittingTest7(TestHost testHost, Composition composition) { var source = "class SyllableBreaking {int GetKeyWord; int get_key_word; string get_keyword; int getkeyword; int wake;}"; @@ -1171,8 +1111,7 @@ await TestAsync(testHost, composition, source, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestIndexer1(TestHost testHost, Composition composition) { var source = @@ -1204,8 +1143,7 @@ await TestAsync(testHost, composition, source, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task DottedPattern1(TestHost testHost, Composition composition) { var source = "namespace Goo { namespace Bar { class Baz { void Quux() { } } } }"; @@ -1222,8 +1160,7 @@ await TestAsync(testHost, composition, source, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task DottedPattern2(TestHost testHost, Composition composition) { var source = "namespace Goo { namespace Bar { class Baz { void Quux() { } } } }"; @@ -1239,8 +1176,7 @@ await TestAsync(testHost, composition, source, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task DottedPattern3(TestHost testHost, Composition composition) { var source = "namespace Goo { namespace Bar { class Baz { void Quux() { } } } }"; @@ -1257,8 +1193,7 @@ await TestAsync(testHost, composition, source, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task DottedPattern4(TestHost testHost, Composition composition) { var source = "namespace Goo { namespace Bar { class Baz { void Quux() { } } } }"; @@ -1275,8 +1210,7 @@ await TestAsync(testHost, composition, source, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task DottedPattern5(TestHost testHost, Composition composition) { var source = "namespace Goo { namespace Bar { class Baz { void Quux() { } } } }"; @@ -1293,8 +1227,7 @@ await TestAsync(testHost, composition, source, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task DottedPattern6(TestHost testHost, Composition composition) { var source = "namespace Goo { namespace Bar { class Baz { void Quux() { } } } }"; @@ -1310,8 +1243,7 @@ await TestAsync(testHost, composition, source, async w => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/Roslyn/issues/7855")] public async Task DottedPattern7(TestHost testHost, Composition composition) { @@ -1720,8 +1652,7 @@ public void Method( }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/57873")] public async Task FindRecordMember1(TestHost testHost, Composition composition) { @@ -1737,8 +1668,7 @@ record Goo(int Member) }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/57873")] public async Task FindRecordMember2(TestHost testHost, Composition composition) { @@ -1755,8 +1685,7 @@ record Goo(int Member) }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/57873")] public async Task FindRecordMember3(TestHost testHost, Composition composition) { @@ -1778,8 +1707,7 @@ private static bool IsFromFile(NavigateToItem item, string fileName) return ((CodeAnalysis.NavigateTo.INavigateToSearchResult)item.Tag).NavigableItem.Document.Name == fileName; } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task NavigateToPrioritizeResultInCurrentDocument1(TestHost testHost) { await TestAsync(testHost, Composition.FirstActiveAndVisible, XElement.Parse(""" @@ -1824,8 +1752,7 @@ public void VisibleMethod() { } }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task NavigateToPrioritizeResultInCurrentDocument2(TestHost testHost) { await TestAsync(testHost, Composition.FirstActiveAndVisible, XElement.Parse(""" @@ -1870,8 +1797,7 @@ public void VisibleMethod() { } }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task NavigateToPrioritizeResultInCurrentDocument3(TestHost testHost) { await TestAsync(testHost, Composition.FirstActiveAndVisible, XElement.Parse(""" @@ -1916,8 +1842,7 @@ public void VisibleMethod() { } }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task NavigateToPrioritizeResultInCurrentDocument4(TestHost testHost) { await TestAsync(testHost, Composition.FirstActiveAndVisible, XElement.Parse(""" @@ -1962,8 +1887,7 @@ public void VisibleMethod() { } }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task NavigateToPrioritizeResultInCurrentDocument5(TestHost testHost) { await TestAsync(testHost, Composition.FirstActiveAndVisible, XElement.Parse(""" diff --git a/src/EditorFeatures/CSharpTest/PdbSourceDocument/AbstractPdbSourceDocumentTests.cs b/src/EditorFeatures/CSharpTest/PdbSourceDocument/AbstractPdbSourceDocumentTests.cs index 853a457c79242..a56caf8c6c3b3 100644 --- a/src/EditorFeatures/CSharpTest/PdbSourceDocument/AbstractPdbSourceDocumentTests.cs +++ b/src/EditorFeatures/CSharpTest/PdbSourceDocument/AbstractPdbSourceDocumentTests.cs @@ -172,7 +172,6 @@ protected static async Task GenerateFileAndVerifyAsync( } finally { - service.CleanupGeneratedFiles(); service.TryGetWorkspace()?.Dispose(); } } diff --git a/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs b/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs index 5052e6df59e93..8356f437d659e 100644 --- a/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs +++ b/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs @@ -21,8 +21,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.PdbSourceDocument { public partial class PdbSourceDocumentTests : AbstractPdbSourceDocumentTests { - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task PreprocessorSymbols1(Location pdbLocation, Location sourceLocation) { var source = """ @@ -42,8 +41,7 @@ public void M() await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.M"), preprocessorSymbols: ["SOME_DEFINED_CONSTANT"]); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task PreprocessorSymbols2(Location pdbLocation, Location sourceLocation) { var source = """ @@ -63,8 +61,7 @@ public void M() await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.M")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Method(Location pdbLocation, Location sourceLocation) { var source = """ @@ -79,8 +76,7 @@ public class C await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.M")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Constructor(Location pdbLocation, Location sourceLocation) { var source = """ @@ -95,8 +91,7 @@ public class C await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C..ctor")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Parameter(Location pdbLocation, Location sourceLocation) { var source = """ @@ -111,8 +106,7 @@ public void M(int [|a|]) await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.M").Parameters.First()); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Class_FromTypeDefinitionDocument(Location pdbLocation, Location sourceLocation) { var source = """ @@ -125,8 +119,7 @@ public class [|C|] await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Constructor_FromTypeDefinitionDocument(Location pdbLocation, Location sourceLocation) { var source = """ @@ -138,8 +131,7 @@ public class [|C|] await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C..ctor")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task NestedClass_FromTypeDefinitionDocument(Location pdbLocation, Location sourceLocation) { var source = """ @@ -154,8 +146,7 @@ public class [|C|] await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("Outer.C")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task NestedClassConstructor_FromTypeDefinitionDocument(Location pdbLocation, Location sourceLocation) { var source = """ @@ -170,8 +161,7 @@ public class [|C|] await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("Outer.C..ctor")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Class_FromTypeDefinitionDocumentOfNestedClass(Location pdbLocation, Location sourceLocation) { var source = """ @@ -186,8 +176,7 @@ public class C await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("Outer")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Constructor_FromTypeDefinitionDocumentOfNestedClass(Location pdbLocation, Location sourceLocation) { var source = """ @@ -203,8 +192,7 @@ public class C } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task NestedClass_FromMethodDocument(Location pdbLocation, Location sourceLocation) { var source = """ @@ -222,8 +210,7 @@ public void M() await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("Outer.C")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task NestedClassConstructor_FromMethodDocument(Location pdbLocation, Location sourceLocation) { var source = """ @@ -242,8 +229,7 @@ public void M() await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("Outer.C..ctor")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Class_FromMethodDocumentOfNestedClass(Location pdbLocation, Location sourceLocation) { var source = """ @@ -262,8 +248,7 @@ public void M() await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("Outer")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Constructor_FromMethodDocumentOfNestedClass(Location pdbLocation, Location sourceLocation) { var source = """ @@ -282,8 +267,7 @@ public void M() await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("Outer..ctor")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Class_FromMethodDocument(Location pdbLocation, Location sourceLocation) { var source = """ @@ -298,8 +282,7 @@ public void M() await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Constructor_FromMethodDocument(Location pdbLocation, Location sourceLocation) { var source = """ @@ -314,8 +297,7 @@ public void M() await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C..ctor")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Field(Location pdbLocation, Location sourceLocation) { var source = """ @@ -327,8 +309,7 @@ public class C await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.f")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Property(Location pdbLocation, Location sourceLocation) { var source = """ @@ -340,8 +321,7 @@ public class C await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.P")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Property_WithBody(Location pdbLocation, Location sourceLocation) { var source = """ @@ -353,8 +333,7 @@ public class C await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.P")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task EventField(Location pdbLocation, Location sourceLocation) { var source = """ @@ -366,8 +345,7 @@ public class C await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.E")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task EventField_WithMethod(Location pdbLocation, Location sourceLocation) { var source = """ @@ -384,8 +362,7 @@ public void M() await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.E")); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Event(Location pdbLocation, Location sourceLocation) { var source = """ @@ -756,8 +733,7 @@ await RunTestAsync(async path => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task SourceFileChecksumIncorrect_NullResult(Location pdbLocation) { var source1 = """ @@ -828,8 +804,7 @@ await RunTestAsync(async path => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task EncodedEmbeddedSource_SJIS(Location pdbLocation) { var source = """ @@ -858,8 +833,7 @@ await RunTestAsync(async path => }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task EncodedEmbeddedSource_SJIS_FallbackEncoding(Location pdbLocation) { var source = """ @@ -918,7 +892,6 @@ await RunTestAsync(async path => } finally { - service.CleanupGeneratedFiles(); service.TryGetWorkspace()?.Dispose(); } }); diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/AbstractKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/AbstractKeywordRecommenderTests.cs index 64530b7750d14..023bdb25ac4cc 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/AbstractKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/AbstractKeywordRecommenderTests.cs @@ -376,8 +376,7 @@ public async Task TestNotAfterSealed() public async Task TestNotAfterStatic() => await VerifyAbsenceAsync(@"static $$"); - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterNestedStatic([CombinatorialValues("class", "struct", "record", "record struct", "record class")] string declarationKind) { await VerifyAbsenceAsync(declarationKind + """ @@ -409,8 +408,7 @@ class C { public async Task TestNotAfterDelegate() => await VerifyAbsenceAsync(@"delegate $$"); - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterNestedAbstract([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { await VerifyAbsenceAsync(declarationKind + """ @@ -419,8 +417,7 @@ await VerifyAbsenceAsync(declarationKind + """ """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterNestedVirtual([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { await VerifyAbsenceAsync(declarationKind + """ @@ -429,8 +426,7 @@ await VerifyAbsenceAsync(declarationKind + """ """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterNestedSealed([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { await VerifyAbsenceAsync(declarationKind + """ diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/AsyncKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/AsyncKeywordRecommenderTests.cs index 5c38244fda792..3637abd1754a3 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/AsyncKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/AsyncKeywordRecommenderTests.cs @@ -330,8 +330,7 @@ class Goo """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/8616")] [CompilerTrait(CompilerFeature.LocalFunctions)] public async Task TestLocalFunction(bool topLevelStatement) @@ -340,8 +339,7 @@ await VerifyKeywordAsync(AddInsideMethod( @"$$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/14525")] [CompilerTrait(CompilerFeature.LocalFunctions)] public async Task TestLocalFunction2(bool topLevelStatement) @@ -350,8 +348,7 @@ await VerifyKeywordAsync(AddInsideMethod( @"unsafe $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/14525")] [CompilerTrait(CompilerFeature.LocalFunctions)] public async Task TestLocalFunction3(bool topLevelStatement) @@ -360,8 +357,7 @@ await VerifyKeywordAsync(AddInsideMethod( @"unsafe $$ void L() { }", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/8616")] [CompilerTrait(CompilerFeature.LocalFunctions)] public async Task TestLocalFunction4(bool topLevelStatement) @@ -388,8 +384,7 @@ public void M(Action a) """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/8616")] [CompilerTrait(CompilerFeature.LocalFunctions)] public async Task TestLocalFunction6(bool topLevelStatement) @@ -398,8 +393,7 @@ await VerifyAbsenceAsync(AddInsideMethod( @"int $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/8616")] [CompilerTrait(CompilerFeature.LocalFunctions)] public async Task TestLocalFunction7(bool topLevelStatement) diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/BoolKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/BoolKeywordRecommenderTests.cs index 866847d12f019..a78023a3bf842 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/BoolKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/BoolKeywordRecommenderTests.cs @@ -80,8 +80,7 @@ class C { """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInFixedStatement(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -118,80 +117,70 @@ ref readonly $$ """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterConstInStatementContext(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"const $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterRefInStatementContext(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"ref $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterRefReadonlyInStatementContext(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"ref readonly $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterConstLocalDeclaration(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"const $$ int local;", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterRefLocalDeclaration(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"ref $$ int local;", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterRefReadonlyLocalDeclaration(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"ref readonly $$ int local;", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterRefLocalFunction(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"ref $$ int Function();", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterRefReadonlyLocalFunction(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"ref readonly $$ int Function();", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterRefExpression(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"ref int x = ref $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInEmptyStatement(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -205,32 +194,28 @@ await VerifyAbsenceAsync( @"enum E : $$"); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInGenericType1(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"IList<$$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInGenericType2(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"IList where T : IGoo, $$"); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestStartOfExpression(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"var q = $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/34324")] public async Task TestAfterNullCoalescingAssignment(bool topLevelStatement) { @@ -150,256 +147,224 @@ await VerifyKeywordAsync(AddInsideMethod( @"q ??= $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInParenthesizedExpression(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"var q = ($$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestPlusEquals(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"q += $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestMinusEquals(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"q -= $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestTimesEquals(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"q *= $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestDivideEquals(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"q /= $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestModEquals(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"q %= $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestXorEquals(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"q ^= $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAndEquals(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"q &= $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestOrEquals(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"q |= $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestLeftShiftEquals(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"q <<= $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestRightShiftEquals(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"q >>= $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterMinus(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"- $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterPlus(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"+ $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterNot(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"! $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterTilde(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"~ $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterBinaryTimes(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"a * $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterBinaryDivide(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"a / $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterBinaryMod(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"a % $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterBinaryPlus(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"a + $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterBinaryMinus(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"a - $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterBinaryLeftShift(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"a << $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterBinaryRightShift(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"a >> $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterBinaryLessThan(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"a < $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterBinaryGreaterThan(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"a > $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterEqualsEquals(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"a == $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterNotEquals(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"a != $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterLessThanEquals(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"a <= $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterGreaterThanEquals(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"a >= $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterNullable(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"a ?? $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterArrayRankSpecifier1(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"new int[ $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterArrayRankSpecifier2(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"new int[expr, $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterConditional1(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -415,104 +380,91 @@ await VerifyKeywordAsync(AddInsideMethod( @"a ? expr | $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInArgument1(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"Goo( $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInArgument2(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"Goo(expr, $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInArgument3(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"new Goo( $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInArgument4(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"new Goo(expr, $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterRef(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"Goo(ref $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterOut(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"Goo(out $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestLambda(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"Action a = i => $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInCollectionInitializer1(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"new System.Collections.Generic.List() { $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInCollectionInitializer2(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"new System.Collections.Generic.List() { expr, $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInForeachIn(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"foreach (var v in $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInAwaitForeachIn(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"await foreach (var v in $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInFromIn(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"var q = from x in $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInJoinIn(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -522,8 +474,7 @@ join a in $$ """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInJoinOn(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -533,8 +484,7 @@ join a in b on $$ """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInJoinEquals(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -544,8 +494,7 @@ join a in b on equals $$ """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestWhere(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -555,8 +504,7 @@ await VerifyKeywordAsync(AddInsideMethod( """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestOrderby1(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -566,8 +514,7 @@ await VerifyKeywordAsync(AddInsideMethod( """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestOrderby2(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -577,8 +524,7 @@ await VerifyKeywordAsync(AddInsideMethod( """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestOrderby3(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -588,8 +534,7 @@ await VerifyKeywordAsync(AddInsideMethod( """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterSelect(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -599,8 +544,7 @@ await VerifyKeywordAsync(AddInsideMethod( """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterGroup(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -610,8 +554,7 @@ await VerifyKeywordAsync(AddInsideMethod( """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterGroupBy(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -621,16 +564,14 @@ group expr by $$ """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterReturn(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"return $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterYieldReturn(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -644,56 +585,49 @@ await VerifyAbsenceAsync( @"[return $$"); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterThrow(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"throw $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInWhile(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"while ($$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInUsing(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"using ($$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInAwaitUsing(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"await using ($$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInLock(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"lock ($$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInIf(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"if ($$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInSwitch(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/RefKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/RefKeywordRecommenderTests.cs index 0d44c741d7a28..87a557f70e647 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/RefKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/RefKeywordRecommenderTests.cs @@ -410,48 +410,42 @@ void Goo() { """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInLambdaDeclaration(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"var q = ($$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInLambdaDeclaration2(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"var q = (ref int a, $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInLambdaDeclaration3(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"var q = (int a, $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInDelegateDeclaration(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"var q = delegate ($$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInDelegateDeclaration2(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"var q = delegate (a, $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInDelegateDeclaration3(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -472,64 +466,56 @@ void main(out goo) { } await VerifyKeywordAsync(text); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestEmptyStatement(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"$$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterReturn(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"return $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInFor(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"for ($$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotInFor(bool topLevelStatement) { await VerifyAbsenceAsync(AddInsideMethod( @"for (var $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInFor2(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"for ($$;", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInFor3(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @"for ($$;;", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterVar(bool topLevelStatement) { await VerifyAbsenceAsync(AddInsideMethod( @"var $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotInUsing(bool topLevelStatement) { await VerifyAbsenceAsync(AddInsideMethod( @@ -709,8 +695,7 @@ await VerifyKeywordAsync( @"delegate $$"); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterAnonymousDelegate(bool topLevelStatement) { await VerifyAbsenceAsync(AddInsideMethod( @@ -789,8 +774,7 @@ class C { """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInUnsafeBlock(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -853,16 +837,14 @@ class C { """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInLocalArrowMethod(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @" ref int Goo() => $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInArrowLambda(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -909,8 +891,7 @@ await VerifyKeywordAsync(AddInsideMethod( } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestRefInFor(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod(""" @@ -918,8 +899,7 @@ await VerifyKeywordAsync(AddInsideMethod(""" """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestRefForeachVariable(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod(""" @@ -927,8 +907,7 @@ await VerifyKeywordAsync(AddInsideMethod(""" """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestRefExpressionInAssignment(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod(""" @@ -938,8 +917,7 @@ await VerifyKeywordAsync(AddInsideMethod(""" """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestRefExpressionAfterReturn(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod(""" diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/SealedKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/SealedKeywordRecommenderTests.cs index ffd1743e5f6e0..641033c2e02fa 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/SealedKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/SealedKeywordRecommenderTests.cs @@ -363,8 +363,7 @@ public async Task TestNotAfterSealed() public async Task TestNotAfterStatic() => await VerifyAbsenceAsync(@"static $$"); - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterNestedStatic([CombinatorialValues("class", "struct", "record", "record struct", "record class")] string declarationKind) { await VerifyAbsenceAsync(declarationKind + """ @@ -396,8 +395,7 @@ class C { public async Task TestNotAfterDelegate() => await VerifyAbsenceAsync(@"delegate $$"); - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterNestedAbstract([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { await VerifyAbsenceAsync(declarationKind + """ @@ -406,8 +404,7 @@ await VerifyAbsenceAsync(declarationKind + """ """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterNestedVirtual([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { await VerifyAbsenceAsync(declarationKind + """ @@ -426,8 +423,7 @@ class C { """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterNestedSealed([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { await VerifyAbsenceAsync(declarationKind + """ diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/StaticKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/StaticKeywordRecommenderTests.cs index 7c8545e96266d..44c3182d827bc 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/StaticKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/StaticKeywordRecommenderTests.cs @@ -420,8 +420,7 @@ public async Task TestNotBetweenGlobalUsings_02() //await VerifyWorkerAsync(source, absent: true, Options.Script); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterNestedAbstract([CombinatorialValues("class", "struct", "record", "record struct", "record class")] string declarationKind) { await VerifyAbsenceAsync(declarationKind + """ @@ -439,8 +438,7 @@ interface C { """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterNestedVirtual([CombinatorialValues("class", "struct", "record", "record struct", "record class")] string declarationKind) { await VerifyAbsenceAsync(declarationKind + """ @@ -458,8 +456,7 @@ interface C { """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterNestedOverride([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { await VerifyAbsenceAsync(declarationKind + """ @@ -468,8 +465,7 @@ await VerifyAbsenceAsync(declarationKind + """ """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterNestedStatic([CombinatorialValues("class", "struct", "record", "record struct", "record class", "interface")] string declarationKind) { await VerifyAbsenceAsync(declarationKind + """ @@ -478,8 +474,7 @@ await VerifyAbsenceAsync(declarationKind + """ """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterNestedSealed([CombinatorialValues("class", "struct", "record", "record struct", "record class")] string declarationKind) { await VerifyAbsenceAsync(declarationKind + """ diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/UsingKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/UsingKeywordRecommenderTests.cs index ff10c3c05a795..4eb3b4193992c 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/UsingKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/UsingKeywordRecommenderTests.cs @@ -56,8 +56,7 @@ await VerifyKeywordAsync(SourceCodeKind.Script, """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInEmptyStatement(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -329,8 +328,7 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestBeforeStatement(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -340,8 +338,7 @@ await VerifyKeywordAsync(AddInsideMethod( """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterStatement(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -351,8 +348,7 @@ await VerifyKeywordAsync(AddInsideMethod( """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterBlock(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -363,8 +359,7 @@ await VerifyKeywordAsync(AddInsideMethod( """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterIf(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -374,8 +369,7 @@ await VerifyKeywordAsync(AddInsideMethod( """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterDo(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -385,8 +379,7 @@ await VerifyKeywordAsync(AddInsideMethod( """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterWhile(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -396,8 +389,7 @@ await VerifyKeywordAsync(AddInsideMethod( """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterFor(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -407,8 +399,7 @@ await VerifyKeywordAsync(AddInsideMethod( """, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAfterForeach(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/VirtualKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/VirtualKeywordRecommenderTests.cs index 536ceced01b0f..c7e5cb44a1e2d 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/VirtualKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/VirtualKeywordRecommenderTests.cs @@ -281,8 +281,7 @@ public async Task TestNotAfterSealed() public async Task TestNotAfterStatic() => await VerifyAbsenceAsync(@"static $$"); - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterNestedStatic([CombinatorialValues("class", "struct", "record", "record struct", "record class")] string declarationKind) { await VerifyAbsenceAsync(declarationKind + """ diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs index 67c4ee21ab9af..9fbb3a1effe27 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs @@ -87,24 +87,21 @@ await VerifyKeywordAsync( @"public delegate $$"); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotInCastType(bool topLevelStatement) { await VerifyAbsenceAsync(AddInsideMethod( @"var str = (($$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotInCastType2(bool topLevelStatement) { await VerifyAbsenceAsync(AddInsideMethod( @"var str = (($$)items) as string;", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInTypeOf(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( @@ -455,8 +452,7 @@ await VerifyKeywordAsync( @"delegate $$"); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestNotAfterAnonymousDelegate(bool topLevelStatement) { await VerifyAbsenceAsync(AddInsideMethod( @@ -500,8 +496,7 @@ class C { """); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestInUnsafeBlock(bool topLevelStatement) { await VerifyKeywordAsync(AddInsideMethod( diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTag.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTag.cs index b9dfcf5fcf18f..3f0061ae429f4 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTag.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTag.cs @@ -28,6 +28,7 @@ using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Formatting; +using Microsoft.VisualStudio.Threading; namespace Microsoft.CodeAnalysis.Editor.InlineHints { @@ -266,7 +267,8 @@ bool KeepOpen() private async Task StartToolTipServiceAsync(IToolTipPresenter toolTipPresenter) { var threadingContext = _taggerProvider.ThreadingContext; - var uiList = await Task.Run(() => CreateDescriptionAsync(threadingContext.DisposalToken)).ConfigureAwait(false); + await TaskScheduler.Default; + var uiList = await CreateDescriptionAsync(threadingContext.DisposalToken).ConfigureAwait(false); await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(threadingContext.DisposalToken); toolTipPresenter.StartOrUpdate(_textView.TextSnapshot.CreateTrackingSpan(_span.Start, _span.Length, SpanTrackingMode.EdgeInclusive), uiList); diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameUserInputComboBox.xaml.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameUserInputComboBox.xaml.cs index d34e9dcb33a8d..b1ce11fbb270d 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameUserInputComboBox.xaml.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameUserInputComboBox.xaml.cs @@ -164,6 +164,7 @@ private void ItemsPresenter_PreviewMouseUp(object sender, MouseButtonEventArgs e private void InnerTextBox_GotFocus(object sender, RoutedEventArgs e) { + e.Handled = true; // Prevent selecting all the text if (!_smartRenameViewModel.IsUsingDropdown) { return; @@ -185,8 +186,7 @@ private void InnerTextBox_PreviewKeyDown(object sender, KeyEventArgs e) { Assumes.NotNull(_dropDownPopup); if ((e.Key is Key.Escape or Key.Space or Key.Enter) - && (_dropDownPopup.IsOpen // Handle these keystrokes when dropdown is present - || _smartRenameViewModel.IsUsingResultPanel && this.TextSelectionLength < this.Text.Length)) // Or when panel is present and text is not yet selected + && _dropDownPopup.IsOpen) // Handle these keystrokes when dropdown is present { _dropDownPopup.IsOpen = false; SelectAllText(); diff --git a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs index fbc7a560ca3a4..6fda65becc9c9 100644 --- a/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs +++ b/src/EditorFeatures/Core.Wpf/Lightup/LightupHelpers.cs @@ -377,7 +377,7 @@ TResult FallbackAccessor(T instance) private static Action CreateFallbackAction() { - void FallbackAction(T instance) + static void FallbackAction(T instance) { if (instance == null) { @@ -392,7 +392,7 @@ void FallbackAction(T instance) private static Action CreateFallbackAction() { - void FallbackAction(T instance, TArg arg) + static void FallbackAction(T instance, TArg arg) { if (instance == null) { diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs index 7c1ff6aa7b44d..cb42cc3076b4f 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.NavigateTo; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Language.NavigateTo.Interfaces; using Microsoft.VisualStudio.Text.PatternMatching; @@ -18,11 +20,13 @@ internal partial class NavigateToItemProvider { private class NavigateToItemProviderCallback : INavigateToSearchCallback { + private readonly Solution _solution; private readonly INavigateToItemDisplayFactory _displayFactory; private readonly INavigateToCallback _callback; - public NavigateToItemProviderCallback(INavigateToItemDisplayFactory displayFactory, INavigateToCallback callback) + public NavigateToItemProviderCallback(Solution solution, INavigateToItemDisplayFactory displayFactory, INavigateToCallback callback) { + _solution = solution; _displayFactory = displayFactory; _callback = callback; } @@ -39,9 +43,41 @@ public void Done(bool isFullyLoaded) } } - public Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) + public Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { - ReportMatchResult(project, result); + foreach (var result in results) + { + var matchedSpans = result.NameMatchSpans.SelectAsArray(t => t.ToSpan()); + + var patternMatch = new PatternMatch( + GetPatternMatchKind(result.MatchKind), + punctuationStripped: false, + result.IsCaseSensitive, + matchedSpans); + + var project = _solution.GetRequiredProject(result.NavigableItem.Document.Project.Id); + var navigateToItem = new NavigateToItem( + result.Name, + result.Kind, + GetNavigateToLanguage(project.Language), + result.SecondarySort, + result, + patternMatch, + _displayFactory); + + try + { + _callback.AddItem(navigateToItem); + } + catch (InvalidOperationException ex) when (FatalError.ReportAndCatch(ex, ErrorSeverity.Critical)) + { + // Mitigation for race condition in platform https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1534364 + // + // Catch this so that don't tear down OOP, but still report the exception so that we ensure this issue + // gets attention and is fixed. + } + } + return Task.CompletedTask; } @@ -54,38 +90,6 @@ public void ReportIncomplete() { } - private void ReportMatchResult(Project project, INavigateToSearchResult result) - { - var matchedSpans = result.NameMatchSpans.SelectAsArray(t => t.ToSpan()); - - var patternMatch = new PatternMatch( - GetPatternMatchKind(result.MatchKind), - punctuationStripped: false, - result.IsCaseSensitive, - matchedSpans); - - var navigateToItem = new NavigateToItem( - result.Name, - result.Kind, - GetNavigateToLanguage(project.Language), - result.SecondarySort, - result, - patternMatch, - _displayFactory); - - try - { - _callback.AddItem(navigateToItem); - } - catch (InvalidOperationException ex) when (FatalError.ReportAndCatch(ex, ErrorSeverity.Critical)) - { - // Mitigation for race condition in platform https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1534364 - // - // Catch this so that don't tear down OOP, but still report the exception so that we ensure this issue - // gets attention and is fixed. - } - } - private static PatternMatchKind GetPatternMatchKind(NavigateToMatchKind matchKind) => matchKind switch { diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs index f648c168d021a..693198634baee 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs @@ -109,9 +109,10 @@ private void StartSearch(INavigateToCallback callback, string searchValue, IImmu ? NavigateToSearchScope.Document : NavigateToSearchScope.Solution; - var roslynCallback = new NavigateToItemProviderCallback(_displayFactory, callback); + var solution = _workspace.CurrentSolution; + var roslynCallback = new NavigateToItemProviderCallback(solution, _displayFactory, callback); var searcher = NavigateToSearcher.Create( - _workspace.CurrentSolution, + solution, _asyncListener, roslynCallback, searchValue, diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/ClassificationFormatDefinitions.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/ClassificationFormatDefinitions.cs new file mode 100644 index 0000000000000..1cd98c1ce6738 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/ClassificationFormatDefinitions.cs @@ -0,0 +1,41 @@ +// 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.ComponentModel.Composition; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Language.StandardClassification; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.QuickInfo; + +internal sealed class ClassificationFormatDefinitions +{ + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = ClassificationTypeDefinitions.ReducedEmphasisText)] + [Name(ClassificationTypeDefinitions.ReducedEmphasisText)] + [Order(After = Priority.High)] + [UserVisible(false)] + private class ReducedEmphasisTextFormat : ClassificationFormatDefinition + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public ReducedEmphasisTextFormat() + { + this.ForegroundOpacity = 0.65f; + } + } +} + +internal sealed class ClassificationTypeDefinitions +{ + // Only used for theming, does not need localized + public const string ReducedEmphasisText = "Reduced Emphasis Text"; + + [Export] + [Name(ReducedEmphasisText)] + [BaseDefinition(PredefinedClassificationTypeNames.Text)] + internal readonly ClassificationTypeDefinition? ReducedEmphasisTextTypeDefinition; +} diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsState.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsState.cs new file mode 100644 index 0000000000000..540c8cfcf9818 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsState.cs @@ -0,0 +1,26 @@ +// 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. + +namespace Microsoft.CodeAnalysis.Editor.QuickInfo; + +/// +/// Represents the potential states of the view. +/// +internal enum OnTheFlyDocsState +{ + /// + /// The view is displaying the on-demand hyperlink. + /// + OnDemandLink, + + /// + /// The view is in the loading state. + /// + Loading, + + /// + /// The view is displaying computed results. + /// + Finished, +} diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml new file mode 100644 index 0000000000000..bee701ec0c2d6 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml.cs new file mode 100644 index 0000000000000..63143db5fd634 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml.cs @@ -0,0 +1,232 @@ +// 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.ComponentModel; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Copilot; +using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.QuickInfo; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Text.Adornments; +using Microsoft.VisualStudio.Text.Editor; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.QuickInfo; + +/// +/// Interaction logic for OnTheFlyDocsView.xaml. +/// +internal sealed partial class OnTheFlyDocsView : UserControl, INotifyPropertyChanged +{ + private readonly ITextView _textView; + private readonly IViewElementFactoryService _viewElementFactoryService; + private readonly IAsynchronousOperationListener _asyncListener; + private readonly IAsyncQuickInfoSession _asyncQuickInfoSession; + private readonly IThreadingContext _threadingContext; + private readonly Document _document; + private readonly OnTheFlyDocsElement _onTheFlyDocsElement; + private readonly ContentControl _responseControl = new(); + private readonly CancellationTokenSource _cancellationTokenSource = new(); + + private OnTheFlyDocsState _currentState = OnTheFlyDocsState.OnDemandLink; + + /// + public event PropertyChangedEventHandler? PropertyChanged; + + /// + /// Event that fires when the user requests results. + /// + public event EventHandler ResultsRequested; + +#pragma warning disable CA1822 // Mark members as static + /// + /// Used to display the "On the fly documentation" directly in the associated XAML file. + /// + public string OnTheFlyDocumentation => EditorFeaturesResources.On_the_fly_documentation; +#pragma warning restore CA1822 // Mark members as static + + public OnTheFlyDocsView(ITextView textView, IViewElementFactoryService viewElementFactoryService, IAsynchronousOperationListenerProvider listenerProvider, IAsyncQuickInfoSession asyncQuickInfoSession, IThreadingContext threadingContext, EditorFeaturesOnTheFlyDocsElement editorFeaturesOnTheFlyDocsElement) + { + _textView = textView; + _viewElementFactoryService = viewElementFactoryService; + _asyncListener = listenerProvider.GetListener(FeatureAttribute.OnTheFlyDocs); + _asyncQuickInfoSession = asyncQuickInfoSession; + _threadingContext = threadingContext; + _onTheFlyDocsElement = editorFeaturesOnTheFlyDocsElement.OnTheFlyDocsElement; + _document = editorFeaturesOnTheFlyDocsElement.Document; + + var sparkle = new ImageElement(new VisualStudio.Core.Imaging.ImageId(CopilotConstants.CopilotIconMonikerGuid, CopilotConstants.CopilotIconSparkleId)); + + OnDemandLinkContent = ToUIElement( + new ContainerElement( + ContainerElementStyle.Wrapped, + new object[] + { + sparkle, + ClassifiedTextElement.CreateHyperlink(EditorFeaturesResources.Tell_me_more, EditorFeaturesResources.Show_an_AI_generated_summary_of_this_code, () => + RequestResults()), + })); + + LoadingContent = ToUIElement( + new ContainerElement( + ContainerElementStyle.Stacked, + new object[] + { + new ClassifiedTextElement(new ClassifiedTextRun( + ClassificationTypeDefinitions.ReducedEmphasisText, EditorFeaturesResources.GitHub_Copilot_thinking)), + new SmoothProgressBar { IsIndeterminate = true, Height = 2, Margin = new Thickness { Top = 2 } }, + })); + + // Ensure the loading content stretches so that the progress bar + // takes the entire width of the quick info tooltip. + if (LoadingContent is FrameworkElement element) + { + element.HorizontalAlignment = HorizontalAlignment.Stretch; + } + + ResultsContent = ToUIElement( + new ContainerElement( + ContainerElementStyle.Stacked, + new object[] + { + new ContainerElement( + ContainerElementStyle.Wrapped, + new object[] + { + sparkle, + ClassifiedTextElement.CreatePlainText(EditorFeaturesResources.GitHub_Copilot), + }), + new ThematicBreakElement(), + _responseControl, + new ThematicBreakElement(), + new ClassifiedTextElement(new ClassifiedTextRun( + ClassificationTypeDefinitions.ReducedEmphasisText, EditorFeaturesResources.AI_generated_content_may_be_inaccurate)), + })); + + ResultsRequested += (_, _) => PopulateAIDocumentationElements(_cancellationTokenSource.Token); + _asyncQuickInfoSession.StateChanged += (_, _) => OnQuickInfoSessionChanged(); + InitializeComponent(); + } + + /// + /// Retrieves the documentation for the given symbol from the Copilot service and displays it in the view. + /// + private void PopulateAIDocumentationElements(CancellationToken cancellationToken) + { + var token = _asyncListener.BeginAsyncOperation(nameof(SetResultTextAsync)); + var copilotService = _document.GetLanguageService(); + if (copilotService is not null) + { + _ = SetResultTextAsync(copilotService, cancellationToken).CompletesAsyncOperation(token); + } + } + + private async Task SetResultTextAsync(ICopilotCodeAnalysisService copilotService, CancellationToken cancellationToken) + { + var stopwatch = SharedStopwatch.StartNew(); + + try + { + var response = await copilotService.GetOnTheFlyDocsAsync(_onTheFlyDocsElement.SymbolSignature, _onTheFlyDocsElement.DeclarationCode, _onTheFlyDocsElement.Language, cancellationToken).ConfigureAwait(false); + var copilotRequestTime = stopwatch.Elapsed; + + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + cancellationToken.ThrowIfCancellationRequested(); + + if (response is null || response.Length == 0) + { + SetResultText(EditorFeaturesResources.An_error_occurred_while_generating_documentation_for_this_code); + CurrentState = OnTheFlyDocsState.Finished; + Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Error_Displayed, KeyValueLogMessage.Create(m => + { + m["ElapsedTime"] = copilotRequestTime; + }, LogLevel.Information)); + } + else + { + SetResultText(response); + CurrentState = OnTheFlyDocsState.Finished; + + Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Results_Displayed, KeyValueLogMessage.Create(m => + { + m["ElapsedTime"] = copilotRequestTime; + m["ResponseLength"] = response.Length; + }, LogLevel.Information)); + } + } + catch (OperationCanceledException) + { + Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Results_Canceled, KeyValueLogMessage.Create(m => + { + m["ElapsedTime"] = stopwatch.Elapsed; + }, LogLevel.Information)); + } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + } + } + + private void OnQuickInfoSessionChanged() + { + if (_asyncQuickInfoSession.State == QuickInfoSessionState.Dismissed) + { + _cancellationTokenSource.Cancel(); + } + } + + public OnTheFlyDocsState CurrentState + { + get => _currentState; + set => OnPropertyChanged(ref _currentState, value); + } + + public UIElement OnDemandLinkContent { get; } + + public UIElement LoadingContent { get; } + + public UIElement ResultsContent { get; } + + public void RequestResults() + { + CurrentState = OnTheFlyDocsState.Loading; + Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Loading_State_Entered, KeyValueLogMessage.Create(m => + { + m["SymbolHeaderText"] = _onTheFlyDocsElement.SymbolSignature; + }, LogLevel.Information)); + + ResultsRequested?.Invoke(this, EventArgs.Empty); + } + + /// + /// Sets the text of the AI-generated response. + /// + /// Response text to display. + public void SetResultText(string text) + { + _responseControl.Content = ToUIElement( + new ContainerElement(ContainerElementStyle.Wrapped, new ClassifiedTextElement([new ClassifiedTextRun(ClassificationTypeNames.Text, text)]))); + } + + private void OnPropertyChanged(ref T member, T value, [CallerMemberName] string? name = null) + { + member = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + + private UIElement ToUIElement(object model) + => _viewElementFactoryService.CreateViewElement(_textView, model); +} diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsViewFactory.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsViewFactory.cs new file mode 100644 index 0000000000000..8a4d463280287 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsViewFactory.cs @@ -0,0 +1,69 @@ +// 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.ComponentModel.Composition; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo; +using Microsoft.CodeAnalysis.Editor.QuickInfo; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text.Adornments; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.CodeAnalysis.QuickInfo; + +[Export(typeof(IViewElementFactory))] +[Name("OnTheFlyDocsElement converter")] +[TypeConversion(from: typeof(EditorFeaturesOnTheFlyDocsElement), to: typeof(UIElement))] +[Order(Before = "Default")] +internal sealed class OnTheFlyDocsViewFactory : IViewElementFactory +{ + private readonly IViewElementFactoryService _factoryService; + private readonly IAsynchronousOperationListenerProvider _listenerProvider; + private readonly IAsyncQuickInfoBroker _asyncQuickInfoBroker; + private readonly IThreadingContext _threadingContext; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public OnTheFlyDocsViewFactory(IViewElementFactoryService factoryService, IAsynchronousOperationListenerProvider listenerProvider, IAsyncQuickInfoBroker asyncQuickInfoBroker, IThreadingContext threadingContext) + { + _factoryService = factoryService; + _listenerProvider = listenerProvider; + _asyncQuickInfoBroker = asyncQuickInfoBroker; + _threadingContext = threadingContext; + } + + public TView? CreateViewElement(ITextView textView, object model) where TView : class + { + if (typeof(TView) != typeof(UIElement)) + { + throw new InvalidOperationException("TView must be UIElement"); + } + + var editorFeaturesOnTheFlyDocsElement = (EditorFeaturesOnTheFlyDocsElement)model; + + Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Showed_Link, KeyValueLogMessage.Create(m => + { + m["SymbolHeaderText"] = editorFeaturesOnTheFlyDocsElement.OnTheFlyDocsElement.SymbolSignature; + }, LogLevel.Information)); + + var quickInfoSession = _asyncQuickInfoBroker.GetSession(textView); + + if (quickInfoSession is null) + { + throw new InvalidOperationException("QuickInfoSession is null"); + } + + return new OnTheFlyDocsView(textView, _factoryService, _listenerProvider, quickInfoSession, _threadingContext, editorFeaturesOnTheFlyDocsElement) as TView; + } +} diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsViewStateVisibilityConverter.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsViewStateVisibilityConverter.cs new file mode 100644 index 0000000000000..713ccc99ce6e0 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsViewStateVisibilityConverter.cs @@ -0,0 +1,22 @@ +// 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.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace Microsoft.CodeAnalysis.Editor.QuickInfo; + +/// +/// Converts the of the view to a value. +/// +internal sealed class OnTheFlyDocsViewStateVisibilityConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + => value is OnTheFlyDocsState state && parameter is OnTheFlyDocsState targetState && state == targetState ? Visibility.Visible : Visibility.Collapsed; + + public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) + => throw new NotImplementedException(); +} diff --git a/src/EditorFeatures/Core.Wpf/StringIndentation/StringIndentationAdornmentManager.VisibleBlock.cs b/src/EditorFeatures/Core.Wpf/StringIndentation/StringIndentationAdornmentManager.VisibleBlock.cs index 6f1533b0da943..a4e0d164dfed9 100644 --- a/src/EditorFeatures/Core.Wpf/StringIndentation/StringIndentationAdornmentManager.VisibleBlock.cs +++ b/src/EditorFeatures/Core.Wpf/StringIndentation/StringIndentationAdornmentManager.VisibleBlock.cs @@ -44,17 +44,21 @@ private VisibleBlock(double x, ImmutableArray<(double start, double end)> ySegme if (span.End.GetContainingLine().Start == span.End) return null; - // We want to draw the line right before the quote character. So -1 to get that character's position. - // Horizontally position the adornment in the center of the character. This position could actually be - // 0 if the entire doc is deleted and we haven't recomputed the updated tags yet. So be resilient for - // the position being out of bounds. + // This position could actually be 0 if the entire doc is deleted + // and we haven't recomputed the updated tags yet. So be resilient + // for the position being out of bounds. if (span.End == 0) return null; + // We want to draw the line right before the quote character. So -1 to get that character's position. + // If we position the adornment right at the end of that character it will visually merge with the + // text, so we want to back it off a little bit to the left. Half of a virtual space is gonna do the trick. + // It is important to keep this value independent of what space character is used to avoid visual bugs + // like https://github.com/dotnet/roslyn/issues/64230 var bufferPosition = span.End - 1; var anchorPointLine = view.GetTextViewLineContainingBufferPosition(bufferPosition); var bounds = anchorPointLine.GetCharacterBounds(bufferPosition); - var x = Math.Floor((bounds.Left + bounds.Right) * 0.5); + var x = Math.Floor(bounds.Right - (anchorPointLine.VirtualSpaceWidth / 2)); var firstLine = view.TextViewLines.FirstVisibleLine; var lastLine = view.TextViewLines.LastVisibleLine; diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs index a89df6cd37880..4e07cb0701ed5 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs @@ -25,6 +25,7 @@ using Microsoft.VisualStudio.Imaging.Interop; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Threading; using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; @@ -74,25 +75,27 @@ public virtual bool TryGetTelemetryId(out Guid telemetryId) return true; } - // NOTE: We want to avoid computing the operations on the UI thread. So we use Task.Run() to do this work on the background thread. - protected Task> GetOperationsAsync( + protected async Task> GetOperationsAsync( IProgress progressTracker, CancellationToken cancellationToken) { - return Task.Run( - () => CodeAction.GetOperationsAsync(this.OriginalSolution, progressTracker, cancellationToken), cancellationToken); + // Avoid computing the operations on the UI thread + await TaskScheduler.Default; + return await CodeAction.GetOperationsAsync(this.OriginalSolution, progressTracker, cancellationToken).ConfigureAwait(false); } - protected Task> GetOperationsAsync( + protected async Task> GetOperationsAsync( CodeActionWithOptions actionWithOptions, object options, IProgress progressTracker, CancellationToken cancellationToken) { - return Task.Run( - () => actionWithOptions.GetOperationsAsync(this.OriginalSolution, options, progressTracker, cancellationToken), cancellationToken); + // Avoid computing the operations on the UI thread + await TaskScheduler.Default; + return await actionWithOptions.GetOperationsAsync(this.OriginalSolution, options, progressTracker, cancellationToken).ConfigureAwait(false); } - protected Task> GetPreviewOperationsAsync(CancellationToken cancellationToken) + protected async Task> GetPreviewOperationsAsync(CancellationToken cancellationToken) { - return Task.Run( - () => CodeAction.GetPreviewOperationsAsync(this.OriginalSolution, cancellationToken), cancellationToken); + // Avoid computing the operations on the UI thread + await TaskScheduler.Default; + return await CodeAction.GetPreviewOperationsAsync(this.OriginalSolution, cancellationToken).ConfigureAwait(false); } public void Invoke(CancellationToken cancellationToken) @@ -165,6 +168,7 @@ private async Task InvokeWorkerAsync(IProgress progressTra // Clear the progress we showed while computing the action. // We'll now show progress as we apply the action. progressTracker.Report(CodeAnalysisProgress.Clear()); + progressTracker.Report(CodeAnalysisProgress.Description(EditorFeaturesResources.Applying_changes)); using (Logger.LogBlock( FunctionId.CodeFixes_ApplyChanges, KeyValueLogMessage.Create(LogType.UserAction, m => CreateLogProperties(m)), cancellationToken)) diff --git a/src/EditorFeatures/Core/CodeActions/IUIThreadOperationContextExtensions.cs b/src/EditorFeatures/Core/CodeActions/IUIThreadOperationContextExtensions.cs index 95b63bb13b892..5344392f3694f 100644 --- a/src/EditorFeatures/Core/CodeActions/IUIThreadOperationContextExtensions.cs +++ b/src/EditorFeatures/Core/CodeActions/IUIThreadOperationContextExtensions.cs @@ -27,14 +27,21 @@ public void Report(CodeAnalysisProgress value) if (value.DescriptionValue != null) scope.Description = value.DescriptionValue; - if (value.IncompleteItemsValue != null) - Interlocked.Add(ref _totalItems, value.IncompleteItemsValue.Value); - - if (value.CompleteItemValue != null) - Interlocked.Add(ref _completedItems, value.CompleteItemValue.Value); + if (value.ClearValue) + { + Interlocked.Exchange(ref _totalItems, 0); + Interlocked.Exchange(ref _completedItems, 0); + } + else + { + if (value.IncompleteItemsValue != null) + Interlocked.Add(ref _totalItems, value.IncompleteItemsValue.Value); + + if (value.CompleteItemValue != null) + Interlocked.Add(ref _completedItems, value.CompleteItemValue.Value); + } scope.Progress.Report(new ProgressInfo(_completedItems, _totalItems)); - } } } diff --git a/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs b/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs index 785c8d8e8eb64..0503a8d028d8f 100644 --- a/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs +++ b/src/EditorFeatures/Core/CommentSelection/AbstractCommentSelectionBase.cs @@ -154,7 +154,7 @@ private void ApplyEdits(Document document, ITextView textView, ITextBuffer subje var formattingOptions = subjectBuffer.GetSyntaxFormattingOptions(_editorOptionsService, document.Project.Services, explicitFormat: false); var formattingSpans = trackingSnapshotSpans.Select(change => CommonFormattingHelpers.GetFormattingSpan(newRoot, change.Span.ToTextSpan())); - var formattedChanges = Formatter.GetFormattedTextChanges(newRoot, formattingSpans, document.Project.Solution.Services, formattingOptions, rules: null, cancellationToken); + var formattedChanges = Formatter.GetFormattedTextChanges(newRoot, formattingSpans, document.Project.Solution.Services, formattingOptions, rules: default, cancellationToken); subjectBuffer.ApplyChanges(formattedChanges); diff --git a/src/EditorFeatures/Core/EditorFeaturesResources.resx b/src/EditorFeatures/Core/EditorFeaturesResources.resx index 922e026e9cfb2..9fee174d54195 100644 --- a/src/EditorFeatures/Core/EditorFeaturesResources.resx +++ b/src/EditorFeatures/Core/EditorFeaturesResources.resx @@ -929,4 +929,25 @@ Do you want to proceed? Obsolete symbol + + AI-generated content may be inaccurate + + + GitHub Copilot + + + GitHub Copilot thinking... + + + Show an AI generated summary of this code + + + Tell me more + + + On-the-fly documentation + + + An error occurred while generating documentation for this code. + \ No newline at end of file diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs index da2e663f06bfd..b5527e89596a0 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; @@ -17,8 +18,9 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class VSTypeScriptDocumentPullDiagnosticHandlerFactory( IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) : DocumentPullDiagnosticHandlerFactory(analyzerService, diagnosticsRefresher, globalOptions) + IGlobalOptionService globalOptions) : DocumentPullDiagnosticHandlerFactory(analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { } @@ -28,7 +30,8 @@ internal class VSTypeScriptDocumentPullDiagnosticHandlerFactory( internal class VSTypeScriptWorkspacePullDiagnosticHandler( LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) : WorkspacePullDiagnosticHandlerFactory(registrationService, analyzerService, diagnosticsRefresher, globalOptions) + IGlobalOptionService globalOptions) : WorkspacePullDiagnosticHandlerFactory(registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { } diff --git a/src/EditorFeatures/Core/GoToDefinition/AbstractGoToCommandHandler`2.cs b/src/EditorFeatures/Core/GoToDefinition/AbstractGoToCommandHandler`2.cs index b1f8f6233b662..8253bca56b7e1 100644 --- a/src/EditorFeatures/Core/GoToDefinition/AbstractGoToCommandHandler`2.cs +++ b/src/EditorFeatures/Core/GoToDefinition/AbstractGoToCommandHandler`2.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; -using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; @@ -24,10 +23,8 @@ using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor.Commanding; -using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; using Microsoft.VisualStudio.Threading; using Microsoft.VisualStudio.Utilities; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.GoToDefinition; @@ -106,7 +103,7 @@ public bool ExecuteCommand(TCommandArgs args, CommandExecutionContext context) if (service == null) return false; - Contract.ThrowIfNull(document); + Roslyn.Utilities.Contract.ThrowIfNull(document); // cancel any prior find-refs that might be in progress. _cancellationTokenSource.Cancel(); @@ -173,7 +170,7 @@ private async Task ExecuteCommandWorkerAsync( var cancellationToken = cancellationTokenSource.Token; var delayTask = DelayAsync(cancellationToken); - var findTask = Task.Run(() => FindResultsAsync(findContext, document, position, cancellationToken), cancellationToken); + var findTask = FindResultsAsync(findContext, document, position, cancellationToken); var firstFinishedTask = await Task.WhenAny(delayTask, findTask).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) @@ -189,7 +186,7 @@ private async Task ExecuteCommandWorkerAsync( if (definitions.Length > 0) { var title = await findContext.GetSearchTitleAsync(cancellationToken).ConfigureAwait(false); - var location = await _streamingPresenter.TryPresentLocationOrNavigateIfOneAsync( + await _streamingPresenter.TryPresentLocationOrNavigateIfOneAsync( _threadingContext, document.Project.Solution.Workspace, title ?? DisplayName, @@ -253,6 +250,9 @@ private async Task PresentResultsInStreamingPresenterAsync( private async Task FindResultsAsync( IFindUsagesContext findContext, Document document, int position, CancellationToken cancellationToken) { + // Ensure that we relinquish the thread so that the caller can proceed with their work. + await Task.Yield().ConfigureAwait(false); + using (Logger.LogBlock(FunctionId, KeyValueLogMessage.Create(LogType.UserAction), cancellationToken)) { await findContext.SetSearchTitleAsync(DisplayName, cancellationToken).ConfigureAwait(false); diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.OpenTextBufferManager.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.OpenTextBufferManager.cs index 18a99ec18cde6..492e1b75c1b09 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.OpenTextBufferManager.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.OpenTextBufferManager.cs @@ -454,7 +454,7 @@ internal void ApplyConflictResolutionEdits(IInlineRenameReplacementInfo conflict // Show merge conflicts comments as unresolvable conflicts, and do not // show any other rename-related spans that overlap a merge conflict comment. mergeResult.MergeConflictCommentSpans.TryGetValue(document.Id, out var mergeConflictComments); - mergeConflictComments ??= []; + mergeConflictComments = mergeConflictComments.NullToEmpty(); foreach (var conflict in mergeConflictComments) { diff --git a/src/EditorFeatures/Core/IntelliSense/QuickInfo/EditorFeaturesOnTheFlyDocsElement.cs b/src/EditorFeatures/Core/IntelliSense/QuickInfo/EditorFeaturesOnTheFlyDocsElement.cs new file mode 100644 index 0000000000000..0f201bd23ce7b --- /dev/null +++ b/src/EditorFeatures/Core/IntelliSense/QuickInfo/EditorFeaturesOnTheFlyDocsElement.cs @@ -0,0 +1,13 @@ +// 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 Microsoft.CodeAnalysis.QuickInfo; + +namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo; + +internal sealed class EditorFeaturesOnTheFlyDocsElement(Document document, OnTheFlyDocsElement onTheFlyDocsElement) +{ + public Document Document { get; } = document; + public OnTheFlyDocsElement OnTheFlyDocsElement { get; } = onTheFlyDocsElement; +} diff --git a/src/EditorFeatures/Core/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs b/src/EditorFeatures/Core/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs index 6cff75d9f36c9..e79ddf07c15bb 100644 --- a/src/EditorFeatures/Core/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs +++ b/src/EditorFeatures/Core/IntelliSense/QuickInfo/IntellisenseQuickInfoBuilder.cs @@ -4,17 +4,18 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Copilot; using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.GoToDefinition; using Microsoft.CodeAnalysis.QuickInfo; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Adornments; @@ -135,6 +136,9 @@ private static async Task BuildInteractiveContentAsync( } } + if (context is not null && quickInfoItem.OnTheFlyDocsElement is not null) + elements.Add(new EditorFeaturesOnTheFlyDocsElement(context.Document, quickInfoItem.OnTheFlyDocsElement)); + return new ContainerElement( ContainerElementStyle.Stacked | ContainerElementStyle.VerticalPadding, elements); @@ -154,7 +158,6 @@ internal static async Task BuildItemAsync( { var context = new IntellisenseQuickInfoBuilderContext(document, classificationOptions, lineFormattingOptions, threadingContext, operationExecutor, asyncListener, streamingPresenter); var content = await BuildInteractiveContentAsync(quickInfoItem, context, cancellationToken).ConfigureAwait(false); - return new IntellisenseQuickInfoItem(trackingSpan, content); } diff --git a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs index 411577b94ea3a..b884102f56884 100644 --- a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens; using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.Composition; @@ -40,9 +41,11 @@ internal class AlwaysActivateInProcLanguageClient( ILspServiceLoggerFactory lspLoggerFactory, IThreadingContext threadingContext, ExportProvider exportProvider, + IDiagnosticSourceManager diagnosticSourceManager, [ImportMany] IEnumerable> buildOnlyDiagnostics) : AbstractInProcLanguageClient(lspServiceProvider, globalOptions, lspLoggerFactory, threadingContext, exportProvider) { private readonly ExperimentalCapabilitiesProvider _experimentalCapabilitiesProvider = defaultCapabilitiesProvider; + private readonly IDiagnosticSourceManager _diagnosticSourceManager = diagnosticSourceManager; private readonly IEnumerable> _buildOnlyDiagnostics = buildOnlyDiagnostics; protected override ImmutableArray SupportedLanguages => ProtocolConstants.RoslynLspLanguages; @@ -69,28 +72,15 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa serverCapabilities.SupportsDiagnosticRequests = true; serverCapabilities.DiagnosticProvider ??= new(); + + // VS does not distinguish between document and workspace diagnostics, so we need to merge them. + var diagnosticSourceNames = _diagnosticSourceManager.GetDocumentSourceProviderNames(clientCapabilities) + .Concat(_diagnosticSourceManager.GetWorkspaceSourceProviderNames(clientCapabilities)) + .Distinct(); serverCapabilities.DiagnosticProvider = serverCapabilities.DiagnosticProvider with { SupportsMultipleContextsDiagnostics = true, - DiagnosticKinds = - [ - // Support a specialized requests dedicated to task-list items. This way the client can ask just - // for these, independently of other diagnostics. They can also throttle themselves to not ask if - // the task list would not be visible. - new(PullDiagnosticCategories.Task), - new(PullDiagnosticCategories.EditAndContinue), - // Dedicated request for workspace-diagnostics only. We will only respond to these if FSA is on. - new(PullDiagnosticCategories.WorkspaceDocumentsAndProject), - // Fine-grained diagnostics requests. Importantly, this separates out syntactic vs semantic - // requests, allowing the former to quickly reach the user without blocking on the latter. In a - // similar vein, compiler diagnostics are explicitly distinct from analyzer-diagnostics, allowing - // the former to appear as soon as possible as they are much more critical for the user and should - // not be delayed by a slow analyzer. - new(PullDiagnosticCategories.DocumentCompilerSyntax), - new(PullDiagnosticCategories.DocumentCompilerSemantic), - new(PullDiagnosticCategories.DocumentAnalyzerSyntax), - new(PullDiagnosticCategories.DocumentAnalyzerSemantic), - ], + DiagnosticKinds = diagnosticSourceNames.Select(n => new VSInternalDiagnosticKind(n)).ToArray(), BuildOnlyDiagnosticIds = _buildOnlyDiagnostics .SelectMany(lazy => lazy.Metadata.BuildOnlyDiagnostics) .Distinct() diff --git a/src/EditorFeatures/Core/Navigation/AbstractDefinitionLocationService.cs b/src/EditorFeatures/Core/Navigation/AbstractDefinitionLocationService.cs index 6f5eb9015334d..c2515dc973c1a 100644 --- a/src/EditorFeatures/Core/Navigation/AbstractDefinitionLocationService.cs +++ b/src/EditorFeatures/Core/Navigation/AbstractDefinitionLocationService.cs @@ -39,47 +39,56 @@ protected AbstractDefinitionLocationService( workspace, document.Id, position, virtualSpace: 0, cancellationToken); } - public async Task GetDefinitionLocationAsync( - Document document, int position, CancellationToken cancellationToken) + public async Task GetDefinitionLocationAsync(Document document, int position, CancellationToken cancellationToken) { - var symbolService = document.GetRequiredLanguageService(); - var (controlFlowTarget, controlFlowSpan) = await symbolService.GetTargetIfControlFlowAsync( - document, position, cancellationToken).ConfigureAwait(false); - if (controlFlowTarget != null) - { - var location = await GetNavigableLocationAsync( - document, controlFlowTarget.Value, cancellationToken).ConfigureAwait(false); - return location is null ? null : new DefinitionLocation(location, new DocumentSpan(document, controlFlowSpan)); - } - else + // We want to compute this as quickly as possible so that the symbol be squiggled and navigated to. We + // don't want to wait on expensive operations like computing source-generators or skeletons if we can avoid + // it. So first try with a frozen document, then fallback to a normal document. This mirrors how go-to-def + // works as well. + return await GetDefinitionLocationWorkerAsync(document.WithFrozenPartialSemantics(cancellationToken)).ConfigureAwait(false) ?? + await GetDefinitionLocationWorkerAsync(document).ConfigureAwait(false); + + async Task GetDefinitionLocationWorkerAsync(Document document) { - // Try to compute the referenced symbol and attempt to go to definition for the symbol. - var (symbol, project, span) = await symbolService.GetSymbolProjectAndBoundSpanAsync( + var symbolService = document.GetRequiredLanguageService(); + var (controlFlowTarget, controlFlowSpan) = await symbolService.GetTargetIfControlFlowAsync( document, position, cancellationToken).ConfigureAwait(false); - if (symbol is null) - return null; - - // if the symbol only has a single source location, and we're already on it, - // try to see if there's a better symbol we could navigate to. - var remappedLocation = await GetAlternativeLocationIfAlreadyOnDefinitionAsync( - project, position, symbol, originalDocument: document, cancellationToken).ConfigureAwait(false); - if (remappedLocation != null) - return new DefinitionLocation(remappedLocation, new DocumentSpan(document, span)); - - var isThirdPartyNavigationAllowed = await IsThirdPartyNavigationAllowedAsync( - symbol, position, document, cancellationToken).ConfigureAwait(false); - - var location = await GoToDefinitionHelpers.GetDefinitionLocationAsync( - symbol, - project.Solution, - _threadingContext, - _streamingPresenter, - thirdPartyNavigationAllowed: isThirdPartyNavigationAllowed, - cancellationToken: cancellationToken).ConfigureAwait(false); - if (location is null) - return null; - - return new DefinitionLocation(location, new DocumentSpan(document, span)); + if (controlFlowTarget != null) + { + var location = await GetNavigableLocationAsync( + document, controlFlowTarget.Value, cancellationToken).ConfigureAwait(false); + return location is null ? null : new DefinitionLocation(location, new DocumentSpan(document, controlFlowSpan)); + } + else + { + // Try to compute the referenced symbol and attempt to go to definition for the symbol. + var (symbol, project, span) = await symbolService.GetSymbolProjectAndBoundSpanAsync( + document, position, cancellationToken).ConfigureAwait(false); + if (symbol is null) + return null; + + // if the symbol only has a single source location, and we're already on it, + // try to see if there's a better symbol we could navigate to. + var remappedLocation = await GetAlternativeLocationIfAlreadyOnDefinitionAsync( + project, position, symbol, originalDocument: document, cancellationToken).ConfigureAwait(false); + if (remappedLocation != null) + return new DefinitionLocation(remappedLocation, new DocumentSpan(document, span)); + + var isThirdPartyNavigationAllowed = await IsThirdPartyNavigationAllowedAsync( + symbol, position, document, cancellationToken).ConfigureAwait(false); + + var location = await GoToDefinitionHelpers.GetDefinitionLocationAsync( + symbol, + project.Solution, + _threadingContext, + _streamingPresenter, + thirdPartyNavigationAllowed: isThirdPartyNavigationAllowed, + cancellationToken: cancellationToken).ConfigureAwait(false); + if (location is null) + return null; + + return new DefinitionLocation(location, new DocumentSpan(document, span)); + } } } @@ -140,8 +149,7 @@ private static async Task IsThirdPartyNavigationAllowedAsync( if (containingTypeDeclaration != null) { - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - Debug.Assert(semanticModel != null); + var semanticModel = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); // Allow third parties to navigate to all symbols except types/constructors // if we are navigating from the corresponding type. diff --git a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs index 805117ccedac8..d0b1146c9362b 100644 --- a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs +++ b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs @@ -205,45 +205,36 @@ private async ValueTask SynchronizeTextChangesAsync( async ValueTask SynchronizeTextChangesAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken) { - // this pushes text changes to the remote side if it can. - // this is purely perf optimization. whether this pushing text change - // worked or not doesn't affect feature's functionality. + // this pushes text changes to the remote side if it can. this is purely perf optimization. whether this + // pushing text change worked or not doesn't affect feature's functionality. // - // this basically see whether it can cheaply find out text changes - // between 2 snapshots, if it can, it will send out that text changes to - // remote side. + // this basically see whether it can cheaply find out text changes between 2 snapshots, if it can, it will + // send out that text changes to remote side. // - // the remote side, once got the text change, will again see whether - // it can use that text change information without any high cost and - // create new snapshot from it. + // the remote side, once got the text change, will again see whether it can use that text change information + // without any high cost and create new snapshot from it. // - // otherwise, it will do the normal behavior of getting full text from - // VS side. this optimization saves times we need to do full text - // synchronization for typing scenario. + // otherwise, it will do the normal behavior of getting full text from VS side. this optimization saves + // times we need to do full text synchronization for typing scenario. - if ((oldDocument.TryGetText(out var oldText) == false) || - (newDocument.TryGetText(out var newText) == false)) + if (!oldDocument.TryGetText(out var oldText) || + !newDocument.TryGetText(out var newText)) { // we only support case where text already exist return; } - // get text changes - var textChanges = newText.GetTextChanges(oldText).AsImmutable(); - if (textChanges.Length == 0) - { - // no changes + // Avoid allocating text before seeing if we can bail out. + var changeRanges = newText.GetChangeRanges(oldText).AsImmutable(); + if (changeRanges.Length == 0) return; - } - // whole document case - if (textChanges.Length == 1 && textChanges[0].Span.Length == oldText.Length) - { - // no benefit here. pulling from remote host is more efficient + // no benefit here. pulling from remote host is more efficient + if (changeRanges is [{ Span.Length: var singleChangeLength }] && singleChangeLength == oldText.Length) return; - } - // only cancelled when remote host gets shutdown + var textChanges = newText.GetTextChanges(oldText).AsImmutable(); + var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); if (client == null) return; diff --git a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCommitter.cs b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCommitter.cs index dcc3fe3db63ed..1168c4eced621 100644 --- a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCommitter.cs +++ b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCommitter.cs @@ -196,12 +196,10 @@ private async Task CreateSolutionWithOriginalNameAsync( // Apply the original name to all linked documents to construct a consistent solution var solution = document.Project.Solution; - foreach (var documentId in document.GetLinkedDocumentIds().Add(document.Id)) - { - solution = solution.WithDocumentText(documentId, newFullText); - } + var finalSolution = solution.WithDocumentTexts( + document.GetLinkedDocumentIds().Add(document.Id).SelectAsArray(id => (id, newFullText))); - return solution; + return finalSolution; } private async Task TryGetSymbolAsync(Solution solutionWithOriginalName, DocumentId documentId, CancellationToken cancellationToken) diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf index 230fbdd0b2d86..fd8b9483a1338 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Relace přejmenování na řádku je pro identifikátor {0} aktivní. Pokud chcete získat přístup k dalším možnostem, znovu volejte přejmenování na řádku. Můžete kdykoli pokračovat v úpravách identifikátoru, který se přejmenovává. @@ -62,6 +72,16 @@ Získat nápovědu pro: {0} + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Přejít na základní typ @@ -207,6 +227,11 @@ Obsolete symbol + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Operátor – přetížení @@ -302,6 +327,11 @@ Run + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Dělený komentář @@ -322,6 +352,11 @@ Velikost tabulátoru + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Výsledky mohou být neúplné, protože řešení stále načítá projekty. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf index 072730f4d00f3..2243c67170acd 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Für den Bezeichner "{0}" ist eine Inline-Umbenennungssitzung aktiv. Rufen Sie die Inline-Umbenennung erneut auf, um auf zusätzliche Optionen zuzugreifen. Sie können den Bezeichner, der gerade umbenannt wird, jederzeit weiterbearbeiten. @@ -62,6 +72,16 @@ Hilfe zu "{0}" abrufen + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Zu Basis wechseln @@ -207,6 +227,11 @@ Obsolete symbol + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Operator - überladen @@ -302,6 +327,11 @@ Run + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Kommentar teilen @@ -322,6 +352,11 @@ Tabstoppgröße + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Die Ergebnisse sind möglicherweise unvollständig, da die Projektmappe noch Projekte lädt. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf index 5a78d5ff51fb5..2cc1faf434f46 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Hay una sesión de cambio de nombre insertado que está activa para el identificador "{0}". Vuelva a invocar el cambio de nombre insertado para acceder a más opciones. Puede continuar editando el identificador cuyo nombre se va a cambiar en cualquier momento. @@ -62,6 +72,16 @@ Obtener ayuda para "{0}" + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Ir a base @@ -207,6 +227,11 @@ Obsolete symbol + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Operador: sobrecargado @@ -302,6 +327,11 @@ Run + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Dividir comentario @@ -322,6 +352,11 @@ Tamaño de tabulación + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Los resultados pueden estar incompletos porque la solución aún está cargando proyectos. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf index 3da06b40397e1..f799bb0145e0f 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Une session de renommage inline est active pour l'identificateur '{0}'. Appelez à nouveau le renommage inline pour accéder à des options supplémentaires. Vous pouvez continuer à modifier l'identificateur renommé à tout moment. @@ -62,6 +72,16 @@ Obtenir de l'aide pour '{0}' + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Accéder à la base @@ -207,6 +227,11 @@ Obsolete symbol + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Opérateur - surchargé @@ -302,6 +327,11 @@ Run + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Diviser le commentaire @@ -322,6 +352,11 @@ Taille des tabulations + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Les résultats sont peut-être incomplets, car la solution est toujours en train de charger des projets. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf index b36a3c74fd020..2a18a501c2814 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Per l'identificatore '{0}' è attiva una sessione di ridenominazione inline. Richiamare nuovamente la ridenominazione inline per accedere alle opzioni aggiuntive. È possibile continuare a modificare l'identificatore da rinominare in qualsiasi momento. @@ -62,6 +72,16 @@ Visualizza la Guida per '{0}' + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Vai a base @@ -207,6 +227,11 @@ Obsolete symbol + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Operatore - Overload @@ -302,6 +327,11 @@ Run + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Dividi commento @@ -322,6 +352,11 @@ Dimensione tabulazione + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. I risultati potrebbero essere incompleti perché la soluzione ancora carica i progetti. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf index b582fb5a423b9..fcfee30eeda27 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. 識別子 '{0}' のインラインの名前変更セッションがアクティブです。追加オプションにアクセスするには、インラインの名前変更をもう一度呼び出します。名前を変更する識別子はいつでも引き続き編集できます。 @@ -62,6 +72,16 @@ '{0}' のヘルプの表示 + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base 基本へ移動 @@ -207,6 +227,11 @@ Obsolete symbol + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded 演算子 - オーバーロード @@ -302,6 +327,11 @@ Run + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment コメントの分割 @@ -322,6 +352,11 @@ タブのサイズ + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. ソリューションでプロジェクトを読み込み中であるため、結果は不完全になる可能性があります。 diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf index 8ab379da7864d..6c8eaceb982f6 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. 식별자 '{0}'의 인라인 이름 바꾸기 세션이 활성 상태입니다. 추가 옵션에 액세스하려면 인라인 이름 바꾸기를 다시 호출하세요. 언제든지 이름을 바꾸려는 식별자를 계속 편집할 수 있습니다. @@ -62,6 +72,16 @@ '{0}'에 대한 도움 받기 + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base 기본으로 이동 @@ -207,6 +227,11 @@ Obsolete symbol + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded 연산자 - 오버로드됨 @@ -302,6 +327,11 @@ Run + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment 주석 분할 @@ -322,6 +352,11 @@ 탭 크기 + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. 솔루션이 아직 프로젝트를 로드하는 중이므로 결과가 불완전할 수 있습니다. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf index 180d567ab7029..13f60bbdf554b 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Wbudowana sesja zmieniania nazwy jest aktywna dla identyfikatora „{0}”. Wywołaj ponownie wbudowane zmienianie nazwy, aby uzyskać dostęp do dodatkowych opcji. W dowolnym momencie możesz kontynuować edytowanie identyfikatora, którego nazwa jest zmieniana. @@ -62,6 +72,16 @@ Uzyskaj pomoc dla „{0}” + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Przejdź do podstawy @@ -207,6 +227,11 @@ Obsolete symbol + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Operator — przeciążony @@ -302,6 +327,11 @@ Run + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Podziel komentarz @@ -322,6 +352,11 @@ Rozmiar tabulacji + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Wyniki mogą być niekompletne, ponieważ rozwiązanie nadal ładuje projekty. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf index 4d07bb8e799ad..54d40bd631201 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Uma sessão de renomeação embutida está ativa para o identificador '{0}'. Invoque a renomeação embutida novamente para acessar opções adicionais. Você pode continuar a editar o identificador que está sendo renomeado a qualquer momento. @@ -62,6 +72,16 @@ Obter ajuda para '{0}' + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Ir Para a Base @@ -207,6 +227,11 @@ Obsolete symbol + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Operador – Sobrecarregado @@ -302,6 +327,11 @@ Run + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Dividir o comentário @@ -322,6 +352,11 @@ Tamanho da Tabulação + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Os resultados podem estar incompletos porque a solução ainda está carregando projetos. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf index 73a7c060f0883..0a8b441320d98 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. Встроенный сеанс переименования активен для идентификатора "{0}". Снова вызовите встроенное переименование, чтобы получить доступ к дополнительным параметрам. Вы можете в любое время продолжить изменение переименованного идентификатора. @@ -62,6 +72,16 @@ Получить справку для "{0}" + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Перейти к базовому @@ -207,6 +227,11 @@ Obsolete symbol + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded Оператор — перегружен @@ -302,6 +327,11 @@ Run + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Разделительный комментарий @@ -322,6 +352,11 @@ Размер интервала табуляции + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Результаты могут быть неполными, поскольку решение еще загружает проекты. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf index 8e5a7e551ee83..3cdad91baa090 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. '{0}' tanımlayıcısı için bir satır içi yeniden adlandırma oturumu etkin. Ek seçeneklere erişmek için satır içi yeniden adlandırmayı tekrar çağırın. İstediğiniz zaman yeniden adlandırılan tanımlayıcıyı düzenlemeye devam edebilirsiniz. @@ -62,6 +72,16 @@ '{0}' için yardım alın + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base Tabana Git @@ -207,6 +227,11 @@ Obsolete symbol + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded İşleç - Aşırı Yüklenmiş @@ -302,6 +327,11 @@ Run + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment Açıklamayı böl @@ -322,6 +352,11 @@ Sekme Boyutu + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. Çözüm projeleri hala yüklemediğinden sonuçlar tamamlanmamış olabilir. diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf index b55d4aa19134d..e58d77cdc186e 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. 标识符“{0}”的内联重命名会话处于活动状态。再次调用内联重命名以访问其他选项。可随时继续编辑正在重命名的标识符。 @@ -62,6 +72,16 @@ 获取有关“{0}”的帮助 + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base 转到基础映像 @@ -207,6 +227,11 @@ Obsolete symbol + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded 运算符-重载 @@ -302,6 +327,11 @@ Run + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment 拆分注释 @@ -322,6 +352,11 @@ 制表符大小 + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. 由于解决方案仍在加载项目,结果可能不完整。 diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf index fe000bb08c980..5c6301e581345 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf @@ -2,6 +2,16 @@ + + AI-generated content may be inaccurate + AI-generated content may be inaccurate + + + + An error occurred while generating documentation for this code. + An error occurred while generating documentation for this code. + + An inline rename session is active for identifier '{0}'. Invoke inline rename again to access additional options. You may continue to edit the identifier being renamed at any time. 識別碼 '{0}' 有正在使用的內嵌重新命名工作階段。再次叫用內嵌重新命名可存取其他選項。您隨時可以繼續編輯正在重新命名的識別碼。 @@ -62,6 +72,16 @@ 取得 '{0}' 的說明 + + GitHub Copilot + GitHub Copilot + + + + GitHub Copilot thinking... + GitHub Copilot thinking... + + Go To Base 移至基底 @@ -207,6 +227,11 @@ Obsolete symbol + + On-the-fly documentation + On-the-fly documentation + + Operator - Overloaded 運算子 - 多載 @@ -302,6 +327,11 @@ Run + + Show an AI generated summary of this code + Show an AI generated summary of this code + + Split comment 分割註解 @@ -322,6 +352,11 @@ 索引標籤大小 + + Tell me more + Tell me more + + The results may be incomplete due to the solution still loading projects. 結果可能不完整,因為方案仍在載入專案。 diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/AbstractCodeActionOrUserDiagnosticTest.cs b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/AbstractCodeActionOrUserDiagnosticTest.cs index 3bffa0849dc4e..6ac112eb45bff 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/AbstractCodeActionOrUserDiagnosticTest.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/AbstractCodeActionOrUserDiagnosticTest.cs @@ -78,7 +78,7 @@ internal TestParameters( bool retainNonFixableDiagnostics = false, bool includeDiagnosticsOutsideSelection = false, string title = null, - TestHost testHost = TestHost.InProcess, + TestHost testHost = TestHost.OutOfProcess, string workspaceKind = null, bool includeNonLocalDocumentDiagnostics = false, bool treatPositionIndicatorsAsCode = false) @@ -259,7 +259,7 @@ private static void AddAnalyzerConfigDocumentWithOptions(EditorTestWorkspace wor Assert.True(applied); return; - string GenerateAnalyzerConfigText(OptionsCollectionAlias options) + static string GenerateAnalyzerConfigText(OptionsCollectionAlias options) { var textBuilder = new StringBuilder(); @@ -414,7 +414,7 @@ internal Task TestInRegularAndScriptAsync( object fixProviderData = null, ParseOptions parseOptions = null, string title = null, - TestHost testHost = TestHost.InProcess) + TestHost testHost = TestHost.OutOfProcess) { return TestInRegularAndScript1Async( initialMarkup, expectedMarkup, @@ -454,7 +454,7 @@ internal Task TestAsync( OptionsCollectionAlias globalOptions = null, object fixProviderData = null, CodeActionPriority? priority = null, - TestHost testHost = TestHost.InProcess) + TestHost testHost = TestHost.OutOfProcess) { return TestAsync( initialMarkup, diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs b/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs index 6027738ffdb05..9d1bfe0e37ead 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs @@ -152,7 +152,7 @@ internal override async Task> GetDiagnosticsAsync( EditorTestWorkspace workspace, TestParameters parameters) { var (analyzer, _) = GetOrCreateDiagnosticProviderAndFixer(workspace, parameters); - AddAnalyzerToWorkspace(workspace, analyzer, parameters); + AddAnalyzerToWorkspace(workspace, analyzer); var document = GetDocumentAndSelectSpan(workspace, out var span); var allDiagnostics = await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(workspace, document, span, includeNonLocalDocumentDiagnostics: parameters.includeNonLocalDocumentDiagnostics); @@ -164,7 +164,7 @@ internal override async Task> GetDiagnosticsAsync( EditorTestWorkspace workspace, TestParameters parameters) { var (analyzer, fixer) = GetOrCreateDiagnosticProviderAndFixer(workspace, parameters); - AddAnalyzerToWorkspace(workspace, analyzer, parameters); + AddAnalyzerToWorkspace(workspace, analyzer); GetDocumentAndSelectSpanOrAnnotatedSpan(workspace, out var document, out var span, out var annotation); diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs b/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs index 6128a5ceb1d2e..97da08493caea 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs @@ -20,6 +20,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Remote.Testing; +using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; @@ -100,26 +101,23 @@ protected override async Task> GetDiagnosticsWorkerAs internal override Task GetCodeRefactoringAsync(EditorTestWorkspace workspace, TestParameters parameters) => throw new NotImplementedException("No refactoring provided in diagnostic test"); - protected static void AddAnalyzerToWorkspace(Workspace workspace, DiagnosticAnalyzer analyzer, TestParameters parameters) + protected static void AddAnalyzerToWorkspace(Workspace workspace, DiagnosticAnalyzer analyzer) { - AnalyzerReference[] analyzeReferences; + AnalyzerReference[] analyzerReferences; if (analyzer != null) { - Contract.ThrowIfTrue(parameters.testHost == TestHost.OutOfProcess, $"Out-of-proc testing is not supported since {analyzer} can't be serialized."); - - analyzeReferences = new[] { new AnalyzerImageReference(ImmutableArray.Create(analyzer)) }; + var analyzerImageReference = new AnalyzerImageReference(ImmutableArray.Create(analyzer)); + analyzerReferences = [analyzerImageReference]; } else { // create a serializable analyzer reference: - analyzeReferences = new[] - { + analyzerReferences = [ new AnalyzerFileReference(DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp).GetType().Assembly.Location, TestAnalyzerAssemblyLoader.LoadFromFile), - new AnalyzerFileReference(DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.VisualBasic).GetType().Assembly.Location, TestAnalyzerAssemblyLoader.LoadFromFile) - }; + new AnalyzerFileReference(DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.VisualBasic).GetType().Assembly.Location, TestAnalyzerAssemblyLoader.LoadFromFile)]; } - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(analyzeReferences)); + workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(analyzerReferences)); } protected static Document GetDocumentAndSelectSpan(EditorTestWorkspace workspace, out TextSpan span) diff --git a/src/EditorFeatures/Test/Diagnostics/AnalyzerLoadFailureTests.cs b/src/EditorFeatures/Test/Diagnostics/AnalyzerLoadFailureTests.cs index 1dd3916cba97b..02be79c9261c3 100644 --- a/src/EditorFeatures/Test/Diagnostics/AnalyzerLoadFailureTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/AnalyzerLoadFailureTests.cs @@ -10,8 +10,7 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics { public class AnalyzerLoadFailureTests { - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void CanCreateDiagnosticForAnalyzerLoadFailure( AnalyzerLoadFailureEventArgs.FailureErrorCode errorCode, [CombinatorialValues(LanguageNames.CSharp, LanguageNames.VisualBasic, null)] string? languageName) diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index 4f0e0ea175216..6bc4b1c231ac1 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -473,8 +473,7 @@ private static string GetGeneratedCodeFromMarkedSource(string markedSource) return null; } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task StartDebuggingSession_CapturingDocuments(bool captureAllDocuments) { var encodingA = Encoding.BigEndianUnicode; @@ -664,8 +663,7 @@ public async Task DifferentDocumentWithSameContent() }, _telemetryLog); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task ProjectThatDoesNotSupportEnC(bool breakMode) { using var _ = CreateWorkspace(out var solution, out var service, [typeof(NoCompilationLanguageService)]); @@ -770,8 +768,7 @@ public async Task DesignTimeOnlyDocument_Dynamic() Assert.Empty(emitDiagnostics); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task DesignTimeOnlyDocument_Wpf([CombinatorialValues(LanguageNames.CSharp, LanguageNames.VisualBasic)] string language, bool delayLoad, bool open, bool designTimeOnlyAddedAfterSessionStarts) { var source = "class A { }"; @@ -874,8 +871,7 @@ public async Task DesignTimeOnlyDocument_Wpf([CombinatorialValues(LanguageNames. EndDebuggingSession(debuggingSession); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task ErrorReadingModuleFile(bool breakMode) { // module file is empty, which will cause a read error: @@ -1067,8 +1063,7 @@ public async Task ErrorReadingSourceFile() }, _telemetryLog); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FileAdded(bool breakMode) { var sourceA = "class C1 { void M() { System.Console.WriteLine(1); } }"; @@ -1153,8 +1148,7 @@ public async Task FileAdded(bool breakMode) /// solution /// /// - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task ModuleDisallowsEditAndContinue_NoChanges(bool breakMode) { var source0 = "class C1 { void M() { System.Console.WriteLine(0); } }"; @@ -1365,8 +1359,7 @@ public async Task Encodings() EndDebuggingSession(debuggingSession); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task RudeEdits(bool breakMode) { var source1 = "class C1 { void M() { System.Console.WriteLine(1); } }"; @@ -1541,8 +1534,7 @@ class C { int Y => 2; } EndDebuggingSession(debuggingSession); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task RudeEdits_DocumentOutOfSync(bool breakMode) { var source0 = "class C1 { void M() { System.Console.WriteLine(0); } }"; @@ -1908,8 +1900,7 @@ public enum DocumentKind AnalyzerConfig, } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task HasChanges_Documents(DocumentKind documentKind) { using var _ = CreateWorkspace(out var solution, out var service); @@ -2159,8 +2150,7 @@ public async Task Project_Add() EndDebuggingSession(debuggingSession); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task Capabilities(bool breakState) { var source1 = "class C { void M() { } }"; @@ -2629,8 +2619,7 @@ public async Task ValidSignificantChange_AddedFileNotObservedBeforeDebuggingSess EndDebuggingSession(debuggingSession); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task ValidSignificantChange_DocumentOutOfSync(bool delayLoad) { var sourceOnDisk = "class C1 { void M() { System.Console.WriteLine(1); } }"; @@ -2683,8 +2672,7 @@ public async Task ValidSignificantChange_DocumentOutOfSync(bool delayLoad) Assert.Empty(debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate()); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task ValidSignificantChange_EmitSuccessful(bool breakMode, bool commitUpdate) { var sourceV1 = "class C1 { void M() { System.Console.WriteLine(1); } }"; @@ -3608,8 +3596,7 @@ public async Task ActiveStatements() AssertEx.Equal(new[] { adjustedActiveLineSpan1, adjustedActiveLineSpan2 }, currentSpans.Select(s => s.LineSpan)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task ActiveStatements_SyntaxErrorOrOutOfSyncDocument(bool isOutOfSync) { var sourceV1 = "class C { void F() => G(1); void G(int a) => System.Console.WriteLine(1); }"; @@ -3673,8 +3660,7 @@ public async Task ActiveStatements_SyntaxErrorOrOutOfSyncDocument(bool isOutOfSy AssertEx.Equal(new[] { activeLineSpan11, activeLineSpan12 }, currentSpans.Select(s => s.LineSpan)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task ActiveStatements_ForeignDocument(bool withPath, bool designTimeOnly) { using var _ = CreateWorkspace(out var solution, out var service, [typeof(NoCompilationLanguageService)]); diff --git a/src/EditorFeatures/Test/InheritanceMargin/InheritanceMarginTests.cs b/src/EditorFeatures/Test/InheritanceMargin/InheritanceMarginTests.cs index 3722290e27ff0..ee20b82e5bc9d 100644 --- a/src/EditorFeatures/Test/InheritanceMargin/InheritanceMarginTests.cs +++ b/src/EditorFeatures/Test/InheritanceMargin/InheritanceMarginTests.cs @@ -897,8 +897,7 @@ public class {{|target1:Bar2|}} : Bar itemForEooInAbstractClass); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public Task TestCSharpOverrideMemberCanFindImplementingInterface(bool testDuplicate, TestHost testHost) { var markup1 = @"using System; @@ -1822,8 +1821,7 @@ End Sub itemForFooInBar); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public Task TestVisualBasicOverrideMemberCanFindImplementingInterface(bool testDuplicate, TestHost testHost) { var markup1 = @" diff --git a/src/EditorFeatures/Test/LinkedFiles/LinkedFileDiffMergingEditorTests.cs b/src/EditorFeatures/Test/LinkedFiles/LinkedFileDiffMergingEditorTests.cs index 9d9b0d488ef3b..47974f2cfb369 100644 --- a/src/EditorFeatures/Test/LinkedFiles/LinkedFileDiffMergingEditorTests.cs +++ b/src/EditorFeatures/Test/LinkedFiles/LinkedFileDiffMergingEditorTests.cs @@ -2,94 +2,113 @@ // 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.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; -namespace Microsoft.CodeAnalysis.Editor.UnitTests.LinkedFiles +namespace Microsoft.CodeAnalysis.Editor.UnitTests.LinkedFiles; + +public sealed class LinkedFileDiffMergingEditorTests : AbstractCodeActionTest { - public partial class LinkedFileDiffMergingEditorTests : AbstractCodeActionTest - { - private const string WorkspaceXml = @" - - - - - - - "; - - protected internal override string GetLanguage() - => LanguageNames.CSharp; - - protected override CodeRefactoringProvider CreateCodeRefactoringProvider(EditorTestWorkspace workspace, TestParameters parameters) - => new TestCodeRefactoringProvider(); - - [WpfFact] - public async Task TestCodeActionPreviewAndApply() + private const string WorkspaceXml = """ + + + + + + + + + """; + + private const string s_expectedCode = """ + internal class C { - // TODO: WPF required due to https://github.com/dotnet/roslyn/issues/46153 - using var workspace = EditorTestWorkspace.Create(WorkspaceXml, composition: EditorTestCompositions.EditorFeaturesWpf); - var codeIssueOrRefactoring = await GetCodeRefactoringAsync(workspace, new TestParameters()); + private class D + { + } + } + """; - var expectedCode = "private class D { }"; + protected internal override string GetLanguage() + => LanguageNames.CSharp; - await TestActionOnLinkedFiles( - workspace, - expectedText: expectedCode, - action: codeIssueOrRefactoring.CodeActions[0].action, - expectedPreviewContents: expectedCode); - } + protected override CodeRefactoringProvider CreateCodeRefactoringProvider(EditorTestWorkspace workspace, TestParameters parameters) + => new TestCodeRefactoringProvider(); - [Fact] - public async Task TestWorkspaceTryApplyChangesDirectCall() - { - using var workspace = EditorTestWorkspace.Create(WorkspaceXml); - var solution = workspace.CurrentSolution; + [WpfFact] + public async Task TestCodeActionPreviewAndApply() + { + // TODO: WPF required due to https://github.com/dotnet/roslyn/issues/46153 + using var workspace = EditorTestWorkspace.Create(WorkspaceXml, composition: EditorTestCompositions.EditorFeaturesWpf); + var codeIssueOrRefactoring = await GetCodeRefactoringAsync(workspace, new TestParameters()); + + await TestActionOnLinkedFiles( + workspace, + expectedText: s_expectedCode, + action: codeIssueOrRefactoring.CodeActions[0].action, + expectedPreviewContents: """ + internal class C + { + private class D + { + ... + """); + } - var documentId = workspace.Documents.Single(d => !d.IsLinkFile).Id; - var text = await workspace.CurrentSolution.GetDocument(documentId).GetTextAsync(); + [Fact] + public async Task TestWorkspaceTryApplyChangesDirectCall() + { + using var workspace = EditorTestWorkspace.Create(WorkspaceXml); + var solution = workspace.CurrentSolution; - var linkedDocumentId = workspace.Documents.Single(d => d.IsLinkFile).Id; - var linkedText = await workspace.CurrentSolution.GetDocument(linkedDocumentId).GetTextAsync(); + var documentId = workspace.Documents.Single(d => !d.IsLinkFile).Id; + var text = await workspace.CurrentSolution.GetRequiredDocument(documentId).GetTextAsync(); - var newSolution = solution - .WithDocumentText(documentId, text.Replace(13, 1, "D")) - .WithDocumentText(linkedDocumentId, linkedText.Replace(0, 6, "private")); + var linkedDocumentId = workspace.Documents.Single(d => d.IsLinkFile).Id; + var linkedText = await workspace.CurrentSolution.GetRequiredDocument(linkedDocumentId).GetTextAsync(); - workspace.TryApplyChanges(newSolution); + var textString = linkedText.ToString(); - var expectedMergedText = "private class D { }"; - Assert.Equal(expectedMergedText, (await workspace.CurrentSolution.GetDocument(documentId).GetTextAsync()).ToString()); - Assert.Equal(expectedMergedText, (await workspace.CurrentSolution.GetDocument(linkedDocumentId).GetTextAsync()).ToString()); - } + var newSolution = solution + .WithDocumentText(documentId, text.Replace(textString.IndexOf("public"), "public".Length, "internal")) + .WithDocumentText(linkedDocumentId, linkedText.Replace(textString.LastIndexOf("public"), "public".Length, "private")); + + workspace.TryApplyChanges(newSolution); - protected override ParseOptions GetScriptOptions() - => throw new NotSupportedException(); + Assert.Equal(s_expectedCode, (await workspace.CurrentSolution.GetRequiredDocument(documentId).GetTextAsync()).ToString()); + Assert.Equal(s_expectedCode, (await workspace.CurrentSolution.GetRequiredDocument(linkedDocumentId).GetTextAsync()).ToString()); + } + + protected override ParseOptions GetScriptOptions() + => throw new NotSupportedException(); - private class TestCodeRefactoringProvider : CodeRefactorings.CodeRefactoringProvider + private sealed class TestCodeRefactoringProvider : CodeRefactoringProvider + { + public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { - public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) - { - var document = context.Document; - var linkedDocument = document.Project.Solution.Projects.Single(p => p != document.Project).Documents.Single(); + var document = context.Document; + var linkedDocument = document.Project.Solution.Projects.Single(p => p != document.Project).Documents.Single(); + var sourceText = await linkedDocument.GetTextAsync(); + var textString = sourceText.ToString(); - var newSolution = document.Project.Solution - .WithDocumentText(document.Id, (await document.GetTextAsync()).Replace(13, 1, "D")) - .WithDocumentText(linkedDocument.Id, (await linkedDocument.GetTextAsync()).Replace(0, 6, "private")); + var newSolution = document.Project.Solution + .WithDocumentText(document.Id, (await document.GetTextAsync()).Replace(textString.IndexOf("public"), "public".Length, "internal")) + .WithDocumentText(linkedDocument.Id, sourceText.Replace(textString.LastIndexOf("public"), "public".Length, "private")); -#pragma warning disable RS0005 - context.RegisterRefactoring(CodeAction.Create("Description", (ct) => Task.FromResult(newSolution)), context.Span); -#pragma warning restore RS0005 - } + context.RegisterRefactoring(CodeAction.Create("Description", _ => Task.FromResult(newSolution)), context.Span); } } } diff --git a/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.TestContext.cs b/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.TestContext.cs index 8044e56df5a09..a5250f7c02806 100644 --- a/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.TestContext.cs +++ b/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.TestContext.cs @@ -168,14 +168,7 @@ public static void VerifyDocumentNotReused(MetadataAsSourceFile a, MetadataAsSou public void Dispose() { - try - { - _metadataAsSourceService.CleanupGeneratedFiles(); - } - finally - { - Workspace.Dispose(); - } + Workspace.Dispose(); } public async Task ResolveSymbolAsync(string symbolMetadataName, Compilation? compilation = null) diff --git a/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.cs b/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.cs index 65be22a0e3a3c..8ec8dcaf5894b 100644 --- a/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.cs +++ b/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.cs @@ -25,9 +25,9 @@ public abstract partial class AbstractMetadataAsSourceTests : IAsyncLifetime public virtual Task InitializeAsync() { - AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.MscorlibRef_v46, "mscorlib.v4_6_1038_0.dll", ImmutableArray.Create(Net461.References.mscorlib.ImageBytes)); - AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.SystemRef_v46, "System.v4_6_1038_0.dll", ImmutableArray.Create(Net461.References.System.ImageBytes)); - AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.SystemCoreRef_v46, "System.Core.v4_6_1038_0.dll", ImmutableArray.Create(Net461.References.SystemCore.ImageBytes)); + AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.MscorlibRef_v46, "mscorlib.v4_6_1038_0.dll", ImmutableArray.Create(Net461.ReferenceInfos.mscorlib.ImageBytes)); + AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.SystemRef_v46, "System.v4_6_1038_0.dll", ImmutableArray.Create(Net461.ReferenceInfos.System.ImageBytes)); + AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.SystemCoreRef_v46, "System.Core.v4_6_1038_0.dll", ImmutableArray.Create(Net461.ReferenceInfos.SystemCore.ImageBytes)); AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.ValueTupleRef, "System.ValueTuple.dll", ImmutableArray.Create(TestResources.NetFX.ValueTuple.tuplelib)); AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.SystemRuntimeFacadeRef, "System.Runtime.dll", ImmutableArray.Create(TestMetadata.ResourcesNet451.SystemRuntime)); AssemblyResolver.TestAccessor.AddInMemoryImage(TestBase.MsvbRef, "Microsoft.VisualBasic.dll", ImmutableArray.Create(TestMetadata.ResourcesNet451.MicrosoftVisualBasic)); diff --git a/src/EditorFeatures/Test/Preview/PreviewWorkspaceTests.cs b/src/EditorFeatures/Test/Preview/PreviewWorkspaceTests.cs index be8daacdf0597..e5c4e0b27f424 100644 --- a/src/EditorFeatures/Test/Preview/PreviewWorkspaceTests.cs +++ b/src/EditorFeatures/Test/Preview/PreviewWorkspaceTests.cs @@ -125,7 +125,7 @@ public async Task TestPreviewServices() using var previewWorkspace = new PreviewWorkspace(EditorTestCompositions.EditorFeatures.GetHostServices()); var persistentService = previewWorkspace.Services.SolutionServices.GetPersistentStorageService(); - await using var storage = await persistentService.GetStorageAsync(SolutionKey.ToSolutionKey(previewWorkspace.CurrentSolution), CancellationToken.None); + var storage = await persistentService.GetStorageAsync(SolutionKey.ToSolutionKey(previewWorkspace.CurrentSolution), CancellationToken.None); Assert.IsType(storage); } diff --git a/src/EditorFeatures/Test/ValueTracking/CSharpValueTrackingTests.cs b/src/EditorFeatures/Test/ValueTracking/CSharpValueTrackingTests.cs index 8ad25a41f096b..242a6957d8ee0 100644 --- a/src/EditorFeatures/Test/ValueTracking/CSharpValueTrackingTests.cs +++ b/src/EditorFeatures/Test/ValueTracking/CSharpValueTrackingTests.cs @@ -17,8 +17,7 @@ public class CSharpValueTrackingTests : AbstractBaseValueTrackingTests protected override TestWorkspace CreateWorkspace(string code, TestComposition composition) => TestWorkspace.CreateCSharp(code, composition: composition); - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestProperty(TestHost testHost) { var code = @@ -51,8 +50,7 @@ await ValidateItemsAsync( }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestPropertyWithThis(TestHost testHost) { var code = @@ -85,8 +83,7 @@ await ValidateItemsAsync( }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestField(TestHost testHost) { var code = @@ -119,8 +116,7 @@ await ValidateItemsAsync( }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestFieldWithThis(TestHost testHost) { var code = @@ -153,8 +149,7 @@ await ValidateItemsAsync( }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestLocal(TestHost testHost) { var code = @@ -181,8 +176,7 @@ public int Add(int x, int y) ValidateItem(initialItems[1], 5); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestParameter(TestHost testHost) { var code = @@ -208,8 +202,7 @@ public int Add(int $$x, int y) ValidateItem(initialItems[1], 3); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestMissingOnMethod(TestHost testHost) { var code = @@ -227,8 +220,7 @@ class C Assert.Empty(initialItems); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestMissingOnClass(TestHost testHost) { var code = @@ -246,8 +238,7 @@ public int Add(int x, int y) Assert.Empty(initialItems); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestMissingOnNamespace(TestHost testHost) { var code = @@ -268,8 +259,7 @@ public int Add(int x, int y) Assert.Empty(initialItems); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task MethodTracking1(TestHost testHost) { var code = @@ -379,8 +369,7 @@ private string CalculateDefault(C c) } } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task MethodTracking2(TestHost testHost) { var code = @@ -547,8 +536,7 @@ public static void Main(string[] args) await ValidateChildrenEmptyAsync(workspace, items[4]); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task MethodTracking3(TestHost testHost) { var code = @@ -622,8 +610,7 @@ public async Task Double(int x) }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task OutParam(TestHost testHost) { var code = @" @@ -694,8 +681,7 @@ void M() await ValidateChildrenEmptyAsync(workspace, children.Single()); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestVariableReferenceStart(TestHost testHost) { var code = @@ -757,8 +743,7 @@ public static int GetM() await ValidateChildrenEmptyAsync(workspace, items.Single()); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestVariableReferenceStart2(TestHost testHost) { var code = @@ -820,8 +805,7 @@ public static int GetM() await ValidateChildrenEmptyAsync(workspace, items.Single()); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestVariableReferenceStart3(TestHost testHost) { var code = @@ -890,8 +874,7 @@ public static int GetM() await ValidateChildrenEmptyAsync(workspace, items.Single()); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestMultipleDeclarators(TestHost testHost) { var code = @@ -960,8 +943,7 @@ public static int GetM() await ValidateChildrenEmptyAsync(workspace, items.Single()); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestIndex(TestHost testHost) { var code = @@ -1010,8 +992,7 @@ public int M(Test localTest) await ValidateChildrenEmptyAsync(workspace, items[3]); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestPropertyValue(TestHost testHost) { var code = diff --git a/src/EditorFeatures/Test/ValueTracking/VisualBasicValueTrackingTests.cs b/src/EditorFeatures/Test/ValueTracking/VisualBasicValueTrackingTests.cs index 76a0905c2ff4b..1a58d63cb0789 100644 --- a/src/EditorFeatures/Test/ValueTracking/VisualBasicValueTrackingTests.cs +++ b/src/EditorFeatures/Test/ValueTracking/VisualBasicValueTrackingTests.cs @@ -17,8 +17,7 @@ public class VisualBasicValueTrackingTests : AbstractBaseValueTrackingTests protected override TestWorkspace CreateWorkspace(string code, TestComposition composition) => TestWorkspace.CreateVisualBasic(code, composition: composition); - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestProperty(TestHost testHost) { var code = @@ -58,8 +57,7 @@ End Class ValidateItem(initialItems[1], 3); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestPropertyValue(TestHost testHost) { var code = @@ -110,8 +108,7 @@ End Class await ValidateChildrenEmptyAsync(workspace, childItems.Single()); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestField(TestHost testHost) { var code = @@ -146,8 +143,7 @@ await ValidateItemsAsync( }); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestLocal(TestHost testHost) { var code = @@ -174,8 +170,7 @@ End Class ValidateItem(initialItems[1], 3); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestParameter(TestHost testHost) { var code = @@ -201,8 +196,7 @@ End Class ValidateItem(initialItems[1], 2); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestVariableReferenceStart(TestHost testHost) { var code = @@ -261,8 +255,7 @@ End Function await ValidateChildrenEmptyAsync(workspace, items.Single()); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestVariableReferenceStart2(TestHost testHost) { var code = @@ -321,8 +314,7 @@ End Function await ValidateChildrenEmptyAsync(workspace, items.Single()); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestMultipleDeclarators(TestHost testHost) { var code = diff --git a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs index aa516e6d36f6e..377d303f6a210 100644 --- a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs +++ b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs @@ -85,13 +85,11 @@ public async Task TestCreateFromTemporaryStorage() var text = SourceText.From("Hello, World!"); - // Create a temporary storage location - var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); // Write text into it - await temporaryStorage.WriteTextAsync(text); + var handle = await temporaryStorageService.WriteToTemporaryStorageAsync(text, CancellationToken.None); // Read text back from it - var text2 = await temporaryStorage.ReadTextAsync(); + var text2 = await handle.ReadFromTemporaryStorageAsync(CancellationToken.None); Assert.NotSame(text, text2); Assert.Equal(text.ToString(), text2.ToString()); @@ -107,13 +105,11 @@ public async Task TestCreateFromTemporaryStorageWithEncoding() var text = SourceText.From("Hello, World!", Encoding.ASCII); - // Create a temporary storage location - var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); // Write text into it - await temporaryStorage.WriteTextAsync(text); + var handle = await temporaryStorageService.WriteToTemporaryStorageAsync(text, CancellationToken.None); // Read text back from it - var text2 = await temporaryStorage.ReadTextAsync(); + var text2 = await handle.ReadFromTemporaryStorageAsync(CancellationToken.None); Assert.NotSame(text, text2); Assert.Equal(text.ToString(), text2.ToString()); diff --git a/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb b/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb index fded62fcefd1f..11e8f0f38c88c 100644 --- a/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb +++ b/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb @@ -319,6 +319,10 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests Public Function IsCodeAnalysisOptionEnabledAsync() As Task(Of Boolean) Implements ICopilotOptionsService.IsCodeAnalysisOptionEnabledAsync Return Task.FromResult(True) End Function + + Public Function IsOnTheFlyDocsOptionEnabledAsync() As Task(Of Boolean) Implements ICopilotOptionsService.IsOnTheFlyDocsOptionEnabledASync + Return Task.FromResult(True) + End Function End Class @@ -351,6 +355,10 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests Public Function StartRefinementSessionAsync(oldDocument As Document, newDocument As Document, primaryDiagnostic As Diagnostic, cancellationToken As CancellationToken) As Task Implements ICopilotCodeAnalysisService.StartRefinementSessionAsync Return Task.CompletedTask End Function + + Public Function GetOnTheFlyDocsAsync(symbolSignature As String, declarationCode As ImmutableArray(Of String), language As String, cancellationToken As CancellationToken) As Task(Of String) Implements ICopilotCodeAnalysisService.GetOnTheFlyDocsAsync + Return Task.FromResult("") + End Function End Class End Class End Namespace diff --git a/src/EditorFeatures/Test2/Diagnostics/GenerateFromUsage/GenerateMethodCrossLanguageTests.vb b/src/EditorFeatures/Test2/Diagnostics/GenerateFromUsage/GenerateMethodCrossLanguageTests.vb index 95485abcafc9f..fab1f2fd4f734 100644 --- a/src/EditorFeatures/Test2/Diagnostics/GenerateFromUsage/GenerateMethodCrossLanguageTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/GenerateFromUsage/GenerateMethodCrossLanguageTests.vb @@ -518,8 +518,7 @@ End Module]]> Await TestAsync(input, expected) End Function - - + Public Async Function GenerateMethodUsingTypeConstraint_2BaseTypeConstraints() As Task Dim input = @@ -588,8 +587,7 @@ End Module]]> Await TestAsync(input, expected) End Function - - + Public Async Function GenerateMethodUsingTypeConstraint_2BaseTypeConstraints_Interfaces() As Task Dim input = @@ -658,8 +656,7 @@ End Module]]> Await TestAsync(input, expected) End Function - - + Public Async Function GenerateMethodUsingTypeConstraint_3BaseTypeConstraints_NoCommonDerived() As Task Dim input = @@ -827,8 +824,7 @@ End Module]]> Await TestAsync(input, expected) End Function - - + Public Async Function GenerateMethodUsingTypeConstraint_3BaseTypeConstraints_CommonDerivedNestedType() As Task Dim input = @@ -912,8 +908,7 @@ End Module]]> Await TestAsync(input, expected) End Function - - + Public Async Function GenerateMethodUsingTypeConstraint_3BaseTypeConstraints_CommonDerivedInstantiatedTypes() As Task Dim input = @@ -1004,8 +999,7 @@ End Module]]> Await TestAsync(input, expected) End Function - - + Public Async Function GenerateMethodUsingTypeConstraint_InstantiatedGenerics() As Task Dim input = diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LinkedFiles.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LinkedFiles.vb index c06edae96258f..696404de123f3 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LinkedFiles.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.LinkedFiles.vb @@ -190,8 +190,7 @@ public class D : [|$$C|] Return TestAPIAndFeature(input, kind, host) End Function - - + Public Async Function TestLinkedFiles_NamespaceInMetadataAndSource() As Task Dim definition = @@ -229,8 +228,7 @@ namespace {|Definition:System|} End Using End Function - - + Public Async Function TestLinkedFiles_LocalSymbol() As Task Dim definition = @@ -269,8 +267,7 @@ namespace {|Definition:System|} End Using End Function - - + Public Async Function TestLinkedFiles_OverrideMethods_DirectCall_MultiTargetting1() As Task Dim definition = @@ -312,8 +309,7 @@ class D End Using End Function - - + Public Async Function TestLinkedFiles_OverrideMethods_DirectCall_MultiTargetting2() As Task Dim definition = @@ -355,8 +351,7 @@ class D End Using End Function - - + Public Async Function TestLinkedFiles_OverrideMethods_IndirectCall_MultiTargetting1() As Task Dim definition = @@ -398,8 +393,7 @@ class D End Using End Function - - + Public Async Function TestLinkedFiles_OverrideMethods_IndirectCall_MultiTargetting2() As Task Dim definition = diff --git a/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb b/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb index 5f09f4a3b8077..af34b5fed0fbe 100644 --- a/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb +++ b/src/EditorFeatures/Test2/GoToDefinition/CSharpGoToDefinitionTests.vb @@ -84,8 +84,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToDefinition Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpGoToDefinitionOnAnonymousMember() As Task Dim workspace = @@ -214,8 +213,7 @@ class Program Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpGotoDefinitionPartialMethod() As Task Dim workspace = @@ -576,8 +574,7 @@ class Program Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpGotoDefinitionThroughOddlyNamedType() As Task Dim workspace = @@ -647,8 +644,7 @@ class Program Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpTestLambdaParameter() As Task Dim workspace = @@ -857,8 +853,7 @@ class C Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpGoToImplementedInterfaceMemberFromImpl1() As Task Dim workspace = @@ -880,8 +875,7 @@ class Foo : IFoo1 Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpGoToImplementedInterfaceMemberFromImpl2() As Task Dim workspace = @@ -903,8 +897,7 @@ class Foo : IFoo1 Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpGoToImplementedInterfaceMemberFromImpl3() As Task Dim workspace = @@ -927,8 +920,7 @@ class Foo : IFoo1, IFoo2 Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpGoToDefinitionInVarPatterns() As Task Dim workspace = @@ -1278,8 +1270,7 @@ namespace System Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpFilterGotoDefResultsFromHiddenCodeForUIPresenters() As Task Dim workspace = @@ -1299,8 +1290,7 @@ namespace System Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpDoNotFilterGotoDefResultsFromHiddenCodeForApis() As Task Dim workspace = @@ -1555,8 +1545,7 @@ namespace System Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpDoNotFilterGeneratedSourceLocations() As Task Dim workspace = @@ -1580,8 +1569,7 @@ partial class [|C|] Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpUseGeneratedSourceLocationsIfNoNongeneratedLocationsAvailable() As Task Dim workspace = @@ -1641,8 +1629,7 @@ unsafe Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpTestAliasAndTarget1() As Task Dim workspace = @@ -1670,8 +1657,7 @@ class Program Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpTestAliasAndTarget2() As Task Dim workspace = @@ -1699,8 +1685,7 @@ class Program Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpTestAliasAndTarget3() As Task Dim workspace = @@ -1728,8 +1713,7 @@ class Program Await TestAsync(workspace) End Function - - + Public Async Function TestCSharpTestAliasAndTarget4() As Task Dim workspace = @@ -1777,8 +1761,7 @@ class Program Await TestAsync(workspace, expectedResult:=False) End Function - - + Public Async Function TestGoToDefinitionOnGlobalKeyword() As Task Dim workspace = @@ -1856,8 +1839,7 @@ namespace QueryPattern Throw New InvalidOperationException("Highlight not found") End Function - - + Public Async Function TestQuerySelect() As Task Dim workspace = @@ -1887,8 +1869,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryWhere() As Task Dim workspace = @@ -1919,8 +1900,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQuerySelectMany1() As Task Dim workspace = @@ -1951,8 +1931,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQuerySelectMany2() As Task Dim workspace = @@ -1983,8 +1962,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryJoin1() As Task Dim workspace = @@ -2015,8 +1993,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryJoin2() As Task Dim workspace = @@ -2047,8 +2024,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryJoin3() As Task Dim workspace = @@ -2079,8 +2055,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryJoin4() As Task Dim workspace = @@ -2111,8 +2086,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryGroupJoin1() As Task Dim workspace = @@ -2143,8 +2117,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryGroupJoin2() As Task Dim workspace = @@ -2175,8 +2148,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryGroupJoin3() As Task Dim workspace = @@ -2207,8 +2179,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryGroupJoin4() As Task Dim workspace = @@ -2239,8 +2210,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryGroupBy1() As Task Dim workspace = @@ -2270,8 +2240,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryGroupBy2() As Task Dim workspace = @@ -2301,8 +2270,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryFromCast1() As Task Dim workspace = @@ -2332,8 +2300,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryFromCast2() As Task Dim workspace = @@ -2363,8 +2330,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryJoinCast1() As Task Dim workspace = @@ -2395,8 +2361,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryJoinCast2() As Task Dim workspace = @@ -2427,8 +2392,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQuerySelectManyCast1() As Task Dim workspace = @@ -2459,8 +2423,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQuerySelectManyCast2() As Task Dim workspace = @@ -2491,8 +2454,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryOrderBySingleParameter() As Task Dim workspace = @@ -2523,8 +2485,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryOrderBySingleParameterWithOrderClause() As Task Dim workspace = @@ -2555,8 +2516,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryOrderByTwoParameterWithoutOrderClause() As Task Dim workspace = @@ -2587,8 +2547,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryOrderByTwoParameterWithOrderClause() As Task Dim workspace = @@ -2619,8 +2578,7 @@ class Test Await TestAsync(workspace) End Function - - + Public Async Function TestQueryDegeneratedSelect() As Task Dim workspace = @@ -2651,8 +2609,7 @@ class Test Await TestAsync(workspace, False) End Function - - + Public Async Function TestQueryLet() As Task Dim workspace = diff --git a/src/EditorFeatures/Test2/GoToDefinition/VisualBasicGoToDefinitionTests.vb b/src/EditorFeatures/Test2/GoToDefinition/VisualBasicGoToDefinitionTests.vb index d933d5d92c03a..3e0549be8d262 100644 --- a/src/EditorFeatures/Test2/GoToDefinition/VisualBasicGoToDefinitionTests.vb +++ b/src/EditorFeatures/Test2/GoToDefinition/VisualBasicGoToDefinitionTests.vb @@ -9,8 +9,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.GoToDefinition Inherits GoToDefinitionTestsBase #Region "Normal Visual Basic Tests" - - + Public Async Function TestVisualBasicGoToDefinitionOnAnonymousMember() As Task Dim workspace = @@ -79,8 +78,7 @@ end class Await TestAsync(workspace) End Function - - + Public Async Function TestVisualBasicPropertyBackingField() As Task Dim workspace = @@ -211,8 +209,7 @@ End Class Await TestAsync(workspace) End Function - - + Public Async Function TestVisualBasicGotoDefinitionPartialMethod() As Task Dim workspace = @@ -287,8 +284,7 @@ End Class Await TestAsync(workspace) End Function - - + Public Async Function TestVisualBasicMe() As Task Dim workspace = @@ -321,8 +317,7 @@ End Class Await TestAsync(workspace) End Function - - + Public Async Function TestVisualBasicMyClass() As Task Dim workspace = @@ -355,8 +350,7 @@ End Class Await TestAsync(workspace) End Function - - + Public Async Function TestVisualBasicMyBase() As Task Dim workspace = @@ -481,8 +475,7 @@ End Class Await TestAsync(workspace) End Function - - + Public Async Function TestVisualBasicFilterGotoDefResultsFromHiddenCodeForUIPresenters() As Task Dim workspace = @@ -502,8 +495,7 @@ End Class Await TestAsync(workspace) End Function - - + Public Async Function TestVisualBasicDoNotFilterGotoDefResultsFromHiddenCodeForApis() As Task Dim workspace = @@ -576,8 +568,7 @@ End Module]]>] Await TestAsync(workspace) End Function - - + Public Async Function TestVisualBasicQueryRangeVariable() As Task Dim workspace = @@ -600,8 +591,7 @@ End Module Await TestAsync(workspace) End Function - - + Public Async Function TestVisualBasicGotoConstant() As Task Dim workspace = @@ -621,8 +611,7 @@ End Module End Function - - + Public Async Function TestCrossLanguageParameterizedPropertyOverride() As Task Dim workspace = @@ -653,8 +642,7 @@ class B : A Await TestAsync(workspace) End Function - - + Public Async Function TestCrossLanguageNavigationToVBModuleMember() As Task Dim workspace = @@ -703,8 +691,7 @@ class C Await TestAsync(workspace, expectedResult:=False) End Function - - + Public Async Function TestGoToDefinitionOnInferredFieldInitializer() As Task Dim workspace = @@ -730,8 +717,7 @@ End Class Await TestAsync(workspace) End Function - - + Public Async Function TestGoToDefinitionGlobalImportAlias() As Task Dim workspace = diff --git a/src/EditorFeatures/Test2/InlineHints/CSharpInlineParameterNameHintsTests.vb b/src/EditorFeatures/Test2/InlineHints/CSharpInlineParameterNameHintsTests.vb index ee40db8abbdf7..ae22b2ce65d06 100644 --- a/src/EditorFeatures/Test2/InlineHints/CSharpInlineParameterNameHintsTests.vb +++ b/src/EditorFeatures/Test2/InlineHints/CSharpInlineParameterNameHintsTests.vb @@ -678,8 +678,7 @@ class Derived : Base Await VerifyParamHints(input, output) End Function - - + Public Async Function TestNotOnEnableDisableBoolean1() As Task Dim input = @@ -703,8 +702,7 @@ class A Await VerifyParamHints(input, input) End Function - - + Public Async Function TestNotOnEnableDisableBoolean2() As Task Dim input = @@ -753,8 +751,7 @@ class A Await VerifyParamHints(input, input) End Function - - + Public Async Function TestOnEnableDisableNonBoolean1() As Task Dim input = @@ -797,8 +794,7 @@ class A Await VerifyParamHints(input, output) End Function - - + Public Async Function TestOnEnableDisableNonBoolean2() As Task Dim input = @@ -841,8 +837,7 @@ class A Await VerifyParamHints(input, output) End Function - - + Public Async Function TestOnSetMethodWithClearContext() As Task Dim input = @@ -866,8 +861,7 @@ class A Await VerifyParamHints(input, input) End Function - - + Public Async Function TestOnSetMethodWithUnclearContext() As Task Dim input = @@ -910,8 +904,7 @@ class A Await VerifyParamHints(input, output) End Function - - + Public Async Function TestMethodWithAlphaSuffix1() As Task Dim input = @@ -935,8 +928,7 @@ class A Await VerifyParamHints(input, input) End Function - - + Public Async Function TestMethodWithNonAlphaSuffix1() As Task Dim input = @@ -979,8 +971,7 @@ class A Await VerifyParamHints(input, output) End Function - - + Public Async Function TestMethodWithNumericSuffix1() As Task Dim input = @@ -1004,8 +995,7 @@ class A Await VerifyParamHints(input, input) End Function - - + Public Async Function TestMethodWithNonNumericSuffix1() As Task Dim input = diff --git a/src/EditorFeatures/Test2/InlineHints/VisualBasicInlineParameterNameHintsTests.vb b/src/EditorFeatures/Test2/InlineHints/VisualBasicInlineParameterNameHintsTests.vb index 365096f1be8ff..67523c1b53e57 100644 --- a/src/EditorFeatures/Test2/InlineHints/VisualBasicInlineParameterNameHintsTests.vb +++ b/src/EditorFeatures/Test2/InlineHints/VisualBasicInlineParameterNameHintsTests.vb @@ -587,8 +587,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InlineHints Await VerifyParamHints(input, output) End Function - - + Public Async Function TestNotOnEnableDisableBoolean1() As Task Dim input = @@ -609,8 +608,7 @@ end class Await VerifyParamHints(input, input) End Function - - + Public Async Function TestNotOnEnableDisableBoolean2() As Task Dim input = @@ -631,8 +629,7 @@ end class Await VerifyParamHints(input, input) End Function - - + Public Async Function TestOnEnableDisableNonBoolean1() As Task Dim input = @@ -669,8 +666,7 @@ end class Await VerifyParamHints(input, output) End Function - - + Public Async Function TestOnEnableDisableNonBoolean2() As Task Dim input = @@ -707,8 +703,7 @@ end class Await VerifyParamHints(input, output) End Function - - + Public Async Function TestOnSetMethodWithClearContext() As Task Dim input = @@ -729,8 +724,7 @@ end class Await VerifyParamHints(input, input) End Function - - + Public Async Function TestOnSetMethodWithUnclearContext() As Task Dim input = @@ -767,8 +761,7 @@ end class Await VerifyParamHints(input, output) End Function - - + Public Async Function TestMethodWithAlphaSuffix1() As Task Dim input = @@ -789,8 +782,7 @@ end class Await VerifyParamHints(input, input) End Function - - + Public Async Function TestMethodWithNonAlphaSuffix1() As Task Dim input = @@ -826,8 +818,7 @@ end class Await VerifyParamHints(input, output) End Function - - + Public Async Function TestMethodWithNumericSuffix1() As Task Dim input = @@ -848,8 +839,7 @@ end class Await VerifyParamHints(input, input) End Function - - + Public Async Function TestMethodWithNonNumericSuffix1() As Task Dim input = diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index 9d605373797f3..05c519f9c5846 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -7979,8 +7979,7 @@ namespace NS End Using End Function - - + diff --git a/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb b/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb index b8e430e8bb1a3..7f0bf0c1ead54 100644 --- a/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/SignatureHelpControllerTests.vb @@ -209,8 +209,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense GetMocks(controller).PresenterSession.Verify(Sub(p) p.SelectNextItem(), Times.Once) End Sub - - + Public Sub UpAndDownKeysShouldStillNavigateWhenDuplicateItemsAreFiltered() Dim item = CreateItems(1).Single() Dim controller = CreateController(CreateWorkspace(), items:={item, item}, waitForPresentation:=True) diff --git a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb index 91bce7c96b290..90a10e6bd54b0 100644 --- a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb @@ -25,8 +25,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Public Class VisualBasicCompletionCommandHandlerTests - - + Public Async Function MultiWordKeywordCommitBehavior() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -43,8 +42,7 @@ End Class End Using End Function - - + Public Async Function MultiWordKeywordCommitBehavior2() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -133,8 +131,7 @@ End Module End Using End Function - - + Public Sub ImplementsCompletionFaultTolerance() Using state = TestStateFactory.CreateVisualBasicTestState( @@ -148,8 +145,7 @@ End Module End Using End Sub - - + Public Async Function TestCommitCharTypedAtTheBeginingOfTheFilterSpan() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function CompletionDismissedAfterEscape1() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -211,8 +206,7 @@ End Class End Using End Function - - + Public Async Function TestEnterOnSoftSelection1() As Task ' Code must be left-aligned because of https://github.com/dotnet/roslyn/issues/27988 Using state = TestStateFactory.CreateVisualBasicTestState( @@ -659,8 +653,7 @@ End Module]]>) End Using End Function - - Public Async Function NotEnumPreselectionAfterBackspace() As Task + Public Async Function NotEnumPreselectionAfterBackspace() As Task Using state = TestStateFactory.CreateVisualBasicTestState( ) End Using End Function - - + Public Async Function TestNumericLiteralWithNoMatch() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -708,8 +700,7 @@ End Module.NormalizedValue, state.GetDocumentText()) End Using End Function - - + Public Async Function TestNumericLiteralWithPartialMatch() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -739,8 +730,7 @@ End Module.NormalizedValue, state.GetDocumentText()) End Using End Function - - + Public Async Function TestNumbersAfterLetters() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -802,8 +792,7 @@ end class End Using End Function - - + Public Async Function TestDeleteWordToLeft() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -820,8 +809,7 @@ end class End Using End Function - - + Public Async Function TestCompletionGenericWithOpenParen() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -838,8 +826,7 @@ end class End Using End Function - - + Public Async Function TestCompletionGenericWithSpace() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -900,8 +887,7 @@ end class End Using End Function - - + Public Async Function DoNotInsertEqualsForNamedParameterCommitWithColon() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -921,8 +907,7 @@ end class End Using End Function - - + Public Async Function DoInsertEqualsForNamedParameterCommitWithSpace() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -943,8 +928,7 @@ end class End Using End Function - - + Public Async Function ConsumeHashForPreprocessorCompletion() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -1009,8 +993,7 @@ End Class End Function End Class - - + Public Async Function TestVerbatimNamedIdentifierFiltering() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -1030,8 +1013,7 @@ End Class End Using End Function - - + Public Async Function TestExclusiveNamedParameterCompletion() As Task Using state = TestStateFactory.CreateTestStateFromWorkspace( @@ -1059,8 +1041,7 @@ End Class End Using End Function - - + Public Async Function TestExclusiveNamedParameterCompletion2() As Task Using state = TestStateFactory.CreateTestStateFromWorkspace( @@ -1095,8 +1076,7 @@ End Class End Using End Function - - + Public Async Function TestDoNotCrashOnEmptyParameterList() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -1108,8 +1088,7 @@ End Class End Using End Function - - + Public Async Function OnlyMatchOnLowercaseIfPrefixWordMatch() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -1124,8 +1103,7 @@ End Module End Using End Function - - + Public Async Function MyBaseFinalize() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -1142,8 +1120,7 @@ End Class End Using End Function - - + Public Async Function TestNamedParameterSortOrder() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -1163,8 +1140,7 @@ End Module End Using End Function - - + Public Async Function TestLineContinuationCharacter() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -1182,8 +1158,7 @@ End Module End Using End Function - - + Public Async Function TestNumberDismissesCompletion() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -1301,8 +1276,7 @@ End Module .NormalizedValue, state.GetDocumentText(), StringComp End Using End Function - - + Public Async Function TestBangFiltersInDocComment() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function BackspaceInvokeCompletionComesUpEvenIfNoMatches() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function BackspaceCompletionSelects() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function BackspaceCompletionNeverFilters() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function DistinguishItemsWithDifferentGlyphs() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function TestInvokeSnippetCommandDismissesCompletion() As Task Using state = TestStateFactory.CreateVisualBasicTestState( $$) @@ -1710,8 +1679,7 @@ End Class End Using End Function - - + Public Async Function TestSurroundWithCommandDismissesCompletion() As Task Using state = TestStateFactory.CreateVisualBasicTestState( $$) @@ -1723,8 +1691,7 @@ End Class End Using End Function - - + Public Async Function XmlCompletionNotTriggeredOnBackspaceInText() As Task Using state = TestStateFactory.CreateVisualBasicTestState( ) End Using End Function - - + Public Async Function XmlCompletionNotTriggeredOnBackspaceInTag() As Task Using state = TestStateFactory.CreateVisualBasicTestState( ) End Using End Function - - + Public Async Function BackspacingLastCharacterDismisses() As Task Using state = TestStateFactory.CreateVisualBasicTestState( $$) @@ -1771,8 +1736,7 @@ End Class]]>) End Using End Function - - + Public Async Function HardSelectionWithBuilderAndOneExactMatch() As Task Using state = TestStateFactory.CreateVisualBasicTestState( Module M @@ -1786,8 +1750,7 @@ End Module) End Using End Function - - + Public Async Function SoftSelectionWithBuilderAndNoExactMatch() As Task Using state = TestStateFactory.CreateVisualBasicTestState( Module M @@ -1803,8 +1766,7 @@ End Module) ' The test verifies the CommitCommandHandler isolated behavior which does not add '()' after 'Main'. ' The integrated VS behavior for the case is to get 'Main()'. - - + Public Sub CommitOnEnter() Dim expected = Module M Sub Main() @@ -1877,8 +1839,7 @@ End Class End Using End Sub - - + Public Async Function SelectKeywordFirst() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -1902,8 +1863,7 @@ End Class End Using End Function - - + Public Async Function ConstructorFiltersAsNew() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -1963,8 +1923,7 @@ End Class) End Using End Function - - + Public Async Function InsertOfOnSpace() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -1983,8 +1942,7 @@ End Class End Using End Function - - + Public Sub DoNotInsertOfOnTab() Using state = TestStateFactory.CreateVisualBasicTestState( @@ -2002,8 +1960,7 @@ End Class End Using End Sub - - + Public Async Function NotInPartialMethodDeclaration() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -2080,8 +2037,7 @@ End Class End Using End Function - - + Public Async Function SoftSelectedWithNoFilterText() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -2116,8 +2072,7 @@ End Class) End Using End Function - - + Public Async Function DismissUponSave() As Task Using state = TestStateFactory.CreateVisualBasicTestState( ) End Using End Function - - + Public Async Function DeleteCompletionInvokedSelectedAndUnfiltered() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - - + Public Async Function FilterPrefixOnlyOnBackspace1() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function BackspaceTriggerOnlyIfOptionEnabled() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function KeywordsForIntrinsicsDeduplicated() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function KeywordDeduplicationLeavesEscapedIdentifiers() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function TestEscapedItemCommittedWithCloseBracket() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Sub CommitOnQuestionMarkForConditionalAccess() Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function DismissOnSelectAllCommand() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function DoNotPreferParameterNames() As Task Using state = TestStateFactory.CreateVisualBasicTestState( ) End Using End Function - - + Public Async Function BooleanPreselection1() As Task Using state = TestStateFactory.CreateVisualBasicTestState( ) End Using End Function - - + Public Async Function BooleanPreselection2() As Task Using state = TestStateFactory.CreateVisualBasicTestState( ) End Using End Function - - + Public Async Function BooleanPreselection3() As Task Using state = TestStateFactory.CreateVisualBasicTestState( Function IEnumerable(Of 'a).ToArray() As ' End Using End Function - - + Public Async Function TestObjectCreationQualifiedName() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Sub CommitGenericDoesNotInsertEllipsis() Using state = TestStateFactory.CreateVisualBasicTestState( ) End Using End Sub - - + Public Sub CommitGenericDoesNotInsertEllipsisCommitOnParen() Using state = TestStateFactory.CreateVisualBasicTestState( ) End Using End Sub - - + Public Sub CommitGenericItemDoesNotInsertEllipsisCommitOnTab() Using state = TestStateFactory.CreateVisualBasicTestState( ) End Using End Sub - - + Public Async Function SymbolAndObjectPreselectionUnification() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function ImplementsClause() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function BackspaceSoftSelectionIfNotPrefixMatch() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function CompletionDoesNotRemoveBracketsOnEnum() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -2983,8 +2918,7 @@ End Class End Using End Function - - + Public Async Function TestMRUKeepsTwoRecentlyUsedItems() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -3004,8 +2938,7 @@ End Class End Using End Function - - + Public Async Function TestDoNotDismissIfEmptyOnBackspaceIfStartedWithBackspace() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -3023,8 +2956,7 @@ End Class End Using End Function - - + Public Async Function TestDoNotDismissIfEmptyOnMultipleBackspaceIfStartedInvoke() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -3045,8 +2977,7 @@ End Class End Using End Function - - + Public Async Function TestMatchWithTurkishIWorkaround1() As Task Using New CultureContext(New Globalization.CultureInfo("tr-TR", useUserOverride:=False)) Using state = TestStateFactory.CreateVisualBasicTestState( @@ -3063,8 +2994,7 @@ Class C End Function - - + Public Async Function TestMatchWithTurkishIWorkaround2() As Task Using New CultureContext(New Globalization.CultureInfo("tr-TR", useUserOverride:=False)) Using state = TestStateFactory.CreateVisualBasicTestState( @@ -3080,8 +3010,7 @@ Class C End Function - - + Public Async Function TestMatchWithTurkishIWorkaround3() As Task Using New CultureContext(New Globalization.CultureInfo("tr-TR", useUserOverride:=False)) Using state = TestStateFactory.CreateVisualBasicTestState( @@ -3101,8 +3030,7 @@ Class C End Function - - + Public Async Function TestMatchWithTurkishIWorkaround4() As Task Using New CultureContext(New Globalization.CultureInfo("tr-TR", useUserOverride:=False)) Using state = TestStateFactory.CreateVisualBasicTestState( @@ -3126,8 +3054,7 @@ Class C End Function - - + Public Async Function TestMatchWithTurkishIWorkaround5() As Task Using New CultureContext(New Globalization.CultureInfo("tr-TR", useUserOverride:=False)) Using state = TestStateFactory.CreateVisualBasicTestState( @@ -3152,8 +3079,7 @@ Class C End Function - - + Public Async Function TestMatchWithTurkishIWorkaround6() As Task Using New CultureContext(New Globalization.CultureInfo("tr-TR", useUserOverride:=False)) Using state = TestStateFactory.CreateVisualBasicTestState( @@ -3173,8 +3099,7 @@ Class C End Function - - + Public Async Function TestMatchWithTurkishIWorkaround7() As Task Using New CultureContext(New Globalization.CultureInfo("tr-TR", useUserOverride:=False)) Using state = TestStateFactory.CreateVisualBasicTestState( @@ -3198,8 +3123,7 @@ Class C End Function - - + Public Async Function TestMatchWithTurkishIWorkaround8() As Task Using New CultureContext(New Globalization.CultureInfo("tr-TR", useUserOverride:=False)) Using state = TestStateFactory.CreateVisualBasicTestState( @@ -3223,8 +3147,7 @@ Class C End Function - - + Public Async Function TestMatchWithTurkishIWorkaround9() As Task Using New CultureContext(New Globalization.CultureInfo("tr-TR", useUserOverride:=False)) Using state = TestStateFactory.CreateVisualBasicTestState( @@ -3248,8 +3171,7 @@ Class C End Function - - + Public Async Function TestMatchWithTurkishIWorkaround10() As Task Using New CultureContext(New Globalization.CultureInfo("tr-TR", useUserOverride:=False)) Using state = TestStateFactory.CreateVisualBasicTestState( diff --git a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_XmlDoc.vb b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_XmlDoc.vb index df068451d6875..1849488ed1239 100644 --- a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_XmlDoc.vb +++ b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_XmlDoc.vb @@ -744,8 +744,7 @@ End Class End Function - - + Public Async Function CommitParam() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function AllowTypingSpace() As Task Using state = TestStateFactory.CreateVisualBasicTestState( Public Class VisualBasicSignatureHelpCommandHandlerTests - - + Public Async Function TestFilterOnNamedParameters1() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -50,8 +49,7 @@ End Class End Using End Function - - + Public Async Function TestFilterOnNamedParameters2() As Task Using state = TestStateFactory.CreateVisualBasicTestState( @@ -148,8 +146,7 @@ End Class End Using End Function - - + Public Async Function TestSigHelpNotDismissedAfterQuote() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function TestSigHelpDismissedAfterComment() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function TestSigHelpNotDismissedAfterSpace() As Task Using state = TestStateFactory.CreateVisualBasicTestState( - + Public Async Function DoNotShowSignatureHelpIfOptionIsTurnedOffUnlessExplicitlyInvoked() As Task Using state = TestStateFactory.CreateVisualBasicTestState( diff --git a/src/EditorFeatures/Test2/ReferenceHighlighting/VisualBasicReferenceHighlightingTests.vb b/src/EditorFeatures/Test2/ReferenceHighlighting/VisualBasicReferenceHighlightingTests.vb index 7cc9edd561498..0810850f7a81b 100644 --- a/src/EditorFeatures/Test2/ReferenceHighlighting/VisualBasicReferenceHighlightingTests.vb +++ b/src/EditorFeatures/Test2/ReferenceHighlighting/VisualBasicReferenceHighlightingTests.vb @@ -191,8 +191,7 @@ End Module , testHost) End Function - - + Public Async Function TestAccessor1(testHost As TestHost) As Task Dim input = @@ -216,8 +215,7 @@ End Class Await VerifyHighlightsAsync(input, testHost) End Function - - + Public Async Function TestAccessor2(testHost As TestHost) As Task Dim input = @@ -241,8 +239,7 @@ End Class Await VerifyHighlightsAsync(input, testHost) End Function - - + Public Async Function TestHighlightParameterizedPropertyParameter(testHost As TestHost) As Task Dim input = diff --git a/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb b/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb index b960777b00914..f764b4ef590d1 100644 --- a/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb @@ -656,8 +656,7 @@ Goo f; End Using End Function - - + Public Async Function TypingInOtherFileTriggersCommit(host As RenameTestHost) As Task Using workspace = CreateWorkspaceWithWaiter( @@ -714,8 +713,7 @@ Goo f; End Using End Function - - + Public Async Function TypingInOtherFileWithConflictTriggersCommit(host As RenameTestHost) As Task Using workspace = CreateWorkspaceWithWaiter( diff --git a/src/EditorFeatures/Test2/Rename/RenameNonRenameableSymbols.vb b/src/EditorFeatures/Test2/Rename/RenameNonRenameableSymbols.vb index d12abe3c8976b..270fc8854246c 100644 --- a/src/EditorFeatures/Test2/Rename/RenameNonRenameableSymbols.vb +++ b/src/EditorFeatures/Test2/Rename/RenameNonRenameableSymbols.vb @@ -366,8 +366,7 @@ class Program End Using End Sub - - + Public Sub CannotRenameElementFromPreviousSubmission(host As RenameTestHost) Using workspace = CreateWorkspaceWithWaiter( @@ -384,8 +383,7 @@ class Program End Using End Sub - - + Public Sub CannotRenameHiddenElement(host As RenameTestHost) Using workspace = CreateWorkspaceWithWaiter( @@ -405,8 +403,7 @@ End Class End Using End Sub - - + Public Sub CannotRenameConstructorInVb(host As RenameTestHost) Using workspace = CreateWorkspaceWithWaiter( @@ -434,8 +431,7 @@ End Class End Using End Sub - - + Public Sub CannotRenameConstructorInVb2(host As RenameTestHost) Using workspace = CreateWorkspaceWithWaiter( @@ -457,8 +453,7 @@ End Class End Using End Sub - - + Public Sub CannotRenameConstructorInVb3(host As RenameTestHost) Using workspace = CreateWorkspaceWithWaiter( @@ -485,8 +480,7 @@ End Class #Region "Rename In Tuples" - - + Public Sub RenameTupleFiledInDeclaration(host As RenameTestHost) @@ -538,8 +532,7 @@ namespace System End Sub - - + Public Sub RenameTupleFiledInLiteral(host As RenameTestHost) @@ -591,8 +584,7 @@ namespace System End Sub - - + Public Sub RenameTupleFiledInFieldAccess(host As RenameTestHost) diff --git a/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs b/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs index ab0b2ed0739c0..44cc65b75a962 100644 --- a/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs +++ b/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs @@ -204,14 +204,14 @@ private protected async Task AssertFormatAsync(string expected, string code, IEn ? formattingService.GetFormattingOptions(options, fallbackOptions: null) : formattingService.DefaultOptions; - var rules = formattingRuleProvider.CreateRule(documentSyntax, 0).Concat(Formatter.GetDefaultFormattingRules(document)); + ImmutableArray rules = [formattingRuleProvider.CreateRule(documentSyntax, 0), .. Formatter.GetDefaultFormattingRules(document)]; AssertFormat(workspace, expected, formattingOptions, rules, clonedBuffer, documentSyntax.Root, spans); // format with node and transform AssertFormatWithTransformation(workspace, expected, formattingOptions, rules, documentSyntax.Root, spans); } - internal void AssertFormatWithTransformation(Workspace workspace, string expected, SyntaxFormattingOptions options, IEnumerable rules, SyntaxNode root, IEnumerable spans) + internal void AssertFormatWithTransformation(Workspace workspace, string expected, SyntaxFormattingOptions options, ImmutableArray rules, SyntaxNode root, IEnumerable spans) { var newRootNode = Formatter.Format(root, spans, workspace.Services.SolutionServices, options, rules, CancellationToken.None); @@ -224,7 +224,7 @@ internal void AssertFormatWithTransformation(Workspace workspace, string expecte Assert.True(newRootNodeFromString.IsEquivalentTo(newRootNode)); } - internal void AssertFormat(Workspace workspace, string expected, SyntaxFormattingOptions options, IEnumerable rules, ITextBuffer clonedBuffer, SyntaxNode root, IEnumerable spans) + internal void AssertFormat(Workspace workspace, string expected, SyntaxFormattingOptions options, ImmutableArray rules, ITextBuffer clonedBuffer, SyntaxNode root, IEnumerable spans) { var result = Formatter.GetFormattedTextChanges(root, spans, workspace.Services.SolutionServices, options, rules, CancellationToken.None); var actual = ApplyResultAndGetFormattedText(clonedBuffer, result); diff --git a/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb b/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb index 5d857106ba245..6fa92b52053b2 100644 --- a/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb +++ b/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb @@ -166,7 +166,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit oldTree As SyntaxTree, newDirtySpan As SnapshotSpan, newTree As SyntaxTree, - cancellationToken As CancellationToken) As IEnumerable(Of AbstractFormattingRule) + cancellationToken As CancellationToken) As ImmutableArray(Of AbstractFormattingRule) ' if the span we are going to format is same as the span that got changed, don't bother to do anything special. ' just do full format of the span. @@ -212,12 +212,17 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit ' for now, do very simple checking. basically, we see whether we get same number of indent operation for the give span. alternative, but little bit ' more expensive and complex, we can actually calculate indentation right after the span, and see whether that is changed. not sure whether that much granularity ' is needed. + Dim coreRules = Formatter.GetDefaultFormattingRules(languageServices) + If GetNumberOfIndentOperations(languageServices, options, oldTree, oldDirtySpan, cancellationToken) = GetNumberOfIndentOperations(languageServices, options, newTree, newDirtySpan, cancellationToken) Then - Return (New NoAnchorFormatterRule()).Concat(Formatter.GetDefaultFormattingRules(languageServices)) + Dim result = New FixedSizeArrayBuilder(Of AbstractFormattingRule)(coreRules.Length + 1) + result.Add(New NoAnchorFormatterRule()) + result.AddRange(coreRules) + Return result.MoveToImmutable() End If - Return Formatter.GetDefaultFormattingRules(languageServices) + Return coreRules End Function Private Shared Function GetNumberOfIndentOperations( diff --git a/src/EditorFeatures/VisualBasicTest/AddMissingImports/VisualBasicAddMissingImportsRefactoringProviderTests.vb b/src/EditorFeatures/VisualBasicTest/AddMissingImports/VisualBasicAddMissingImportsRefactoringProviderTests.vb index a891c47e995e5..16a69892e0cb2 100644 --- a/src/EditorFeatures/VisualBasicTest/AddMissingImports/VisualBasicAddMissingImportsRefactoringProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/AddMissingImports/VisualBasicAddMissingImportsRefactoringProviderTests.vb @@ -316,8 +316,7 @@ End Namespace Await TestInRegularAndScriptAsync(code, expected) End Function - - + Public Async Function AddMissingImports_AddMultipleImports_NoPreviousImports() As Task Dim code = " Class C @@ -359,8 +358,7 @@ End Namespace Await TestInRegularAndScriptAsync(code, expected, placeSystemNamespaceFirst:=False, separateImportDirectiveGroups:=False) End Function - - + Public Async Function AddMissingImports_Extension() As Task Dim code = " Imports System.Runtime.CompilerServices @@ -401,8 +399,7 @@ End Namespace Await TestInRegularAndScriptAsync(code, expected) End Function - - + Public Async Function AddMissingImports_Extension_Overload() As Task Dim code = " Imports System.Runtime.CompilerServices @@ -601,8 +598,7 @@ End Namespace Await TestInRegularAndScriptAsync(code, expected) End Function - - + Public Async Function AddMissingImports_Extension_Select() As Task Dim code = " Imports System.Collections.Generic @@ -647,8 +643,7 @@ End Namespace Await TestInRegularAndScriptAsync(code, expected) End Function - - + Public Async Function AddMissingImports_Extension_Select_Overload() As Task Dim code = " Imports System.Collections.Generic diff --git a/src/EditorFeatures/VisualBasicTest/AutomaticCompletion/AutomaticLineEnderTests.vb b/src/EditorFeatures/VisualBasicTest/AutomaticCompletion/AutomaticLineEnderTests.vb index 61edba2e6fdc9..8940d2ca9f33b 100644 --- a/src/EditorFeatures/VisualBasicTest/AutomaticCompletion/AutomaticLineEnderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/AutomaticCompletion/AutomaticLineEnderTests.vb @@ -32,16 +32,14 @@ $$, $$) ) End Sub - - + Public Sub TestNamespace() Test(Namespace NS $$ End Namespace, Namespace NS$$) End Sub - - + Public Sub TestClass() Test(Class C $$ @@ -118,8 +116,7 @@ $$ End Class) End Sub - - + Public Sub TestDim_After_MalformedStatement() Test(Class C Sub Method() @@ -156,8 +153,7 @@ End Class ) End Sub - - + Public Sub TestIf_Trivia() Test( @@ -178,8 +174,7 @@ End Class ) End Sub - - + Public Sub TestIf_Trivia2() Test( @@ -200,8 +195,7 @@ End Class ) End Sub - - + Public Sub TestEndOfFile_SkippedToken() Test( diff --git a/src/EditorFeatures/VisualBasicTest/AutomaticCompletion/AutomaticParenthesesCompletion.vb b/src/EditorFeatures/VisualBasicTest/AutomaticCompletion/AutomaticParenthesesCompletion.vb index 645f3d1a5c20d..08d71f6ff16d4 100644 --- a/src/EditorFeatures/VisualBasicTest/AutomaticCompletion/AutomaticParenthesesCompletion.vb +++ b/src/EditorFeatures/VisualBasicTest/AutomaticCompletion/AutomaticParenthesesCompletion.vb @@ -301,8 +301,7 @@ End Class End Using End Sub - - + Public Sub TestOverTypeAfterIntegerLiteral() Dim code = Imports System.Collections.Generic Class C @@ -320,8 +319,7 @@ End Class End Using End Sub - - + Public Sub TestOverTypeAfterDateLiteral() Dim code = Class C Sub Method() diff --git a/src/EditorFeatures/VisualBasicTest/AutomaticEndConstructCorrection/AutomaticEndConstructCorrectorTests.vb b/src/EditorFeatures/VisualBasicTest/AutomaticEndConstructCorrection/AutomaticEndConstructCorrectorTests.vb index 2e73bf13e8324..1eea36c4a08d4 100644 --- a/src/EditorFeatures/VisualBasicTest/AutomaticEndConstructCorrection/AutomaticEndConstructCorrectorTests.vb +++ b/src/EditorFeatures/VisualBasicTest/AutomaticEndConstructCorrection/AutomaticEndConstructCorrectorTests.vb @@ -212,8 +212,7 @@ End Class.Value VerifyContinuousEdits(code, "Shared", Function(s) "Function", removeOriginalContent:=False, split:="Function") End Sub - - + Public Sub TestMultiLineLambdaSubToFunction() Dim code = Class A Public Sub F() @@ -228,8 +227,7 @@ End Class.Value Verify(code, "Function") End Sub - - + Public Sub TestMultiLineLambdaFunctionToSub() Dim code = Class A Public Sub F() @@ -243,8 +241,7 @@ End Class.Value Verify(code, "Sub") End Sub - - + Public Sub BugFix5290() Dim code = Public Class Class1 Sub M() @@ -256,8 +253,7 @@ End [|Class|].Value VerifyEnd(code, "Structure", "Class") End Sub - - + Public Sub TestBugFix5276() Dim code = Class A [|Func$$tion|] Test() As Integer @@ -267,8 +263,7 @@ End Class.Value VerifyContinuousEdits(code, " ", Function(s) "Function", removeOriginalContent:=False) End Sub - - + Public Sub TestBugFix5283() Dim code = Class A [|$$Function|] Test() As Integer diff --git a/src/EditorFeatures/VisualBasicTest/BraceMatching/VisualBasicBraceMatcherTests.vb b/src/EditorFeatures/VisualBasicTest/BraceMatching/VisualBasicBraceMatcherTests.vb index fc4383e1aa424..a71ffeb23e31c 100644 --- a/src/EditorFeatures/VisualBasicTest/BraceMatching/VisualBasicBraceMatcherTests.vb +++ b/src/EditorFeatures/VisualBasicTest/BraceMatching/VisualBasicBraceMatcherTests.vb @@ -372,8 +372,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.BraceMatching Await TestInClassAsync(code, expected) End Function - - + Public Async Function TestConditionalDirectiveWithSingleMatchingDirective() As Task Dim code = Class C @@ -397,8 +396,7 @@ End Class Await TestAsync(code, expected) End Function - - + Public Async Function TestConditionalDirectiveWithTwoMatchingDirectives() As Task Dim code = Class C @@ -422,8 +420,7 @@ End Class Await TestAsync(code, expected) End Function - - + Public Async Function TestConditionalDirectiveWithAllMatchingDirectives() As Task Dim code = Class C @@ -449,8 +446,7 @@ End Class Await TestAsync(code, expected) End Function - - + Public Async Function TestRegionDirective() As Task Dim code = Class C @@ -472,8 +468,7 @@ End Class Await TestAsync(code, expected) End Function - - + Public Async Function TestInterleavedDirectivesInner() As Task Dim code = #Const CHK = True @@ -509,8 +504,7 @@ End Module Await TestAsync(code, expected) End Function - - + Public Async Function TestInterleavedDirectivesOuter() As Task Dim code = #Const CHK = True @@ -546,8 +540,7 @@ End Module Await TestAsync(code, expected) End Function - - + Public Async Function TestUnmatchedDirective1() As Task Dim code = Class C @@ -568,8 +561,7 @@ End Class Await TestAsync(code, expected) End Function - - + Public Async Function TestUnmatchedDirective2() As Task Dim code = @@ -592,8 +584,7 @@ End Class Await TestAsync(code, expected) End Function - - + Public Async Function TestUnmatchedIncompleteConditionalDirective() As Task Dim code = @@ -615,8 +606,7 @@ End Class Await TestAsync(code, expected) End Function - - + Public Async Function TestUnmatchedCompleteConditionalDirective() As Task Dim code = @@ -638,8 +628,7 @@ End Class Await TestAsync(code, expected) End Function - - + Public Async Function TestUnmatchedConditionalDirective() As Task Dim code = diff --git a/src/EditorFeatures/VisualBasicTest/CaseCorrecting/CaseCorrectionServiceTests.vb b/src/EditorFeatures/VisualBasicTest/CaseCorrecting/CaseCorrectionServiceTests.vb index 61accc89cc76e..da3fe27a072e4 100644 --- a/src/EditorFeatures/VisualBasicTest/CaseCorrecting/CaseCorrectionServiceTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CaseCorrecting/CaseCorrectionServiceTests.vb @@ -111,8 +111,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestConstructorNew1() As Task Dim input = Class C @@ -139,8 +138,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestConstructorNew2() As Task Dim input = Class B @@ -175,8 +173,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestConstructorNew3() As Task Dim input = Class C @@ -204,8 +201,7 @@ End Class End Function - - + Public Async Function TestConstructorNew4() As Task Dim input = Class C @@ -236,8 +232,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestAlias1() As Task Dim input = Imports S = System.String @@ -814,8 +809,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestOverloadResolutionFailure() As Task Dim input = Option Strict On @@ -838,8 +832,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestUnResolvedTypeDoesNotBindToAnyAccessibleSymbol() As Task Dim unchangeCode = Option Strict On @@ -923,8 +916,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestCharacterTypeSuffix() As Task Dim input = @@ -946,8 +938,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestULTypeSuffix() As Task Dim input = @@ -971,8 +962,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestFTypeSuffix() As Task Dim input = @@ -994,8 +984,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestRTypeSuffix() As Task Dim input = @@ -1017,8 +1006,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestDTypeSuffix() As Task Dim input = @@ -1040,8 +1028,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestDateLiteral() As Task Dim input = @@ -1065,8 +1052,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestEscaping1() As Task Dim input = @@ -1088,8 +1074,7 @@ End Namespace Await TestAsync(input, expected) End Function - - + Public Async Function TestEscaping2() As Task Dim input = @@ -1131,8 +1116,7 @@ End Module Await TestAsync(input, expected) End Function - - + Public Async Function TestREMInComment() As Task Dim input = rem this is a comment @@ -1176,8 +1160,7 @@ End Module Await TestAsync(input, expected) End Function - - + Public Async Function AvoidNodesWithSyntaxErrorsAndStringLiterals() As Task Dim input = Class C @@ -1218,8 +1201,7 @@ End Class #Region "Preprocessor" - - + Public Async Function TestPreprocessor() As Task Dim input = #if true then @@ -1234,8 +1216,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestPreprocessorLiterals() As Task Dim input = #const goo = 2.0d @@ -1248,8 +1229,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestPreprocessorInMethodBodies() As Task Dim input = Module Program @@ -1272,8 +1252,7 @@ End Module Await TestAsync(input, expected) End Function - - + Public Async Function TestPreprocessorAroundClass() As Task Dim input = #if true then @@ -1292,8 +1271,7 @@ End Class Await TestAsync(input, expected) End Function - - + Public Async Function TestRemCommentAfterPreprocessor() As Task Dim input = #const goo = 42 rem goo @@ -1950,8 +1928,7 @@ End Namespace Await TestAsync(input, expected) End Function - - + Public Async Function SkippedTokens() As Task Dim input = @@ -1968,8 +1945,7 @@ End Namespace Await TestAsync(input, expected) End Function - - + Public Async Function TestAttribute() As Task Dim input = - + Public Async Function TestNewOnRightSideOfDot() As Task Dim input = - + Public Async Function TestNotInLeadingWhitespace() As Task Dim markup = " class C @@ -24,8 +23,7 @@ end class Await TestChangeSignatureViaCodeActionAsync(markup, expectedCodeAction:=False) End Function - - + Public Async Function TestNotInLeadingTrivia1() As Task Dim markup = " class C @@ -38,8 +36,7 @@ end class Await TestChangeSignatureViaCodeActionAsync(markup, expectedCodeAction:=False) End Function - - + Public Async Function TestNotInLeadingTrivia2() As Task Dim markup = " class C @@ -52,8 +49,7 @@ end class Await TestChangeSignatureViaCodeActionAsync(markup, expectedCodeAction:=False) End Function - - + Public Async Function TestNotInLeadingAttributes1() As Task Dim markup = " class C @@ -66,8 +62,7 @@ end class Await TestChangeSignatureViaCodeActionAsync(markup, expectedCodeAction:=False) End Function - - + Public Async Function TestNotInLeadingAttributes2() As Task Dim markup = " class C diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests.vb index d6efc8e53c121..152222bb3a22f 100644 --- a/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests.vb @@ -506,8 +506,7 @@ expectedContainers:=ImmutableArray.Create("Goo"), expectedDocumentName:="Bar.vb") End Function - - + Public Async Function TestPreserveBanner1() As Task Await TestAddDocumentInRegularAndScriptAsync( "' I am a banner! @@ -528,8 +527,7 @@ expectedContainers:=ImmutableArray(Of String).Empty, expectedDocumentName:="Bar.vb") End Function - - + Public Async Function TestPreserveBanner2() As Task Await TestAddDocumentInRegularAndScriptAsync( "''' I am a doc comment! @@ -550,8 +548,7 @@ expectedContainers:=ImmutableArray(Of String).Empty, expectedDocumentName:="Bar.vb") End Function - - + Public Async Function TestPreserveBanner3() As Task Await TestAddDocumentInRegularAndScriptAsync( "' I am a banner! diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests_Dialog.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests_Dialog.vb index 03ea8f80faa20..9d71a41dfd401 100644 --- a/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests_Dialog.vb +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/GenerateType/GenerateTypeTests_Dialog.vb @@ -428,8 +428,7 @@ newFileFolderContainers:=ImmutableArray(Of String).Empty, newFileName:="Test2.vb") End Function - - + Public Async Function GenerateType_UsingsNotNeeded_InNewFile_InFolder() As Task Dim markupString = @@ -462,8 +461,7 @@ newFileFolderContainers:=ImmutableArray.Create("outer", "inner"), newFileName:="Test2.vb") End Function - - + Public Async Function GenerateType_InValidFolderNameNotMadeNamespace() As Task Dim markupString = @@ -496,8 +494,7 @@ newFileName:="Test2.vb") End Function - - + Public Async Function GenerateType_UsingsNeeded_InNewFile_InFolder() As Task Dim markupString = @@ -535,8 +532,7 @@ newFileFolderContainers:=ImmutableArray.Create("outer", "inner"), newFileName:="Test2.vb") End Function - - + Public Async Function GenerateType_UsingsPresentAlready_InNewFile_InFolder() As Task Dim markupString = @@ -576,8 +572,7 @@ newFileFolderContainers:=ImmutableArray.Create("outer"), newFileName:="Test2.vb") End Function - - + Public Async Function GenerateType_UsingsNotNeeded_InNewFile_InFolder_NotSimpleName() As Task Dim markupString = @@ -744,8 +739,7 @@ projectName:="Assembly2") End Function #End Region #Region "SameLanguage DifferentProject NewFile" - - + Public Async Function GenerateTypeIntoSameLanguageDifferentProjectNewFile() As Task Dim markupString = @@ -780,8 +774,7 @@ newFileFolderContainers:=ImmutableArray(Of String).Empty, projectName:="Assembly2") End Function - - + Public Async Function GenerateTypeIntoSameLanguageDifferentProjectNewFile_Folders_Usings() As Task Dim markupString = @@ -823,8 +816,7 @@ newFileFolderContainers:=ImmutableArray.Create("outer", "inner"), projectName:="Assembly2") End Function - - + Public Async Function GenerateTypeIntoSameLanguageDifferentProjectNewFile_Folders_NoUsings_NotSimpleName() As Task Dim markupString = @@ -861,8 +853,7 @@ newFileFolderContainers:=ImmutableArray(Of String).Empty, projectName:="Assembly2") End Function - - + Public Async Function GenerateTypeIntoSameLanguageDifferentProjectNewFile_Folders_NoUsings_NotSimpleName_ProjectReference() As Task Dim markupString = @@ -940,8 +931,7 @@ newFileFolderContainers:=ImmutableArray(Of String).Empty, projectName:="Assembly2") End Function - - + Public Async Function GenerateTypeIntoDifferentLanguageNewFile_Folders_Imports() As Task Dim markupString = @@ -990,8 +980,7 @@ newFileFolderContainers:=ImmutableArray.Create("outer", "inner"), projectName:="Assembly2") End Function - - + Public Async Function GenerateTypeIntoDifferentLanguageNewFile_Folders_NoImports_NotSimpleName() As Task Dim markupString = @@ -1027,8 +1016,7 @@ newFileFolderContainers:=ImmutableArray.Create("outer", "inner"), projectName:="Assembly2") End Function - - + Public Async Function GenerateTypeIntoDifferentLanguageNewFile_Folders_Imports_DefaultNamespace() As Task Dim markupString = @@ -1077,8 +1065,7 @@ newFileFolderContainers:=ImmutableArray.Create("outer", "inner"), projectName:="Assembly2") End Function - - + Public Async Function GenerateTypeIntoDifferentLanguageNewFile_Folders_NoImports_NotSimpleName_DefaultNamespace() As Task Dim markupString = @@ -1281,8 +1268,7 @@ existingFilename:="Test2.cs", projectName:="Assembly2") End Function - - + Public Async Function GenerateTypeIntoDifferentLanguageNewFileAdjustTheFileExtension() As Task Dim markupString = diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/InlineMethod/VisualBasicInlineMethodTests.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/InlineMethod/VisualBasicInlineMethodTests.vb index 035d4cdb4b5a6..7edcc4255ead6 100644 --- a/src/EditorFeatures/VisualBasicTest/CodeActions/InlineMethod/VisualBasicInlineMethodTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/InlineMethod/VisualBasicInlineMethodTests.vb @@ -7,9 +7,10 @@ Imports Microsoft.CodeAnalysis.Testing Imports Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.InlineTemporary Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.InlineMethod - + Public Class VisualBasicInlineMethodTests + Private Class TestVerifier Inherits VisualBasicCodeRefactoringVerifier(Of VisualBasicInlineMethodRefactoringProvider).Test Private Const Marker As String = "##" @@ -795,8 +796,8 @@ Imports System Public Class TestClass Public Sub Caller(i As Integer, j As Integer) Dim x = Function() - Return i * j - End Function() + Return i * j + End Function() End Sub ## Private Function Callee(i As Integer, j As Integer) as Func(Of Integer) @@ -1287,7 +1288,7 @@ Public Class TestClass End Class", " Public Class TestClass Public Sub Caller(i As Integer) - Dim y = If (true, 10, 100) + Dim y = If(true, 10, 100) End Sub ## Private Function Callee(a As Boolean) As Integer diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.vb index 1c8fe90a00b76..18ae6eaee372e 100644 --- a/src/EditorFeatures/VisualBasicTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.vb +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/MoveType/MoveTypeTests.MoveToNewFile.vb @@ -107,8 +107,7 @@ End Class Await TestMoveTypeToNewFileAsync(code, codeAfterMove, expectedDocumentName, destinationDocumentText, index:=1) End Function - - + Public Async Function MoveNestedTypeToNewFile_RemoveComments() As Task Dim code = " @@ -186,8 +185,7 @@ End Class Await TestMoveTypeToNewFileAsync(code, codeAfterMove, expectedDocumentName, destinationDocumentText) End Function - - + Public Async Function TestTypeInheritance() As Task Dim code = " @@ -228,8 +226,7 @@ End Class Await TestMoveTypeToNewFileAsync(code, codeAfterMove, expectedDocumentName, destinationDocumentText) End Function - - + Public Async Function TestLeadingBlankLines1() As Task Dim code = "' Banner Text @@ -272,8 +269,7 @@ end class code, codeAfterMove, expectedDocumentName, destinationDocumentText) End Function - - + Public Async Function TestLeadingBlankLines2() As Task Dim code = "' Banner Text diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb index 212722a514f25..8f3d04fa10b09 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb @@ -59,8 +59,7 @@ End Class Await VerifyItemExistsAsync(text.Value, "Goo()", "Sub Derived.Goo()") End Function - - + Public Async Function TestHideFinalize() As Task Dim text = Class goo Overrides $$ @@ -69,8 +68,7 @@ End Class Await VerifyItemIsAbsentAsync(text.Value, "Finalize()") End Function - - + Public Async Function TestShowShadowingFinalize() As Task Dim text = Class goo Overridable Shadows Sub Finalize() @@ -86,8 +84,7 @@ End class Await VerifyItemIsAbsentAsync(text.Value, "goo.Finalize()") End Function - - + Public Async Function TestShowObjectOverrides() As Task Dim text = Class goo Overrides $$ @@ -514,8 +511,7 @@ End Class Await VerifyItemIsAbsentAsync(markup.Value, "Goo(t As T, s As S)") End Function - - + Public Async Function TestGenericMethodTypeParametersNotRenamed() As Task Dim text = Class CGoo Overridable Function Something(Of X)(arg As X) As X @@ -1092,8 +1088,7 @@ End Class]]> Await VerifyCustomCommitProviderAsync(markupBeforeCommit.Value.Replace(vbLf, vbCrLf), "Item(i As Integer)", expectedCode.Value.Replace(vbLf, vbCrLf)) End Function - - + Public Async Function TestCommitOptionalKeywordAndParameterValuesAreGenerated() As Task Dim markupBeforeCommit = Await VerifyCustomCommitProviderAsync(markupBeforeCommit.Value.Replace(vbLf, vbCrLf), "goo(Of T)(x As T)", expectedCode.Value.Replace(vbLf, vbCrLf)) End Function - - + Public Async Function TestCommitGenericMethodOnArraySubstitutedGenericType() As Task Dim markupBeforeCommit = Class A(Of T) Public Overridable Sub M(Of U As T)() @@ -1477,8 +1471,7 @@ End Class Await VerifyCustomCommitProviderAsync(markupBeforeCommit.Value.Replace(vbLf, vbCrLf), "goo(ByRef x As Integer, y As String)", expectedCode.Value.Replace(vbLf, vbCrLf)) End Function - - + Public Async Function TestCommitGenericMethodTypeParametersNotRenamed() As Task Dim markupBeforeCommit = Class CGoo Overridable Function Something(Of X)(arg As X) As X @@ -1531,8 +1524,7 @@ End Class Await VerifyCustomCommitProviderAsync(markupBeforeCommit.Value.Replace(vbLf, vbCrLf), "Goo()", expectedCode.Value.Replace(vbLf, vbCrLf)) End Function - - + Public Async Function TestOptionalArguments() As Task Dim markupBeforeCommit = Class CBase Public Overridable Sub goo(Optional x As Integer = 42) @@ -1563,8 +1555,7 @@ End Class Await VerifyCustomCommitProviderAsync(markupBeforeCommit.Value.Replace(vbLf, vbCrLf), "goo(x As Integer = 42)", expectedCode.Value.Replace(vbLf, vbCrLf)) End Function - - + Public Async Function TestParameterizedProperty() As Task Dim markupBeforeCommit = Public Class Goo Public Overridable Property Bar(bay As Integer) As Integer @@ -1610,8 +1601,7 @@ End Class Await VerifyCustomCommitProviderAsync(markupBeforeCommit.Value.Replace(vbLf, vbCrLf), "Bar(bay As Integer)", expectedCode.Value.Replace(vbLf, vbCrLf)) End Function - - + Public Async Function TestOverrideDefaultPropertiesByName() As Task Dim markupBeforeCommit = Class A Default Overridable ReadOnly Property Goo(x As Integer) As Object @@ -1651,8 +1641,7 @@ End Class #Region "Commit: With Trivia" - - + Public Async Function TestCommitSurroundingTriviaDirective() As Task Dim markupBeforeCommit = Class Base Public Overridable Sub Goo() @@ -1714,8 +1703,7 @@ End Class Await VerifyCustomCommitProviderAsync(markupBeforeCommit.Value.Replace(vbLf, vbCrLf), "Goo()", expectedCode.Value.Replace(vbLf, vbCrLf)) End Function - - + Public Async Function TestCommitAfterTriviaDirective() As Task Dim markupBeforeCommit = Class Base Public Overridable Sub Goo() @@ -1777,8 +1765,7 @@ End Class Await VerifyCustomCommitProviderAsync(markupBeforeCommit.Value.Replace(vbLf, vbCrLf), "Goo()", expectedCode.Value.Replace(vbLf, vbCrLf)) End Function - - + Public Async Function TestCommitAfterComment() As Task Dim markupBeforeCommit = Class Base Public Overridable Sub Goo() @@ -1808,8 +1795,7 @@ End Class End Function #End Region - - + Public Async Function TestWitheventsFieldNotOffered() As Task Dim text = Public Class C1 Public WithEvents w As C1 = Me @@ -1822,8 +1808,7 @@ End Class Await VerifyItemIsAbsentAsync(text.Value, "w") End Function - - + Public Async Function TestEventsNotOffered() As Task Dim text = diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SymbolCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SymbolCompletionProviderTests.vb index ae2834a60a70a..4af4ecf9a3460 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SymbolCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SymbolCompletionProviderTests.vb @@ -2635,7 +2635,7 @@ End Module Await VerifyItemIsAbsentAsync(markup, "[Structure]") End Function - + Public Async Function TestKeywordEscaping2() As Task Dim markup = Module [Structure] @@ -2657,7 +2657,7 @@ End Module Await VerifyItemIsAbsentAsync(markup, "[rem]") End Function - + Public Async Function TestKeywordEscaping3() As Task Dim markup = Namespace Goo diff --git a/src/EditorFeatures/VisualBasicTest/DocumentationComments/DocumentationCommentTests.vb b/src/EditorFeatures/VisualBasicTest/DocumentationComments/DocumentationCommentTests.vb index d9c95b0e266fd..da55365f986c3 100644 --- a/src/EditorFeatures/VisualBasicTest/DocumentationComments/DocumentationCommentTests.vb +++ b/src/EditorFeatures/VisualBasicTest/DocumentationComments/DocumentationCommentTests.vb @@ -81,8 +81,7 @@ End Class VerifyTypingCharacter(code, expected) End Sub - - + Public Sub TestTypingCharacter_NoReturnType() Const code = " Class C @@ -144,8 +143,7 @@ End Class VerifyTypingCharacter(code, expected) End Sub - - + Public Sub TestTypingCharacter_NotAfterClassName() Const code = " Class C''$$ @@ -158,8 +156,7 @@ End Class VerifyTypingCharacter(code, expected) End Sub - - + Public Sub TestTypingCharacter_NotInsideClass() Const code = " Class C @@ -174,8 +171,7 @@ End Class VerifyTypingCharacter(code, expected) End Sub - - + Public Sub TestTypingCharacter_NotAfterConstructorName() Const code = " Class C @@ -190,8 +186,7 @@ End Class VerifyTypingCharacter(code, expected) End Sub - - + Public Sub TestTypingCharacter_NotInsideConstructor() Const code = " Class C @@ -210,8 +205,7 @@ End Class VerifyTypingCharacter(code, expected) End Sub - - + Public Sub TestTypingCharacter_NotInsideMethodBody() Const code = " Class C @@ -230,8 +224,7 @@ End Class VerifyTypingCharacter(code, expected) End Sub - - + Public Sub TestTypingCharacter_NoReturnsOnWriteOnlyProperty() Const code = " Class C @@ -324,8 +317,7 @@ End Class VerifyPressingEnter(code, expected) End Sub - - + Public Sub TestPressingEnter_Module() Const code = " '''$$Module M @@ -526,8 +518,7 @@ End Class VerifyPressingEnter(code, expected) End Sub - - + Public Sub TestPressingEnter_InsertApostrophes8() Const code = " ''' $$ @@ -543,8 +534,7 @@ End Class VerifyPressingEnter(code, expected) End Sub - - + Public Sub TestPressingEnter_InsertApostrophes9_AutoGenerateXmlDocCommentsOff() Const code = " ''' $$ @@ -563,8 +553,7 @@ End Class }) End Sub - - + Public Sub TestPressingEnter_DoNotInsertApostrophes1() Const code = " ''' @@ -602,8 +591,7 @@ End Class VerifyPressingEnter(code, expected) End Sub - - + Public Sub TestPressingEnter_NotInsideMethodBody() Const code = " Class C @@ -623,8 +611,7 @@ End Class VerifyPressingEnter(code, expected) End Sub - - + Public Sub TestPressingEnter_NotBeforeDocComment() Const code = " Class c1 @@ -652,8 +639,7 @@ Public Async Function TestGoo() As Task VerifyPressingEnter(code, expected) End Sub - - + Public Sub TestPressingEnter_InTextBeforeSpace() Const code = " Class C @@ -677,8 +663,7 @@ End Class VerifyPressingEnter(code, expected) End Sub - - + Public Sub TestPressingEnter_Indentation1() Const code = " Class C @@ -702,8 +687,7 @@ End Class VerifyPressingEnter(code, expected) End Sub - - + Public Sub TestPressingEnter_Indentation2() Const code = " Class C @@ -727,8 +711,7 @@ End Class VerifyPressingEnter(code, expected) End Sub - - + Public Sub TestPressingEnter_Indentation3() Const code = " Class C @@ -752,8 +735,7 @@ End Class VerifyPressingEnter(code, expected) End Sub - - + Public Sub TestPressingEnter_Indentation4() Const code = " Class C @@ -777,8 +759,7 @@ End Class VerifyPressingEnter(code, expected) End Sub - - + Public Sub TestPressingEnter_Indentation5_UseTabs() Const code = " Class C @@ -802,8 +783,7 @@ End Class VerifyPressingEnter(code, expected, useTabs:=True) End Sub - - + Public Sub TestPressingEnter_Selection1() Const code = " ''' @@ -823,8 +803,7 @@ End Class VerifyPressingEnter(code, expected) End Sub - - + Public Sub TestPressingEnter_Selection2() Const code = " ''' @@ -862,8 +841,7 @@ End Class VerifyInsertCommentCommand(code, expected) End Sub - - + Public Sub TestCommand_Class_AutoGenerateXmlDocCommentsOff() Const code = " Class C @@ -901,8 +879,7 @@ End Class VerifyInsertCommentCommand(code, expected) End Sub - - + Public Sub TestCommand_Method1() Const code = " Class C @@ -969,8 +946,7 @@ End Class VerifyInsertCommentCommand(code, expected) End Sub - - + Public Sub TestCommand_FirstModuleOnLine() Const code = " $$Module M : End Module : Module N : End Module @@ -985,8 +961,7 @@ Module M : End Module : Module N : End Module VerifyInsertCommentCommand(code, expected) End Sub - - + Public Sub TestCommand_NotOnSecondModuleOnLine() Const code = "Module M : End Module : $$Module N : End Module" Const expected = "Module M : End Module : $$Module N : End Module" @@ -994,8 +969,7 @@ Module M : End Module : Module N : End Module VerifyInsertCommentCommand(code, expected) End Sub - - + Public Sub TestCommand_FirstPropertyOnLine() Const code = " Module M @@ -1014,8 +988,7 @@ End Module VerifyInsertCommentCommand(code, expected) End Sub - - + Public Sub TestOpenLineAbove1() Const code = " Class C @@ -1039,8 +1012,7 @@ End Class VerifyOpenLineAbove(code, expected) End Sub - - + Public Sub TestOpenLineAbove2() Const code = " Class C @@ -1072,8 +1044,7 @@ End Class "End Module") End Sub - - + Public Sub TestOpenLineAbove3() Const code = " Class C @@ -1099,8 +1070,7 @@ End Class VerifyOpenLineAbove(code, expected) End Sub - - + Public Sub TestOpenLineAbove4_Tabs() Const code = " Class C @@ -1124,8 +1094,7 @@ End Class VerifyOpenLineAbove(code, expected, useTabs:=True) End Sub - - + Public Sub TestOpenLineBelow1() Const code = " Class C @@ -1149,8 +1118,7 @@ End Class VerifyOpenLineBelow(code, expected) End Sub - - + Public Sub TestOpenLineBelow2() Const code = " Class C @@ -1174,8 +1142,7 @@ End Class VerifyOpenLineBelow(code, expected) End Sub - - + Public Sub TestOpenLineBelow3() Const code = " ''' @@ -1189,8 +1156,7 @@ End Class VerifyOpenLineBelow(code, expected) End Sub - - + Public Sub TestOpenLineBelow4_Tabs() Const code = " Class C diff --git a/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructCommandHandlerTests.vb b/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructCommandHandlerTests.vb index 1d978446b435a..583abaf9ac0ee 100644 --- a/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructCommandHandlerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructCommandHandlerTests.vb @@ -110,8 +110,7 @@ End Module.Value.Replace(vbLf, vbCrLf) VerifyAppliedAfterReturnUsingCommandHandler(code, {4, -1}, expected, {5, 8}) End Sub - - + Public Sub EndConstruct_NotOnLineFollowingToken() VerifyStatementEndConstructNotApplied( text:="Class C diff --git a/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/MethodBlockTests.vb b/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/MethodBlockTests.vb index e85e767b237e1..02407969683d2 100644 --- a/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/MethodBlockTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/MethodBlockTests.vb @@ -201,8 +201,7 @@ End Class", caret:={2, -1}) End Sub - - + Public Sub TestVerifyInvalidLocation02() VerifyStatementEndConstructApplied( before:="Sub S", diff --git a/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/PropertyBlockTests.vb b/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/PropertyBlockTests.vb index cab2c5e44e5e5..d8db42f7098b1 100644 --- a/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/PropertyBlockTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/PropertyBlockTests.vb @@ -24,8 +24,7 @@ End Class", caret:={1, -1}) End Sub - - + Public Sub DoNotApplyForMustInheritProperty() VerifyStatementEndConstructNotApplied( text:="MustInherit Class C @@ -247,8 +246,7 @@ End Class", caret:={2, -1}) End Sub - - + Public Sub DoNotApplyForGetInReadOnly() VerifyStatementEndConstructNotApplied( text:="Class c1 @@ -268,8 +266,7 @@ End Class", caret:={1, -1}) End Sub - - + Public Sub DoNotApplyInsideAnInterface() VerifyStatementEndConstructNotApplied( text:="Interface IGoo diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndenterTests.vb b/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndenterTests.vb index 6db73bca2df6a..13b6f620852c6 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndenterTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndenterTests.vb @@ -1248,8 +1248,7 @@ End Class.Value expectedIndentation:=26) End Sub - - + Public Sub TestBugFix1417_2() Dim code = Sub Main() @@ -1381,8 +1380,7 @@ End Class.Value expectedIndentation:=12) End Sub - - + Public Sub TestImplicitLineContinuationExpression1() Dim code = Class C @@ -1400,8 +1398,7 @@ End Class.Value expectedIndentation:=12) End Sub - - + Public Sub TestImplicitLineContinuationExpression2() Dim code = Module Program @@ -1897,8 +1894,7 @@ End Module #Region "Bugs" - - + Public Sub TestBugFix4481() Dim code = _ @@ -1910,8 +1906,7 @@ End Module expectedIndentation:=4) End Sub - - + Public Sub TestBugFix4481_2() Dim code = _ @@ -1923,8 +1918,7 @@ End Module expectedIndentation:=6) End Sub - - + Public Sub TestBug5559() Dim code = Public Class Class1 @@ -1944,8 +1938,7 @@ End Class.Value expectedIndentation:=8) End Sub - - + Public Sub TestBug5586() Dim code = Module Program @@ -1961,8 +1954,7 @@ End Module.Value expectedIndentation:=16) End Sub - - + Public Sub TestBug5629() Dim code = Module Module1 @@ -1980,8 +1972,7 @@ End Module.Value expectedIndentation:=20) End Sub - - + Public Sub TestBug5730() Dim code = Module Program @@ -1998,8 +1989,7 @@ End Module expectedIndentation:=12) End Sub - - + Public Sub TestBug5730_1() Dim code = Module Program @@ -2015,8 +2005,7 @@ End Module.Value expectedIndentation:=20) End Sub - - + Public Sub TestBug5666() Dim code = Module Program @@ -2035,8 +2024,7 @@ End Module expectedIndentation:=12) End Sub - - + Public Sub TestBug5430_1() Dim code = My.Resources.XmlLiterals.IndentationTest2 @@ -2047,8 +2035,7 @@ End Module expectedIndentation:=16) End Sub - - + Public Sub TestBug6374() Dim code = Imports System @@ -2069,8 +2056,7 @@ End Module.Value expectedIndentation:=8) End Sub - - + Public Sub TestMissingEndStatement() Dim code = Module Module1 @@ -2957,8 +2943,7 @@ End Class expectedIndentation:=12) End Sub - - + Public Sub IndentationOfReturnInFileWithTabs1() Dim code = " diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormatterTestBase.vb b/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormatterTestBase.vb index 3b9aceb35410f..be91476713e7e 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormatterTestBase.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormatterTestBase.vb @@ -2,6 +2,7 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports System.Collections.Immutable Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Editor.UnitTests @@ -66,7 +67,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Formatting workspace.Documents.First(Function(d) d.SelectedSpans.Any()).SelectedSpans, workspace.Services.SolutionServices, options, - rules, + rules.ToImmutableArray(), CancellationToken.None) AssertResult(expected, clonedBuffer, changes) diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormattingEngineTests.vb b/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormattingEngineTests.vb index 75cce2d2446df..dced91769d1e0 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormattingEngineTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormattingEngineTests.vb @@ -22,8 +22,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Formatting } End Function - - + Public Async Function SeparateGroups_KeepMultipleLinesBetweenGroups() As Task Dim code = "[| Imports System.A @@ -47,8 +46,7 @@ Imports MS.B expected, code, baseIndentation:=0, options:=SeparateImportDirectiveGroups) End Function - - + Public Async Function SeparateGroups_DoNotGroupIfNotSorted() As Task Dim code = "[| Imports System.B @@ -68,8 +66,7 @@ Imports MS.A expected, code, baseIndentation:=0, options:=SeparateImportDirectiveGroups) End Function - - + Public Async Function SeparateGroups_GroupIfSorted() As Task Dim code = "[| Imports System.A @@ -90,8 +87,7 @@ Imports MS.B expected, code, baseIndentation:=0, options:=SeparateImportDirectiveGroups) End Function - - + Public Async Function SeparateGroups_GroupIfSorted_RecognizeSystemNotFirst() As Task Dim code = "[| Imports MS.A diff --git a/src/EditorFeatures/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassCommandHandlerTests.vb b/src/EditorFeatures/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassCommandHandlerTests.vb index 49f5c03ec338b..b375830b6604a 100644 --- a/src/EditorFeatures/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassCommandHandlerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassCommandHandlerTests.vb @@ -20,8 +20,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.ImplementAbstractC Public Class ImplementAbstractClassCommandHandlerTests - - + Public Sub TestSimpleCases() Dim code = Imports System @@ -49,8 +48,7 @@ End Class Sub(x, y) AssertEx.AssertContainsToleratingWhitespaceDifferences(x, y)) End Sub - - + Public Sub TestInvocationAfterWhitespaceTrivia() Dim code = Imports System @@ -128,8 +126,7 @@ End Class End Using End Sub - - + Public Sub TestEnterNotOnSameLine() Dim code = MustInherit Class Base diff --git a/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceCommandHandlerTests.vb b/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceCommandHandlerTests.vb index 9c27d29619d63..b763aeee6b18d 100644 --- a/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceCommandHandlerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceCommandHandlerTests.vb @@ -99,8 +99,7 @@ End Interface Sub(expected, actual, view) AssertEx.AssertContainsToleratingWhitespaceDifferences(expected, actual)) End Sub - - + Public Sub TestInterfacesWithDuplicateMember() Dim code = Interface IGoo @@ -229,8 +228,7 @@ End Class End Sub - - + Public Sub TestInvocationAfterWhitespaceTrivia() Dim code = Imports System @@ -254,8 +252,7 @@ End Interface End Sub - - + Public Sub TestInvocationAfterCommentTrivia() Dim code = Imports System @@ -305,8 +302,7 @@ End Interface Sub(expected, actual, view) Assert.Equal(expected.Trim(), actual.Trim())) End Sub - - + Public Sub TestWithEndBlockMissing() Dim code = Imports System @@ -331,8 +327,7 @@ End Class Sub(expected, actual, view) AssertEx.AssertEqualToleratingWhitespaceDifferences(expected, actual)) End Sub - - + Public Sub TestWithEndBlockMissing2() Dim code = Imports System @@ -363,8 +358,7 @@ End Class - - + Public Sub TestWithStatementSeparator() Dim code = Imports System @@ -397,8 +391,7 @@ End Class Sub(expected, actual, view) AssertEx.AssertEqualToleratingWhitespaceDifferences(expected, actual)) End Sub - - + Public Sub TestCursorNotOnSameLine() Dim code = Imports System @@ -432,8 +425,7 @@ End Class Sub(expected, actual, view) AssertEx.AssertEqualToleratingWhitespaceDifferences(expected, actual)) End Sub - - + Public Sub TestCursorPlacedOnBlankLineAfter() Dim code = Imports System @@ -467,8 +459,7 @@ End Class End Sub) End Sub - - + Public Sub TestMultipleImplementationWithCaseDifference() Dim code = Interface IA @@ -503,8 +494,7 @@ End Class Sub(expected, actual, view) AssertEx.AssertContainsToleratingWhitespaceDifferences(expected, actual)) End Sub - - + Public Sub TestFullyQualifiedName() Dim code = Namespace N diff --git a/src/EditorFeatures/VisualBasicTest/LineCommit/CommitWithViewTests.vb b/src/EditorFeatures/VisualBasicTest/LineCommit/CommitWithViewTests.vb index 63757135a5786..1ce661c79a8a1 100644 --- a/src/EditorFeatures/VisualBasicTest/LineCommit/CommitWithViewTests.vb +++ b/src/EditorFeatures/VisualBasicTest/LineCommit/CommitWithViewTests.vb @@ -624,8 +624,7 @@ End Module End Sub - - + Public Sub TestBetterStartIndentation() Using testData = CommitTestData.Create( @@ -1012,8 +1011,7 @@ End Module End Using End Sub - - + Public Sub TestMissingThenInIf() Using testData = CommitTestData.Create( @@ -1047,8 +1045,7 @@ End Class End Using End Sub - - + Public Sub TestMissingThenInElseIf() Using testData = CommitTestData.Create( diff --git a/src/EditorFeatures/VisualBasicTest/QuickInfo/SemanticQuickInfoSourceTests.vb b/src/EditorFeatures/VisualBasicTest/QuickInfo/SemanticQuickInfoSourceTests.vb index eaf574c6bb378..5c0bb86e6608e 100644 --- a/src/EditorFeatures/VisualBasicTest/QuickInfo/SemanticQuickInfoSourceTests.vb +++ b/src/EditorFeatures/VisualBasicTest/QuickInfo/SemanticQuickInfoSourceTests.vb @@ -1046,8 +1046,7 @@ End Module Documentation(VBWorkspaceResources.If_condition_returns_True_the_function_calculates_and_returns_expressionIfTrue_Otherwise_it_returns_expressionIfFalse)) End Function - - + Public Async Function TestAddHandlerStatement() As Task Await TestInMethodAsync("$$AddHandler goo, bar", MainDescription($"AddHandler {VBWorkspaceResources.event_}, {VBWorkspaceResources.handler}"), @@ -1055,8 +1054,7 @@ End Module SymbolGlyph(Glyph.Keyword)) End Function - - + Public Async Function TestRemoveHandlerStatement() As Task Await TestInMethodAsync("$$RemoveHandler goo, bar", MainDescription($"RemoveHandler {VBWorkspaceResources.event_}, {VBWorkspaceResources.handler}"), @@ -1071,8 +1069,7 @@ End Module Documentation(VBWorkspaceResources.Returns_a_System_Type_object_for_the_specified_type_name)) End Function - - + Public Async Function TestGetXmlNamespaceExpression() As Task Await TestWithReferencesAsync( diff --git a/src/EditorFeatures/VisualBasicTest/SplitComment/SplitCommentCommandHandlerTests.vb b/src/EditorFeatures/VisualBasicTest/SplitComment/SplitCommentCommandHandlerTests.vb index 17b909cb3a123..f3616eab7a90d 100644 --- a/src/EditorFeatures/VisualBasicTest/SplitComment/SplitCommentCommandHandlerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/SplitComment/SplitCommentCommandHandlerTests.vb @@ -15,8 +15,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.SplitComment Return EditorTestWorkspace.CreateVisualBasic(markup) End Function - - + Public Sub TestSplitStartOfComment() TestHandled( "Module Program @@ -34,8 +33,7 @@ End Module ") End Sub - - + Public Sub TestSplitStartOfDoubleComment1() TestHandled( "Module Program @@ -53,8 +51,7 @@ End Module ") End Sub - - + Public Sub TestSplitStartOfDoubleComment2() TestHandled( "Module Program @@ -72,8 +69,7 @@ End Module ") End Sub - - + Public Sub TestSplitStartOfDoubleComment3() TestHandled( "Module Program @@ -91,8 +87,7 @@ End Module ") End Sub - - + Public Sub TestSplitStartOfCommentWithLeadingSpace1() TestHandled( "Module Program @@ -110,8 +105,7 @@ End Module ") End Sub - - + Public Sub TestSplitStartOfCommentWithLeadingSpace2() TestHandled( "Module Program @@ -129,8 +123,7 @@ End Module ") End Sub - - + Public Sub TestSplitMiddleOfComment() TestHandled( "Module Program @@ -148,8 +141,7 @@ End Module ") End Sub - - + Public Sub TestSplitEndOfComment() TestNotHandled( "Module Program @@ -160,8 +152,7 @@ End Module ") End Sub - - + Public Sub TestNotAtEndOfFile() TestNotHandled( "Module Program @@ -169,8 +160,7 @@ End Module ' Test Comment[||]") End Sub - - + Public Sub TestSplitCommentOutOfMethod() TestHandled( "Module Program @@ -190,8 +180,7 @@ End Module ") End Sub - - + Public Sub TestSplitCommentOutOfModule() TestHandled( "Module Program @@ -211,8 +200,7 @@ End Module ") End Sub - - + Public Sub TestSplitCommentOutOfClass() TestHandled( "Class Program @@ -232,8 +220,7 @@ End Class ") End Sub - - + Public Sub TestSplitCommentOutOfNamespace() TestHandled( "Namespace TestNamespace @@ -257,8 +244,7 @@ End Namespace ") End Sub - - + Public Sub TestSplitCommentWithLineContinuation() TestNotHandled( "Module Program @@ -270,8 +256,7 @@ End Module ") End Sub - - + @@ -291,8 +276,7 @@ end class", end class") End Sub - - + @@ -312,8 +296,7 @@ end class", end class") End Sub - - + Public Sub TestSplitWithCommentAfterwards1() TestNotHandled( "public class Program @@ -323,8 +306,7 @@ end class") end class") End Sub - - + Public Sub TestSplitWithCommentAfterwards2() TestNotHandled( "public class Program @@ -336,8 +318,7 @@ end class") end class") End Sub - - + Public Sub TestSplitWithCommentAfterwards3() TestNotHandled( "public class Program @@ -348,8 +329,7 @@ end class") end class") End Sub - - + Public Sub TestSplitWithCommentAfterwards4() TestNotHandled( "public class Program diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs index 2e64dded07bb1..6648928c9825f 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs @@ -3906,7 +3906,7 @@ static void DummySequencePoint() { } }"; - var references = TargetFrameworkUtil.Mscorlib461ExtendedReferences.Concat(new[] { Net461.SystemThreadingTasks, TestMetadata.SystemThreadingTasksExtensions.PortableLib }); + var references = TargetFrameworkUtil.Mscorlib461ExtendedReferences.Concat(new[] { Net461.References.SystemThreadingTasks, TestMetadata.SystemThreadingTasksExtensions.PortableLib }); var comp = CreateEmptyCompilation(new[] { source, AsyncStreamsTypes }, references: references); WithRuntimeInstance(comp, references, runtime => { diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MissingAssemblyTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MissingAssemblyTests.cs index 1110ff1c127f4..439c9a71faea2 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MissingAssemblyTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MissingAssemblyTests.cs @@ -475,7 +475,7 @@ public void ShouldTryAgain_ObjectDisposedException() [Fact] public void ShouldTryAgain_RPC_E_DISCONNECTED() { - IntPtr gmdbpf(AssemblyIdentity assemblyIdentity, out uint uSize) + static IntPtr gmdbpf(AssemblyIdentity assemblyIdentity, out uint uSize) { Marshal.ThrowExceptionForHR(unchecked((int)0x80010108)); throw ExceptionUtilities.Unreachable(); @@ -489,7 +489,7 @@ IntPtr gmdbpf(AssemblyIdentity assemblyIdentity, out uint uSize) [Fact] public void ShouldTryAgain_Exception() { - IntPtr gmdbpf(AssemblyIdentity assemblyIdentity, out uint uSize) + static IntPtr gmdbpf(AssemblyIdentity assemblyIdentity, out uint uSize) { throw new Exception(); } diff --git a/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/ExpressionCompilerTestHelpers.cs b/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/ExpressionCompilerTestHelpers.cs index c0564d89ed97c..7838bdea83a01 100644 --- a/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/ExpressionCompilerTestHelpers.cs +++ b/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/ExpressionCompilerTestHelpers.cs @@ -547,7 +547,7 @@ internal static void VerifyResolutionRequests(EEMetadataReferenceResolver resolv actual.Free(); expected.Free(); - void sort(ArrayBuilder<(AssemblyIdentity, AssemblyIdentity, int)> builder) + static void sort(ArrayBuilder<(AssemblyIdentity, AssemblyIdentity, int)> builder) { builder.Sort((x, y) => AssemblyIdentityComparer.SimpleNameComparer.Compare(x.Item1.GetDisplayName(), y.Item1.GetDisplayName())); } diff --git a/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs index 98ab13f19a3f9..f513c3af27576 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs @@ -19,6 +19,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Indentation; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -265,11 +266,11 @@ public override void AddAlignTokensOperations(List list, S } } - public override void AddSuppressOperations(List list, SyntaxNode node, in NextSuppressOperationAction nextOperation) + public override void AddSuppressOperations(ArrayBuilder list, SyntaxNode node, in NextSuppressOperationAction nextOperation) { base.AddSuppressOperations(list, node, in nextOperation); - // not sure exactly what is happening here, but removing the bellow causesthe indentation to be wrong. + // not sure exactly what is happening here, but removing the bellow causes the indentation to be wrong. // remove suppression rules for array and collection initializer if (node.IsInitializerForArrayOrCollectionCreationExpression()) diff --git a/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs b/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs index e12e26279379c..d22b7ece85de4 100644 --- a/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs +++ b/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs @@ -888,8 +888,8 @@ public override async Task> DetermineCascadedSymbolsFrom return convertedMethodGroups; } - protected override IEnumerable GetFormattingRules(Document document) - => Formatter.GetDefaultFormattingRules(document).Concat(new ChangeSignatureFormattingRule()); + protected override ImmutableArray GetFormattingRules(Document document) + => [.. Formatter.GetDefaultFormattingRules(document), new ChangeSignatureFormattingRule()]; protected override TArgumentSyntax AddNameToArgument(TArgumentSyntax newArgument, string name) { diff --git a/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs index ae26da1577ddb..03b08066988bc 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.EnableNullable; @@ -67,14 +68,21 @@ private static async Task EnableNullableReferenceTypesAsync( Project project, CodeActionPurpose purpose, CodeActionOptionsProvider fallbackOptions, IProgress _, CancellationToken cancellationToken) { var solution = project.Solution; - foreach (var document in project.Documents) - { - if (await document.IsGeneratedCodeAsync(cancellationToken).ConfigureAwait(false)) - continue; + var updatedDocumentRoots = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync( + source: project.Documents, + produceItems: static async (document, callback, fallbackOptions, cancellationToken) => + { + if (await document.IsGeneratedCodeAsync(cancellationToken).ConfigureAwait(false)) + return; - var updatedDocumentRoot = await EnableNullableReferenceTypesAsync(document, fallbackOptions, cancellationToken).ConfigureAwait(false); - solution = solution.WithDocumentSyntaxRoot(document.Id, updatedDocumentRoot); - } + var updatedDocumentRoot = await EnableNullableReferenceTypesAsync( + document, fallbackOptions, cancellationToken).ConfigureAwait(false); + callback((document.Id, updatedDocumentRoot)); + }, + args: fallbackOptions, + cancellationToken).ConfigureAwait(false); + + solution = solution.WithDocumentSyntaxRoots(updatedDocumentRoots); if (purpose is CodeActionPurpose.Apply) { diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/PropertySubPatternCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/PropertySubPatternCompletionProvider.cs index ec19bbc3cca08..b2bb05471d9c9 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/PropertySubPatternCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/PropertySubPatternCompletionProvider.cs @@ -196,7 +196,7 @@ private static (PropertyPatternClauseSyntax?, ExpressionSyntax?) TryGetPropertyP return default; - bool IsExtendedPropertyPattern(MemberAccessExpressionSyntax memberAccess, [NotNullWhen(true)] out PropertyPatternClauseSyntax? propertyPatternClause) + static bool IsExtendedPropertyPattern(MemberAccessExpressionSyntax memberAccess, [NotNullWhen(true)] out PropertyPatternClauseSyntax? propertyPatternClause) { while (memberAccess.Parent.IsKind(SyntaxKind.SimpleMemberAccessExpression)) { diff --git a/src/Features/CSharp/Portable/Debugging/DataTipInfoGetter.cs b/src/Features/CSharp/Portable/Debugging/DataTipInfoGetter.cs index c6068148d8f59..86b1ba4a94e65 100644 --- a/src/Features/CSharp/Portable/Debugging/DataTipInfoGetter.cs +++ b/src/Features/CSharp/Portable/Debugging/DataTipInfoGetter.cs @@ -37,7 +37,7 @@ internal static async Task GetInfoAsync(Document document, int : default; } - if (expression.IsAnyLiteralExpression()) + if (expression is LiteralExpressionSyntax) { // If the user hovers over a literal, give them a DataTip for the type of the // literal they're hovering over. diff --git a/src/Features/CSharp/Portable/DecompiledSource/CSharpDecompiledSourceService.cs b/src/Features/CSharp/Portable/DecompiledSource/CSharpDecompiledSourceService.cs index 6b4a2e9bd14db..9cdedae3a1579 100644 --- a/src/Features/CSharp/Portable/DecompiledSource/CSharpDecompiledSourceService.cs +++ b/src/Features/CSharp/Portable/DecompiledSource/CSharpDecompiledSourceService.cs @@ -63,7 +63,7 @@ public static async Task FormatDocumentAsync(Document document, Syntax document, [node.FullSpan], options, - CSharpDecompiledSourceFormattingRule.Instance.Concat(Formatter.GetDefaultFormattingRules(document)), + [CSharpDecompiledSourceFormattingRule.Instance, .. Formatter.GetDefaultFormattingRules(document)], cancellationToken).ConfigureAwait(false); return formattedDoc; diff --git a/src/Features/CSharp/Portable/EncapsulateField/CSharpEncapsulateFieldService.cs b/src/Features/CSharp/Portable/EncapsulateField/CSharpEncapsulateFieldService.cs index f9c34dedd824e..d52d8ba83cd72 100644 --- a/src/Features/CSharp/Portable/EncapsulateField/CSharpEncapsulateFieldService.cs +++ b/src/Features/CSharp/Portable/EncapsulateField/CSharpEncapsulateFieldService.cs @@ -203,6 +203,6 @@ protected static string MakeUnique(string baseName, INamedTypeSymbol containingT return NameGenerator.GenerateUniqueName(baseName, containingTypeMemberNames.ToSet(), StringComparer.Ordinal); } - internal override IEnumerable GetConstructorNodes(INamedTypeSymbol containingType) + protected override IEnumerable GetConstructorNodes(INamedTypeSymbol containingType) => containingType.Constructors.SelectMany(c => c.DeclaringSyntaxReferences.Select(d => d.GetSyntax())); } diff --git a/src/Features/CSharp/Portable/GoToDefinition/CSharpGoToDefinitionSymbolService.cs b/src/Features/CSharp/Portable/GoToDefinition/CSharpGoToDefinitionSymbolService.cs index 15c8118bed05b..af747c6470b09 100644 --- a/src/Features/CSharp/Portable/GoToDefinition/CSharpGoToDefinitionSymbolService.cs +++ b/src/Features/CSharp/Portable/GoToDefinition/CSharpGoToDefinitionSymbolService.cs @@ -6,6 +6,8 @@ using System.Composition; using System.Diagnostics; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.GoToDefinition; @@ -16,16 +18,12 @@ namespace Microsoft.CodeAnalysis.CSharp.GoToDefinition; [ExportLanguageService(typeof(IGoToDefinitionSymbolService), LanguageNames.CSharp), Shared] -internal class CSharpGoToDefinitionSymbolService : AbstractGoToDefinitionSymbolService +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpGoToDefinitionSymbolService() : AbstractGoToDefinitionSymbolService { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpGoToDefinitionSymbolService() - { - } - - protected override ISymbol FindRelatedExplicitlyDeclaredSymbol(ISymbol symbol, Compilation compilation) - => symbol; + protected override Task FindRelatedExplicitlyDeclaredSymbolAsync(Project project, ISymbol symbol, CancellationToken cancellationToken) + => Task.FromResult(symbol); protected override int? GetTargetPositionIfControlFlow(SemanticModel semanticModel, SyntaxToken token) { diff --git a/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs index 3daf3339608c8..83eb0d70ac6b4 100644 --- a/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InvertIf/CSharpInvertIfCodeRefactoringProvider.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.InvertIf; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -21,15 +22,11 @@ namespace Microsoft.CodeAnalysis.CSharp.InvertIf; using static SyntaxFactory; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.InvertIf), Shared] -internal sealed class CSharpInvertIfCodeRefactoringProvider : AbstractInvertIfCodeRefactoringProvider< +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class CSharpInvertIfCodeRefactoringProvider() : AbstractInvertIfCodeRefactoringProvider< SyntaxKind, StatementSyntax, IfStatementSyntax, StatementSyntax> { - [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] - public CSharpInvertIfCodeRefactoringProvider() - { - } - protected override string GetTitle() => CSharpFeaturesResources.Invert_if; diff --git a/src/Features/CSharp/Portable/MetadataAsSource/CSharpMetadataAsSourceService.cs b/src/Features/CSharp/Portable/MetadataAsSource/CSharpMetadataAsSourceService.cs index 3f51710433897..e8681c83af413 100644 --- a/src/Features/CSharp/Portable/MetadataAsSource/CSharpMetadataAsSourceService.cs +++ b/src/Features/CSharp/Portable/MetadataAsSource/CSharpMetadataAsSourceService.cs @@ -57,8 +57,8 @@ protected override async Task AddAssemblyInfoRegionAsync(Document docu return document.WithSyntaxRoot(newRoot); } - protected override IEnumerable GetFormattingRules(Document document) - => s_memberSeparationRule.Concat(Formatter.GetDefaultFormattingRules(document)); + protected override ImmutableArray GetFormattingRules(Document document) + => [s_memberSeparationRule, .. Formatter.GetDefaultFormattingRules(document)]; protected override async Task ConvertDocCommentsToRegularCommentsAsync(Document document, IDocumentationCommentFormattingService docCommentFormattingService, CancellationToken cancellationToken) { diff --git a/src/Features/CSharp/Portable/QuickInfo/CSharpSemanticQuickInfoProvider.cs b/src/Features/CSharp/Portable/QuickInfo/CSharpSemanticQuickInfoProvider.cs index 0e3ee36ac2166..d91544be24d58 100644 --- a/src/Features/CSharp/Portable/QuickInfo/CSharpSemanticQuickInfoProvider.cs +++ b/src/Features/CSharp/Portable/QuickInfo/CSharpSemanticQuickInfoProvider.cs @@ -3,13 +3,20 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Copilot; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.GoToDefinition; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.QuickInfo; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CSharp.QuickInfo; @@ -127,4 +134,46 @@ protected override NullableFlowState GetNullabilityAnalysis(SemanticModel semant return typeInfo.Nullability.FlowState; } + + protected override async Task GetOnTheFlyDocsElementAsync(QuickInfoContext context, CancellationToken cancellationToken) + { + var document = context.Document; + var position = context.Position; + + if (document.GetLanguageService() is not { } copilotService || + !await copilotService.IsAvailableAsync(cancellationToken).ConfigureAwait(false)) + { + return null; + } + + if (document.GetLanguageService() is not { } service || + !await service.IsOnTheFlyDocsOptionEnabledAsync().ConfigureAwait(false)) + { + return null; + } + + var symbolService = document.GetRequiredLanguageService(); + var (symbol, _, _) = await symbolService.GetSymbolProjectAndBoundSpanAsync( + document, position, cancellationToken).ConfigureAwait(false); + + if (symbol is null) + { + return null; + } + + if (symbol.DeclaringSyntaxReferences.Length == 0) + { + return null; + } + + var maxLength = 1000; + var symbolStrings = symbol.DeclaringSyntaxReferences.Select(reference => + { + var span = reference.Span; + var sourceText = reference.SyntaxTree.GetText(cancellationToken); + return sourceText.GetSubText(new Text.TextSpan(span.Start, Math.Min(maxLength, span.Length))).ToString(); + }).ToImmutableArray(); + + return new OnTheFlyDocsElement(symbol.ToDisplayString(), symbolStrings, symbol.Language); + } } diff --git a/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs b/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs index c96dbb0c61400..adf0adae32d83 100644 --- a/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs @@ -5,6 +5,7 @@ #nullable disable using System.Collections.Generic; +using System.Collections.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -84,13 +85,8 @@ protected override async Task UpdatePropertyAsync( return updatedProperty.WithTrailingTrivia(trailingTrivia).WithAdditionalAnnotations(SpecializedFormattingAnnotation); } - protected override IEnumerable GetFormattingRules(Document document) - { - var rules = new List { new SingleLinePropertyFormattingRule() }; - rules.AddRange(Formatter.GetDefaultFormattingRules(document)); - - return rules; - } + protected override ImmutableArray GetFormattingRules(Document document) + => [new SingleLinePropertyFormattingRule(), .. Formatter.GetDefaultFormattingRules(document)]; private class SingleLinePropertyFormattingRule : AbstractFormattingRule { diff --git a/src/Features/CSharpTest/AddDebuggerDisplay/AddDebuggerDisplayTests.cs b/src/Features/CSharpTest/AddDebuggerDisplay/AddDebuggerDisplayTests.cs index 0c82ed1fef90a..a42150ea0aa99 100644 --- a/src/Features/CSharpTest/AddDebuggerDisplay/AddDebuggerDisplayTests.cs +++ b/src/Features/CSharpTest/AddDebuggerDisplay/AddDebuggerDisplayTests.cs @@ -4,15 +4,17 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp.AddDebuggerDisplay; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Xunit; -using VerifyCS = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.CSharpCodeRefactoringVerifier< - Microsoft.CodeAnalysis.CSharp.AddDebuggerDisplay.CSharpAddDebuggerDisplayCodeRefactoringProvider>; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.AddDebuggerDisplay { + using VerifyCS = CSharpCodeRefactoringVerifier; + + [UseExportProvider] [Trait(Traits.Feature, Traits.Features.CodeActionsAddDebuggerDisplay)] public sealed class AddDebuggerDisplayTests { diff --git a/src/Features/CSharpTest/ConvertCast/ConvertDirectCastToTryCastTests.cs b/src/Features/CSharpTest/ConvertCast/ConvertDirectCastToTryCastTests.cs index a6e95f5bbfa59..5c095d186aa4a 100644 --- a/src/Features/CSharpTest/ConvertCast/ConvertDirectCastToTryCastTests.cs +++ b/src/Features/CSharpTest/ConvertCast/ConvertDirectCastToTryCastTests.cs @@ -14,6 +14,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertConversionOperat { using VerifyCS = CSharpCodeRefactoringVerifier; + [UseExportProvider] [Trait(Traits.Feature, Traits.Features.ConvertCast)] public class ConvertDirectCastToTryCastTests { diff --git a/src/Features/CSharpTest/ConvertCast/ConvertTryCastToDirectCastTests.cs b/src/Features/CSharpTest/ConvertCast/ConvertTryCastToDirectCastTests.cs index 7a124319b2737..c2747d36481e2 100644 --- a/src/Features/CSharpTest/ConvertCast/ConvertTryCastToDirectCastTests.cs +++ b/src/Features/CSharpTest/ConvertCast/ConvertTryCastToDirectCastTests.cs @@ -14,6 +14,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertConversionOperat { using VerifyCS = CSharpCodeRefactoringVerifier; + [UseExportProvider] [Trait(Traits.Feature, Traits.Features.ConvertCast)] public class ConvertTryCastToDirectCastTests { diff --git a/src/Features/CSharpTest/ConvertIfToSwitch/ConvertIfToSwitchTests.cs b/src/Features/CSharpTest/ConvertIfToSwitch/ConvertIfToSwitchTests.cs index 94f70ea7d34bc..ff958c2b38960 100644 --- a/src/Features/CSharpTest/ConvertIfToSwitch/ConvertIfToSwitchTests.cs +++ b/src/Features/CSharpTest/ConvertIfToSwitch/ConvertIfToSwitchTests.cs @@ -16,6 +16,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeActions.ConvertIfTo { using VerifyCS = CSharpCodeRefactoringVerifier; + [UseExportProvider] [Trait(Traits.Feature, Traits.Features.CodeActionsConvertIfToSwitch)] public class ConvertIfToSwitchTests { @@ -492,8 +493,7 @@ void M(int i) await VerifyCS.VerifyRefactoringAsync(source, source); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestIsExpression( [CombinatorialValues(LanguageVersion.CSharp8, LanguageVersion.CSharp9)] LanguageVersion languageVersion) { diff --git a/src/Features/CSharpTest/ConvertLinq/ConvertForEachToLinqQueryTests.cs b/src/Features/CSharpTest/ConvertLinq/ConvertForEachToLinqQueryTests.cs index 5edb251a13646..959a63de1a199 100644 --- a/src/Features/CSharpTest/ConvertLinq/ConvertForEachToLinqQueryTests.cs +++ b/src/Features/CSharpTest/ConvertLinq/ConvertForEachToLinqQueryTests.cs @@ -4156,10 +4156,10 @@ List M(IEnumerable nums) return /*30*/ /* 1 *//* 2 *//* 3 *//* 4 */// 5 /*31*//* 6 */ (from/* 8 *//* 7 *//* 9 */x /* 10 */ in/* 11 */nums/* 12 */// 13 - /* 14 */// 15 - /* 16 *//* 17 */ - let y /* 18 */ = /* 19 */ x + 1/* 20 *///21 - select y)/* 24 *//*27*///28 + /* 14 */// 15 + /* 16 *//* 17 */ + let y /* 18 */ = /* 19 */ x + 1/* 20 *///21 + select y)/* 24 *//*27*///28 .ToList()/* 22 *//* 23 *//* 25 *///26 ; //32 } @@ -4205,7 +4205,7 @@ List M(IEnumerable nums) /*25*//* 14 */// 15 /* 6 */ (from/* 8 *//* 7 *//* 9 */x /* 10 */ in/* 11 */nums/* 12 */// 13 - select x + 1)/* 18 *//*21*///22 + select x + 1)/* 18 *//*21*///22 .ToList()/* 16 *//* 17 *//* 19 *///20 ; //26 } @@ -4223,7 +4223,8 @@ List M(IEnumerable nums) { /*23*/ return /*24*/ /* 1 *//* 2 *//* 3 *//* 4 */// 5 - /*25*/nums /* 12 */.Select( + /*25*/ + nums /* 12 */.Select( /* 6 *//* 7 *//* 14 */// 15 /* 9 */x /* 10 */ => x + 1/* 18 *//*21*///22 /* 8 *//* 11 */// 13 @@ -4268,7 +4269,7 @@ int M(IEnumerable nums) /*23*//* 14 */// 15 /* 6 */ (from/* 8 *//* 7 *//* 9 */x /* 10 */ in/* 11 */nums/* 12 */// 13 - select x)/* 10 *//*19*///20 + select x)/* 10 *//*19*///20 .Count()/* 16 *//* 17 *///18 ; //24 } @@ -4286,7 +4287,8 @@ int M(IEnumerable nums) { /*21*/ return /*22*/ /* 1 *//* 2 *//* 3 *//* 4 */// 5 - /*23*/nums /* 12 *//* 6 *//* 7 *//* 14 */// 15 + /*23*/ + nums /* 12 *//* 6 *//* 7 *//* 14 */// 15 /* 9 *//* 10 *//* 10 *//*19*///20 /* 8 *//* 11 */// 13 .Count()/* 16 *//* 17 *///18 @@ -4328,11 +4330,11 @@ void M(IEnumerable nums) { foreach (var (a /* 12 */ , b /*16*/ ) in /* 1 */from/* 2 */int /* 3 */ n1 /* 4 */in/* 5 */nums/* 6 */// 7 - /* 8*/// 9 - /* 10 *//* 11 */ - let a /* 12 */ = /* 13 */ n1 + n1/* 14*//* 15 */ - let b /*16*/ = /*17*/ n1 * n1/*18*///19 - select (a /* 12 */ , b /*16*/ )/*22*//*23*/) + /* 8*/// 9 + /* 10 *//* 11 */ + let a /* 12 */ = /* 13 */ n1 + n1/* 14*//* 15 */ + let b /*16*/ = /*17*/ n1 * n1/*18*///19 + select (a /* 12 */ , b /*16*/ )/*22*//*23*/) { /*20*/ Console.WriteLine(a + b);//21 @@ -4384,7 +4386,7 @@ void M(IEnumerable nums) /* 10 */ where/* 11 *//* 12 */n1 /* 13 */ > /* 14 */ 0/* 15 */// 16 select n1/* 4 *//* 21 */// 22 - /*23*//*24*/ + /*23*//*24*/ ) { /*19*/ diff --git a/src/Features/CSharpTest/ConvertNamespace/ConvertNamespaceRefactoringTests.cs b/src/Features/CSharpTest/ConvertNamespace/ConvertNamespaceRefactoringTests.cs index 63276e4ab6594..b7c16c95881c4 100644 --- a/src/Features/CSharpTest/ConvertNamespace/ConvertNamespaceRefactoringTests.cs +++ b/src/Features/CSharpTest/ConvertNamespace/ConvertNamespaceRefactoringTests.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.ConvertNamespace; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Roslyn.Test.Utilities; using Xunit; @@ -17,6 +18,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertNamespace { using VerifyCS = CSharpCodeRefactoringVerifier; + [UseExportProvider] public class ConvertNamespaceRefactoringTests { public static IEnumerable EndOfDocumentSequences diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index 5afc6c7b2a6d6..9f5265d6f9a0d 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.ConvertPrimaryToRegularConstructor; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Xunit; @@ -13,6 +14,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertPrimaryToRegular using VerifyCS = CSharpCodeRefactoringVerifier; +[UseExportProvider] public class ConvertPrimaryToRegularConstructorTests { private const string FieldNamesCamelCaseWithFieldUnderscorePrefixEditorConfig = """ diff --git a/src/Features/CSharpTest/ConvertProgram/ConvertToProgramMainRefactoringTests.cs b/src/Features/CSharpTest/ConvertProgram/ConvertToProgramMainRefactoringTests.cs index e2a42e80b6614..1fdac5e56aa39 100644 --- a/src/Features/CSharpTest/ConvertProgram/ConvertToProgramMainRefactoringTests.cs +++ b/src/Features/CSharpTest/ConvertProgram/ConvertToProgramMainRefactoringTests.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.ConvertProgram; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Xunit; @@ -15,6 +16,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertProgram { using VerifyCS = CSharpCodeRefactoringVerifier; + [UseExportProvider] public class ConvertToProgramMainRefactoringTests { [Fact] diff --git a/src/Features/CSharpTest/ConvertProgram/ConvertToTopLevelStatementsRefactoringTests.cs b/src/Features/CSharpTest/ConvertProgram/ConvertToTopLevelStatementsRefactoringTests.cs index 53ce410193fc7..9b03d72ae6249 100644 --- a/src/Features/CSharpTest/ConvertProgram/ConvertToTopLevelStatementsRefactoringTests.cs +++ b/src/Features/CSharpTest/ConvertProgram/ConvertToTopLevelStatementsRefactoringTests.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.ConvertProgram; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Xunit; @@ -15,6 +16,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertProgram { using VerifyCS = CSharpCodeRefactoringVerifier; + [UseExportProvider] public class ConvertToTopLevelStatementsRefactoringTests { [Fact] diff --git a/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs b/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs index 6a59132c0fd36..cfad68025c998 100644 --- a/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs +++ b/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs @@ -15,6 +15,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertToInterpolatedSt using VerifyCS = CSharpCodeRefactoringVerifier; +[UseExportProvider] [Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)] public class ConvertConcatenationToInterpolatedStringTests { diff --git a/src/Features/CSharpTest/ConvertToRawString/ConvertInterpolatedStringToRawStringTests.cs b/src/Features/CSharpTest/ConvertToRawString/ConvertInterpolatedStringToRawStringTests.cs index 3ce45a521362c..e674d034b3c94 100644 --- a/src/Features/CSharpTest/ConvertToRawString/ConvertInterpolatedStringToRawStringTests.cs +++ b/src/Features/CSharpTest/ConvertToRawString/ConvertInterpolatedStringToRawStringTests.cs @@ -14,6 +14,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertToRawString; using VerifyCS = CSharpCodeRefactoringVerifier< ConvertStringToRawStringCodeRefactoringProvider>; +[UseExportProvider] [Trait(Traits.Feature, Traits.Features.CodeActionsConvertToRawString)] public class ConvertInterpolatedStringToRawStringTests { diff --git a/src/Features/CSharpTest/ConvertToRawString/ConvertRegularStringToRawStringTests.cs b/src/Features/CSharpTest/ConvertToRawString/ConvertRegularStringToRawStringTests.cs index 974bf8623616d..771a5585b224f 100644 --- a/src/Features/CSharpTest/ConvertToRawString/ConvertRegularStringToRawStringTests.cs +++ b/src/Features/CSharpTest/ConvertToRawString/ConvertRegularStringToRawStringTests.cs @@ -14,6 +14,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertToRawString using VerifyCS = CSharpCodeRefactoringVerifier< ConvertStringToRawStringCodeRefactoringProvider>; + [UseExportProvider] [Trait(Traits.Feature, Traits.Features.CodeActionsConvertToRawString)] public class ConvertRegularStringToRawStringTests { diff --git a/src/Features/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs b/src/Features/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs index 2937f37c13f73..96848f438dc7f 100644 --- a/src/Features/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs +++ b/src/Features/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs @@ -10,12 +10,10 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.ConvertTupleToStruct; -using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.NamingStyles; using Microsoft.CodeAnalysis.Remote.Testing; -using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Roslyn.Test.Utilities; @@ -47,7 +45,7 @@ private static async Task TestAsync( string? equivalenceKey = null, LanguageVersion languageVersion = LanguageVersion.CSharp9, OptionsCollection? options = null, - TestHost testHost = TestHost.InProcess, + TestHost testHost = TestHost.OutOfProcess, string[]? actions = null) { if (index != 0) diff --git a/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs b/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs index 83d05ebe6d16f..e0a01c8c2e449 100644 --- a/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs +++ b/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs @@ -445,8 +445,8 @@ void Method() var parameters = new TestParameters(); using var workspace = CreateWorkspaceFromOptions(source, parameters); - var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create(new CSharpCompilerDiagnosticAnalyzer())); - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); + var analyzerReference = new AnalyzerImageReference([new CSharpCompilerDiagnosticAnalyzer()]); + workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference])); var diagnosticService = Assert.IsType(workspace.ExportProvider.GetExportedValue()); var incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace); diff --git a/src/Features/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/Features/CSharpTest/EditAndContinue/ActiveStatementTests.cs index 3ed08b00727ff..2fe58e3c168d2 100644 --- a/src/Features/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -843,8 +843,7 @@ public void Parameter_Update_TypeOrRefKind_RuntimeTypeChanged(string oldType) capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Parameter_Update_RefKind_RuntimeTypeUnchanged( [CombinatorialValues("ref", "out", "in", "ref readonly")] string oldModifiers, [CombinatorialValues("ref", "out", "in", "ref readonly")] string newModifiers) @@ -10453,7 +10452,7 @@ static IEnumerable F() // should not contain RUDE_EDIT_INSERT_AROUND edits.VerifySemanticDiagnostics( active, - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] @@ -10484,7 +10483,7 @@ static IEnumerable F() edits.VerifySemanticDiagnostics( active, - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] @@ -10667,7 +10666,7 @@ static async Task F() // should not contain RUDE_EDIT_INSERT_AROUND edits.VerifySemanticDiagnostics( active, - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] @@ -10758,7 +10757,7 @@ static async Task F() edits.VerifySemanticDiagnostics( active, - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] @@ -10786,7 +10785,7 @@ static async void F() var active = GetActiveStatements(src1, src2); edits.VerifySemanticDiagnostics(active, - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] diff --git a/src/Features/CSharpTest/EditAndContinue/BreakpointSpansTests.cs b/src/Features/CSharpTest/EditAndContinue/BreakpointSpansTests.cs index 9f31982904a00..146e2dae0d36b 100644 --- a/src/Features/CSharpTest/EditAndContinue/BreakpointSpansTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/BreakpointSpansTests.cs @@ -4401,8 +4401,7 @@ public void InstanceConstructor_NoInitializer_Attributes() }"); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void InstanceConstructor_Primary_ImplicitBaseInitializer_OutsideOfIdentifierAndNonEmptyParameters( [CombinatorialValues("class", "struct", "record", "record struct")] string keyword, [CombinatorialValues( @@ -4425,8 +4424,7 @@ public void InstanceConstructor_Primary_ImplicitBaseInitializer_OutsideOfIdentif TestSpan(source.Replace("class", keyword)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void InstanceConstructor_Primary_ImplicitBaseInitializer_OnIdentifierOrNonEmptyParameters_NonRecord( [CombinatorialValues("class", "struct")] string keyword, [CombinatorialValues( @@ -4443,8 +4441,7 @@ public void InstanceConstructor_Primary_ImplicitBaseInitializer_OnIdentifierOrNo TestSpan(source.Replace("class", keyword)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void InstanceConstructor_Primary_ImplicitBaseInitializer_OnIdentifierOrNonEmptyParameters_Record( [CombinatorialValues("record", "record struct")] string keyword, [CombinatorialValues( @@ -4465,8 +4462,7 @@ public void InstanceConstructor_Primary_ImplicitBaseInitializer_OnIdentifierOrNo TestSpan(source.Replace("record", keyword)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void InstanceConstructor_Primary_ImplicitBaseInitializer_NoBreakpoint( [CombinatorialValues("class", "struct", "record", "record struct")] string keyword, [CombinatorialValues( @@ -4481,8 +4477,7 @@ public void InstanceConstructor_Primary_ImplicitBaseInitializer_NoBreakpoint( TestMissing(source.Replace("class", keyword)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void InstanceConstructor_Primary_ExplicitBaseInitializer_OutsideOfIdentifierAndNonEmptyParameters( [CombinatorialValues("class", "struct", "record", "record struct")] string keyword, [CombinatorialValues( @@ -4509,8 +4504,7 @@ public void InstanceConstructor_Primary_ExplicitBaseInitializer_OutsideOfIdentif TestSpan(source.Replace("class", keyword)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void InstanceConstructor_Primary_ExplicitBaseInitializer_OnIdentifierOrNonEmptyParameters_NonRecord( [CombinatorialValues("class", "struct")] string keyword, [CombinatorialValues( @@ -4525,8 +4519,7 @@ public void InstanceConstructor_Primary_ExplicitBaseInitializer_OnIdentifierOrNo TestSpan(source.Replace("class", keyword)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void InstanceConstructor_Primary_ExplicitBaseInitializer_OnIdentifierOrNonEmptyParameters_Record( [CombinatorialValues("record", "record struct")] string keyword, [CombinatorialValues( @@ -4541,8 +4534,7 @@ public void InstanceConstructor_Primary_ExplicitBaseInitializer_OnIdentifierOrNo TestSpan(source.Replace("record", keyword)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void InstanceConstructor_Primary_ExplicitBaseInitializer_NoBreakpoint( [CombinatorialValues("class", "struct", "record", "record struct")] string keyword, [CombinatorialValues( diff --git a/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs index e060c80f69f0f..147cfafd24b9a 100644 --- a/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -4725,8 +4725,7 @@ public void Lambdas_Update_CeaseCapture_ConstructorInitializer_Base() SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C..ctor"), preserveLocalVariables: true)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Lambdas_Update_CeaseCapture_PrimaryParameter_InPrimaryConstructor_First( [CombinatorialValues("class", "struct", "record", "record struct")] string keyword) { @@ -4739,8 +4738,7 @@ public void Lambdas_Update_CeaseCapture_PrimaryParameter_InPrimaryConstructor_Fi SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(m => m.Parameters is [_, _]), preserveLocalVariables: true)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Lambdas_Update_CeaseCapture_PrimaryParameter_InPrimaryConstructor_Second( [CombinatorialValues("class", "struct", "record", "record struct")] string keyword) { @@ -4753,8 +4751,7 @@ public void Lambdas_Update_CeaseCapture_PrimaryParameter_InPrimaryConstructor_Se SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(m => m.Parameters is [_, _]), preserveLocalVariables: true)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/68731")] public void Lambdas_Update_CeaseCapture_PrimaryParameter_InPrimaryConstructor_BaseInitializer( [CombinatorialValues("class", "record")] string keyword) @@ -5246,8 +5243,7 @@ public void Lambdas_Update_Capturing_ConstructorInitializer_Base() SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C..ctor"), preserveLocalVariables: true)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Lambdas_Update_Capturing_PrimaryParameter_InPrimaryConstructor_First( [CombinatorialValues("class", "struct", "record", "record struct")] string keyword) { @@ -5260,8 +5256,7 @@ public void Lambdas_Update_Capturing_PrimaryParameter_InPrimaryConstructor_First capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.NewTypeDefinition); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Lambdas_Update_Capturing_PrimaryParameter_InPrimaryConstructor_Second( [CombinatorialValues("class", "struct", "record", "record struct")] string keyword) { @@ -6569,7 +6564,7 @@ public void F() capabilities: EditAndContinueCapabilities.Baseline); edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } #endregion @@ -9615,7 +9610,7 @@ async Task WaitAsync() capabilities: EditAndContinueCapabilities.Baseline); edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] @@ -9649,7 +9644,7 @@ IEnumerable Iter() capabilities: EditAndContinueCapabilities.Baseline); edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] @@ -11484,7 +11479,7 @@ static IEnumerable F() edits.VerifySemanticDiagnostics( targetFrameworks: [TargetFramework.Mscorlib40AndSystemCore], - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } #endregion @@ -12079,7 +12074,7 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] @@ -12431,7 +12426,7 @@ static async Task F() edits.VerifySemanticDiagnostics( targetFrameworks: [TargetFramework.MinimalAsync], - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); } [Fact] diff --git a/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 7967d1f73cb8a..dc32458ce0022 100644 --- a/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -1968,70 +1968,132 @@ public void Struct_Modifiers_Partial_InsertDelete(string modifier) } [Fact] - public void Class_ImplementingInterface_Add() + public void Class_ImplementingInterface_Add_Implicit_NonVirtual() { - var src1 = @" -using System; + var src1 = """ + interface I + { + void F(); + } + """; -public interface ISample -{ - string Get(); -} + var src2 = """ + interface I + { + void F(); + } -public interface IConflict -{ - string Get(); -} + class C : I + { + public void F() {} + } + """; -public class BaseClass : ISample -{ - public virtual string Get() => string.Empty; -} -"; - var src2 = @" -using System; + var edits = GetTopEdits(src1, src2); + edits.VerifySemantics( + [SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C"))], + capabilities: EditAndContinueCapabilities.NewTypeDefinition); + } -public interface ISample -{ - string Get(); -} + [Fact] + public void Class_ImplementingInterface_Add_Implicit_Virtual() + { + var src1 = """ + interface I + { + void F(); + } + """; -public interface IConflict -{ - string Get(); -} + var src2 = """ + interface I + { + void F(); + } -public class BaseClass : ISample -{ - public virtual string Get() => string.Empty; -} + class C : I + { + public virtual void F() {} + } + """; -public class SubClass : BaseClass, IConflict -{ - public override string Get() => string.Empty; + var edits = GetTopEdits(src1, src2); + edits.VerifySemantics( + [SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C"))], + capabilities: EditAndContinueCapabilities.NewTypeDefinition); + } - string IConflict.Get() => String.Empty; -} -"; + [Fact] + public void Class_ImplementingInterface_Add_Implicit_Override() + { + var src1 = """ + interface I + { + void F(); + } + + class C : I + { + public virtual void F() {} + } + """; + + var src2 = """ + interface I + { + void F(); + } + + class C : I + { + public virtual void F() {} + } + + class D : C + { + public override void F() {} + } + """; var edits = GetTopEdits(src1, src2); + edits.VerifySemantics( + [SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("D"))], + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); + } - edits.VerifyEdits( - @"Insert [public class SubClass : BaseClass, IConflict -{ - public override string Get() => string.Empty; + [Theory] + [InlineData("void F();", "void I.F() {}")] + [InlineData("int F { get; }", "int I.F { get; }")] + [InlineData("event System.Action F;", "event System.Action I.F { add {} remove {} }")] + public void Class_ImplementingInterface_Add_Explicit_NonVirtual(string memberDef, string explicitImpl) + { + var src1 = $$""" + interface I + { + {{memberDef}} + } + """; - string IConflict.Get() => String.Empty; -}]@219", - "Insert [: BaseClass, IConflict]@241", - "Insert [public override string Get() => string.Empty;]@272", - "Insert [string IConflict.Get() => String.Empty;]@325", - "Insert [()]@298", - "Insert [()]@345"); + var src2 = $$""" + interface I + { + {{memberDef}} + } + + class C : I + { + {{explicitImpl}} + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + [SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C"))], + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); - // Here we add a class implementing an interface and a method inside it with explicit interface specifier. - // We want to be sure that adding the method will not tirgger a rude edit as it happens if adding a single method with explicit interface specifier. edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "class C", GetResource("class"))], capabilities: EditAndContinueCapabilities.NewTypeDefinition); } @@ -2312,15 +2374,28 @@ void F() {} public void Type_Generic_InsertMembers_Reloadable() { var src1 = ReloadableAttributeSrc + @" +interface IExplicit +{ + void F() {} +} + [CreateNewOnMetadataUpdate] -class C +class C : IExplicit { + void IExplicit.F() {} } "; var src2 = ReloadableAttributeSrc + @" +interface IExplicit +{ + void F() {} +} + [CreateNewOnMetadataUpdate] -class C +class C : IExplicit { + void IExplicit.F() {} + void M() {} int P1 { get; set; } int P2 { get => 1; set {} } @@ -2337,6 +2412,10 @@ class D {} var edits = GetTopEdits(src1, src2); edits.VerifySemantics( [SemanticEdit(SemanticEditKind.Replace, c => c.GetMember("C"))], + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, "void M()", "CreateNewOnMetadataUpdateAttribute")], capabilities: EditAndContinueCapabilities.NewTypeDefinition); } @@ -7534,7 +7613,7 @@ public async Task WaitAsync() }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); VerifyPreserveLocalVariables(edits, preserveLocalVariables: false); } @@ -9667,7 +9746,7 @@ public void MethodUpdate_AddYieldReturn() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + capabilities: EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation); VerifyPreserveLocalVariables(edits, preserveLocalVariables: false); } @@ -10730,8 +10809,7 @@ public void Constructor_Parameter_AddAttribute_Record_ReplacingCustomPropertyWit capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Parameter_Update_TypeOrRefKind_RuntimeTypeChanged( [CombinatorialValues("int", "in byte", "ref byte", "out byte", "ref readonly byte")] string type, bool direction) @@ -12836,8 +12914,7 @@ public void Constructor_Instance_Insert_ReplacingExplicitWithDefault_WithStackAl ]); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Insert_UpdatingImplicit( [CombinatorialValues("record", "class")] string keyword, [CombinatorialValues("public", "protected")] string accessibility) @@ -12856,8 +12933,7 @@ public void Constructor_Instance_Insert_UpdatingImplicit( capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Insert_UpdatingImplicit_Partial( [CombinatorialValues("record", "class")] string keyword, [CombinatorialValues("public", "protected")] string accessibility) @@ -12882,8 +12958,7 @@ public void Constructor_Instance_Insert_UpdatingImplicit_Partial( ]); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Insert_AddingParameterless( [CombinatorialValues("record", "class")] string keyword, [CombinatorialValues("public", "internal", "private", "protected", "private protected", "internal protected")] string accessibility) @@ -12899,8 +12974,7 @@ public void Constructor_Instance_Insert_AddingParameterless( capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Insert_AddingParameterless_Primary( [CombinatorialValues("record", "class")] string keyword, [CombinatorialValues("public", "internal", "private", "protected", "private protected", "internal protected")] string accessibility) @@ -12916,8 +12990,7 @@ public void Constructor_Instance_Insert_AddingParameterless_Primary( capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Insert_AddingParameterless_Partial( [CombinatorialValues("record", "class")] string keyword, [CombinatorialValues("public", "internal", "private", "protected", "private protected", "internal protected")] string accessibility) @@ -12943,8 +13016,7 @@ public void Constructor_Instance_Insert_AddingParameterless_Partial( capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Insert_AddingParameterless_Partial_Primary( [CombinatorialValues("record", "class")] string keyword, [CombinatorialValues("public", "internal", "private", "protected", "private protected", "internal protected")] string accessibility) @@ -12970,8 +13042,7 @@ public void Constructor_Instance_Insert_AddingParameterless_Partial_Primary( capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Insert_ReplacingSynthesizedWithCustom_ChangingAccessibilty( [CombinatorialValues("record", "class")] string keyword, [CombinatorialValues("", "private", "protected", "internal", "private protected", "internal protected")] string accessibility) @@ -12988,8 +13059,7 @@ public void Constructor_Instance_Insert_ReplacingSynthesizedWithCustom_ChangingA capabilities: EditAndContinueCapabilities.Baseline); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Insert_ReplacingSynthesizedWithCustom_ChangingAccessibilty_AbstractType( [CombinatorialValues("record", "class")] string keyword, [CombinatorialValues("", "private", "public", "internal", "private protected", "internal protected")] string accessibility) @@ -13161,8 +13231,7 @@ public void Constructor_Instance_Delete_SemanticError() Diagnostic(RudeEditKind.ChangingAccessibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Delete_UpdatingImplicit( [CombinatorialValues("record", "class")] string keyword, [CombinatorialValues("public", "protected")] string accessibility) @@ -13213,8 +13282,7 @@ class C ]); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Delete_WithParameters([CombinatorialValues("record", "class")] string keyword) { var src1 = keyword + " C { public C(int x) { } }"; @@ -13227,8 +13295,7 @@ public void Constructor_Instance_Delete_WithParameters([CombinatorialValues("rec capabilities: EditAndContinueCapabilities.Baseline); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Delete_Primary_WithParameters([CombinatorialValues("record", "class")] string keyword) { var src1 = keyword + " C(int a) { public C(bool b) { } }"; @@ -13308,8 +13375,7 @@ public void Constructor_Instance_Delete_Public_PartialWithInitializerUpdate() ]); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Delete_ReplacingCustomWithSynthesized( [CombinatorialValues("record", "class")] string keyword) { @@ -13322,8 +13388,7 @@ public void Constructor_Instance_Delete_ReplacingCustomWithSynthesized( SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters is []), preserveLocalVariables: true)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Delete_ReplacingCustomWithSynthesized_ChangingAccessibility( [CombinatorialValues("record", "class")] string keyword, [CombinatorialValues("", "private", "protected", "internal", "private protected", "internal protected")] string accessibility) @@ -13340,8 +13405,7 @@ public void Constructor_Instance_Delete_ReplacingCustomWithSynthesized_ChangingA capabilities: EditAndContinueCapabilities.Baseline); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Delete_ReplacingCustomWithSynthesized_AbstractType( [CombinatorialValues("record", "class")] string keyword) { @@ -13354,8 +13418,7 @@ public void Constructor_Instance_Delete_ReplacingCustomWithSynthesized_AbstractT SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Delete_ReplacingCustomWithSynthesized_AbstractType_ChangingAccessibility( [CombinatorialValues("record", "class")] string keyword, [CombinatorialValues("", "private", "public", "internal", "private protected", "internal protected")] string accessibility) @@ -13420,8 +13483,7 @@ public void Constructor_Instance_Delete_Primary_ReplacingWithSynthesized_Record( SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C"))); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Delete_Primary_ReplacingWithRegular( [CombinatorialValues("record", "class")] string keyword, [CombinatorialValues("", "private", "protected", "internal", "private protected", "internal protected")] string accessibility) @@ -13438,8 +13500,7 @@ public void Constructor_Instance_Delete_Primary_ReplacingWithRegular( capabilities: EditAndContinueCapabilities.Baseline); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void Constructor_Instance_Delete_Primary_ReplacingWithRegular_AbstractType( [CombinatorialValues("record", "class")] string keyword, [CombinatorialValues("", "private", "public", "internal", "private protected", "internal protected")] string accessibility) @@ -16055,8 +16116,7 @@ class C : B ]); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void MemberInitializer_Update_Lambda_ConstructorWithMemberInitializers_ReplacingCustomWithSynthesized_Primary( [CombinatorialValues("", "()")] string initializer, bool isInsert) { diff --git a/src/Features/CSharpTest/EnableNullable/EnableNullableTests.cs b/src/Features/CSharpTest/EnableNullable/EnableNullableTests.cs index 55ab94f4c6afa..b01e956ea20eb 100644 --- a/src/Features/CSharpTest/EnableNullable/EnableNullableTests.cs +++ b/src/Features/CSharpTest/EnableNullable/EnableNullableTests.cs @@ -9,15 +9,19 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.CodeRefactorings.EnableNullable; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Roslyn.Utilities; using Xunit; -using VerifyCS = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.CSharpCodeRefactoringVerifier< - Microsoft.CodeAnalysis.CSharp.CodeRefactorings.EnableNullable.EnableNullableCodeRefactoringProvider>; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeActions.EnableNullable { + using VerifyCS = CSharpCodeRefactoringVerifier; + + [UseExportProvider] public class EnableNullableTests { private static readonly Func s_enableNullableInFixedSolution = diff --git a/src/Features/CSharpTest/GenerateComparisonOperators/GenerateComparisonOperatorsTests.cs b/src/Features/CSharpTest/GenerateComparisonOperators/GenerateComparisonOperatorsTests.cs index c8b37d02f525e..5dd27ea025918 100644 --- a/src/Features/CSharpTest/GenerateComparisonOperators/GenerateComparisonOperatorsTests.cs +++ b/src/Features/CSharpTest/GenerateComparisonOperators/GenerateComparisonOperatorsTests.cs @@ -401,7 +401,7 @@ class C : IComparable, IComparable [||] } """; - string GetFixedCode(string type) + static string GetFixedCode(string type) => $@"using System; class C : IComparable, IComparable diff --git a/src/Features/CSharpTest/GenerateDefaultConstructors/GenerateDefaultConstructorsTests.cs b/src/Features/CSharpTest/GenerateDefaultConstructors/GenerateDefaultConstructorsTests.cs index 852c2887faf45..f53fc904d6e85 100644 --- a/src/Features/CSharpTest/GenerateDefaultConstructors/GenerateDefaultConstructorsTests.cs +++ b/src/Features/CSharpTest/GenerateDefaultConstructors/GenerateDefaultConstructorsTests.cs @@ -22,6 +22,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.GenerateDefaultConstruc using VerifyRefactoring = CSharpCodeRefactoringVerifier< GenerateDefaultConstructorsCodeRefactoringProvider>; + [UseExportProvider] public class GenerateDefaultConstructorsTests { private static async Task TestRefactoringAsync(string source, string fixedSource, int index = 0) diff --git a/src/Features/CSharpTest/GenerateFromMembers/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersTests.cs b/src/Features/CSharpTest/GenerateFromMembers/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersTests.cs index c2bce6842af06..163e0e096f175 100644 --- a/src/Features/CSharpTest/GenerateFromMembers/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersTests.cs +++ b/src/Features/CSharpTest/GenerateFromMembers/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersTests.cs @@ -14,6 +14,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.GenerateFromMembers.Add { using VerifyCS = CSharpCodeRefactoringVerifier; + [UseExportProvider] [Trait(Traits.Feature, Traits.Features.CodeActionsAddConstructorParametersFromMembers)] public class AddConstructorParametersFromMembersTests { diff --git a/src/Features/CSharpTest/InlineTemporary/InlineTemporaryTests.cs b/src/Features/CSharpTest/InlineTemporary/InlineTemporaryTests.cs index d23dbb7d5117b..12a86d1e86b75 100644 --- a/src/Features/CSharpTest/InlineTemporary/InlineTemporaryTests.cs +++ b/src/Features/CSharpTest/InlineTemporary/InlineTemporaryTests.cs @@ -6,7 +6,6 @@ using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.CodeRefactorings.InlineTemporary; -using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.UnitTests; @@ -16,7 +15,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings.InlineTemporary { [Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)] - public class InlineTemporaryTests : AbstractCSharpCodeActionTest_NoEditor + public sealed class InlineTemporaryTests : AbstractCSharpCodeActionTest_NoEditor { protected override CodeRefactoringProvider CreateCodeRefactoringProvider(TestWorkspace workspace, TestParameters parameters) => new CSharpInlineTemporaryCodeRefactoringProvider(); @@ -253,16 +252,23 @@ public void M() [Fact] public async Task Conversion_NoConversion() { - await TestFixOneAsync( + await TestAsync( """ - { int [||]x = 3; + class C + { + void F(){ int [||]x = 3; x.ToString(); } + } + """, + """ + class C + { + void F(){ + 3.ToString(); } + } """, - """ - { - 3.ToString(); } - """); + CSharpParseOptions.Default); } [Fact] @@ -690,7 +696,7 @@ class Program static void Main() { int x = 2; - Bar(x < x, x > 1+2); + Bar(x < x, x > 1 + 2); } static void Bar(object a, object b) @@ -5739,7 +5745,7 @@ public class Goo { public void Bar() { - var target = new List<object;gt>(); + var target = new List<object>(); var [||]newItems = new List<Goo>(); target.AddRange(newItems); } @@ -5763,7 +5769,7 @@ public class Goo { public void Bar() { - var target = new List<object;gt>(); + var target = new List<object>(); target.AddRange(new List<Goo>()); } } diff --git a/src/Features/CSharpTest/IntroduceVariable/IntroduceVariableTests.cs b/src/Features/CSharpTest/IntroduceVariable/IntroduceVariableTests.cs index 82cf8a801c445..dd30dd77f6304 100644 --- a/src/Features/CSharpTest/IntroduceVariable/IntroduceVariableTests.cs +++ b/src/Features/CSharpTest/IntroduceVariable/IntroduceVariableTests.cs @@ -5977,7 +5977,7 @@ void M() { int x = 1; int {|Rename:y1|} = C.y; - var t = new { y= y1, y= y1 }; // this is an error already + var t = new { y = y1, y = y1 }; // this is an error already } } """; diff --git a/src/Features/CSharpTest/InvertIf/InvertIfTests.Elseless.cs b/src/Features/CSharpTest/InvertIf/InvertIfTests.Elseless.cs index 856f322b68f74..b69f6484d85c0 100644 --- a/src/Features/CSharpTest/InvertIf/InvertIfTests.Elseless.cs +++ b/src/Features/CSharpTest/InvertIf/InvertIfTests.Elseless.cs @@ -3,177 +3,90 @@ // See the LICENSE file in the project root for more information. using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.InvertIf +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.InvertIf; + +public partial class InvertIfTests { - [Trait(Traits.Feature, Traits.Features.CodeActionsInvertIf)] - public partial class InvertIfTests + [Fact] + public async Task IfWithoutElse_MoveIfBodyToElseClause1() { - [Fact] - public async Task IfWithoutElse_MoveIfBodyToElseClause1() - { - await TestInRegularAndScriptAsync( - """ - class C + await TestAsync(""" + class C + { + void M() { - void M() + switch (o) { - switch (o) - { - case 1: - if (c) - { - [||]if (c) - { - return 1; - } - } - return 2; - } - } - } - """, - """ - class C - { - void M() - { - switch (o) - { - case 1: - if (c) - { - [||]if (!c) - { - } - else - { - return 1; - } - } - return 2; - } - } - } - """); - } - - [Fact] - public async Task IfWithoutElse_MoveIfBodyToElseClause2() - { - await TestInRegularAndScriptAsync( - """ - class C - { - void M() - { - switch (o) - { - case 1: + case 1: + if (c) + { [||]if (c) { - f(); + return 1; } - g(); - g(); - break; - } + } + return 2; } } - """, - """ - class C + } + """, """ + class C + { + void M() { - void M() + switch (o) { - switch (o) - { - case 1: + case 1: + if (c) + { [||]if (!c) { } else { - f(); + return 1; } - g(); - g(); - break; - } - } - } - """); - } - - [Fact] - public async Task IfWithoutElse_MoveIfBodyToElseClause3() - { - await TestInRegularAndScriptAsync( - """ - class C - { - void M() - { - [||]if (c) - { - f(); - } - g(); - g(); - } - } - """, - """ - class C - { - void M() - { - if (!c) - { - } - else - { - f(); - } - g(); - g(); + } + return 2; } } - """); - } + } + """); + } - [Fact] - public async Task IfWithoutElse_MoveIfBodyToElseClause4() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact] + public async Task IfWithoutElse_MoveIfBodyToElseClause2() + { + await TestAsync(""" + class C + { + void M() { - bool M() + switch (o) { - if (c) - { + case 1: [||]if (c) { f(); } g(); - } - return false; + g(); + break; } } - """, - """ - class C + } + """, """ + class C + { + void M() { - bool M() + switch (o) { - if (c) - { - if (!c) + case 1: + [||]if (!c) { } else @@ -181,36 +94,74 @@ bool M() f(); } g(); - } - return false; + g(); + break; + } + } + } + """); + } + + [Fact] + public async Task IfWithoutElse_MoveIfBodyToElseClause3() + { + await TestAsync(""" + class C + { + void M() + { + [||]if (c) + { + f(); + } + g(); + g(); + } + } + """, """ + class C + { + void M() + { + if (!c) + { + } + else + { + f(); } + g(); + g(); } - """); - } + } + """); + } - [Fact] - public async Task IfWithoutElse_MoveIfBodyToElseClause5() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact] + public async Task IfWithoutElse_MoveIfBodyToElseClause4() + { + await TestAsync(""" + class C + { + bool M() { - void M() + if (c) { [||]if (c) { f(); } - - g(); g(); } + return false; } - """, - """ - class C + } + """, """ + class C + { + bool M() { - void M() + if (c) { if (!c) { @@ -219,435 +170,487 @@ void M() { f(); } - - g(); g(); } + return false; + } + } + """); + } + + [Fact] + public async Task IfWithoutElse_MoveIfBodyToElseClause5() + { + await TestAsync(""" + class C + { + void M() + { + [||]if (c) + { + f(); + } + + g(); + g(); } - """); - } + } + """, """ + class C + { + void M() + { + if (!c) + { + } + else + { + f(); + } - [Fact] - public async Task IfWithoutElse_MoveIfBodyToElseClause6() - { - await TestInRegularAndScriptAsync( - """ - class C + g(); + g(); + } + } + """); + } + + [Fact] + public async Task IfWithoutElse_MoveIfBodyToElseClause6() + { + await TestAsync(""" + class C + { + void M() { - void M() + switch (o) { - switch (o) - { - case 1: - [||]if (c) + case 1: + [||]if (c) + { + if (c) { - if (c) - { - f(); - return 1; - } + f(); + return 1; } + } - f(); - return 2; - } + f(); + return 2; } } - """, - """ - class C + } + """, """ + class C + { + void M() { - void M() + switch (o) { - switch (o) - { - case 1: - [||]if (!c) - { - } - else + case 1: + [||]if (!c) + { + } + else + { + if (c) { - if (c) - { - f(); - return 1; - } + f(); + return 1; } + } - f(); - return 2; - } + f(); + return 2; } } - """); - } + } + """); + } - [Fact] - public async Task IfWithoutElse_MoveIfBodyToElseClause7() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact] + public async Task IfWithoutElse_MoveIfBodyToElseClause7() + { + await TestAsync(""" + class C + { + void M() { - void M() + switch (o) { - switch (o) - { - case 1: - if (c) + case 1: + if (c) + { + [||]if (c) { - [||]if (c) - { - f(); - return 1; - } + f(); + return 1; } + } - f(); - return 2; - } + f(); + return 2; } } - """, - """ - class C + } + """, """ + class C + { + void M() { - void M() + switch (o) { - switch (o) - { - case 1: - if (c) + case 1: + if (c) + { + if (!c) + { + } + else { - if (!c) - { - } - else - { - f(); - return 1; - } + f(); + return 1; } + } - f(); - return 2; - } + f(); + return 2; } } - """); - } + } + """); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40909")] - public async Task IfWithoutElse_MoveIfBodyToElseClause8() - { - await TestInRegularAndScriptAsync( - """ - using System.Diagnostics; - class C + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40909")] + public async Task IfWithoutElse_MoveIfBodyToElseClause8() + { + await TestAsync(""" + using System.Diagnostics; + class C + { + private static bool IsFalse(bool val) { - private static bool IsFalse(bool val) { + [|if|] (!val) { - [|if|] (!val) - { - return true; - } - Debug.Assert(val); + return true; } - return false; + Debug.Assert(val); } + return false; } - """, - """ - using System.Diagnostics; - class C + } + """, """ + using System.Diagnostics; + class C + { + private static bool IsFalse(bool val) { - private static bool IsFalse(bool val) { + if (val) { - if (val) - { - } - else - { - return true; - } - Debug.Assert(val); } - return false; + else + { + return true; + } + Debug.Assert(val); } + return false; } - """); - } + } + """); + } - [Fact] - public async Task IfWithoutElse_MoveSubsequentStatementsToIfBody1() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact] + public async Task IfWithoutElse_MoveSubsequentStatementsToIfBody1() + { + await TestAsync(""" + class C + { + void M() { - void M() + foreach (var item in list) { - foreach (var item in list) + [||]if (!c) + { + continue; + } + // comments + f(); + } + } + } + """, """ + class C + { + void M() + { + foreach (var item in list) + { + [||]if (c) { - [||]if (!c) - { - continue; - } // comments f(); } } } - """, - """ - class C + } + """); + } + + [Fact] + public async Task IfWithoutElse_MoveSubsequentStatementsToIfBody2() + { + await TestAsync(""" + class C + { + void M() { - void M() + while (c) { - foreach (var item in list) + if (c) { [||]if (c) { - // comments - f(); + continue; } + if (c()) + return; } } } - """); - } - - [Fact] - public async Task IfWithoutElse_MoveSubsequentStatementsToIfBody2() - { - await TestInRegularAndScriptAsync( - """ - class C + } + """, """ + class C + { + void M() { - void M() + while (c) { - while (c) + if (c) { - if (c) + [||]if (!c) { - [||]if (c) - { - continue; - } if (c()) return; } } } } - """, - """ - class C + } + """); + } + + [Fact] + public async Task IfWithoutElse_MoveSubsequentStatementsToIfBody3() + { + await TestAsync(""" + class C + { + void M() { - void M() + while (c) { - while (c) { - if (c) + [||]if (c) { - [||]if (!c) - { - if (c()) - return; - } + continue; } + if (c()) + return; } } } - """); - } - - [Fact] - public async Task IfWithoutElse_MoveSubsequentStatementsToIfBody3() - { - await TestInRegularAndScriptAsync( - """ - class C + } + """, """ + class C + { + void M() { - void M() + while (c) { - while (c) { + [||]if (!c) { - [||]if (c) - { - continue; - } if (c()) return; } } } } - """, - """ - class C + } + """); + } + + [Fact] + public async Task IfWithoutElse_SwapIfBodyWithSubsequentStatements1() + { + await TestAsync(""" + class C + { + void M() { - void M() + foreach (var item in list) { - while (c) - { - { - [||]if (!c) - { - if (c()) - return; - } - } - } + [||]if (c) + break; + return; } } - """); - } - - [Fact] - public async Task IfWithoutElse_SwapIfBodyWithSubsequentStatements1() - { - await TestInRegularAndScriptAsync( - """ - class C + } + """, """ + class C + { + void M() { - void M() + foreach (var item in list) { - foreach (var item in list) - { - [||]if (c) - break; + [||]if (!c) return; - } + break; } } - """, - """ - class C + } + """); + } + + [Fact] + public async Task IfWithoutElse_SwapIfBodyWithSubsequentStatements2() + { + await TestAsync(""" + class C + { + void M() { - void M() + foreach (var item in list) { - foreach (var item in list) + [||]if (!c) { - [||]if (!c) - return; - break; + return; } + break; } } - """); - } - - [Fact] - public async Task IfWithoutElse_SwapIfBodyWithSubsequentStatements2() - { - await TestInRegularAndScriptAsync( - """ - class C + } + """, """ + class C + { + void M() { - void M() + foreach (var item in list) { - foreach (var item in list) + [||]if (c) { - [||]if (!c) - { - return; - } break; } + return; } } - """, - """ - class C + } + """); + } + + [Fact] + public async Task IfWithoutElse_WithElseClause1() + { + await TestAsync(""" + class C + { + void M() { - void M() + foreach (var item in list) { - foreach (var item in list) - { - [||]if (c) - { - break; - } + [||]if (!c) return; - } + f(); } } - """); - } - - [Fact] - public async Task IfWithoutElse_WithElseClause1() - { - await TestInRegularAndScriptAsync( - """ - class C + } + """, """ + class C + { + void M() { - void M() + foreach (var item in list) { - foreach (var item in list) - { - [||]if (!c) - return; + if (c) f(); - } + else + return; } } - """, - """ - class C + } + """); + } + + [Fact] + public async Task IfWithoutElse_WithNegatedCondition1() + { + await TestAsync(""" + class C + { + void M() + { + [||]if (c) { } + } + } + """, """ + class C + { + void M() { - void M() - { - foreach (var item in list) - { - if (c) - f(); - else - return; - } - } + if (!c) { } } - """); - } + } + """); + } - [Fact] - public async Task IfWithoutElse_WithNegatedCondition1() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact] + public async Task IfWithoutElse_WithNearmostJumpStatement1() + { + await TestAsync(""" + class C + { + void M() { - void M() + foreach (var item in list) { - [||]if (c) { } + [||]if (c) + { + f(); + } } } - """, - """ - class C + } + """, """ + class C + { + void M() { - void M() + foreach (var item in list) { - if (!c) { } + [||]if (!c) + { + continue; + } + f(); } } - """); - } + } + """); + } - [Fact] - public async Task IfWithoutElse_WithNearmostJumpStatement1() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact] + public async Task IfWithoutElse_WithNearmostJumpStatement2() + { + await TestAsync(""" + class C + { + void M() { - void M() + foreach (var item in list) { - foreach (var item in list) { [||]if (c) { @@ -656,13 +659,14 @@ void M() } } } - """, - """ - class C + } + """, """ + class C + { + void M() { - void M() + foreach (var item in list) { - foreach (var item in list) { [||]if (!c) { @@ -672,252 +676,205 @@ void M() } } } - """); - } + } + """); + } - [Fact] - public async Task IfWithoutElse_WithNearmostJumpStatement2() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact] + public async Task IfWithoutElse_WithNearmostJumpStatement3() + { + await TestAsync(""" + class C + { + void M() { - void M() + [||]if (c) { - foreach (var item in list) - { - { - [||]if (c) - { - f(); - } - } - } + f(); } } - """, - """ - class C + } + """, """ + class C + { + void M() { - void M() + [||]if (!c) { - foreach (var item in list) - { - { - [||]if (!c) - { - continue; - } - f(); - } - } + return; } + f(); } - """); - } + } + """); + } - [Fact] - public async Task IfWithoutElse_WithNearmostJumpStatement3() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact] + public async Task IfWithoutElse_WithNearmostJumpStatement4() + { + await TestAsync(""" + class C + { + void M() { - void M() + for (;;) { [||]if (c) { - f(); + break; } } } - """, - """ - class C + } + """, """ + class C + { + void M() { - void M() + for (;;) { [||]if (!c) { - return; + continue; } - f(); + break; } } - """); - } + } + """); + } - [Fact] - public async Task IfWithoutElse_WithNearmostJumpStatement4() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact] + public async Task IfWithoutElse_WithSubsequentExitPointStatement1() + { + await TestAsync(""" + class C + { + void M() { - void M() + switch (o) { - for (;;) - { + case 1: [||]if (c) { - break; + f(); + f(); } - } + break; } } - """, - """ - class C + } + """, """ + class C + { + void M() { - void M() + switch (o) { - for (;;) - { + case 1: [||]if (!c) { - continue; + break; } + f(); + f(); break; - } } } - """); - } + } + """); + } - [Fact] - public async Task IfWithoutElse_WithSubsequentExitPointStatement1() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact] + public async Task IfWithoutElse_WithSubsequentExitPointStatement2() + { + await TestAsync(""" + class C + { + void M() { - void M() + switch (o) { - switch (o) - { - case 1: - [||]if (c) + case 1: + [||]if (c) + { + if (c) { - f(); - f(); + return 1; } - break; - } + } + + return 2; } } - """, - """ - class C + } + """, """ + class C + { + void M() { - void M() + switch (o) { - switch (o) - { - case 1: - [||]if (!c) - { - break; - } - f(); - f(); - break; - } + case 1: + [||]if (!c) + { + return 2; + } + if (c) + { + return 1; + } + + return 2; } } - """); - } + } + """); + } - [Fact] - public async Task IfWithoutElse_WithSubsequentExitPointStatement2() - { - await TestInRegularAndScriptAsync( - """ - class C + [Theory] + [InlineData("get")] + [InlineData("set")] + [InlineData("init")] + public async Task IfWithoutElse_InPropertyAccessors(string accessor) + { + await TestAsync($$""" + class C + { + private bool _b; + + public string Prop { - void M() + {{accessor}} { - switch (o) + [||]if (_b) { - case 1: - [||]if (c) - { - if (c) - { - return 1; - } - } - - return 2; + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); } } } - """, - """ - class C + } + """, $$""" + class C + { + private bool _b; + + public string Prop { - void M() + {{accessor}} { - switch (o) + if (!_b) { - case 1: - [||]if (!c) - { - return 2; - } - if (c) - { - return 1; - } - - return 2; + return; } + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); } } - """); - } - - [Theory] - [InlineData("get")] - [InlineData("set")] - [InlineData("init")] - public async Task IfWithoutElse_InPropertyAccessors(string accessor) - { - await TestInRegularAndScriptAsync( -$@"class C -{{ - private bool _b; - - public string Prop - {{ - {accessor} - {{ - [||]if (_b) - {{ - Console.WriteLine(); - Console.WriteLine(); - Console.WriteLine(); - }} - }} - }} -}}", -$@"class C -{{ - private bool _b; - - public string Prop - {{ - {accessor} - {{ - if (!_b) - {{ - return; - }} - Console.WriteLine(); - Console.WriteLine(); - Console.WriteLine(); - }} - }} -}}"); - } + } + """); } } diff --git a/src/Features/CSharpTest/InvertIf/InvertIfTests.cs b/src/Features/CSharpTest/InvertIf/InvertIfTests.cs index 1fec5e17cbf1b..9dee33f7b2f7e 100644 --- a/src/Features/CSharpTest/InvertIf/InvertIfTests.cs +++ b/src/Features/CSharpTest/InvertIf/InvertIfTests.cs @@ -3,33 +3,28 @@ // See the LICENSE file in the project root for more information. using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.InvertIf; -using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Testing; using Roslyn.Test.Utilities; using Xunit; -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.InvertIf +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.InvertIf; + +[UseExportProvider, Trait(Traits.Feature, Traits.Features.CodeActionsInvertIf)] +public partial class InvertIfTests { - [Trait(Traits.Feature, Traits.Features.CodeActionsInvertIf)] - public partial class InvertIfTests : AbstractCSharpCodeActionTest_NoEditor + private static Task TestInsideMethodAsync( + string initial, + string expected) { - private async Task TestFixOneAsync( - string initial, - string expected) - { - await TestInRegularAndScriptAsync(CreateTreeText(initial), CreateTreeText(expected)); - } - - protected override CodeRefactoringProvider CreateCodeRefactoringProvider(TestWorkspace workspace, TestParameters parameters) - => new CSharpInvertIfCodeRefactoringProvider(); + return TestAsync(CreateTreeText(initial), CreateTreeText(expected)); - private static string CreateTreeText(string initial) + static string CreateTreeText(string initial) { - return - """ + return $$""" class A { bool a = true; @@ -39,428 +34,499 @@ class A void Goo() { - """ + initial + """ + {{initial}} } } """; } + } - [Fact] - public async Task TestSingleLine_Identifier() + private static async Task TestAsync(string initial, string expected, LanguageVersion languageVersion = LanguageVersion.Latest) + { + await new CSharpCodeRefactoringVerifier.Test { - await TestFixOneAsync( + TestCode = initial, + FixedCode = expected, + LanguageVersion = languageVersion, + CompilerDiagnostics = CompilerDiagnostics.None, + }.RunAsync(); + } + + [Fact] + public async Task TestSingleLine_Identifier() + { + await TestInsideMethodAsync( @"[||]if (a) { a(); } else { b(); }", @"if (!a) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_IdentifierWithTrivia() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_IdentifierWithTrivia() + { + await TestInsideMethodAsync( @"[||]if /*0*/(/*1*/a/*2*/)/*3*/ { a(); } else { b(); }", @"if /*0*/(/*1*/!a/*2*/)/*3*/ { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_NotIdentifier() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_NotIdentifier() + { + await TestInsideMethodAsync( @"[||]if (!a) { a(); } else { b(); }", @"if (a) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_NotIdentifierWithTrivia() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_NotIdentifierWithTrivia() + { + await TestInsideMethodAsync( @"[||]if /*0*/(/*1*/!/*1b*/a/*2*/)/*3*/ { a(); } else { b(); }", @"if /*0*/(/*1*/a/*2*/)/*3*/ { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_EqualsEquals() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_EqualsEquals() + { + await TestInsideMethodAsync( @"[||]if (a == b) { a(); } else { b(); }", @"if (a != b) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_NotEquals() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_NotEquals() + { + await TestInsideMethodAsync( @"[||]if (a != b) { a(); } else { b(); }", @"if (a == b) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_GreaterThan() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_GreaterThan() + { + await TestInsideMethodAsync( @"[||]if (a > b) { a(); } else { b(); }", @"if (a <= b) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_GreaterThanEquals() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_GreaterThanEquals() + { + await TestInsideMethodAsync( @"[||]if (a >= b) { a(); } else { b(); }", @"if (a < b) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_LessThan() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_LessThan() + { + await TestInsideMethodAsync( @"[||]if (a < b) { a(); } else { b(); }", @"if (a >= b) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_LessThanEquals() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_LessThanEquals() + { + await TestInsideMethodAsync( @"[||]if (a <= b) { a(); } else { b(); }", @"if (a > b) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_DoubleParentheses() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_DoubleParentheses() + { + await TestInsideMethodAsync( @"[||]if ((a)) { a(); } else { b(); }", @"if (!a) { b(); } else { a(); }"); - } + } - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/26427")] - public async Task TestSingleLine_DoubleParenthesesWithInnerTrivia() - { - await TestFixOneAsync( + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/26427")] + public async Task TestSingleLine_DoubleParenthesesWithInnerTrivia() + { + await TestInsideMethodAsync( @"[||]if ((/*1*/a/*2*/)) { a(); } else { b(); }", @"if (/*1*/!a/*2*/) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_DoubleParenthesesWithMiddleTrivia() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_DoubleParenthesesWithMiddleTrivia() + { + await TestInsideMethodAsync( @"[||]if (/*1*/(a)/*2*/) { a(); } else { b(); }", @"if (/*1*/!a/*2*/) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_DoubleParenthesesWithOutsideTrivia() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_DoubleParenthesesWithOutsideTrivia() + { + await TestInsideMethodAsync( @"[||]if /*before*/((a))/*after*/ { a(); } else { b(); }", @"if /*before*/(!a)/*after*/ { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_Is() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_Is() + { + await TestInsideMethodAsync( @"[||]if (a is Goo) { a(); } else { b(); }", @"if (a is not Goo) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_MethodCall() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_MethodCall() + { + await TestInsideMethodAsync( @"[||]if (a.Goo()) { a(); } else { b(); }", @"if (!a.Goo()) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_Or() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_Or() + { + await TestInsideMethodAsync( @"[||]if (a || b) { a(); } else { b(); }", @"if (!a && !b) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_Or2() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_Or2() + { + await TestInsideMethodAsync( @"[||]if (!a || !b) { a(); } else { b(); }", @"if (a && b) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_Or3() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_Or3() + { + await TestInsideMethodAsync( @"[||]if (!a || b) { a(); } else { b(); }", @"if (a && !b) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_Or4() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_Or4() + { + await TestInsideMethodAsync( @"[||]if (a | b) { a(); } else { b(); }", @"if (!a & !b) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_And() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_And() + { + await TestInsideMethodAsync( @"[||]if (a && b) { a(); } else { b(); }", @"if (!a || !b) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_And2() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_And2() + { + await TestInsideMethodAsync( @"[||]if (!a && !b) { a(); } else { b(); }", @"if (a || b) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_And3() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_And3() + { + await TestInsideMethodAsync( @"[||]if (!a && b) { a(); } else { b(); }", @"if (a || !b) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_And4() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_And4() + { + await TestInsideMethodAsync( @"[||]if (a & b) { a(); } else { b(); }", @"if (!a | !b) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_ParenthesizeAndForPrecedence() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_ParenthesizeAndForPrecedence() + { + await TestInsideMethodAsync( @"[||]if (a && b || c) { a(); } else { b(); }", @"if ((!a || !b) && !c) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_Plus() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_Plus() + { + await TestInsideMethodAsync( @"[||]if (a + b) { a(); } else { b(); }", @"if (!(a + b)) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_True() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_True() + { + await TestInsideMethodAsync( @"[||]if (true) { a(); } else { b(); }", @"if (false) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_TrueWithTrivia() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_TrueWithTrivia() + { + await TestInsideMethodAsync( @"[||]if (/*1*/true/*2*/) { a(); } else { b(); }", @"if (/*1*/false/*2*/) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_False() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_False() + { + await TestInsideMethodAsync( @"[||]if (false) { a(); } else { b(); }", @"if (true) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_OtherLiteralExpression() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_OtherLiteralExpression() + { + await TestInsideMethodAsync( @"[||]if (literalexpression) { a(); } else { b(); }", @"if (!literalexpression) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_TrueAndFalse() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_TrueAndFalse() + { + await TestInsideMethodAsync( @"[||]if (true && false) { a(); } else { b(); }", @"if (false || true) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_NoCurlyBraces() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_NoCurlyBraces() + { + await TestInsideMethodAsync( @"[||]if (a) a(); else b();", @"if (!a) b(); else a();"); - } + } - [Fact] - public async Task TestSingleLine_CurlyBracesOnIf() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_CurlyBracesOnIf() + { + await TestInsideMethodAsync( @"[||]if (a) { a(); } else b();", @"if (!a) b(); else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_CurlyBracesOnElse() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_CurlyBracesOnElse() + { + await TestInsideMethodAsync( @"[||]if (a) a(); else { b(); }", @"if (!a) { b(); } else a();"); - } + } - [Fact] - public async Task TestSingleLine_IfElseIf() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_IfElseIf() + { + await TestInsideMethodAsync( @"[||]if (a) { a(); } else if (b) { b(); }", @"if (!a) { if (b) { b(); } } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_IfElseIfElse() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_IfElseIfElse() + { + await TestInsideMethodAsync( @"[||]if (a) { a(); } else if (b) { b(); } else { c(); }", @"if (!a) { if (b) { b(); } else { c(); } } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_CompoundConditional() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_CompoundConditional() + { + await TestInsideMethodAsync( @"[||]if (((a == b) && (c != d)) || ((e < f) && (!g))) { a(); } else { b(); }", @"if ((a != b || c == d) && (e >= f || g)) { b(); } else { a(); }"); - } + } - [Fact] - public async Task TestSingleLine_Trivia() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_Trivia() + { + await TestInsideMethodAsync( @"[||]if /*1*/ (a) /*2*/ { /*3*/ a() /*4*/; /*5*/ } /*6*/ else if /*7*/ (b) /*8*/ { /*9*/ b(); /*10*/ } /*11*/ else /*12*/ { /*13*/ c(); /*14*/} /*15*/", @"if /*1*/ (!a) /*2*/ { if /*7*/ (b) /*8*/ { /*9*/ b(); /*10*/ } /*11*/ else /*12*/ { /*13*/ c(); /*14*/} /*6*/ } else { /*3*/ a() /*4*/; /*5*/ } /*15*/"); - } + } - [Fact] - public async Task TestKeepTriviaWithinExpression_BrokenCode() - { - await TestInRegularAndScriptAsync( - """ - class A + [Fact] + public async Task TestKeepTriviaWithinExpression_BrokenCode() + { + await TestAsync(""" + class A + { + void Goo() { - void Goo() + [||]if (a || + b && + c < // comment + d) { - [||]if (a || - b && - c < // comment - d) - { - a(); - } - else - { - b(); - } + a(); + } + else + { + b(); } } - """, - """ - class A + } + """, """ + class A + { + void Goo() { - void Goo() + if (!a && + (!b || + c >= // comment + d)) { - if (!a && - (!b || - c >= // comment - d)) - { - b(); - } - else - { - a(); - } + b(); + } + else + { + a(); } } - """); - } + } + """); + } - [Fact] - public async Task TestKeepTriviaWithinExpression() - { - await TestInRegularAndScriptAsync( - """ - class A + [Fact] + public async Task TestKeepTriviaWithinExpression() + { + await TestAsync(""" + class A + { + void Goo() { - void Goo() + bool a = true; + bool b = true; + bool c = true; + bool d = true; + + [||]if (a || + b && + c < // comment + d) { - bool a = true; - bool b = true; - bool c = true; - bool d = true; - - [||]if (a || - b && - c < // comment - d) - { - a(); - } - else - { - b(); - } + a(); + } + else + { + b(); } } - """, - """ - class A + } + """, """ + class A + { + void Goo() { - void Goo() + bool a = true; + bool b = true; + bool c = true; + bool d = true; + + if (!a && + (!b || + c >= // comment + d)) + { + b(); + } + else { - bool a = true; - bool b = true; - bool c = true; - bool d = true; + a(); + } + } + } + """); + } - if (!a && - (!b || - c >= // comment - d)) + [Fact] + public async Task TestMultiline_IfElseIfElse() + { + await TestAsync(""" + class A + { + void Goo() + { + [||]if (a) + { + a(); + } + else if (b) + { + b(); + } + else + { + c(); + } + } + } + """, """ + class A + { + void Goo() + { + if (!a) + { + if (b) { b(); } else { - a(); + c(); } } + else + { + a(); + } } - """); - } + } + """); + } - [Fact] - public async Task TestMultiline_IfElseIfElse() - { - await TestInRegularAndScriptAsync( - """ - class A + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35525")] + public async Task TestMultiline_IfElseIfElseSelection1() + { + await TestAsync(""" + class A + { + void Goo() { - void Goo() + [|if (a) { - [||]if (a) - { - a(); - } - else if (b) + a(); + } + else if (b) + { + b(); + } + else + { + c(); + }|] + } + } + """, """ + class A + { + void Goo() + { + if (!a) + { + if (b) { b(); } @@ -469,245 +535,195 @@ void Goo() c(); } } + else + { + a(); + } } - """, - """ - class A + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35525")] + public async Task TestMultiline_IfElseIfElseSelection2() + { + await TestAsync(""" + class A + { + void Goo() { - void Goo() + [|if (a) + { + a(); + }|] + else if (b) { - if (!a) + b(); + } + else + { + c(); + } + } + } + """, """ + class A + { + void Goo() + { + if (!a) + { + if (b) { - if (b) - { - b(); - } - else - { - c(); - } + b(); } else { - a(); + c(); } } + else + { + a(); + } } - """); - } + } + """); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35525")] - public async Task TestMultiline_IfElseIfElseSelection1() - { - await TestInRegularAndScriptAsync( - """ - class A + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35525")] + public async Task TestMultilineMissing_IfElseIfElseSubSelection() + { + var code = """ + class A + { + void Goo() { - void Goo() + if (a) { - [|if (a) - { - a(); - } - else if (b) - { - b(); - } - else - { - c(); - }|] + a(); } - } - """, - """ - class A - { - void Goo() + [|else if (b) { - if (!a) - { - if (b) - { - b(); - } - else - { - c(); - } - } - else - { - a(); - } + b(); } + else + { + c(); + }|] } - """); - } + } + """; - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35525")] - public async Task TestMultiline_IfElseIfElseSelection2() - { - await TestInRegularAndScriptAsync( - """ - class A + await TestAsync(code, code); + } + + [Fact] + public async Task TestMultiline_IfElse() + { + await TestAsync(""" + class A + { + void Goo() { - void Goo() - { - [|if (a) - { - a(); - }|] - else if (b) - { - b(); - } - else - { - c(); - } - } + [||]if (foo) + bar(); + else + if (baz) + Quux(); } - """, - """ - class A + } + """, """ + class A + { + void Goo() { - void Goo() + if (!foo) { - if (!a) - { - if (b) - { - b(); - } - else - { - c(); - } - } - else - { - a(); - } + if (baz) + Quux(); } + else + bar(); } - """); - } + } + """); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35525")] - public async Task TestMultilineMissing_IfElseIfElseSubSelection() - { - await TestMissingInRegularAndScriptAsync( - """ - class A + [Fact] + public async Task TestMultiline_OpenCloseBracesSameLine() + { + await TestAsync(""" + class A + { + void Goo() { - void Goo() - { - if (a) - { - a(); - } - [|else if (b) - { - b(); - } - else - { - c(); - }|] + [||]if (foo) { + x(); + x(); + } else { + y(); + y(); } } - """); - } - - [Fact] - public async Task TestMultiline_IfElse() - { - await TestInRegularAndScriptAsync( - """ - class A + } + """, """ + class A + { + void Goo() { - void Goo() + if (!foo) { - [||]if (foo) - bar(); - else - if (baz) - Quux(); + y(); + y(); } - } - """, - """ - class A - { - void Goo() + else { - if (!foo) - { - if (baz) - Quux(); - } - else - bar(); + x(); + x(); } } - """); - } + } + """); + } - [Fact] - public async Task TestMultiline_OpenCloseBracesSameLine() - { - await TestInRegularAndScriptAsync( - """ - class A - { - void Goo() - { - [||]if (foo) { - x(); - x(); - } else { - y(); - y(); - } - } + [Fact] + public async Task TestMultiline_Trivia() + { + await TestAsync(""" + class A + { + void Goo() + { /*1*/ + [||]if (a) /*2*/ + { /*3*/ + /*4*/ + goo(); /*5*/ + /*6*/ + } /*7*/ + else if (b) /*8*/ + { /*9*/ + /*10*/ + goo(); /*11*/ + /*12*/ + } /*13*/ + else /*14*/ + { /*15*/ + /*16*/ + goo(); /*17*/ + /*18*/ + } /*19*/ + /*20*/ } - """, - """ - class A - { - void Goo() + } + """, """ + class A + { + void Goo() + { /*1*/ + if (!a) /*2*/ { - if (!foo) - { - y(); - y(); - } - else - { - x(); - x(); - } - } - } - """); - } - [Fact] - public async Task TestMultiline_Trivia() - { - await TestInRegularAndScriptAsync( - """ - class A - { - void Goo() - { /*1*/ - [||]if (a) /*2*/ - { /*3*/ - /*4*/ - goo(); /*5*/ - /*6*/ - } /*7*/ - else if (b) /*8*/ + if (b) /*8*/ { /*9*/ /*10*/ goo(); /*11*/ @@ -719,963 +735,907 @@ void Goo() goo(); /*17*/ /*18*/ } /*19*/ - /*20*/ } + else + { /*3*/ + /*4*/ + goo(); /*5*/ + /*6*/ + } /*7*/ + /*20*/ } - """, - """ - class A - { - void Goo() - { /*1*/ - if (!a) /*2*/ - { - if (b) /*8*/ - { /*9*/ - /*10*/ - goo(); /*11*/ - /*12*/ - } /*13*/ - else /*14*/ - { /*15*/ - /*16*/ - goo(); /*17*/ - /*18*/ - } /*19*/ - } - else - { /*3*/ - /*4*/ - goo(); /*5*/ - /*6*/ - } /*7*/ - /*20*/ - } - } - """); - } + } + """); + } - [Fact] - public async Task TestOverlapsHiddenPosition1() - { - await TestMissingInRegularAndScriptAsync( - """ - class C + [Fact] + public async Task TestOverlapsHiddenPosition1() + { + var code = """ + class C + { + void F() { - void F() + #line hidden + [||]if (a) { - #line hidden - [||]if (a) - { - a(); - } - else - { - b(); - } - #line default + a(); + } + else + { + b(); } + #line default } - """); - } + } + """; - [Fact] - public async Task TestOverlapsHiddenPosition2() - { - await TestMissingInRegularAndScriptAsync( - """ - class C + await TestAsync(code, code); + } + + [Fact] + public async Task TestOverlapsHiddenPosition2() + { + var code = """ + class C + { + void F() { - void F() + [||]if (a) { - [||]if (a) - { - #line hidden - a(); - #line default - } - else - { - b(); - } + #line hidden + a(); + #line default + } + else + { + b(); } } - """); - } + } + """; - [Fact] - public async Task TestOverlapsHiddenPosition3() - { - await TestMissingInRegularAndScriptAsync( - """ - class C + await TestAsync(code, code); + } + + [Fact] + public async Task TestOverlapsHiddenPosition3() + { + var code = """ + class C + { + void F() { - void F() + [||]if (a) { - [||]if (a) - { - a(); - } - else - { - #line hidden - b(); - #line default - } + a(); + } + else + { + #line hidden + b(); + #line default } } - """); - } + } + """; - [Fact] - public async Task TestOverlapsHiddenPosition4() - { - await TestMissingInRegularAndScriptAsync( - """ - class C + await TestAsync(code, code); + } + + [Fact] + public async Task TestOverlapsHiddenPosition4() + { + var code = """ + class C + { + void F() { - void F() + [||]if (a) { - [||]if (a) - { - #line hidden - a(); - } - else - { - b(); - #line default - } + #line hidden + a(); + } + else + { + b(); + #line default } } - """); - } + } + """; - [Fact] - public async Task TestOverlapsHiddenPosition5() - { - await TestMissingInRegularAndScriptAsync( - """ - class C + await TestAsync(code, code); + } + + [Fact] + public async Task TestOverlapsHiddenPosition5() + { + var code = """ + class C + { + void F() { - void F() + [||]if (a) { - [||]if (a) - { - a(); - #line hidden - } - else - { - #line default - b(); - } + a(); + #line hidden + } + else + { + #line default + b(); } } - """); - } + } + """; - [Fact] - public async Task TestOverlapsHiddenPosition6() - { - await TestInRegularAndScriptAsync( - """ - #line hidden - class C + await TestAsync(code, code); + } + + [Fact] + public async Task TestOverlapsHiddenPosition6() + { + await TestAsync(""" + #line hidden + class C + { + void F() { - void F() + #line default + [||]if (a) { - #line default - [||]if (a) - { - a(); - } - else - { - b(); - } + a(); + } + else + { + b(); } } - """, - - """ - #line hidden - class C + } + """, """ + #line hidden + class C + { + void F() { - void F() + #line default + if (!a) { - #line default - if (!a) - { - b(); - } - else - { - a(); - } + b(); + } + else + { + a(); } } - """); - } + } + """); - [Fact] - public async Task TestOverlapsHiddenPosition7() - { - await TestInRegularAndScriptAsync( - """ - #line hidden - class C + } + + [Fact] + public async Task TestOverlapsHiddenPosition7() + { + await TestAsync(""" + #line hidden + class C + { + void F() { - void F() + #line default + [||]if (a) { - #line default - [||]if (a) - { - a(); - } - else - { - b(); - } - #line hidden + a(); + } + else + { + b(); } + #line hidden } - #line default - """, - - """ - #line hidden - class C + } + #line default + """, """ + #line hidden + class C + { + void F() { - void F() + #line default + if (!a) { - #line default - if (!a) - { - b(); - } - else - { - a(); - } - #line hidden + b(); + } + else + { + a(); } + #line hidden } - #line default - """); - } + } + #line default + """); + } - [Fact] - public async Task TestSingleLine_SimplifyToLengthEqualsZero() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_SimplifyToLengthEqualsZero() + { + await TestInsideMethodAsync( @"string x; [||]if (x.Length > 0) { GreaterThanZero(); } else { EqualsZero(); } } } ", @"string x; if (x.Length == 0) { EqualsZero(); } else { GreaterThanZero(); } } } "); - } + } - [Fact] - public async Task TestSingleLine_SimplifyToLengthEqualsZero2() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_SimplifyToLengthEqualsZero2() + { + await TestInsideMethodAsync( @"string[] x; [||]if (x.Length > 0) { GreaterThanZero(); } else { EqualsZero(); } } } ", @"string[] x; if (x.Length == 0) { EqualsZero(); } else { GreaterThanZero(); } } } "); - } + } - [Fact] - public async Task TestSingleLine_SimplifyToLengthEqualsZero3() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_SimplifyToLengthEqualsZero3() + { + await TestInsideMethodAsync( @"string x; [||]if (x.Length > 0x0) { a(); } else { b(); } } } ", @"string x; if (x.Length == 0x0) { b(); } else { a(); } } } "); - } + } - [Fact] - public async Task TestSingleLine_SimplifyToLengthEqualsZero4() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_SimplifyToLengthEqualsZero4() + { + await TestInsideMethodAsync( @"string x; [||]if (0 < x.Length) { a(); } else { b(); } } } ", @"string x; if (0 == x.Length) { b(); } else { a(); } } } "); - } + } - [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545986")] - public async Task TestSingleLine_SimplifyToEqualsZero1() - { - await TestFixOneAsync( + [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545986")] + public async Task TestSingleLine_SimplifyToEqualsZero1() + { + await TestInsideMethodAsync( @"byte x = 1; [||]if (0 < x) { a(); } else { b(); } } } ", @"byte x = 1; if (0 == x) { b(); } else { a(); } } } "); - } + } - [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545986")] - public async Task TestSingleLine_SimplifyToEqualsZero2() - { - await TestFixOneAsync( + [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545986")] + public async Task TestSingleLine_SimplifyToEqualsZero2() + { + await TestInsideMethodAsync( @"ushort x = 1; [||]if (0 < x) { a(); } else { b(); } } } ", @"ushort x = 1; if (0 == x) { b(); } else { a(); } } } "); - } + } - [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545986")] - public async Task TestSingleLine_SimplifyToEqualsZero3() - { - await TestFixOneAsync( + [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545986")] + public async Task TestSingleLine_SimplifyToEqualsZero3() + { + await TestInsideMethodAsync( @"uint x = 1; [||]if (0 < x) { a(); } else { b(); } } } ", @"uint x = 1; if (0 == x) { b(); } else { a(); } } } "); - } + } - [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545986")] - public async Task TestSingleLine_SimplifyToEqualsZero4() - { - await TestFixOneAsync( + [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545986")] + public async Task TestSingleLine_SimplifyToEqualsZero4() + { + await TestInsideMethodAsync( @"ulong x = 1; [||]if (x > 0) { a(); } else { b(); } } } ", @"ulong x = 1; if (x == 0) { b(); } else { a(); } } } "); - } + } - [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545986")] - public async Task TestSingleLine_SimplifyToNotEqualsZero1() - { - await TestFixOneAsync( + [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545986")] + public async Task TestSingleLine_SimplifyToNotEqualsZero1() + { + await TestInsideMethodAsync( @"ulong x = 1; [||]if (0 == x) { a(); } else { b(); } } } ", @"ulong x = 1; if (0 != x) { b(); } else { a(); } } } "); - } + } - [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545986")] - public async Task TestSingleLine_SimplifyToNotEqualsZero2() - { - await TestFixOneAsync( + [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545986")] + public async Task TestSingleLine_SimplifyToNotEqualsZero2() + { + await TestInsideMethodAsync( @"ulong x = 1; [||]if (x == 0) { a(); } else { b(); } } } ", @"ulong x = 1; if (x != 0) { b(); } else { a(); } } } "); - } + } - [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/530505")] - public async Task TestSingleLine_SimplifyLongLengthEqualsZero() - { - await TestFixOneAsync( + [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/530505")] + public async Task TestSingleLine_SimplifyLongLengthEqualsZero() + { + await TestInsideMethodAsync( @"string[] x; [||]if (x.LongLength > 0) { GreaterThanZero(); } else { EqualsZero(); } } } ", @"string[] x; if (x.LongLength == 0) { EqualsZero(); } else { GreaterThanZero(); } } } "); - } + } - [Fact] - public async Task TestSingleLine_DoesNotSimplifyToLengthEqualsZero() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_DoesNotSimplifyToLengthEqualsZero() + { + await TestInsideMethodAsync( @"string x; [||]if (x.Length >= 0) { a(); } else { b(); } } } ", @"string x; if (x.Length < 0) { b(); } else { a(); } } } "); - } + } - [Fact] - public async Task TestSingleLine_DoesNotSimplifyToLengthEqualsZero2() - { - await TestFixOneAsync( + [Fact] + public async Task TestSingleLine_DoesNotSimplifyToLengthEqualsZero2() + { + await TestInsideMethodAsync( @"string x; [||]if (x.Length > 0.0f) { GreaterThanZero(); } else { EqualsZero(); } } } ", @"string x; if (x.Length <= 0.0f) { EqualsZero(); } else { GreaterThanZero(); } } } "); - } + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/29434")] - public async Task TestIsExpression() - { - await TestInRegularAndScriptAsync( + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/29434")] + public async Task TestIsExpression() + { + await TestAsync( @"class C { void M(object o) { [||]if (o is C) { a(); } else { } } }", @"class C { void M(object o) { if (o is not C) { } else { a(); } } }"); - } + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/43224")] - public async Task TestEmptyIf() - { - await TestInRegularAndScriptAsync( - @"class C { void M(string s){ [||]if (s == ""a""){}else{ s = ""b""}}}", - @"class C { void M(string s){ if (s != ""a""){ s = ""b""}}}"); - } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/43224")] + public async Task TestEmptyIf() + { + await TestAsync( + @"class C { void M(string s){ [||]if (s == ""a""){}else{ s = ""b""}}}", + @"class C { void M(string s){ if (s != ""a"") { s = ""b""}}}"); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/43224")] - public async Task TestOnlySingleLineCommentIf() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/43224")] + public async Task TestOnlySingleLineCommentIf() + { + await TestAsync(""" + class C + { + void M(string s) { - void M(string s) + [||]if (s == "a") { - [||]if (s == "a") - { - // A single line comment - } - else - { - s = "b" - } + // A single line comment + } + else + { + s = "b" } } - """, - """ - class C + } + """, """ + class C + { + void M(string s) { - void M(string s) + if (s != "a") { - if (s != "a") - { - s = "b" - } - else - { - // A single line comment - } + s = "b" + } + else + { + // A single line comment } } - """); - } + } + """); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/43224")] - public async Task TestOnlyMultilineLineCommentIf() - { - await TestInRegularAndScriptAsync( - """ - class C - { - void M(string s) + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/43224")] + public async Task TestOnlyMultilineLineCommentIf() + { + await TestAsync(""" + class C + { + void M(string s) + { + [||]if (s == "a") { - [||]if (s == "a") - { - /* - * This is - * a multiline - * comment with - * two words - * per line. - */ - } - else - { - s = "b" - } + /* + * This is + * a multiline + * comment with + * two words + * per line. + */ + } + else + { + s = "b" } } - """, - """ - class C - { - void M(string s) + } + """, """ + class C + { + void M(string s) + { + if (s != "a") { - if (s != "a") - { - s = "b" - } - else - { - /* - * This is - * a multiline - * comment with - * two words - * per line. - */ - } + s = "b" + } + else + { + /* + * This is + * a multiline + * comment with + * two words + * per line. + */ } } - """); - } + } + """); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51359")] - public async Task TestIsCheck_CSharp6() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51359")] + public async Task TestIsCheck_CSharp6() + { + await TestAsync(""" + class C + { + int M() { - int M() + [||]if (c is object) { - [||]if (c is object) - { - return 1; - } - else - { - return 2; - } + return 1; + } + else + { + return 2; } } - """, - """ - class C + } + """, """ + class C + { + int M() { - int M() + if (!(c is object)) { - if (!(c is object)) - { - return 2; - } - else - { - return 1; - } + return 2; + } + else + { + return 1; } } - """, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp6)); - } + } + """, LanguageVersion.CSharp6); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51359")] - public async Task TestIsCheck_CSharp8() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51359")] + public async Task TestIsCheck_CSharp8() + { + await TestAsync(""" + class C + { + int M() { - int M() + [||]if (c is object) { - [||]if (c is object) - { - return 1; - } - else - { - return 2; - } + return 1; + } + else + { + return 2; } } - """, - """ - class C + } + """, """ + class C + { + int M() { - int M() + if (c is null) { - if (c is null) - { - return 2; - } - else - { - return 1; - } + return 2; + } + else + { + return 1; } } - """, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp8)); - } + } + """, LanguageVersion.CSharp8); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51359")] - public async Task TestIsCheck_CSharp9() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51359")] + public async Task TestIsCheck_CSharp9() + { + await TestAsync(""" + class C + { + int M() { - int M() + [||]if (c is object) { - [||]if (c is object) - { - return 1; - } - else - { - return 2; - } + return 1; + } + else + { + return 2; } } - """, - """ - class C + } + """, """ + class C + { + int M() { - int M() + if (c is null) { - if (c is null) - { - return 2; - } - else - { - return 1; - } + return 2; + } + else + { + return 1; } } - """, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp9)); - } + } + """, LanguageVersion.CSharp9); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51359")] - public async Task TestIsNotObjectCheck_CSharp8() - { - // Not terrific. But the starting code is not legal C#8 either. In this case because we don't even support - // 'not' patterns wee dont' bother diving into the pattern to negate it, and we instead just negate the - // expression. - await TestInRegularAndScriptAsync( - """ - class C + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51359")] + public async Task TestIsNotObjectCheck_CSharp8() + { + // Not terrific. But the starting code is not legal C#8 either. In this case because we don't even support + // 'not' patterns wee don't bother diving into the pattern to negate it, and we instead just negate the + // expression. + await TestAsync(""" + class C + { + int M() { - int M() + [||]if (c is not object) { - [||]if (c is not object) - { - return 1; - } - else - { - return 2; - } + return 1; + } + else + { + return 2; } } - """, - """ - class C + } + """, """ + class C + { + int M() { - int M() + if (c is object) { - if (c is object) - { - return 2; - } - else - { - return 1; - } + return 2; + } + else + { + return 1; } } - """, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp8)); - } + } + """, LanguageVersion.CSharp8); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51359")] - public async Task TestIsNotObjectCheck_CSharp9() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51359")] + public async Task TestIsNotObjectCheck_CSharp9() + { + await TestAsync(""" + class C + { + int M() { - int M() + [||]if (c is not object) { - [||]if (c is not object) - { - return 1; - } - else - { - return 2; - } + return 1; + } + else + { + return 2; } } - """, - """ - class C + } + """, """ + class C + { + int M() { - int M() + if (c is not null) { - if (c is not null) - { - return 2; - } - else - { - return 1; - } + return 2; + } + else + { + return 1; } } - """, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp9)); - } + } + """, LanguageVersion.CSharp9); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63311")] - public async Task TestLiftedNullable_GreaterThan() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63311")] + public async Task TestLiftedNullable_GreaterThan() + { + await TestAsync(""" + class C + { + void M(int? p) { - void M(int? p) + [||]if (p > 10) { - [||]if (p > 10) - { - System.Console.WriteLine("p is not null and p.Value > 10"); - } + System.Console.WriteLine("p is not null and p.Value > 10"); } } - """, - """ - class C + } + """, """ + class C + { + void M(int? p) { - void M(int? p) + if (!(p > 10)) { - if (!(p > 10)) - { - return; - } - System.Console.WriteLine("p is not null and p.Value > 10"); + return; } + System.Console.WriteLine("p is not null and p.Value > 10"); } - """); - } + } + """); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63311")] - public async Task TestLiftedNullable_GreaterThanOrEqual() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63311")] + public async Task TestLiftedNullable_GreaterThanOrEqual() + { + await TestAsync(""" + class C + { + void M(int? p) { - void M(int? p) + [||]if (p >= 10) { - [||]if (p >= 10) - { - System.Console.WriteLine("p is not null and p.Value >= 10"); - } + System.Console.WriteLine("p is not null and p.Value >= 10"); } } - """, - """ - class C + } + """, """ + class C + { + void M(int? p) { - void M(int? p) + if (!(p >= 10)) { - if (!(p >= 10)) - { - return; - } - System.Console.WriteLine("p is not null and p.Value >= 10"); + return; } + System.Console.WriteLine("p is not null and p.Value >= 10"); } - """); - } + } + """); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63311")] - public async Task TestLiftedNullable_LessThan() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63311")] + public async Task TestLiftedNullable_LessThan() + { + await TestAsync(""" + class C + { + void M(int? p) { - void M(int? p) + [||]if (p < 10) { - [||]if (p < 10) - { - System.Console.WriteLine("p is not null and p.Value < 10"); - } + System.Console.WriteLine("p is not null and p.Value < 10"); } } - """, - """ - class C + } + """, """ + class C + { + void M(int? p) { - void M(int? p) + if (!(p < 10)) { - if (!(p < 10)) - { - return; - } - System.Console.WriteLine("p is not null and p.Value < 10"); + return; } + System.Console.WriteLine("p is not null and p.Value < 10"); } - """); - } + } + """); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63311")] - public async Task TestLiftedNullable_LessThanOrEqual() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63311")] + public async Task TestLiftedNullable_LessThanOrEqual() + { + await TestAsync(""" + class C + { + void M(int? p) { - void M(int? p) + [||]if (p <= 10) { - [||]if (p <= 10) - { - System.Console.WriteLine("p is not null and p.Value <= 10"); - } + System.Console.WriteLine("p is not null and p.Value <= 10"); } } - """, - """ - class C + } + """, """ + class C + { + void M(int? p) { - void M(int? p) + if (!(p <= 10)) { - if (!(p <= 10)) - { - return; - } - System.Console.WriteLine("p is not null and p.Value <= 10"); + return; } + System.Console.WriteLine("p is not null and p.Value <= 10"); } - """); - } + } + """); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63311")] - public async Task TestNullableReference_GreaterThan() - { - await TestInRegularAndScriptAsync( - """ - #nullable enable - using System; - class C + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63311")] + public async Task TestNullableReference_GreaterThan() + { + await TestAsync(""" + #nullable enable + using System; + class C + { + void M(C? p) { - void M(C? p) + [||]if (p > new C()) { - [||]if (p > new C()) - { - Console.WriteLine("Null-handling semantics may actually change depending on the operator implementation"); - } + Console.WriteLine("Null-handling semantics may actually change depending on the operator implementation"); } - - public static bool operator <(C? left, C? right) => throw new NotImplementedException(); - public static bool operator >(C? left, C? right) => throw new NotImplementedException(); - public static bool operator <=(C? left, C? right) => throw new NotImplementedException(); - public static bool operator >=(C? left, C? right) => throw new NotImplementedException(); } - """, - """ - #nullable enable - using System; - class C + + public static bool operator <(C? left, C? right) => throw new NotImplementedException(); + public static bool operator >(C? left, C? right) => throw new NotImplementedException(); + public static bool operator <=(C? left, C? right) => throw new NotImplementedException(); + public static bool operator >=(C? left, C? right) => throw new NotImplementedException(); + } + """, """ + #nullable enable + using System; + class C + { + void M(C? p) { - void M(C? p) + if (p <= new C()) { - if (p <= new C()) - { - return; - } - Console.WriteLine("Null-handling semantics may actually change depending on the operator implementation"); + return; } - - public static bool operator <(C? left, C? right) => throw new NotImplementedException(); - public static bool operator >(C? left, C? right) => throw new NotImplementedException(); - public static bool operator <=(C? left, C? right) => throw new NotImplementedException(); - public static bool operator >=(C? left, C? right) => throw new NotImplementedException(); + Console.WriteLine("Null-handling semantics may actually change depending on the operator implementation"); } - """); - } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40585")] - public async Task TestYieldBreak() - { - await TestInRegularAndScriptAsync( - """ - using System.Collections; + public static bool operator <(C? left, C? right) => throw new NotImplementedException(); + public static bool operator >(C? left, C? right) => throw new NotImplementedException(); + public static bool operator <=(C? left, C? right) => throw new NotImplementedException(); + public static bool operator >=(C? left, C? right) => throw new NotImplementedException(); + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40585")] + public async Task TestYieldBreak() + { + await TestAsync(""" + using System.Collections; - class Program + class Program + { + public static IEnumerable Method(bool condition) { - public static IEnumerable Method(bool condition) + [||]if (condition) { - [||]if (condition) - { - yield return 1; - } + yield return 1; } } - """, - """ - using System.Collections; + } + """, """ + using System.Collections; - class Program + class Program + { + public static IEnumerable Method(bool condition) { - public static IEnumerable Method(bool condition) + if (!condition) { - if (!condition) - { - yield break; - } - yield return 1; + yield break; } + yield return 1; } - """); - } + } + """); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42715")] - public async Task PreserveSpacing() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42715")] + public async Task PreserveSpacing() + { + await TestAsync(""" + class C + { + string? M(string s) { - string? M(string s) - { - var l = s.ToLowerCase(); + var l = s.ToLowerCase(); - [||]if (l == "hello") - { - return null; - } + [||]if (l == "hello") + { + return null; + } - return l; + return l; - } } - """, - """ - class C + } + """, """ + class C + { + string? M(string s) { - string? M(string s) - { - var l = s.ToLowerCase(); + var l = s.ToLowerCase(); - if (l != "hello") - { - return l; - } + if (l != "hello") + { + return l; + } - return null; + return null; - } } - """); - } + } + """); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42715")] - public async Task PreserveSpacing_WithComments() - { - await TestInRegularAndScriptAsync( - """ - class C + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42715")] + public async Task PreserveSpacing_WithComments() + { + await TestAsync(""" + class C + { + string? M(string s) { - string? M(string s) - { - var l = s.ToLowerCase(); + var l = s.ToLowerCase(); - [||]if (l == "hello") - { - // null 1 - return null; // null 2 - // null 3 - } + [||]if (l == "hello") + { + // null 1 + return null; // null 2 + // null 3 + } - // l 1 - return l; // l 2 - // l 3 + // l 1 + return l; // l 2 + // l 3 - } } - """, - """ - class C + } + """, """ + class C + { + string? M(string s) { - string? M(string s) - { - var l = s.ToLowerCase(); + var l = s.ToLowerCase(); - if (l != "hello") - { - // l 1 - return l; // l 2 - // null 3 - } + if (l != "hello") + { + // l 1 + return l; // l 2 + // null 3 + } - // null 1 - return null; // null 2 - // l 3 + // null 1 + return null; // null 2 + // l 3 - } } - """); - } + } + """); + } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42715")] - public async Task PreserveSpacing_NoTrivia() - { - await TestInRegularAndScriptAsync( - """ - class C - { - string? M(bool b) - {[||]if(b){return(true);}return(false);} - } - """, - """ - class C - { - string? M(bool b) - { if (!b) { return (false); } return (true); } - } - """); - } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42715")] + public async Task PreserveSpacing_NoTrivia() + { + await TestAsync(""" + class C + { + string? M(bool b) + {[||]if(b){return(true);}return(false);} + } + """, """ + class C + { + string? M(bool b) + { if (!b) { return (false); } return (true); } + } + """); } } diff --git a/src/Features/CSharpTest/ReplaceConditionalWithStatements/ReplaceConditionalWithStatementsTests.cs b/src/Features/CSharpTest/ReplaceConditionalWithStatements/ReplaceConditionalWithStatementsTests.cs index 09ef2850b043b..e2676d9da45ed 100644 --- a/src/Features/CSharpTest/ReplaceConditionalWithStatements/ReplaceConditionalWithStatementsTests.cs +++ b/src/Features/CSharpTest/ReplaceConditionalWithStatements/ReplaceConditionalWithStatementsTests.cs @@ -2,19 +2,17 @@ // 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.Linq; -using System.Text; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.ReplaceConditionalWithStatements; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ReplaceConditionalWithStatements; using VerifyCS = CSharpCodeRefactoringVerifier; +[UseExportProvider] public class ReplaceConditionalWithStatementsTests { [Fact] diff --git a/src/Features/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyFixAllTests.cs b/src/Features/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyFixAllTests.cs index a7b52d7e9a043..abfadc014a842 100644 --- a/src/Features/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyFixAllTests.cs +++ b/src/Features/CSharpTest/UseExpressionBody/Refactoring/UseExpressionBodyFixAllTests.cs @@ -284,8 +284,7 @@ void M3() }", parameters: new TestParameters(options: UseBlockBody)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FixAllDoesNotFixDifferentSymbolKinds(bool forMethods) { var fixAllAnnotationForMethods = forMethods ? "{|FixAllInDocument:|}" : string.Empty; diff --git a/src/Features/CSharpTest/UseNamedArguments/UseNamedArgumentsTests.cs b/src/Features/CSharpTest/UseNamedArguments/UseNamedArgumentsTests.cs index 022a140db17fe..570233da04cb8 100644 --- a/src/Features/CSharpTest/UseNamedArguments/UseNamedArgumentsTests.cs +++ b/src/Features/CSharpTest/UseNamedArguments/UseNamedArgumentsTests.cs @@ -15,6 +15,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseNamedArguments { using VerifyCS = CSharpCodeRefactoringVerifier; + [UseExportProvider] [Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] public class UseNamedArgumentsTests { diff --git a/src/Features/CSharpTest/UsePatternCombinators/CSharpUsePatternCombinatorsDiagnosticAnalyzerTests.cs b/src/Features/CSharpTest/UsePatternCombinators/CSharpUsePatternCombinatorsDiagnosticAnalyzerTests.cs index d239bdf229bc9..8089f6dcd6f70 100644 --- a/src/Features/CSharpTest/UsePatternCombinators/CSharpUsePatternCombinatorsDiagnosticAnalyzerTests.cs +++ b/src/Features/CSharpTest/UsePatternCombinators/CSharpUsePatternCombinatorsDiagnosticAnalyzerTests.cs @@ -103,7 +103,7 @@ public async Task TestMissingOnExpression(string expression) await TestAllMissingOnExpressionAsync(expression); } - [InlineData("i == default || i > default(int)", "i is default(int) or > (default(int))")] + [InlineData("i == default || i > default(int)", "i is default(int) or > default(int)")] [InlineData("!(o is C c)", "o is not C c")] [InlineData("o is int ii && o is long jj", "o is int ii and long jj")] [InlineData("o is string || o is Exception", "o is string or Exception")] diff --git a/src/Features/CSharpTest/UseRecursivePatterns/UseRecursivePatternsRefactoringTests.cs b/src/Features/CSharpTest/UseRecursivePatterns/UseRecursivePatternsRefactoringTests.cs index 1d57cf4fa0694..4b859cd985342 100644 --- a/src/Features/CSharpTest/UseRecursivePatterns/UseRecursivePatternsRefactoringTests.cs +++ b/src/Features/CSharpTest/UseRecursivePatterns/UseRecursivePatternsRefactoringTests.cs @@ -14,6 +14,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings.UseRec { using VerifyCS = CSharpCodeRefactoringVerifier; + [UseExportProvider] [Trait(Traits.Feature, Traits.Features.CodeActionsUseRecursivePatterns)] public class UseRecursivePatternsRefactoringTests { diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index 84a7d0ffe9b66..fb4f6cd8669ac 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -20,6 +20,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.SymbolSearch; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -215,28 +216,27 @@ private static async Task FindResultsInUnreferencedProjectSourceSymbolsAsync( var viableUnreferencedProjects = GetViableUnreferencedProjects(project); - // Search all unreferenced projects in parallel. - using var _ = ArrayBuilder.GetInstance(out var findTasks); - // Create another cancellation token so we can both search all projects in parallel, // but also stop any searches once we get enough results. using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - foreach (var unreferencedProject in viableUnreferencedProjects) - { - if (!unreferencedProject.SupportsCompilation) - continue; - - // Search in this unreferenced project. But don't search in any of its' - // direct references. i.e. we don't want to search in its metadata references - // or in the projects it references itself. We'll be searching those entities - // individually. - findTasks.Add(ProcessReferencesAsync( - allSymbolReferences, maxResults, linkedTokenSource, - finder.FindInSourceSymbolsInProjectAsync(projectToAssembly, unreferencedProject, exact, linkedTokenSource.Token))); - } - - await Task.WhenAll(findTasks).ConfigureAwait(false); + // Defer to the ProducerConsumer. We're search the unreferenced projects in parallel. As we get results, we'll + // add them to the 'allSymbolReferences' queue. If we get enough results, we'll cancel all the other work. + await ProducerConsumer>.RunParallelAsync( + source: viableUnreferencedProjects, + produceItems: static async (project, onItemsFound, args, cancellationToken) => + { + // Search in this unreferenced project. But don't search in any of its' direct references. i.e. we + // don't want to search in its metadata references or in the projects it references itself. We'll be + // searching those entities individually. + var references = await args.finder.FindInSourceSymbolsInProjectAsync( + args.projectToAssembly, project, args.exact, cancellationToken).ConfigureAwait(false); + onItemsFound(references); + }, + consumeItems: static (symbolReferencesEnumerable, args, cancellationToken) => + ProcessReferencesAsync(args.allSymbolReferences, args.maxResults, symbolReferencesEnumerable, args.linkedTokenSource), + args: (projectToAssembly, allSymbolReferences, maxResults, finder, exact, linkedTokenSource), + linkedTokenSource.Token).ConfigureAwait(false); } private async Task FindResultsInUnreferencedMetadataSymbolsAsync( @@ -246,7 +246,7 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( { // Only do this if none of the project searches produced any results. We may have a // lot of metadata to search through, and it would be good to avoid that if we can. - if (allSymbolReferences.Count > 0) + if (!allSymbolReferences.IsEmpty) return; // Keep track of the references we've seen (so that we don't process them multiple times @@ -257,29 +257,33 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( var newReferences = GetUnreferencedMetadataReferences(project, seenReferences); - // Search all metadata references in parallel. - using var _ = ArrayBuilder.GetInstance(out var findTasks); - // Create another cancellation token so we can both search all projects in parallel, // but also stop any searches once we get enough results. using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - foreach (var (referenceProject, reference) in newReferences) - { - var compilation = referenceToCompilation.GetOrAdd( - reference, r => CreateCompilation(project, r)); - - // Ignore netmodules. First, they're incredibly esoteric and barely used. - // Second, the SymbolFinder API doesn't even support searching them. - if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly) + // Defer to the ProducerConsumer. We're search the metadata references in parallel. As we get results, we'll + // add them to the 'allSymbolReferences' queue. If we get enough results, we'll cancel all the other work. + await ProducerConsumer>.RunParallelAsync( + source: newReferences, + produceItems: static async (tuple, onItemsFound, args, cancellationToken) => { - findTasks.Add(ProcessReferencesAsync( - allSymbolReferences, maxResults, linkedTokenSource, - finder.FindInMetadataSymbolsAsync(assembly, referenceProject, reference, exact, linkedTokenSource.Token))); - } - } - - await Task.WhenAll(findTasks).ConfigureAwait(false); + var (referenceProject, reference) = tuple; + var compilation = args.referenceToCompilation.GetOrAdd( + reference, r => CreateCompilation(args.project, r)); + + // Ignore netmodules. First, they're incredibly esoteric and barely used. + // Second, the SymbolFinder API doesn't even support searching them. + if (compilation.GetAssemblyOrModuleSymbol(reference) is not IAssemblySymbol assembly) + return; + + var references = await args.finder.FindInMetadataSymbolsAsync( + assembly, referenceProject, reference, args.exact, cancellationToken).ConfigureAwait(false); + onItemsFound(references); + }, + consumeItems: static (symbolReferencesEnumerable, args, cancellationToken) => + ProcessReferencesAsync(args.allSymbolReferences, args.maxResults, symbolReferencesEnumerable, args.linkedTokenSource), + args: (referenceToCompilation, project, allSymbolReferences, maxResults, finder, exact, newReferences, linkedTokenSource), + linkedTokenSource.Token).ConfigureAwait(false); } /// @@ -290,7 +294,7 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( private static ImmutableArray<(Project, PortableExecutableReference)> GetUnreferencedMetadataReferences( Project project, HashSet seenReferences) { - var result = ArrayBuilder<(Project, PortableExecutableReference)>.GetInstance(); + using var _ = ArrayBuilder<(Project, PortableExecutableReference)>.GetInstance(out var result); var solution = project.Solution; foreach (var p in solution.Projects) @@ -311,27 +315,31 @@ private async Task FindResultsInUnreferencedMetadataSymbolsAsync( } } - return result.ToImmutableAndFree(); + return result.ToImmutableAndClear(); } private static async Task ProcessReferencesAsync( ConcurrentQueue allSymbolReferences, int maxResults, - CancellationTokenSource linkedTokenSource, - Task> task) + IAsyncEnumerable> reader, + CancellationTokenSource linkedTokenSource) { - AddRange(allSymbolReferences, await task.ConfigureAwait(false)); - - // If we've gone over the max amount of items we're looking for, attempt to cancel all existing work that is - // still searching. - if (allSymbolReferences.Count >= maxResults) + await foreach (var symbolReferences in reader) { - try - { - linkedTokenSource.Cancel(); - } - catch (ObjectDisposedException) + linkedTokenSource.Token.ThrowIfCancellationRequested(); + AddRange(allSymbolReferences, symbolReferences); + + // If we've gone over the max amount of items we're looking for, attempt to cancel all existing work that is + // still searching. + if (allSymbolReferences.Count >= maxResults) { + try + { + linkedTokenSource.Cancel(); + } + catch (ObjectDisposedException) + { + } } } } @@ -419,7 +427,7 @@ int IEqualityComparer.GetHashCode(PortableExecutabl private static HashSet GetViableUnreferencedProjects(Project project) { var solution = project.Solution; - var viableProjects = new HashSet(solution.Projects); + var viableProjects = new HashSet(solution.Projects.Where(p => p.SupportsCompilation)); // Clearly we can't reference ourselves. viableProjects.Remove(project); diff --git a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs index 99322262f53c0..fc9ea2a994409 100644 --- a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs +++ b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs @@ -24,6 +24,7 @@ using Microsoft.CodeAnalysis.Recommendations; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -57,7 +58,7 @@ public abstract Task ChangeSignatureAsync( LineFormattingOptionsProvider fallbackOptions, CancellationToken cancellationToken); - protected abstract IEnumerable GetFormattingRules(Document document); + protected abstract ImmutableArray GetFormattingRules(Document document); protected abstract T TransferLeadingWhitespaceTrivia(T newArgument, SyntaxNode oldArgument) where T : SyntaxNode; @@ -414,17 +415,23 @@ private static async Task> FindChangeSignatureR } // Update the documents using the updated syntax trees - foreach (var docId in nodesToUpdate.Keys) - { - var updatedDoc = currentSolution.GetRequiredDocument(docId).WithSyntaxRoot(updatedRoots[docId]); - var cleanupOptions = await updatedDoc.GetCodeCleanupOptionsAsync(context.FallbackOptions, cancellationToken).ConfigureAwait(false); + var changedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync( + source: nodesToUpdate.Keys, + produceItems: static async (docId, callback, args, cancellationToken) => + { + var updatedDoc = args.currentSolution.GetRequiredDocument(docId).WithSyntaxRoot(args.updatedRoots[docId]); + var cleanupOptions = await updatedDoc.GetCodeCleanupOptionsAsync(args.context.FallbackOptions, cancellationToken).ConfigureAwait(false); - var docWithImports = await ImportAdder.AddImportsFromSymbolAnnotationAsync(updatedDoc, cleanupOptions.AddImportOptions, cancellationToken).ConfigureAwait(false); - var reducedDoc = await Simplifier.ReduceAsync(docWithImports, Simplifier.Annotation, cleanupOptions.SimplifierOptions, cancellationToken: cancellationToken).ConfigureAwait(false); - var formattedDoc = await Formatter.FormatAsync(reducedDoc, SyntaxAnnotation.ElasticAnnotation, cleanupOptions.FormattingOptions, cancellationToken).ConfigureAwait(false); + var docWithImports = await ImportAdder.AddImportsFromSymbolAnnotationAsync(updatedDoc, cleanupOptions.AddImportOptions, cancellationToken).ConfigureAwait(false); + var reducedDoc = await Simplifier.ReduceAsync(docWithImports, Simplifier.Annotation, cleanupOptions.SimplifierOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + var formattedDoc = await Formatter.FormatAsync(reducedDoc, SyntaxAnnotation.ElasticAnnotation, cleanupOptions.FormattingOptions, cancellationToken).ConfigureAwait(false); - currentSolution = currentSolution.WithDocumentSyntaxRoot(docId, (await formattedDoc.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!); - } + callback((formattedDoc.Id, await formattedDoc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false))); + }, + args: (currentSolution, updatedRoots, context), + cancellationToken).ConfigureAwait(false); + + currentSolution = currentSolution.WithDocumentSyntaxRoots(changedDocuments); telemetryTimer.Stop(); ChangeSignatureLogger.LogCommitInformation(telemetryNumberOfDeclarationsToUpdate, telemetryNumberOfReferencesToUpdate, telemetryTimer.Elapsed); diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs index 5b0a56aaff0be..78d6303864837 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -76,7 +77,6 @@ internal abstract class AbstractSuppressionBatchFixAllProvider : FixAllProvider { var cancellationToken = fixAllContext.CancellationToken; var fixAllState = fixAllContext.State; - var fixesBag = new ConcurrentBag<(Diagnostic diagnostic, CodeAction action)>(); using (Logger.LogBlock( FunctionId.CodeFixes_FixAllOccurrencesComputation_Document_Fixes, @@ -86,73 +86,48 @@ internal abstract class AbstractSuppressionBatchFixAllProvider : FixAllProvider cancellationToken.ThrowIfCancellationRequested(); var progressTracker = fixAllContext.Progress; - using var _1 = ArrayBuilder.GetInstance(out var tasks); - using var _2 = ArrayBuilder.GetInstance(out var documentsToFix); - // Determine the set of documents to actually fix. We can also use this to update the progress bar with // the amount of remaining work to perform. We'll update the progress bar as we compute each fix in // AddDocumentFixesAsync. - foreach (var (document, diagnosticsToFix) in documentsAndDiagnosticsToFixMap) - { - if (!diagnosticsToFix.IsDefaultOrEmpty) - documentsToFix.Add(document); - } - - progressTracker.AddItems(documentsToFix.Count); - - foreach (var document in documentsToFix) - { - var diagnosticsToFix = documentsAndDiagnosticsToFixMap[document]; - tasks.Add(AddDocumentFixesAsync( - document, diagnosticsToFix, fixesBag, fixAllState, progressTracker, cancellationToken)); - } + var source = documentsAndDiagnosticsToFixMap.WhereAsArray(static (kvp, _) => !kvp.Value.IsDefaultOrEmpty, state: false); + progressTracker.AddItems(source.Length); - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - return [.. fixesBag]; - } - - private async Task AddDocumentFixesAsync( - Document document, ImmutableArray diagnostics, - ConcurrentBag<(Diagnostic diagnostic, CodeAction action)> fixes, - FixAllState fixAllState, IProgress progressTracker, CancellationToken cancellationToken) - { - try - { - await this.AddDocumentFixesAsync(document, diagnostics, fixes, fixAllState, cancellationToken).ConfigureAwait(false); - } - finally - { - progressTracker.ItemCompleted(); + return await ProducerConsumer<(Diagnostic diagnostic, CodeAction action)>.RunParallelAsync( + source, + produceItems: static async (tuple, callback, args, cancellationToken) => + { + using var _ = args.progressTracker.ItemCompletedScope(); + + var (document, diagnosticsToFix) = tuple; + await args.@this.AddDocumentFixesAsync( + document, diagnosticsToFix, callback, args.fixAllState, cancellationToken).ConfigureAwait(false); + }, + args: (@this: this, fixAllState, progressTracker), + cancellationToken).ConfigureAwait(false); } } protected virtual async Task AddDocumentFixesAsync( Document document, ImmutableArray diagnostics, - ConcurrentBag<(Diagnostic diagnostic, CodeAction action)> fixes, + Action<(Diagnostic diagnostic, CodeAction action)> onItemFound, FixAllState fixAllState, CancellationToken cancellationToken) { Debug.Assert(!diagnostics.IsDefault); cancellationToken.ThrowIfCancellationRequested(); - var registerCodeFix = GetRegisterCodeFixAction(fixAllState, fixes); - - var fixerTasks = new List(); - foreach (var diagnostic in diagnostics) - { - cancellationToken.ThrowIfCancellationRequested(); - fixerTasks.Add(Task.Run(() => + var registerCodeFix = GetRegisterCodeFixAction(fixAllState, onItemFound); + await RoslynParallel.ForEachAsync( + source: diagnostics, + cancellationToken, + async (diagnostic, cancellationToken) => { var context = new CodeFixContext(document, diagnostic, registerCodeFix, cancellationToken); - // TODO: Wrap call to ComputeFixesAsync() below in IExtensionManager.PerformFunctionAsync() so that + // TODO: Wrap call to RegisterCodeFixesAsync() below in IExtensionManager.PerformFunctionAsync() so that // a buggy extension that throws can't bring down the host? - return fixAllState.Provider.RegisterCodeFixesAsync(context) ?? Task.CompletedTask; - }, cancellationToken)); - } - - await Task.WhenAll(fixerTasks).ConfigureAwait(false); + if (fixAllState.Provider.RegisterCodeFixesAsync(context) is Task task) + await task.ConfigureAwait(false); + }).ConfigureAwait(false); } private async Task GetFixAsync( @@ -198,7 +173,7 @@ protected virtual async Task AddDocumentFixesAsync( private static Action> GetRegisterCodeFixAction( FixAllState fixAllState, - ConcurrentBag<(Diagnostic diagnostic, CodeAction action)> result) + Action<(Diagnostic diagnostic, CodeAction action)> onItemFound) { return (action, diagnostics) => { @@ -209,7 +184,7 @@ private static Action> GetRegisterCodeFix if (currentAction is { EquivalenceKey: var equivalenceKey } && equivalenceKey == fixAllState.CodeActionEquivalenceKey) { - result.Add((diagnostics.First(), currentAction)); + onItemFound((diagnostics.First(), currentAction)); } foreach (var nestedAction in currentAction.NestedActions) @@ -265,11 +240,8 @@ private static async Task TryMergeFixesAsync( // Finally, apply the changes to each document to the solution, producing the // new solution. - var currentSolution = oldSolution; - foreach (var (documentId, finalText) in documentIdToFinalText) - currentSolution = currentSolution.WithDocumentText(documentId, finalText); - - return currentSolution; + var finalSolution = oldSolution.WithDocumentTexts(documentIdToFinalText); + return finalSolution; } private static async Task>> GetDocumentIdToChangedDocumentsAsync( @@ -294,7 +266,7 @@ private static async Task TryMergeFixesAsync( return documentIdToChangedDocuments; } - private static async Task> GetDocumentIdToFinalTextAsync( + private static async Task> GetDocumentIdToFinalTextAsync( Solution oldSolution, IReadOnlyDictionary> documentIdToChangedDocuments, ImmutableArray<(Diagnostic diagnostic, CodeAction action)> diagnosticsAndCodeActions, @@ -316,7 +288,7 @@ private static async Task> GetDocume } await Task.WhenAll(getFinalDocumentTasks).ConfigureAwait(false); - return documentIdToFinalText; + return documentIdToFinalText.SelectAsArray(kvp => (kvp.Key, kvp.Value)); } private static async Task GetFinalDocumentTextAsync( diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningBatchFixAllProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningBatchFixAllProvider.cs index bce19c8db6a56..d45b0ba87f178 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningBatchFixAllProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaWarningBatchFixAllProvider.cs @@ -4,6 +4,7 @@ #nullable disable +using System; using System.Collections.Concurrent; using System.Collections.Immutable; using System.Linq; @@ -26,7 +27,7 @@ internal sealed class PragmaWarningBatchFixAllProvider(AbstractSuppressionCodeFi protected override async Task AddDocumentFixesAsync( Document document, ImmutableArray diagnostics, - ConcurrentBag<(Diagnostic diagnostic, CodeAction action)> fixes, + Action<(Diagnostic diagnostic, CodeAction action)> onItemFound, FixAllState fixAllState, CancellationToken cancellationToken) { var pragmaActionsBuilder = ArrayBuilder.GetInstance(); @@ -59,7 +60,7 @@ protected override async Task AddDocumentFixesAsync( pragmaDiagnosticsBuilder.ToImmutableAndFree(), fixAllState, cancellationToken); - fixes.Add((diagnostic: null, pragmaBatchFix)); + onItemFound((diagnostic: null, pragmaBatchFix)); } } } diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs index a52525a81854c..84ee578089529 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs @@ -33,12 +33,12 @@ private sealed class RemoveSuppressionBatchFixAllProvider(AbstractSuppressionCod protected override async Task AddDocumentFixesAsync( Document document, ImmutableArray diagnostics, - ConcurrentBag<(Diagnostic diagnostic, CodeAction action)> fixes, + Action<(Diagnostic diagnostic, CodeAction action)> onItemFound, FixAllState fixAllState, CancellationToken cancellationToken) { // Batch all the pragma remove suppression fixes by executing them sequentially for the document. - var pragmaActionsBuilder = ArrayBuilder.GetInstance(); - var pragmaDiagnosticsBuilder = ArrayBuilder.GetInstance(); + using var _1 = ArrayBuilder.GetInstance(out var pragmaActionsBuilder); + using var _2 = ArrayBuilder.GetInstance(out var pragmaDiagnosticsBuilder); foreach (var diagnostic in diagnostics.Where(d => d.Location.IsInSource && d.IsSuppressed)) { @@ -62,7 +62,7 @@ protected override async Task AddDocumentFixesAsync( } else { - fixes.Add((diagnostic, codeAction)); + onItemFound((diagnostic, codeAction)); } } } @@ -73,11 +73,11 @@ protected override async Task AddDocumentFixesAsync( { var pragmaBatchFix = PragmaBatchFixHelpers.CreateBatchPragmaFix( _suppressionFixProvider, document, - pragmaActionsBuilder.ToImmutableAndFree(), - pragmaDiagnosticsBuilder.ToImmutableAndFree(), + pragmaActionsBuilder.ToImmutableAndClear(), + pragmaDiagnosticsBuilder.ToImmutableAndClear(), fixAllState, cancellationToken); - fixes.Add((diagnostic: null, pragmaBatchFix)); + onItemFound((diagnostic: null, pragmaBatchFix)); } } diff --git a/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs b/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs index 49dd3e0320e36..ed273b931259a 100644 --- a/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs @@ -89,20 +89,15 @@ public async Task HasRefactoringsAsync( CodeActionOptionsProvider options, CancellationToken cancellationToken) { - var extensionManager = document.Project.Solution.Services.GetRequiredService(); - foreach (var provider in GetProviders(document)) { cancellationToken.ThrowIfCancellationRequested(); - RefactoringToMetadataMap.TryGetValue(provider, out var providerMetadata); var refactoring = await GetRefactoringFromProviderAsync( - document, state, provider, providerMetadata, extensionManager, options, cancellationToken).ConfigureAwait(false); + document, state, provider, options, cancellationToken).ConfigureAwait(false); if (refactoring != null) - { return true; - } } return false; @@ -119,16 +114,15 @@ public async Task> GetRefactoringsAsync( using (TelemetryLogging.LogBlockTimeAggregated(FunctionId.CodeRefactoring_Summary, $"Pri{priority.GetPriorityInt()}")) using (Logger.LogBlock(FunctionId.Refactoring_CodeRefactoringService_GetRefactoringsAsync, cancellationToken)) { - var extensionManager = document.Project.Solution.Services.GetRequiredService(); - using var _ = ArrayBuilder>.GetInstance(out var tasks); + using var _ = PooledDictionary.GetInstance(out var providerToIndex); - foreach (var provider in GetProviders(document)) - { - if (priority != null && priority != provider.RequestPriority) - continue; + var orderedProviders = GetProviders(document).Where(p => priority == null || p.RequestPriority == priority).ToImmutableArray(); - tasks.Add(Task.Run(async () => + var pairs = await ProducerConsumer<(CodeRefactoringProvider provider, CodeRefactoring codeRefactoring)>.RunParallelAsync( + source: orderedProviders, + produceItems: static async (provider, callback, args, cancellationToken) => { + // Run all providers in parallel to get the set of refactorings for this document. // Log an individual telemetry event for slow code refactoring computations to // allow targeted trace notifications for further investigation. 500 ms seemed like // a good value so as to not be too noisy, but if fired, indicates a potential @@ -136,27 +130,33 @@ public async Task> GetRefactoringsAsync( const int CodeRefactoringTelemetryDelay = 500; var providerName = provider.GetType().Name; - RefactoringToMetadataMap.TryGetValue(provider, out var providerMetadata); var logMessage = KeyValueLogMessage.Create(m => { m[TelemetryLogging.KeyName] = providerName; - m[TelemetryLogging.KeyLanguageName] = document.Project.Language; + m[TelemetryLogging.KeyLanguageName] = args.document.Project.Language; }); - using (addOperationScope(providerName)) + using (args.addOperationScope(providerName)) using (RoslynEventSource.LogInformationalBlock(FunctionId.Refactoring_CodeRefactoringService_GetRefactoringsAsync, providerName, cancellationToken)) using (TelemetryLogging.LogBlockTime(FunctionId.CodeRefactoring_Delay, logMessage, CodeRefactoringTelemetryDelay)) { - return await GetRefactoringFromProviderAsync(document, state, provider, providerMetadata, - extensionManager, options, cancellationToken).ConfigureAwait(false); + var refactoring = await args.@this.GetRefactoringFromProviderAsync( + args.document, args.state, provider, args.options, cancellationToken).ConfigureAwait(false); + if (refactoring != null) + callback((provider, refactoring)); } }, - cancellationToken)); - } + args: (@this: this, document, state, options, addOperationScope), + cancellationToken).ConfigureAwait(false); + + // Order the refactorings by the order of the providers. + foreach (var provider in orderedProviders) + providerToIndex.Add(provider, providerToIndex.Count); - var results = await Task.WhenAll(tasks).ConfigureAwait(false); - return results.WhereNotNull().ToImmutableArray(); + return pairs + .OrderBy((tuple1, tuple2) => providerToIndex[tuple1.provider] - providerToIndex[tuple2.provider]) + .SelectAsArray(t => t.codeRefactoring); } } @@ -164,11 +164,13 @@ public async Task> GetRefactoringsAsync( TextDocument textDocument, TextSpan state, CodeRefactoringProvider provider, - CodeChangeProviderMetadata? providerMetadata, - IExtensionManager extensionManager, CodeActionOptionsProvider options, CancellationToken cancellationToken) { + RefactoringToMetadataMap.TryGetValue(provider, out var providerMetadata); + + var extensionManager = textDocument.Project.Solution.Services.GetRequiredService(); + return extensionManager.PerformFunctionAsync( provider, async cancellationToken => diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs index b9dd43e57681d..9630a575205ff 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs @@ -24,6 +24,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.RemoveUnnecessaryImports; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -487,32 +488,25 @@ private static SyntaxNode CreateImport(SyntaxGenerator syntaxGenerator, string n var refLocationGroups = refLocationsInSolution.GroupBy(loc => loc.Document.Id); - var fixedDocuments = await Task.WhenAll( - refLocationGroups.Select(refInOneDocument => - FixReferencingDocumentAsync( - solutionWithChangedNamespace.GetRequiredDocument(refInOneDocument.Key), + var fixedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync( + source: refLocationGroups, + produceItems: static async (refInOneDocument, callback, args, cancellationToken) => + { + var result = await FixReferencingDocumentAsync( + args.solutionWithChangedNamespace.GetRequiredDocument(refInOneDocument.Key), refInOneDocument, - newNamespace, - fallbackOptions, - cancellationToken))).ConfigureAwait(false); - - var solutionWithFixedReferences = await MergeDocumentChangesAsync(solutionWithChangedNamespace, fixedDocuments, cancellationToken).ConfigureAwait(false); + args.newNamespace, + args.fallbackOptions, + cancellationToken).ConfigureAwait(false); + callback((result.Id, await result.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false))); + }, + args: (solutionWithChangedNamespace, newNamespace, fallbackOptions), + cancellationToken).ConfigureAwait(false); + var solutionWithFixedReferences = solutionWithChangedNamespace.WithDocumentSyntaxRoots(fixedDocuments); return (solutionWithFixedReferences, refLocationGroups.SelectAsArray(g => g.Key)); } - private static async Task MergeDocumentChangesAsync(Solution originalSolution, Document[] changedDocuments, CancellationToken cancellationToken) - { - foreach (var document in changedDocuments) - { - originalSolution = originalSolution.WithDocumentSyntaxRoot( - document.Id, - await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false)); - } - - return originalSolution; - } - private readonly struct LocationForAffectedSymbol(ReferenceLocation location, bool isReferenceToExtensionMethod) { public ReferenceLocation ReferenceLocation { get; } = location; @@ -773,40 +767,39 @@ private static async Task RemoveUnnecessaryImportsAsync( CodeCleanupOptionsProvider fallbackOptions, CancellationToken cancellationToken) { - using var _ = PooledHashSet.GetInstance(out var linkedDocumentsToSkip); - var documentsToProcessBuilder = ArrayBuilder.GetInstance(); + using var _1 = PooledHashSet.GetInstance(out var linkedDocumentsToSkip); + using var _2 = ArrayBuilder.GetInstance(out var documentsToProcess); foreach (var id in ids) { if (linkedDocumentsToSkip.Contains(id)) - { continue; - } var document = solution.GetRequiredDocument(id); linkedDocumentsToSkip.AddRange(document.GetLinkedDocumentIds()); - documentsToProcessBuilder.Add(document); - - document = await RemoveUnnecessaryImportsWorkerAsync( - document, - CreateImports(document, names, withFormatterAnnotation: false), - cancellationToken).ConfigureAwait(false); - solution = document.Project.Solution; + documentsToProcess.Add(document); } - var documentsToProcess = documentsToProcessBuilder.ToImmutableAndFree(); - - var changeDocuments = await Task.WhenAll(documentsToProcess.Select( - doc => RemoveUnnecessaryImportsWorkerAsync( + var changedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync( + source: documentsToProcess, + produceItems: static async (doc, callback, args, cancellationToken) => + { + var result = await RemoveUnnecessaryImportsWorkerAsync( doc, - CreateImports(doc, names, withFormatterAnnotation: false), - cancellationToken))).ConfigureAwait(false); + CreateImports(doc, args.names, withFormatterAnnotation: false), + args.fallbackOptions, + cancellationToken).ConfigureAwait(false); + callback((result.Id, await result.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false))); + }, + args: (names, fallbackOptions), + cancellationToken).ConfigureAwait(false); - return await MergeDocumentChangesAsync(solution, changeDocuments, cancellationToken).ConfigureAwait(false); + return solution.WithDocumentSyntaxRoots(changedDocuments); - async Task RemoveUnnecessaryImportsWorkerAsync( + async static Task RemoveUnnecessaryImportsWorkerAsync( Document doc, IEnumerable importsToRemove, + CodeCleanupOptionsProvider fallbackOptions, CancellationToken token) { var removeImportService = doc.GetRequiredLanguageService(); diff --git a/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs b/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs index 5095803afa12f..04092ff61a635 100644 --- a/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs +++ b/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -235,16 +236,17 @@ private static async Task> ComputeNonEmptyComp SharedSyntaxContextsWithSpeculativeModel sharedContext, CancellationToken cancellationToken) { - var completionContextTasks = new List>(); - foreach (var provider in providers) - { - completionContextTasks.Add(GetContextAsync( - provider, document, caretPosition, trigger, - options, completionListSpan, sharedContext, cancellationToken)); - } - - var completionContexts = await Task.WhenAll(completionContextTasks).ConfigureAwait(false); - return completionContexts.Where(HasAnyItems).ToImmutableArray(); + return await ProducerConsumer.RunParallelAsync( + source: providers, + produceItems: static async (provider, callback, args, cancellationToken) => + { + var context = await GetContextAsync( + provider, args.document, args.caretPosition, args.trigger, args.options, args.completionListSpan, args.sharedContext, cancellationToken).ConfigureAwait(false); + if (HasAnyItems(context)) + callback(context); + }, + args: (document, caretPosition, trigger, options, completionListSpan, sharedContext), + cancellationToken).ConfigureAwait(false); } private CompletionList MergeAndPruneCompletionLists( diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs index 8f7035e04710f..48e23d1fe301c 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -167,7 +168,11 @@ protected static bool TryFindFirstSymbolMatchesTargetTypes( return index < symbolList.Length; } - private static SupportedPlatformData? ComputeSupportedPlatformData(CompletionContext completionContext, ImmutableArray symbols, Dictionary>? invalidProjectMap, List? totalProjects) + private static SupportedPlatformData? ComputeSupportedPlatformData( + CompletionContext completionContext, + ImmutableArray symbols, + Dictionary>? invalidProjectMap, + List? totalProjects) { SupportedPlatformData? supportedPlatformData = null; if (invalidProjectMap != null) @@ -271,7 +276,19 @@ private async Task> GetItemsAsync( return CreateItems(completionContext, itemsForCurrentDocument, _ => syntaxContext, invalidProjectMap: null, totalProjects: null); } - var contextAndSymbolLists = await GetPerContextSymbolsAsync(completionContext, document, options, new[] { document.Id }.Concat(relatedDocumentIds), cancellationToken).ConfigureAwait(false); + using var _ = PooledDictionary.GetInstance(out var documentIdToIndex); + documentIdToIndex.Add(document.Id, 0); + foreach (var documentId in relatedDocumentIds) + documentIdToIndex.Add(documentId, documentIdToIndex.Count); + + var contextAndSymbolLists = await GetPerContextSymbolsAsync(completionContext, document, options, documentIdToIndex.Keys, cancellationToken).ConfigureAwait(false); + + // We want the resultant contexts ordered in the same order the related documents came in. Importantly, the + // context for *our* starting document should be placed first. + contextAndSymbolLists = contextAndSymbolLists + .OrderBy((tuple1, tuple2) => documentIdToIndex[tuple1.documentId] - documentIdToIndex[tuple2.documentId]) + .ToImmutableArray(); + var symbolToContextMap = UnionSymbols(contextAndSymbolLists); var missingSymbolsMap = FindSymbolsMissingInLinkedContexts(symbolToContextMap, contextAndSymbolLists); var totalProjects = contextAndSymbolLists.Select(t => t.documentId.ProjectId).ToList(); @@ -297,10 +314,7 @@ private static Dictionary UnionSymbols( // We need to use the SemanticModel any particular symbol came from in order to generate its description correctly. // Therefore, when we add a symbol to set of union symbols, add a mapping from it to its SyntaxContext. foreach (var symbol in symbols.GroupBy(s => new { s.Symbol.Name, s.Symbol.Kind }).Select(g => g.First())) - { - if (!result.ContainsKey(symbol)) - result.Add(symbol, syntaxContext); - } + result.TryAdd(symbol, syntaxContext); } return result; @@ -311,33 +325,23 @@ private static Dictionary UnionSymbols( { var solution = document.Project.Solution; - using var _1 = ArrayBuilder symbols)>>.GetInstance(out var tasks); - using var _2 = ArrayBuilder<(DocumentId documentId, TSyntaxContext syntaxContext, ImmutableArray symbols)>.GetInstance(out var perContextSymbols); - - foreach (var relatedDocumentId in relatedDocuments) - { - tasks.Add(Task.Run(async () => + return await ProducerConsumer<(DocumentId documentId, TSyntaxContext syntaxContext, ImmutableArray symbols)>.RunParallelAsync( + source: relatedDocuments, + produceItems: static async (relatedDocumentId, callback, args, cancellationToken) => { - var relatedDocument = solution.GetRequiredDocument(relatedDocumentId); - var syntaxContext = await completionContext.GetSyntaxContextWithExistingSpeculativeModelAsync(relatedDocument, cancellationToken).ConfigureAwait(false) as TSyntaxContext; + var relatedDocument = args.solution.GetRequiredDocument(relatedDocumentId); + var syntaxContext = await args.completionContext.GetSyntaxContextWithExistingSpeculativeModelAsync( + relatedDocument, cancellationToken).ConfigureAwait(false) as TSyntaxContext; Contract.ThrowIfNull(syntaxContext); - var symbols = await TryGetSymbolsForContextAsync(completionContext, syntaxContext, options, cancellationToken).ConfigureAwait(false); - - return (relatedDocument.Id, syntaxContext, symbols); - }, cancellationToken)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); - - foreach (var task in tasks) - { - var (relatedDocumentId, syntaxContext, symbols) = await task.ConfigureAwait(false); - if (!symbols.IsDefault) - perContextSymbols.Add((relatedDocumentId, syntaxContext, symbols)); - } - - return perContextSymbols.ToImmutableAndClear(); + var symbols = await args.@this.TryGetSymbolsForContextAsync( + args.completionContext, syntaxContext, args.options, cancellationToken).ConfigureAwait(false); + + if (!symbols.IsDefault) + callback((relatedDocument.Id, syntaxContext, symbols)); + }, + args: (@this: this, solution, completionContext, options), + cancellationToken).ConfigureAwait(false); } /// diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionCacheServiceFactory.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionCacheServiceFactory.cs index 580e9a849c32a..3d31cab69e03c 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionCacheServiceFactory.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionCacheServiceFactory.cs @@ -48,10 +48,10 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) } var workQueue = new AsyncBatchingWorkQueue( - TimeSpan.FromSeconds(1), - _processBatchAsync, - _listenerProvider.GetListener(FeatureAttribute.CompletionSet), - _disposalToken); + TimeSpan.FromSeconds(1), + _processBatchAsync, + _listenerProvider.GetListener(FeatureAttribute.CompletionSet), + _disposalToken); return new ImportCompletionCacheService( _peItemsCache, _projectItemsCache, workQueue); diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/DefaultExtensionMethodImportCompletionCacheServiceFactory.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/DefaultExtensionMethodImportCompletionCacheServiceFactory.cs index eeb06b8ae680e..bdb72d22b7d3f 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/DefaultExtensionMethodImportCompletionCacheServiceFactory.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/DefaultExtensionMethodImportCompletionCacheServiceFactory.cs @@ -17,6 +17,6 @@ namespace Microsoft.CodeAnalysis.Completion.Providers; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DefaultExtensionMethodImportCompletionCacheServiceFactory(IAsynchronousOperationListenerProvider listenerProvider) - : AbstractImportCompletionCacheServiceFactory(listenerProvider, ExtensionMethodImportCompletionHelper.BatchUpdateCacheAsync, CancellationToken.None) + : AbstractImportCompletionCacheServiceFactory(listenerProvider, ExtensionMethodImportCompletionHelper.BatchUpdateCacheAsync, CancellationToken.None) { } diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/DefaultTypeImportCompletionCacheServiceFactory.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/DefaultTypeImportCompletionCacheServiceFactory.cs index 6fc678e011184..66c8086aaea6c 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/DefaultTypeImportCompletionCacheServiceFactory.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/DefaultTypeImportCompletionCacheServiceFactory.cs @@ -14,6 +14,6 @@ namespace Microsoft.CodeAnalysis.Completion.Providers; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DefaultTypeImportCompletionCacheServiceFactory(IAsynchronousOperationListenerProvider listenerProvider) - : AbstractImportCompletionCacheServiceFactory(listenerProvider, AbstractTypeImportCompletionService.BatchUpdateCacheAsync, CancellationToken.None) + : AbstractImportCompletionCacheServiceFactory(listenerProvider, AbstractTypeImportCompletionService.BatchUpdateCacheAsync, CancellationToken.None) { } diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs index 979bed9aade96..3fc61edaab320 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Completion.Providers; @@ -87,43 +88,44 @@ public static async ValueTask UpdateCacheAsync(Project project, CancellationToke try { // Find applicable symbols in parallel - using var _1 = ArrayBuilder?>>.GetInstance(out var tasks); + var peReferenceMethodSymbolsTask = ProducerConsumer.RunParallelAsync( + source: GetAllRelevantPeReferences(_originatingDocument.Project), + produceItems: static (peReference, callback, args, cancellationToken) => + args.@this.GetExtensionMethodSymbolsFromPeReferenceAsync(peReference, callback, args.forceCacheCreation, cancellationToken), + args: (@this: this, forceCacheCreation), + cancellationToken); + + var projectMethodSymbolsTask = ProducerConsumer.RunParallelAsync( + source: GetAllRelevantProjects(_originatingDocument.Project), + produceItems: static (project, callback, args, cancellationToken) => + args.@this.GetExtensionMethodSymbolsFromProjectAsync(project, callback, args.forceCacheCreation, cancellationToken), + args: (@this: this, forceCacheCreation), + cancellationToken); + + var results = await Task.WhenAll(peReferenceMethodSymbolsTask, projectMethodSymbolsTask).ConfigureAwait(false); - foreach (var peReference in GetAllRelevantPeReferences(_originatingDocument.Project)) - { - tasks.Add(Task.Run(() => GetExtensionMethodSymbolsFromPeReferenceAsync( - peReference, - forceCacheCreation, - cancellationToken).AsTask(), cancellationToken)); - } - - foreach (var project in GetAllRelevantProjects(_originatingDocument.Project)) - { - tasks.Add(Task.Run(() => GetExtensionMethodSymbolsFromProjectAsync( - project, - forceCacheCreation, - cancellationToken), cancellationToken)); - } - - using var _2 = ArrayBuilder.GetInstance(out var symbols); var isPartialResult = false; - var results = await Task.WhenAll(tasks).ConfigureAwait(false); - - foreach (var result in results) + using var _ = ArrayBuilder.GetInstance(results[0].Length + results[1].Length, out var symbols); + foreach (var methodArray in results) { - // `null` indicates we don't have the index ready for the corresponding project/PE. - // returns what we have even it means we only show partial results. - if (result == null) + foreach (var method in methodArray) { - isPartialResult = true; - continue; + // `null` indicates we don't have the index ready for the corresponding project/PE. + // returns what we have even it means we only show partial results. + if (method is null) + { + isPartialResult = true; + } + else + { + symbols.Add(method); + } } - - symbols.AddRange(result); } - var browsableSymbols = symbols.ToImmutable() + var browsableSymbols = symbols + .ToImmutable() .FilterToVisibleAndBrowsableSymbols(hideAdvancedMembers, _originatingSemanticModel.Compilation); return (browsableSymbols, isPartialResult); @@ -148,8 +150,9 @@ private static ImmutableArray GetAllRelevantProjects(Project project) private static ImmutableArray GetAllRelevantPeReferences(Project project) => project.MetadataReferences.OfType().ToImmutableArray(); - private async Task?> GetExtensionMethodSymbolsFromProjectAsync( + private async Task GetExtensionMethodSymbolsFromProjectAsync( Project project, + Action callback, bool forceCacheCreation, CancellationToken cancellationToken) { @@ -161,13 +164,12 @@ private static ImmutableArray GetAllRelevantPeRefer else if (!_cacheService.ProjectItemsCache.TryGetValue(project.Id, out cacheEntry)) { // Use cached data if available, even checksum doesn't match. otherwise, returns null indicating cache not ready. - return null; + callback(null); + return; } if (!cacheEntry.ContainsExtensionMethod) - { - return ImmutableArray.Empty; - } + return; var originatingAssembly = _originatingSemanticModel.Compilation.Assembly; var filter = CreateAggregatedFilter(cacheEntry); @@ -183,13 +185,19 @@ private static ImmutableArray GetAllRelevantPeRefer var matchingMethodSymbols = GetPotentialMatchingSymbolsFromAssembly( compilation.Assembly, filter, internalsVisible, cancellationToken); - return project == _originatingDocument.Project - ? GetExtensionMethodsForSymbolsFromSameCompilation(matchingMethodSymbols, cancellationToken) - : GetExtensionMethodsForSymbolsFromDifferentCompilation(matchingMethodSymbols, cancellationToken); + if (project == _originatingDocument.Project) + { + GetExtensionMethodsForSymbolsFromSameCompilation(matchingMethodSymbols, callback, cancellationToken); + } + else + { + GetExtensionMethodsForSymbolsFromDifferentCompilation(matchingMethodSymbols, callback, cancellationToken); + } } - private async ValueTask?> GetExtensionMethodSymbolsFromPeReferenceAsync( + private async Task GetExtensionMethodSymbolsFromPeReferenceAsync( PortableExecutableReference peReference, + Action callback, bool forceCacheCreation, CancellationToken cancellationToken) { @@ -210,7 +218,8 @@ private static ImmutableArray GetAllRelevantPeRefer else { // No cached data immediately available, returns null to indicate index not ready - return null; + callback(null); + return; } } @@ -218,7 +227,7 @@ private static ImmutableArray GetAllRelevantPeRefer !symbolInfo.ContainsExtensionMethod || _originatingSemanticModel.Compilation.GetAssemblyOrModuleSymbol(peReference) is not IAssemblySymbol assembly) { - return ImmutableArray.Empty; + return; } var filter = CreateAggregatedFilter(symbolInfo); @@ -226,15 +235,14 @@ private static ImmutableArray GetAllRelevantPeRefer var matchingMethodSymbols = GetPotentialMatchingSymbolsFromAssembly(assembly, filter, internalsVisible, cancellationToken); - return GetExtensionMethodsForSymbolsFromSameCompilation(matchingMethodSymbols, cancellationToken); + GetExtensionMethodsForSymbolsFromSameCompilation(matchingMethodSymbols, callback, cancellationToken); } - private ImmutableArray GetExtensionMethodsForSymbolsFromDifferentCompilation( + private void GetExtensionMethodsForSymbolsFromDifferentCompilation( MultiDictionary matchingMethodSymbols, + Action callback, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var builder); - // Matching extension method symbols are grouped based on their receiver type. foreach (var (declaredReceiverType, methodSymbols) in matchingMethodSymbols) { @@ -292,21 +300,16 @@ private ImmutableArray GetExtensionMethodsForSymbolsFromDifferent } if (_originatingSemanticModel.IsAccessible(_position, methodInOriginatingCompilation)) - { - builder.Add(methodInOriginatingCompilation); - } + callback(methodInOriginatingCompilation); } } - - return builder.ToImmutableAndClear(); } - private ImmutableArray GetExtensionMethodsForSymbolsFromSameCompilation( + private void GetExtensionMethodsForSymbolsFromSameCompilation( MultiDictionary matchingMethodSymbols, + Action callback, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var builder); - // Matching extension method symbols are grouped based on their receiver type. foreach (var (receiverType, methodSymbols) in matchingMethodSymbols) { @@ -315,9 +318,7 @@ private ImmutableArray GetExtensionMethodsForSymbolsFromSameCompi // If we already checked an extension method with same receiver type before, and we know it can't be applied // to the receiverTypeSymbol, then no need to proceed further. if (_checkedReceiverTypes.TryGetValue(receiverType, out var cachedResult) && !cachedResult) - { continue; - } // We haven't seen this type yet. Try to check by reducing one extension method // to the given receiver type and save the result. @@ -335,14 +336,10 @@ private ImmutableArray GetExtensionMethodsForSymbolsFromSameCompi foreach (var methodSymbol in methodSymbols) { if (_originatingSemanticModel.IsAccessible(_position, methodSymbol)) - { - builder.Add(methodSymbol); - } + callback(methodSymbol); } } } - - return builder.ToImmutableAndClear(); } private MultiDictionary GetPotentialMatchingSymbolsFromAssembly( diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/IImportCompletionCacheService.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/IImportCompletionCacheService.cs index c2621629fdf4c..64b2d8787533e 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/IImportCompletionCacheService.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/IImportCompletionCacheService.cs @@ -2,11 +2,7 @@ // 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.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Roslyn.Utilities; diff --git a/src/Features/Core/Portable/Copilot/CopilotConstants.cs b/src/Features/Core/Portable/Copilot/CopilotConstants.cs new file mode 100644 index 0000000000000..7b6f866d9f06a --- /dev/null +++ b/src/Features/Core/Portable/Copilot/CopilotConstants.cs @@ -0,0 +1,15 @@ +// 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; + +namespace Microsoft.CodeAnalysis.Copilot; + +internal static class CopilotConstants +{ + public const int CopilotIconLogoId = 1; + public const int CopilotIconSparkleId = 2; + public const int CopilotIconSparkleBlueId = 3; + public static readonly Guid CopilotIconMonikerGuid = new("{4515B9BD-70A1-45FA-9545-D4536417C596}"); +} diff --git a/src/Features/Core/Portable/Copilot/ICopilotCodeAnalysisService.cs b/src/Features/Core/Portable/Copilot/ICopilotCodeAnalysisService.cs index 4904eb815373b..df8cbbdda3cad 100644 --- a/src/Features/Core/Portable/Copilot/ICopilotCodeAnalysisService.cs +++ b/src/Features/Core/Portable/Copilot/ICopilotCodeAnalysisService.cs @@ -59,4 +59,15 @@ internal interface ICopilotCodeAnalysisService : ILanguageService /// which might be used to provide additional context to Copilot for the refinement session. /// Task StartRefinementSessionAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken); + + /// + /// Method to fetch the on-the-fly documentation based on a a symbols + /// and the code for the symbols in . + /// + /// is a formatted string representation of an .
+ /// is a list of a code definitions from an . + /// is the language of the originating . + ///
+ ///
+ Task GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken); } diff --git a/src/Features/Core/Portable/Copilot/ICopilotOptionsService.cs b/src/Features/Core/Portable/Copilot/ICopilotOptionsService.cs index 6e38e58545cdd..62752f0adc64f 100644 --- a/src/Features/Core/Portable/Copilot/ICopilotOptionsService.cs +++ b/src/Features/Core/Portable/Copilot/ICopilotOptionsService.cs @@ -23,4 +23,10 @@ internal interface ICopilotOptionsService : ILanguageService /// Returns true if Copilot background code analysis feature is enabled. ///
Task IsCodeAnalysisOptionEnabledAsync(); + + /// + /// Returns true if Copilot on-the-fly docs feature is enabled. + /// + /// + Task IsOnTheFlyDocsOptionEnabledAsync(); } diff --git a/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs index 30f9b128620d2..12fecbaad1f64 100644 --- a/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics; @@ -105,15 +106,14 @@ public async Task RunAnalysisAsync(Solution solution, ProjectId? projectId, Acti else { // We run analysis for all the projects concurrently as this is a user invoked operation. - using var _ = ArrayBuilder.GetInstance(solution.ProjectIds.Count, out var tasks); - foreach (var project in solution.Projects) - tasks.Add(Task.Run(() => AnalyzeProjectCoreAsync(project, onAfterProjectAnalyzed, cancellationToken), cancellationToken)); - - await Task.WhenAll(tasks).ConfigureAwait(false); + await RoslynParallel.ForEachAsync( + source: solution.Projects, + cancellationToken, + (project, cancellationToken) => AnalyzeProjectCoreAsync(project, onAfterProjectAnalyzed, cancellationToken)).ConfigureAwait(false); } } - private async Task AnalyzeProjectCoreAsync(Project project, Action onAfterProjectAnalyzed, CancellationToken cancellationToken) + private async ValueTask AnalyzeProjectCoreAsync(Project project, Action onAfterProjectAnalyzed, CancellationToken cancellationToken) { // Execute force analysis for the project. await _diagnosticAnalyzerService.ForceAnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index 6ac5f90bcae97..8fadcb3bc2123 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Diagnostics; diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 7125e80813e02..91e07ddb08d02 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -2567,7 +2567,7 @@ private async Task> AnalyzeSemanticsAsync( { if (processedSymbols.Add(newContainingType)) { - if (capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition)) + if (capabilities.GrantNewTypeDefinition(containingType)) { semanticEdits.Add(SemanticEditInfo.CreateReplace(containingTypeSymbolKey, IsPartialTypeEdit(oldContainingType, newContainingType, oldTree, newTree) ? containingTypeSymbolKey : null)); @@ -2601,7 +2601,7 @@ private async Task> AnalyzeSemanticsAsync( // https://github.com/dotnet/roslyn/issues/54881 diagnosticContext.Report(RudeEditKind.ChangingTypeParameters, cancellationToken); } - else if (!capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition)) + else if (!capabilities.GrantNewTypeDefinition(oldType)) { diagnosticContext.Report(RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, cancellationToken); } @@ -2883,7 +2883,7 @@ newSymbol is IPropertySymbol newProperty && // therefore inserting the $ type Contract.ThrowIfFalse(newSymbol is INamedTypeSymbol || IsGlobalMain(newSymbol)); - if (!capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition)) + if (!capabilities.GrantNewTypeDefinition((newSymbol as INamedTypeSymbol) ?? newSymbol.ContainingType)) { diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.InsertNotSupportedByRuntime, @@ -3205,7 +3205,7 @@ IFieldSymbol or { if (processedSymbols.Add(newContainingType)) { - if (capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition)) + if (capabilities.GrantNewTypeDefinition(newContainingType)) { var oldContainingTypeKey = SymbolKey.Create(oldContainingType, cancellationToken); semanticEdits.Add(SemanticEditInfo.CreateReplace(oldContainingTypeKey, @@ -3889,7 +3889,7 @@ private void ReportMemberOrLambdaBodyUpdateRudeEdits( if (!oldStateMachineInfo.IsStateMachine && newStateMachineInfo.IsStateMachine && - !capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition)) + !capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition | EditAndContinueCapabilities.AddExplicitInterfaceImplementation)) { // Adding a state machine, either for async or iterator, will require creating a new helper class // so is a rude edit if the runtime doesn't support it @@ -5684,9 +5684,11 @@ bool CanAddNewLambda(SyntaxNode newLambda, LambdaBody newLambdaBody1, LambdaBody } } - // If the old verison of the method had any lambdas the nwe know a closure type exists and a new one isn't needed. + // If the old version of the method had any lambdas then we know a closure type exists and a new one isn't needed. // We also know that adding a local function won't create a new closure type. // Otherwise, we assume a new type is needed. + // We also assume that the closure type does not implement an interface explicitly, + // so we do not need AddExplicitInterfaceImplementation capability. if (!oldHasLambdas && !isLocalFunction) { diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs index ea8381ceb81b2..36607b513ff06 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs @@ -65,6 +65,11 @@ internal enum EditAndContinueCapabilities /// Adding a static or instance field to an existing generic type. ///
GenericAddFieldToExistingType = 1 << 9, + + /// + /// The runtime supports adding to InterfaceImpl table. + /// + AddExplicitInterfaceImplementation = 1 << 10, } internal static class EditAndContinueCapabilitiesParser @@ -87,6 +92,7 @@ public static EditAndContinueCapabilities Parse(ImmutableArray capabilit nameof(EditAndContinueCapabilities.GenericAddMethodToExistingType) => EditAndContinueCapabilities.GenericAddMethodToExistingType, nameof(EditAndContinueCapabilities.GenericUpdateMethod) => EditAndContinueCapabilities.GenericUpdateMethod, nameof(EditAndContinueCapabilities.GenericAddFieldToExistingType) => EditAndContinueCapabilities.GenericAddFieldToExistingType, + nameof(EditAndContinueCapabilities.AddExplicitInterfaceImplementation) => EditAndContinueCapabilities.AddExplicitInterfaceImplementation, // To make it eaiser for runtimes to specify more broad capabilities "AddDefinitionToExistingType" => EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddStaticFieldToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType, @@ -123,6 +129,9 @@ public static ImmutableArray ToStringArray(this EditAndContinueCapabilit if (capabilities.HasFlag(EditAndContinueCapabilities.UpdateParameters)) builder.Add(nameof(EditAndContinueCapabilities.UpdateParameters)); + if (capabilities.HasFlag(EditAndContinueCapabilities.AddExplicitInterfaceImplementation)) + builder.Add(nameof(EditAndContinueCapabilities.AddExplicitInterfaceImplementation)); + return builder.ToImmutableAndClear(); } } diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilitiesGrantor.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilitiesGrantor.cs index e5eda77eb92a3..71dc24654c92c 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilitiesGrantor.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilitiesGrantor.cs @@ -18,4 +18,19 @@ public bool Grant(EditAndContinueCapabilities capabilities) GrantedCapabilities |= capabilities; return (_availableCapabilities & capabilities) == capabilities; } + + public bool GrantNewTypeDefinition(INamedTypeSymbol type) + { + if (!Grant(EditAndContinueCapabilities.NewTypeDefinition)) + { + return false; + } + + if (type.HasExplicitlyImplementedInterfaceMember() && !Grant(EditAndContinueCapabilities.AddExplicitInterfaceImplementation)) + { + return false; + } + + return true; + } } diff --git a/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs b/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs index 4a0c0810683d9..b9d00876835b6 100644 --- a/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs +++ b/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs @@ -187,4 +187,10 @@ private static bool ParsePrimaryParameterBackingFieldName(string fieldName, [Not public static ISymbol PartialAsImplementation(this ISymbol symbol) => symbol is IMethodSymbol { PartialImplementationPart: { } impl } ? impl : symbol; + + /// + /// Returns true if any member of the type implements an interface member explicitly. + /// + public static bool HasExplicitlyImplementedInterfaceMember(this INamedTypeSymbol type) + => type.GetMembers().Any(static member => member.ExplicitInterfaceImplementations().Any()); } diff --git a/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs b/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs index 5715e2012bebf..82afe2421891b 100644 --- a/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs +++ b/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs @@ -21,7 +21,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Rename; -using Microsoft.CodeAnalysis.Rename.ConflictEngine; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; @@ -31,8 +30,16 @@ namespace Microsoft.CodeAnalysis.EncapsulateField; internal abstract partial class AbstractEncapsulateFieldService : ILanguageService { + private static readonly CultureInfo EnUSCultureInfo = new("en-US"); + private static readonly SymbolRenameOptions s_symbolRenameOptions = new( + RenameOverloads: false, + RenameInStrings: false, + RenameInComments: false, + RenameFile: false); + protected abstract Task RewriteFieldNameAndAccessibilityAsync(string originalFieldName, bool makePrivate, Document document, SyntaxAnnotation declarationAnnotation, CodeAndImportGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken); protected abstract Task> GetFieldsAsync(Document document, TextSpan span, CancellationToken cancellationToken); + protected abstract IEnumerable GetConstructorNodes(INamedTypeSymbol containingType); public async Task EncapsulateFieldsInSpanAsync(Document document, TextSpan span, CleanCodeGenerationOptionsProvider fallbackOptions, bool useDefaultBehavior, CancellationToken cancellationToken) { @@ -44,7 +51,7 @@ public async Task EncapsulateFieldsInSpanAsync(Document return new EncapsulateFieldResult( firstField.ToDisplayString(), firstField.GetGlyph(), - c => EncapsulateFieldsAsync(document, fields, fallbackOptions, useDefaultBehavior, c)); + cancellationToken => EncapsulateFieldsAsync(document, fields, fallbackOptions, useDefaultBehavior, cancellationToken)); } public async Task> GetEncapsulateFieldCodeActionsAsync(Document document, TextSpan span, CleanCodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) @@ -74,19 +81,16 @@ public async Task> GetEncapsulateFieldCodeActionsAsyn } private ImmutableArray EncapsulateAllFields(Document document, ImmutableArray fields, CleanCodeGenerationOptionsProvider fallbackOptions) - { - return - [ + => [ CodeAction.Create( - FeaturesResources.Encapsulate_fields_and_use_property, - c => EncapsulateFieldsAsync(document, fields, fallbackOptions, updateReferences: true, c), - nameof(FeaturesResources.Encapsulate_fields_and_use_property)), + FeaturesResources.Encapsulate_fields_and_use_property, + cancellationToken => EncapsulateFieldsAsync(document, fields, fallbackOptions, updateReferences: true, cancellationToken), + nameof(FeaturesResources.Encapsulate_fields_and_use_property)), CodeAction.Create( FeaturesResources.Encapsulate_fields_but_still_use_field, - c => EncapsulateFieldsAsync(document, fields, fallbackOptions, updateReferences: false, c), + cancellationToken => EncapsulateFieldsAsync(document, fields, fallbackOptions, updateReferences: false, cancellationToken), nameof(FeaturesResources.Encapsulate_fields_but_still_use_field)), ]; - } private ImmutableArray EncapsulateOneField(Document document, IFieldSymbol field, CleanCodeGenerationOptionsProvider fallbackOptions) { @@ -94,12 +98,12 @@ private ImmutableArray EncapsulateOneField(Document document, IField return [ CodeAction.Create( - string.Format(FeaturesResources.Encapsulate_field_colon_0_and_use_property, field.Name), - c => EncapsulateFieldsAsync(document, fields, fallbackOptions, updateReferences: true, c), - nameof(FeaturesResources.Encapsulate_field_colon_0_and_use_property) + "_" + field.Name), + string.Format(FeaturesResources.Encapsulate_field_colon_0_and_use_property, field.Name), + cancellationToken => EncapsulateFieldsAsync(document, fields, fallbackOptions, updateReferences: true, cancellationToken), + nameof(FeaturesResources.Encapsulate_field_colon_0_and_use_property) + "_" + field.Name), CodeAction.Create( string.Format(FeaturesResources.Encapsulate_field_colon_0_but_still_use_field, field.Name), - c => EncapsulateFieldsAsync(document, fields, fallbackOptions, updateReferences: false, c), + cancellationToken => EncapsulateFieldsAsync(document, fields, fallbackOptions, updateReferences: false, cancellationToken), nameof(FeaturesResources.Encapsulate_field_colon_0_but_still_use_field) + "_" + field.Name), ]; } @@ -126,9 +130,7 @@ public async Task EncapsulateFieldsAsync( cancellationToken).ConfigureAwait(false); if (!result.HasValue) - { return solution; - } return await RemoteUtilities.UpdateSolutionAsync( solution, result.Value, cancellationToken).ConfigureAwait(false); @@ -182,23 +184,6 @@ private async Task EncapsulateFieldAsync( fieldDeclaration.GetSyntax(cancellationToken).WithAdditionalAnnotations(declarationAnnotation))); var solution = document.Project.Solution; - - foreach (var linkedDocumentId in document.GetLinkedDocumentIds()) - { - var linkedDocument = solution.GetDocument(linkedDocumentId); - var linkedRoot = await linkedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var linkedFieldNode = linkedRoot.FindNode(fieldDeclaration.Span); - if (linkedFieldNode.Span != fieldDeclaration.Span) - { - continue; - } - - var updatedRoot = linkedRoot.ReplaceNode(linkedFieldNode, linkedFieldNode.WithAdditionalAnnotations(declarationAnnotation)); - solution = solution.WithDocumentSyntaxRoot(linkedDocumentId, updatedRoot); - } - - document = solution.GetDocument(document.Id); - // Resolve the annotated symbol and prepare for rename. var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); @@ -220,18 +205,6 @@ private async Task EncapsulateFieldAsync( document = await Formatter.FormatAsync(document.WithSyntaxRoot(rewrittenFieldDeclaration), Formatter.Annotation, formattingOptions, cancellationToken).ConfigureAwait(false); - solution = document.Project.Solution; - foreach (var linkedDocumentId in document.GetLinkedDocumentIds()) - { - var linkedDocument = solution.GetDocument(linkedDocumentId); - var linkedDocumentFormattingOptions = await linkedDocument.GetSyntaxFormattingOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); - var updatedLinkedRoot = await RewriteFieldNameAndAccessibilityAsync(finalFieldName, markFieldPrivate, linkedDocument, declarationAnnotation, fallbackOptions, cancellationToken).ConfigureAwait(false); - var updatedLinkedDocument = await Formatter.FormatAsync(linkedDocument.WithSyntaxRoot(updatedLinkedRoot), Formatter.Annotation, linkedDocumentFormattingOptions, cancellationToken).ConfigureAwait(false); - solution = updatedLinkedDocument.Project.Solution; - } - - document = solution.GetDocument(document.Id); - semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var newRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); @@ -262,11 +235,13 @@ private async Task UpdateReferencesAsync( bool updateReferences, Solution solution, Document document, IFieldSymbol field, string finalFieldName, string generatedPropertyName, CodeCleanupOptionsProvider fallbackOptions, CancellationToken cancellationToken) { if (!updateReferences) - { return solution; - } var projectId = document.Project.Id; + var linkedDocumentIds = document.GetLinkedDocumentIds(); + using var _ = PooledHashSet.GetInstance(out var linkedProjectIds); + linkedProjectIds.AddRange(linkedDocumentIds.Select(d => d.ProjectId)); + if (field.IsReadOnly) { // Inside the constructor we want to rename references the field to the final field name. @@ -274,9 +249,8 @@ private async Task UpdateReferencesAsync( if (finalFieldName != field.Name && constructorLocations.Count > 0) { solution = await RenameAsync( - solution, field, finalFieldName, - (docId, span) => IntersectsWithAny(docId, span, constructorLocations), - fallbackOptions, + solution, field, finalFieldName, fallbackOptions, linkedProjectIds, + filter: (docId, span) => IntersectsWithAny(docId, span, constructorLocations), cancellationToken).ConfigureAwait(false); document = solution.GetDocument(document.Id); @@ -288,16 +262,17 @@ private async Task UpdateReferencesAsync( // Outside the constructor we want to rename references to the field to final property name. return await RenameAsync( - solution, field, generatedPropertyName, - (documentId, span) => !IntersectsWithAny(documentId, span, constructorLocations), - fallbackOptions, + solution, field, generatedPropertyName, fallbackOptions, linkedProjectIds, + filter: (documentId, span) => !IntersectsWithAny(documentId, span, constructorLocations), cancellationToken).ConfigureAwait(false); } else { // Just rename everything. - return await Renamer.RenameSymbolAsync( - solution, field, new SymbolRenameOptions(), generatedPropertyName, cancellationToken).ConfigureAwait(false); + return await RenameAsync( + solution, field, generatedPropertyName, fallbackOptions, linkedProjectIds, + filter: static (documentId, span) => true, + cancellationToken).ConfigureAwait(false); } } @@ -305,21 +280,19 @@ private static async Task RenameAsync( Solution solution, IFieldSymbol field, string finalName, - Func filter, CodeCleanupOptionsProvider fallbackOptions, + HashSet linkedProjectIds, + Func filter, CancellationToken cancellationToken) { - var options = new SymbolRenameOptions( - RenameOverloads: false, - RenameInStrings: false, - RenameInComments: false, - RenameFile: false); - var initialLocations = await Renamer.FindRenameLocationsAsync( - solution, field, options, cancellationToken).ConfigureAwait(false); + solution, field, s_symbolRenameOptions, cancellationToken).ConfigureAwait(false); - var resolution = await initialLocations.Filter(filter).ResolveConflictsAsync( - field, finalName, nonConflictSymbolKeys: default, fallbackOptions, cancellationToken).ConfigureAwait(false); + // Ensure we don't update any files in projects linked to us. That will be taken care of automatically when we + // edit the files in the current project + var resolution = await initialLocations + .Filter((documentId, span) => !linkedProjectIds.Contains(documentId.ProjectId) && filter(documentId, span)) + .ResolveConflictsAsync(field, finalName, nonConflictSymbolKeys: default, fallbackOptions, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(resolution.IsSuccessful); @@ -343,8 +316,6 @@ private static bool IntersectsWithAny(DocumentId documentId, TextSpan span, ISet private ISet<(DocumentId documentId, TextSpan span)> GetConstructorLocations(Solution solution, INamedTypeSymbol containingType) => GetConstructorNodes(containingType).Select(n => (solution.GetRequiredDocument(n.SyntaxTree).Id, n.Span)).ToSet(); - internal abstract IEnumerable GetConstructorNodes(INamedTypeSymbol containingType); - protected static async Task AddPropertyAsync( Document document, Solution destinationSolution, @@ -465,6 +436,4 @@ protected static string GeneratePropertyName(string fieldName) var firstCharacter = EnUSCultureInfo.TextInfo.ToUpper(baseName[0]); return firstCharacter.ToString() + baseName[1..]; } - - private static readonly CultureInfo EnUSCultureInfo = new("en-US"); } diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/DefaultUnitTestingDocumentTrackingService.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/DefaultUnitTestingDocumentTrackingService.cs deleted file mode 100644 index 97e279c6cb2a2..0000000000000 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/DefaultUnitTestingDocumentTrackingService.cs +++ /dev/null @@ -1,31 +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.Immutable; -using System.Composition; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.SolutionCrawler; - -[ExportWorkspaceService(typeof(IUnitTestingDocumentTrackingService), ServiceLayer.Default)] -[Shared] -internal sealed class DefaultUnitTestingDocumentTrackingService : IUnitTestingDocumentTrackingService -{ - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DefaultUnitTestingDocumentTrackingService() - { - } - - public bool SupportsDocumentTracking => false; - - public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } - - public ImmutableArray GetVisibleDocuments() - => []; - - public DocumentId? TryGetActiveDocument() - => null; -} diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/IUnitTestingDocumentTrackingService.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/IUnitTestingDocumentTrackingService.cs deleted file mode 100644 index bdd1f6262ce7d..0000000000000 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/IUnitTestingDocumentTrackingService.cs +++ /dev/null @@ -1,30 +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.Immutable; -using Microsoft.CodeAnalysis.Host; - -namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.SolutionCrawler; - -internal interface IUnitTestingDocumentTrackingService : IWorkspaceService -{ - bool SupportsDocumentTracking { get; } - - /// - /// Get the of the active document. May be null if there is no active document - /// or the active document is not in the workspace. - /// - DocumentId? TryGetActiveDocument(); - - /// - /// Get a read only collection of the s of all the visible documents in the workspace. - /// - ImmutableArray GetVisibleDocuments(); - - /// - /// Raised when a text buffer that's not part of a workspace is changed. - /// - event EventHandler NonRoslynBufferTextChanged; -} diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/IUnitTestingDocumentTrackingServiceExtensions.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/IUnitTestingDocumentTrackingServiceExtensions.cs deleted file mode 100644 index 8d5735b865092..0000000000000 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/IUnitTestingDocumentTrackingServiceExtensions.cs +++ /dev/null @@ -1,36 +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.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.SolutionCrawler; - -internal static class IUnitTestingDocumentTrackingServiceExtensions -{ - /// - /// Gets the active the user is currently working in. May be null if - /// there is no active document or the active document is not in this . - /// - public static Document? GetActiveDocument(this IUnitTestingDocumentTrackingService service, Solution solution) - { - // Note: GetDocument checks that the DocId is contained in the solution, and returns null if not. - return solution.GetDocument(service.TryGetActiveDocument()); - } - - /// - /// Get a read only collection of all the unique visible documents in the workspace that are - /// contained within . - /// - public static ImmutableArray GetVisibleDocuments(this IUnitTestingDocumentTrackingService service, Solution solution) - => service.GetVisibleDocuments() - .Select(solution.GetDocument) - .WhereNotNull() - .Distinct() - .ToImmutableArray(); -} diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.AbstractUnitTestingPriorityProcessor.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.AbstractUnitTestingPriorityProcessor.cs index ddaa204b9282b..826660ba0a116 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.AbstractUnitTestingPriorityProcessor.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.AbstractUnitTestingPriorityProcessor.cs @@ -37,7 +37,6 @@ public AbstractUnitTestingPriorityProcessor( _lazyAnalyzers = lazyAnalyzers; Processor = processor; - Processor._documentTracker.NonRoslynBufferTextChanged += OnNonRoslynBufferTextChanged; } public ImmutableArray Analyzers @@ -117,31 +116,6 @@ protected async Task WaitForHigherPriorityOperationsAsync() } } } - - public override void Shutdown() - { - base.Shutdown(); - - Processor._documentTracker.NonRoslynBufferTextChanged -= OnNonRoslynBufferTextChanged; - } - - private void OnNonRoslynBufferTextChanged(object? sender, EventArgs e) - { - // There are 2 things incremental processor takes care of - // - // #1 is making sure we delay processing any work until there is enough idle (ex, typing) in host. - // #2 is managing cancellation and pending works. - // - // we used to do #1 and #2 only for Roslyn files. and that is usually fine since most of time solution contains only roslyn files. - // - // but for mixed solution (ex, Roslyn files + HTML + JS + CSS), #2 still makes sense but #1 doesn't. We want - // to pause any work while something is going on in other project types as well. - // - // we need to make sure we play nice with neighbors as well. - // - // now, we don't care where changes are coming from. if there is any change in host, we pause ourselves for a while. - UpdateLastAccessTime(); - } } } } diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingIncrementalAnalyzerProcessor.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingIncrementalAnalyzerProcessor.cs index 9f3ef2fd09d1c..7b883e0bed9fd 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingIncrementalAnalyzerProcessor.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingIncrementalAnalyzerProcessor.cs @@ -30,7 +30,6 @@ private partial class UnitTestingIncrementalAnalyzerProcessor private readonly UnitTestingRegistration _registration; private readonly IAsynchronousOperationListener _listener; - private readonly IUnitTestingDocumentTrackingService _documentTracker; private readonly UnitTestingNormalPriorityProcessor _normalPriorityProcessor; private readonly UnitTestingLowPriorityProcessor _lowPriorityProcessor; @@ -58,8 +57,6 @@ public UnitTestingIncrementalAnalyzerProcessor( var lazyAllAnalyzers = new Lazy>(() => GetIncrementalAnalyzers(_registration, analyzersGetter, onlyHighPriorityAnalyzer: false)); // event and worker queues - _documentTracker = _registration.Services.GetRequiredService(); - var globalNotificationService = _registration.Services.ExportProvider.GetExports().FirstOrDefault()?.Value; _normalPriorityProcessor = new UnitTestingNormalPriorityProcessor(listener, this, lazyAllAnalyzers, globalNotificationService, normalBackOffTimeSpan, shutdownToken); diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingLowPriorityProcessor.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingLowPriorityProcessor.cs index e7a622ffc754b..a9bf69e2f06c9 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingLowPriorityProcessor.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingLowPriorityProcessor.cs @@ -51,13 +51,8 @@ protected override async Task ExecuteAsync() // we wait for global operation, higher and normal priority processor to finish its working await WaitForHigherPriorityOperationsAsync().ConfigureAwait(false); - // process any available project work, preferring the active project. - var preferableProjectId = Processor._documentTracker.SupportsDocumentTracking - ? Processor._documentTracker.TryGetActiveDocument()?.ProjectId - : null; - if (_workItemQueue.TryTakeAnyWork( - preferableProjectId, + preferableProjectId: null, out var workItem, out var projectCancellation)) { await ProcessProjectAsync(Analyzers, workItem, projectCancellation).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingNormalPriorityProcessor.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingNormalPriorityProcessor.cs index 9c6186807918a..9c8fb46ddf534 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingNormalPriorityProcessor.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/UnitTestingWorkCoordinator.UnitTestingNormalPriorityProcessor.cs @@ -120,12 +120,6 @@ protected override async Task ExecuteAsync() // okay, there must be at least one item in the map ResetStates(); - if (await TryProcessOneHigherPriorityDocumentAsync().ConfigureAwait(false)) - { - // successfully processed a high priority document. - return; - } - // process one of documents remaining if (!_workItemQueue.TryTakeAnyWork( _currentProjectProcessing, @@ -175,77 +169,6 @@ private void SetProjectProcessing(ProjectId currentProject) _currentProjectProcessing = currentProject; } - private IEnumerable GetPrioritizedPendingDocuments() - { - // First the active document - var activeDocumentId = Processor._documentTracker.TryGetActiveDocument(); - if (activeDocumentId != null) - { - yield return activeDocumentId; - } - - // Now any visible documents - foreach (var visibleDocumentId in Processor._documentTracker.GetVisibleDocuments()) - { - yield return visibleDocumentId; - } - - // Any other high priority documents - foreach (var (documentId, _) in _higherPriorityDocumentsNotProcessed) - { - yield return documentId; - } - } - - private async Task TryProcessOneHigherPriorityDocumentAsync() - { - try - { - if (!Processor._documentTracker.SupportsDocumentTracking) - { - return false; - } - - foreach (var documentId in GetPrioritizedPendingDocuments()) - { - if (CancellationToken.IsCancellationRequested) - { - return true; - } - - // this is a best effort algorithm with some shortcomings. - // - // the most obvious issue is if there is a new work item (without a solution change - but very unlikely) - // for a opened document we already processed, the work item will be treated as a regular one rather than higher priority one - // (opened document) - // see whether we have work item for the document - if (!_workItemQueue.TryTake(documentId, out var workItem, out var documentCancellation)) - { - RemoveHigherPriorityDocument(documentId); - continue; - } - - // okay now we have work to do - await ProcessDocumentAsync(Analyzers, workItem, documentCancellation).ConfigureAwait(false); - - RemoveHigherPriorityDocument(documentId); - return true; - } - - return false; - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) - { - throw ExceptionUtilities.Unreachable(); - } - } - - private void RemoveHigherPriorityDocument(DocumentId documentId) - { - // remove opened document processed - _higherPriorityDocumentsNotProcessed.TryRemove(documentId, out _); - } - private async Task ProcessDocumentAsync(ImmutableArray analyzers, UnitTestingWorkItem workItem, CancellationToken cancellationToken) { Contract.ThrowIfNull(workItem.DocumentId); diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs index 55600bdde6add..64d222b91d20f 100644 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs @@ -35,14 +35,14 @@ public async Task SearchDocumentAsync( Document document, string searchPattern, IImmutableSet kinds, - Func onResultFound, + Func, Task> onResultsFound, CancellationToken cancellationToken) { if (_searchService != null) { var results = await _searchService.SearchDocumentAsync(document, searchPattern, kinds, cancellationToken).ConfigureAwait(false); - foreach (var result in results) - await onResultFound(Convert(result)).ConfigureAwait(false); + if (results.Length > 0) + await onResultsFound(results.SelectAsArray(Convert)).ConfigureAwait(false); } } @@ -53,7 +53,7 @@ public async Task SearchProjectsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -78,8 +78,9 @@ async Task ProcessProjectAsync(Project project) { var results = await _searchService.SearchProjectAsync( project, priorityDocuments.WhereAsArray(d => d.Project == project), searchPattern, kinds, cancellationToken).ConfigureAwait(false); - foreach (var result in results) - await onResultFound(project, Convert(result)).ConfigureAwait(false); + + if (results.Length > 0) + await onResultsFound(results.SelectAsArray(Convert)).ConfigureAwait(false); } await onProjectCompleted().ConfigureAwait(false); diff --git a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs index d0762770efc95..f715ff1338f30 100644 --- a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs @@ -54,10 +54,17 @@ public readonly struct Update( private DebuggingSessionId _sessionId; public WatchHotReloadService(HostWorkspaceServices services, ImmutableArray capabilities) - : this(services.SolutionServices, () => ValueTaskFactory.FromResult(capabilities)) + : this(services.SolutionServices, () => ValueTaskFactory.FromResult(AddImplicitDotNetCapabilities(capabilities))) { } + /// + /// Adds capabilities that are available by default on runtimes supported by dotnet-watch: .NET and Mono + /// and not on .NET Framework (they are not in . + /// + private static ImmutableArray AddImplicitDotNetCapabilities(ImmutableArray capabilities) + => capabilities.Add(nameof(EditAndContinueCapabilities.AddExplicitInterfaceImplementation)); + /// /// Starts the watcher. /// @@ -121,6 +128,7 @@ public void EndSession() { Contract.ThrowIfFalse(_sessionId != default, "Session has not started"); _encService.EndDebuggingSession(_sessionId); + _sessionId = default; } internal TestAccessor GetTestAccessor() diff --git a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs index 232b266014dab..4e3563c936b61 100644 --- a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs +++ b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs @@ -5,6 +5,7 @@ #nullable disable using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; @@ -73,8 +74,6 @@ public IStreamingProgressTracker ProgressTracker // any of these. public ValueTask OnStartedAsync(CancellationToken cancellationToken) => default; public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => default; - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) => default; - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) => default; // More complicated forwarding functions. These need to map from the symbols // used by the FAR engine to the INavigableItems used by the streaming FAR @@ -107,17 +106,21 @@ public async ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationTok await context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false); } - public async ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, ReferenceLocation location, CancellationToken cancellationToken) + public async ValueTask OnReferencesFoundAsync( + ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) { - var definitionItem = await GetDefinitionItemAsync(group, cancellationToken).ConfigureAwait(false); - var referenceItem = await location.TryCreateSourceReferenceItemAsync( - classificationOptions, - definitionItem, - includeHiddenLocations: false, - cancellationToken).ConfigureAwait(false); - - if (referenceItem != null) - await context.OnReferenceFoundAsync(referenceItem, cancellationToken).ConfigureAwait(false); + foreach (var (group, _, location) in references) + { + var definitionItem = await GetDefinitionItemAsync(group, cancellationToken).ConfigureAwait(false); + var referenceItem = await location.TryCreateSourceReferenceItemAsync( + classificationOptions, + definitionItem, + includeHiddenLocations: false, + cancellationToken).ConfigureAwait(false); + + if (referenceItem != null) + await context.OnReferenceFoundAsync(referenceItem, cancellationToken).ConfigureAwait(false); + } } } } diff --git a/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/AbstractGenerateEqualsAndGetHashCodeService.cs b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/AbstractGenerateEqualsAndGetHashCodeService.cs index e39103ea0b9c2..1d835f2358fea 100644 --- a/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/AbstractGenerateEqualsAndGetHashCodeService.cs +++ b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/AbstractGenerateEqualsAndGetHashCodeService.cs @@ -28,12 +28,12 @@ protected abstract bool TryWrapWithUnchecked( public async Task FormatDocumentAsync(Document document, SyntaxFormattingOptions options, CancellationToken cancellationToken) { - var rules = new List { new FormatLargeBinaryExpressionRule(document.GetRequiredLanguageService()) }; - rules.AddRange(Formatter.GetDefaultFormattingRules(document)); - + var formatBinaryRule = new FormatLargeBinaryExpressionRule(document.GetRequiredLanguageService()); var formattedDocument = await Formatter.FormatAsync( document, s_specializedFormattingAnnotation, - options, rules, cancellationToken).ConfigureAwait(false); + options, + [formatBinaryRule, .. Formatter.GetDefaultFormattingRules(document)], + cancellationToken).ConfigureAwait(false); return formattedDocument; } diff --git a/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs b/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs index 28ced7c05cc82..851d23ed45f4c 100644 --- a/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs +++ b/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs @@ -44,7 +44,7 @@ await context.ReportNoResultsAsync( var (symbol, project) = symbolAndProjectOpt.Value; var solution = project.Solution; - var bases = await FindBaseHelpers.FindBasesAsync(symbol, solution, cancellationToken).ConfigureAwait(false); + var bases = FindBaseHelpers.FindBases(symbol, solution, cancellationToken); if (bases.Length == 0 && symbol is IMethodSymbol { MethodKind: MethodKind.Constructor } constructor) { var nextConstructor = await FindNextConstructorInChainAsync(solution, constructor, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/GoToBase/FindBaseHelpers.cs b/src/Features/Core/Portable/GoToBase/FindBaseHelpers.cs index b7ec56faeae7f..25a40bdc1271f 100644 --- a/src/Features/Core/Portable/GoToBase/FindBaseHelpers.cs +++ b/src/Features/Core/Portable/GoToBase/FindBaseHelpers.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.GoToBase; internal static class FindBaseHelpers { - public static ValueTask> FindBasesAsync( + public static ImmutableArray FindBases( ISymbol symbol, Solution solution, CancellationToken cancellationToken) { if (symbol is INamedTypeSymbol @@ -23,16 +23,12 @@ public static ValueTask> FindBasesAsync( } namedTypeSymbol) { var result = BaseTypeFinder.FindBaseTypesAndInterfaces(namedTypeSymbol).CastArray(); - return ValueTaskFactory.FromResult(result); + return result; } - if (symbol.Kind is SymbolKind.Property or - SymbolKind.Method or - SymbolKind.Event) - { - return BaseTypeFinder.FindOverriddenAndImplementedMembersAsync(symbol, solution, cancellationToken); - } + if (symbol.Kind is SymbolKind.Property or SymbolKind.Method or SymbolKind.Event) + return BaseTypeFinder.FindOverriddenAndImplementedMembers(symbol, solution, cancellationToken); - return ValueTaskFactory.FromResult(ImmutableArray.Empty); + return []; } } diff --git a/src/Features/Core/Portable/GoToDefinition/AbstractGoToDefinitionSymbolService.cs b/src/Features/Core/Portable/GoToDefinition/AbstractGoToDefinitionSymbolService.cs index b8670f91f4d2a..5bb83805b1e99 100644 --- a/src/Features/Core/Portable/GoToDefinition/AbstractGoToDefinitionSymbolService.cs +++ b/src/Features/Core/Portable/GoToDefinition/AbstractGoToDefinitionSymbolService.cs @@ -2,7 +2,6 @@ // 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; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -16,16 +15,17 @@ namespace Microsoft.CodeAnalysis.GoToDefinition; internal abstract class AbstractGoToDefinitionSymbolService : IGoToDefinitionSymbolService { - protected abstract ISymbol FindRelatedExplicitlyDeclaredSymbol(ISymbol symbol, Compilation compilation); + protected abstract Task FindRelatedExplicitlyDeclaredSymbolAsync(Project project, ISymbol symbol, CancellationToken cancellationToken); protected abstract int? GetTargetPositionIfControlFlow(SemanticModel semanticModel, SyntaxToken token); - public async Task<(ISymbol?, Project, TextSpan)> GetSymbolProjectAndBoundSpanAsync(Document document, int position, CancellationToken cancellationToken) + public async Task<(ISymbol? symbol, Project project, TextSpan boundSpan)> GetSymbolProjectAndBoundSpanAsync(Document document, int position, CancellationToken cancellationToken) { var project = document.Project; var services = document.Project.Solution.Services; - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + // We don't need nullable information to compute the symbol. So avoid expensive work computing this. + var semanticModel = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); var semanticInfo = await SymbolFinder.GetSemanticInfoAtPositionAsync(semanticModel, position, services, cancellationToken).ConfigureAwait(false); // Prefer references to declarations. It's more likely that the user is attempting to @@ -58,9 +58,8 @@ internal abstract class AbstractGoToDefinitionSymbolService : IGoToDefinitionSym project = mapping.Project; } - // The compilation will have already been realised, either by the semantic model or the symbol mapping - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - return (FindRelatedExplicitlyDeclaredSymbol(symbol, compilation), project, semanticInfo.Span); + var explicitlyDeclaredSymbol = await FindRelatedExplicitlyDeclaredSymbolAsync(project, symbol, cancellationToken).ConfigureAwait(false); + return (explicitlyDeclaredSymbol, project, semanticInfo.Span); } public async Task<(int? targetPosition, TextSpan tokenSpan)> GetTargetIfControlFlowAsync(Document document, int position, CancellationToken cancellationToken) @@ -72,7 +71,8 @@ internal abstract class AbstractGoToDefinitionSymbolService : IGoToDefinitionSym if (token == default) return default; - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + // We don't need nullable information to compute control flow targets. So avoid expensive work computing this. + var semanticModel = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); return (GetTargetPositionIfControlFlow(semanticModel, token), token.Span); } diff --git a/src/Features/Core/Portable/GoToDefinition/IGoToDefinitionSymbolService.cs b/src/Features/Core/Portable/GoToDefinition/IGoToDefinitionSymbolService.cs index c2353232129a8..5076dc0268dd7 100644 --- a/src/Features/Core/Portable/GoToDefinition/IGoToDefinitionSymbolService.cs +++ b/src/Features/Core/Portable/GoToDefinition/IGoToDefinitionSymbolService.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.GoToDefinition; internal interface IGoToDefinitionSymbolService : ILanguageService { - Task<(ISymbol?, Project, TextSpan)> GetSymbolProjectAndBoundSpanAsync(Document document, int position, CancellationToken cancellationToken); + Task<(ISymbol? symbol, Project project, TextSpan boundSpan)> GetSymbolProjectAndBoundSpanAsync(Document document, int position, CancellationToken cancellationToken); /// /// If the position is on a control flow keyword (continue, break, yield, return , etc), returns the relevant position in the corresponding control flow statement. diff --git a/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.cs b/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.cs index ede4efcdcb5d3..7779de2fa8b44 100644 --- a/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.cs +++ b/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -552,10 +553,14 @@ private async Task GetChangedCallerAsync(Document document, // After: // void Caller() { var x = ((Func)(() => 1))(); } // Func Callee() { return () => 1; } + // + // Also, ensure that the node is formatted properly at the destination location. This is needed as the + // location of the destination node might be very different (indentation/nesting wise) from the original + // method where the inlined code is coming from. inlineExpression = (TExpressionSyntax)syntaxGenerator.AddParentheses( syntaxGenerator.CastExpression( GenerateTypeSyntax(calleeMethodSymbol.ReturnType, allowVar: false), - syntaxGenerator.AddParentheses(inlineMethodContext.InlineExpression))); + syntaxGenerator.AddParentheses(inlineMethodContext.InlineExpression.WithAdditionalAnnotations(Formatter.Annotation)))); } diff --git a/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs b/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs index 9ad70f7081ced..03f44f9dc54bd 100644 --- a/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.IntroduceParameter; @@ -241,17 +242,26 @@ private async Task IntroduceParameterAsync(Document originalDocument, var rewriter = new IntroduceParameterDocumentRewriter(this, originalDocument, expression, methodSymbol, containingMethod, selectedCodeAction, fallbackOptions, allOccurrences); - foreach (var (project, projectCallSites) in methodCallSites.GroupBy(kvp => kvp.Key.Project)) - { - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - foreach (var (document, invocations) in projectCallSites) + var changedRoots = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync( + source: methodCallSites.GroupBy(kvp => kvp.Key.Project), + produceItems: static async (tuple, callback, rewriter, cancellationToken) => { - var newRoot = await rewriter.RewriteDocumentAsync(compilation, document, invocations, cancellationToken).ConfigureAwait(false); - modifiedSolution = modifiedSolution.WithDocumentSyntaxRoot(document.Id, newRoot); - } - } - - return modifiedSolution; + var (project, projectCallSites) = tuple; + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + await RoslynParallel.ForEachAsync( + projectCallSites, + cancellationToken, + async (tuple, cancellationToken) => + { + var (document, invocations) = tuple; + var newRoot = await rewriter.RewriteDocumentAsync(compilation, document, invocations, cancellationToken).ConfigureAwait(false); + callback((document.Id, newRoot)); + }).ConfigureAwait(false); + }, + args: rewriter, + cancellationToken).ConfigureAwait(false); + + return modifiedSolution.WithDocumentSyntaxRoots(changedRoots); } /// diff --git a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.AbstractIntroduceVariableCodeAction.cs b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.AbstractIntroduceVariableCodeAction.cs index 89515c43368be..0ae59492bed8c 100644 --- a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.AbstractIntroduceVariableCodeAction.cs +++ b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.AbstractIntroduceVariableCodeAction.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.IntroduceVariable; internal partial class AbstractIntroduceVariableService { - internal abstract class AbstractIntroduceVariableCodeAction : CodeAction + private abstract class AbstractIntroduceVariableCodeAction : CodeAction { private readonly bool _allOccurrences; private readonly bool _isConstant; diff --git a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.CodeAction.cs b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.CodeAction.cs index e7cbeb85533ae..ab8443bec9cff 100644 --- a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.CodeAction.cs +++ b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.CodeAction.cs @@ -2,27 +2,22 @@ // 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 Microsoft.CodeAnalysis.CodeCleanup; namespace Microsoft.CodeAnalysis.IntroduceVariable; internal partial class AbstractIntroduceVariableService { - private class IntroduceVariableCodeAction : AbstractIntroduceVariableCodeAction + private sealed class IntroduceVariableCodeAction( + TService service, + SemanticDocument document, + CodeCleanupOptions options, + TExpressionSyntax expression, + bool allOccurrences, + bool isConstant, + bool isLocal, + bool isQueryLocal) : AbstractIntroduceVariableCodeAction( + service, document, options, expression, allOccurrences, isConstant, isLocal, isQueryLocal) { - internal IntroduceVariableCodeAction( - TService service, - SemanticDocument document, - CodeCleanupOptions options, - TExpressionSyntax expression, - bool allOccurrences, - bool isConstant, - bool isLocal, - bool isQueryLocal) - : base(service, document, options, expression, allOccurrences, isConstant, isLocal, isQueryLocal) - { - } } } diff --git a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.IntroduceVariableAllOccurrenceCodeAction.cs b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.IntroduceVariableAllOccurrenceCodeAction.cs index 7800caa54892a..258785ef00557 100644 --- a/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.IntroduceVariableAllOccurrenceCodeAction.cs +++ b/src/Features/Core/Portable/IntroduceVariable/AbstractIntroduceVariableService.IntroduceVariableAllOccurrenceCodeAction.cs @@ -2,40 +2,22 @@ // 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; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CaseCorrection; using Microsoft.CodeAnalysis.CodeCleanup; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Simplification; namespace Microsoft.CodeAnalysis.IntroduceVariable; internal partial class AbstractIntroduceVariableService { - private class IntroduceVariableAllOccurrenceCodeAction : AbstractIntroduceVariableCodeAction + private class IntroduceVariableAllOccurrenceCodeAction( + TService service, + SemanticDocument document, + CodeCleanupOptions options, + TExpressionSyntax expression, + bool allOccurrences, + bool isConstant, + bool isLocal, + bool isQueryLocal) : AbstractIntroduceVariableCodeAction( + service, document, options, expression, allOccurrences, isConstant, isLocal, isQueryLocal) { - internal IntroduceVariableAllOccurrenceCodeAction( - TService service, - SemanticDocument document, - CodeCleanupOptions options, - TExpressionSyntax expression, - bool allOccurrences, - bool isConstant, - bool isLocal, - bool isQueryLocal) - : base(service, document, options, expression, allOccurrences, isConstant, isLocal, isQueryLocal) - { - } - - protected override async Task PostProcessChangesAsync(Document document, CancellationToken cancellationToken) - { - document = await Simplifier.ReduceAsync(document, Simplifier.Annotation, Options.SimplifierOptions, cancellationToken).ConfigureAwait(false); - document = await Formatter.FormatAsync(document, Formatter.Annotation, Options.FormattingOptions, cancellationToken).ConfigureAwait(false); - document = await CaseCorrector.CaseCorrectAsync(document, CaseCorrector.Annotation, cancellationToken).ConfigureAwait(false); - return document; - } } } diff --git a/src/Features/Core/Portable/IntroduceVariable/IntroduceVariableCodeRefactoringProvider.cs b/src/Features/Core/Portable/IntroduceVariable/IntroduceVariableCodeRefactoringProvider.cs index 121a6645a8aec..b6755b077c613 100644 --- a/src/Features/Core/Portable/IntroduceVariable/IntroduceVariableCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/IntroduceVariable/IntroduceVariableCodeRefactoringProvider.cs @@ -15,16 +15,11 @@ namespace Microsoft.CodeAnalysis.IntroduceVariable; [ExtensionOrder(After = PredefinedCodeRefactoringProviderNames.ConvertAnonymousTypeToClass)] [ExtensionOrder(After = PredefinedCodeRefactoringProviderNames.InvertConditional)] [ExtensionOrder(After = PredefinedCodeRefactoringProviderNames.InvertLogical)] -[ExportCodeRefactoringProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, - Name = PredefinedCodeRefactoringProviderNames.IntroduceVariable), Shared] -internal class IntroduceVariableCodeRefactoringProvider : CodeRefactoringProvider +[ExportCodeRefactoringProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, Name = PredefinedCodeRefactoringProviderNames.IntroduceVariable), Shared] +[method: ImportingConstructor] +[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] +internal sealed class IntroduceVariableCodeRefactoringProvider() : CodeRefactoringProvider { - [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] - public IntroduceVariableCodeRefactoringProvider() - { - } - public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var (document, textSpan, cancellationToken) = context; diff --git a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService+CompatAbstractMetadataFormattingRule.cs b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService+CompatAbstractMetadataFormattingRule.cs index 7bf7c7cfc4da7..eda6341df098a 100644 --- a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService+CompatAbstractMetadataFormattingRule.cs +++ b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService+CompatAbstractMetadataFormattingRule.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.ComponentModel; using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.MetadataAsSource; @@ -18,7 +19,7 @@ protected abstract class CompatAbstractMetadataFormattingRule : AbstractMetadata #pragma warning disable CS0809 // Obsolete member overrides non-obsolete member [Obsolete("Do not call this method directly (it will Stack Overflow).", error: true)] [EditorBrowsable(EditorBrowsableState.Never)] - public sealed override void AddSuppressOperations(List list, SyntaxNode node, in NextSuppressOperationAction nextOperation) + public sealed override void AddSuppressOperations(ArrayBuilder list, SyntaxNode node, in NextSuppressOperationAction nextOperation) { var nextOperationCopy = nextOperation; AddSuppressOperationsSlow(list, node, ref nextOperationCopy); @@ -73,7 +74,7 @@ public sealed override AdjustSpacesOperation GetAdjustSpacesOperation(in SyntaxT /// Returns SuppressWrappingIfOnSingleLineOperations under a node either by itself or by /// filtering/replacing operations returned by NextOperation /// - public virtual void AddSuppressOperationsSlow(List list, SyntaxNode node, ref NextSuppressOperationAction nextOperation) + public virtual void AddSuppressOperationsSlow(ArrayBuilder list, SyntaxNode node, ref NextSuppressOperationAction nextOperation) => base.AddSuppressOperations(list, node, in nextOperation); /// diff --git a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.cs b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.cs index 874bd38c45b91..912efe1b8e8bf 100644 --- a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.cs +++ b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.cs @@ -85,7 +85,7 @@ public async Task AddSourceToAsync( /// /// provide formatting rules to be used when formatting MAS file /// - protected abstract IEnumerable GetFormattingRules(Document document); + protected abstract ImmutableArray GetFormattingRules(Document document); /// /// Prepends a region directive at the top of the document with a name containing diff --git a/src/Features/Core/Portable/MetadataAsSource/IMetadataAsSourceFileService.cs b/src/Features/Core/Portable/MetadataAsSource/IMetadataAsSourceFileService.cs index d7aa71f7fd5ad..97fdf62b5f8f0 100644 --- a/src/Features/Core/Portable/MetadataAsSource/IMetadataAsSourceFileService.cs +++ b/src/Features/Core/Portable/MetadataAsSource/IMetadataAsSourceFileService.cs @@ -28,8 +28,6 @@ internal interface IMetadataAsSourceFileService bool TryRemoveDocumentFromWorkspace(string filePath); - void CleanupGeneratedFiles(); - bool IsNavigableMetadataSymbol(ISymbol symbol); Workspace? TryGetWorkspace(); diff --git a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs index d0abbcdeb54b1..5ee9ab69cb256 100644 --- a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs +++ b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs @@ -22,15 +22,15 @@ namespace Microsoft.CodeAnalysis.MetadataAsSource; [Export(typeof(IMetadataAsSourceFileService)), Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class MetadataAsSourceFileService([ImportMany] IEnumerable> providers) : IMetadataAsSourceFileService +internal sealed class MetadataAsSourceFileService : IMetadataAsSourceFileService { + private const string MetadataAsSource = nameof(MetadataAsSource); + /// /// Set of providers that can be used to generate source for a symbol (for example, by decompiling, or by /// extracting it from a pdb). /// - private readonly ImmutableArray> _providers = [.. ExtensionOrderer.Order(providers)]; + private readonly Lazy>> _providers; /// /// Workspace created the first time we generate any metadata for any symbol. @@ -38,34 +38,33 @@ internal class MetadataAsSourceFileService([ImportMany] IEnumerable - /// A lock to guard the mutex and filesystem data below. We want to ensure we generate into that and clean that - /// up safely. + /// A lock to ensure we initialize and cleanup stale data only once. /// private readonly SemaphoreSlim _gate = new(initialCount: 1); /// - /// We create a mutex so other processes can see if our directory is still alive. We destroy the mutex when - /// we purge our generated files. + /// We create a mutex so other processes can see if our directory is still alive. As long as we own the mutex, no + /// other VS instance will try to delete our _rootTemporaryPathWithGuid folder. /// - private Mutex? _mutex; - private string? _rootTemporaryPathWithGuid; - private readonly string _rootTemporaryPath = Path.Combine(Path.GetTempPath(), "MetadataAsSource"); - - private static string CreateMutexName(string directoryName) - => "MetadataAsSource-" + directoryName; - - private string GetRootPathWithGuid_NoLock() + private readonly Mutex _mutex; + private readonly string _rootTemporaryPathWithGuid; + private readonly string _rootTemporaryPath = Path.Combine(Path.GetTempPath(), MetadataAsSource); + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public MetadataAsSourceFileService( + [ImportMany] IEnumerable> providers) { - if (_rootTemporaryPathWithGuid == null) - { - var guidString = Guid.NewGuid().ToString("N"); - _rootTemporaryPathWithGuid = Path.Combine(_rootTemporaryPath, guidString); - _mutex = new Mutex(initiallyOwned: true, name: CreateMutexName(guidString)); - } + _providers = new(() => [.. ExtensionOrderer.Order(providers)]); - return _rootTemporaryPathWithGuid; + var guidString = Guid.NewGuid().ToString("N"); + _rootTemporaryPathWithGuid = Path.Combine(_rootTemporaryPath, guidString); + _mutex = new Mutex(initiallyOwned: true, name: CreateMutexName(guidString)); } + private static string CreateMutexName(string directoryName) + => $"{MetadataAsSource}-{directoryName}"; + public async Task GetGeneratedFileAsync( Workspace sourceWorkspace, Project sourceProject, @@ -87,28 +86,74 @@ public async Task GetGeneratedFileAsync( using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - _workspace ??= new MetadataAsSourceWorkspace(this, sourceWorkspace.Services.HostServices); + if (_workspace is null) + { + _workspace = new MetadataAsSourceWorkspace(this, sourceWorkspace.Services.HostServices); + + // We're being initialized the first time. Use this time to clean up any stale metadata-as-source files + // from previous VS sessions. + CleanupGeneratedFiles(_rootTemporaryPath); + } Contract.ThrowIfNull(_workspace); - var tempPath = GetRootPathWithGuid_NoLock(); // We don't want to track telemetry for signatures only requests, only where we try to show source using var telemetryMessage = signaturesOnly ? null : new TelemetryMessage(cancellationToken); - foreach (var lazyProvider in _providers) + foreach (var lazyProvider in _providers.Value) { var provider = lazyProvider.Value; - var providerTempPath = Path.Combine(tempPath, provider.GetType().Name); + var providerTempPath = Path.Combine(_rootTemporaryPathWithGuid, provider.GetType().Name); var result = await provider.GetGeneratedFileAsync(_workspace, sourceWorkspace, sourceProject, symbol, signaturesOnly, options, providerTempPath, telemetryMessage, cancellationToken).ConfigureAwait(false); if (result is not null) - { return result; - } } } // The decompilation provider can always return something throw ExceptionUtilities.Unreachable(); + + static void CleanupGeneratedFiles(string rootDirectory) + { + try + { + if (Directory.Exists(rootDirectory)) + { + // Let's look through directories to delete. + foreach (var directoryInfo in new DirectoryInfo(rootDirectory).EnumerateDirectories()) + { + // Is there a mutex for this one? If so, that means it's a folder open in another VS instance. + // We should leave it alone. If not, then it's a folder from a previous VS run. Delete that + // now. + if (Mutex.TryOpenExisting(CreateMutexName(directoryInfo.Name), out var acquiredMutex)) + { + acquiredMutex.Dispose(); + } + else + { + TryDeleteFolderWhichContainsReadOnlyFiles(directoryInfo.FullName); + } + } + } + } + catch (Exception) + { + } + } + + static void TryDeleteFolderWhichContainsReadOnlyFiles(string directoryPath) + { + try + { + foreach (var fileInfo in new DirectoryInfo(directoryPath).EnumerateFiles("*", SearchOption.AllDirectories)) + IOUtilities.PerformIO(() => fileInfo.IsReadOnly = false); + + IOUtilities.PerformIO(() => Directory.Delete(directoryPath, recursive: true)); + } + catch (Exception) + { + } + } } private static void AssertIsMainThread(MetadataAsSourceWorkspace workspace) @@ -127,7 +172,7 @@ public bool TryAddDocumentToWorkspace(string filePath, SourceTextContainer sourc { AssertIsMainThread(workspace); - foreach (var provider in _providers) + foreach (var provider in _providers.Value) { if (!provider.IsValueCreated) continue; @@ -150,7 +195,7 @@ public bool TryRemoveDocumentFromWorkspace(string filePath) { AssertIsMainThread(workspace); - foreach (var provider in _providers) + foreach (var provider in _providers.Value) { if (!provider.IsValueCreated) continue; @@ -186,7 +231,7 @@ public bool ShouldCollapseOnOpen(string? filePath, BlockStructureOptions blockSt AssertIsMainThread(workspace); - foreach (var provider in _providers) + foreach (var provider in _providers.Value) { if (!provider.IsValueCreated) continue; @@ -205,7 +250,7 @@ public bool ShouldCollapseOnOpen(string? filePath, BlockStructureOptions blockSt Project? project = null; using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - foreach (var provider in _providers) + foreach (var provider in _providers.Value) { if (!provider.IsValueCreated) continue; @@ -229,80 +274,6 @@ public bool ShouldCollapseOnOpen(string? filePath, BlockStructureOptions blockSt return new SymbolMappingResult(project, resolutionResult.Symbol); } - public void CleanupGeneratedFiles() - { - using (_gate.DisposableWait()) - { - // Release our mutex to indicate we're no longer using our directory and reset state - if (_mutex != null) - { - _mutex.Dispose(); - _mutex = null; - _rootTemporaryPathWithGuid = null; - } - - // Only cleanup for providers that have actually generated a file. This keeps us from accidentally loading - // lazy providers on cleanup that weren't used - var workspace = _workspace; - if (workspace != null) - { - foreach (var provider in _providers) - { - if (!provider.IsValueCreated) - continue; - - provider.Value.CleanupGeneratedFiles(workspace); - } - } - - try - { - if (Directory.Exists(_rootTemporaryPath)) - { - var deletedEverything = true; - - // Let's look through directories to delete. - foreach (var directoryInfo in new DirectoryInfo(_rootTemporaryPath).EnumerateDirectories()) - { - // Is there a mutex for this one? - if (Mutex.TryOpenExisting(CreateMutexName(directoryInfo.Name), out var acquiredMutex)) - { - acquiredMutex.Dispose(); - deletedEverything = false; - continue; - } - - TryDeleteFolderWhichContainsReadOnlyFiles(directoryInfo.FullName); - } - - if (deletedEverything) - { - Directory.Delete(_rootTemporaryPath); - } - } - } - catch (Exception) - { - } - } - } - - private static void TryDeleteFolderWhichContainsReadOnlyFiles(string directoryPath) - { - try - { - foreach (var fileInfo in new DirectoryInfo(directoryPath).EnumerateFiles("*", SearchOption.AllDirectories)) - { - fileInfo.IsReadOnly = false; - } - - Directory.Delete(directoryPath, recursive: true); - } - catch (Exception) - { - } - } - public bool IsNavigableMetadataSymbol(ISymbol symbol) { symbol = symbol.OriginalDefinition; diff --git a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj index f06b4057c313c..0d983d125edbc 100644 --- a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj +++ b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj @@ -50,6 +50,7 @@ + diff --git a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs index 0c45abfa34f00..a967f30ae4c55 100644 --- a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs +++ b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs @@ -288,12 +288,9 @@ private static async Task PropagateChangeToLinkedDocumentsAsync(Docume var formattedText = await formattedDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var solution = formattedDocument.Project.Solution; - foreach (var documentId in formattedDocument.GetLinkedDocumentIds()) - { - solution = solution.WithDocumentText(documentId, formattedText); - } - - return solution; + var finalSolution = solution.WithDocumentTexts( + formattedDocument.GetLinkedDocumentIds().SelectAsArray(id => (id, formattedText))); + return finalSolution; } private static string GetNewSymbolName(ISymbol symbol, string targetNamespace) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 41ba1ca6bbe81..f43c6f2ca15b6 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -14,9 +13,8 @@ using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.PatternMatching; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Storage; using Roslyn.Utilities; @@ -63,17 +61,19 @@ public async Task SearchCachedDocumentsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + Contract.ThrowIfTrue(projects.IsEmpty); Contract.ThrowIfTrue(projects.Select(p => p.Language).Distinct().Count() != 1); Debug.Assert(priorityDocuments.All(d => projects.Contains(d.Project))); - var onItemFound = GetOnItemFoundCallback(solution, activeDocument, onResultFound, cancellationToken); + var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound); var documentKeys = projects.SelectManyAsArray(p => p.Documents.Select(DocumentKey.ToDocumentKey)); var priorityDocumentKeys = priorityDocuments.SelectAsArray(DocumentKey.ToDocumentKey); @@ -81,7 +81,7 @@ public async Task SearchCachedDocumentsAsync( var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false); if (client != null) { - var callback = new NavigateToSearchServiceCallback(onItemFound, onProjectCompleted); + var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted, cancellationToken); await client.TryInvokeAsync( (service, callbackId, cancellationToken) => service.SearchCachedDocumentsAsync(documentKeys, priorityDocumentKeys, searchPattern, [.. kinds], callbackId, cancellationToken), @@ -92,7 +92,7 @@ await client.TryInvokeAsync( var storageService = solution.Services.GetPersistentStorageService(); await SearchCachedDocumentsInCurrentProcessAsync( - storageService, documentKeys, priorityDocumentKeys, searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + storageService, documentKeys, priorityDocumentKeys, searchPattern, kinds, onItemsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); } public static async Task SearchCachedDocumentsInCurrentProcessAsync( @@ -101,16 +101,14 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( ImmutableArray priorityDocumentKeys, string searchPattern, IImmutableSet kinds, - Func onItemFound, + Func, VoidResult, CancellationToken, Task> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); // Quick abort if OOP is now fully loaded. if (!ShouldSearchCachedDocuments(out _, out _)) return; - // If the user created a dotted pattern then we'll grab the last part of the name var (patternName, patternContainer) = PatternMatcher.GetNameAndContainer(searchPattern); var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); @@ -120,84 +118,52 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( using var _1 = GetPooledHashSet(priorityDocumentKeys, out var priorityDocumentKeysSet); - // Sort the groups into a high pri group (projects that contain a high-pri doc), and low pri groups (those - // that don't). - using var _2 = GetPooledHashSet(groups.Where(g => g.Any(priorityDocumentKeysSet.Contains)), out var highPriorityGroups); - using var _3 = GetPooledHashSet(groups.Where(g => !highPriorityGroups.Contains(g)), out var lowPriorityGroups); - - await ProcessProjectGroupsAsync(highPriorityGroups).ConfigureAwait(false); - await ProcessProjectGroupsAsync(lowPriorityGroups).ConfigureAwait(false); - + // Sort the groups into a high pri group (projects that contain a high-pri doc), and low pri groups (those that + // don't), and process in that order. + await ProducerConsumer.RunParallelAsync( + Prioritize(groups, g => g.Any(priorityDocumentKeysSet.Contains)), + ProcessSingleProjectGroupAsync, onItemsFound, args: default, cancellationToken).ConfigureAwait(false); return; - async Task ProcessProjectGroupsAsync(HashSet> groups) + async Task ProcessSingleProjectGroupAsync( + IGrouping group, + Action onItemFound, + VoidResult _, + CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - using var _ = ArrayBuilder.GetInstance(out var tasks); - - foreach (var group in groups) - tasks.Add(ProcessProjectGroupAsync(group)); + if (cancellationToken.IsCancellationRequested) + return; - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - async Task ProcessProjectGroupAsync(IGrouping group) - { - cancellationToken.ThrowIfCancellationRequested(); - await Task.Yield().ConfigureAwait(false); var project = group.Key; - // Break the project into high-pri docs and low pri docs. - using var _1 = GetPooledHashSet(group.Where(priorityDocumentKeysSet.Contains), out var highPriDocs); - using var _2 = GetPooledHashSet(group.Where(d => !highPriDocs.Contains(d)), out var lowPriDocs); - - await SearchCachedDocumentsInCurrentProcessAsync( - storageService, patternName, patternContainer, declaredSymbolInfoKindsSet, - onItemFound, highPriDocs, cancellationToken).ConfigureAwait(false); - - await SearchCachedDocumentsInCurrentProcessAsync( - storageService, patternName, patternContainer, declaredSymbolInfoKindsSet, - onItemFound, lowPriDocs, cancellationToken).ConfigureAwait(false); + // Break the project into high-pri docs and low pri docs, and process in that order. + await RoslynParallel.ForEachAsync( + Prioritize(group, priorityDocumentKeysSet.Contains), + cancellationToken, + async (documentKey, cancellationToken) => + { + var index = await GetIndexAsync(storageService, documentKey, cancellationToken).ConfigureAwait(false); + if (index == null) + return; + + ProcessIndex( + documentKey, document: null, patternName, patternContainer, declaredSymbolInfoKindsSet, + index, linkedIndices: null, onItemFound, cancellationToken); + }).ConfigureAwait(false); // done with project. Let the host know. await onProjectCompleted().ConfigureAwait(false); } } - private static async Task SearchCachedDocumentsInCurrentProcessAsync( - IChecksummedPersistentStorageService storageService, - string patternName, - string? patternContainer, - DeclaredSymbolInfoKindSet kinds, - Func onItemFound, - HashSet documentKeys, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - using var _ = ArrayBuilder.GetInstance(out var tasks); - - foreach (var documentKey in documentKeys) - { - tasks.Add(Task.Run(async () => - { - var index = await GetIndexAsync(storageService, documentKey, cancellationToken).ConfigureAwait(false); - if (index == null) - return; - - await ProcessIndexAsync( - documentKey, document: null, patternName, patternContainer, kinds, onItemFound, index, cancellationToken).ConfigureAwait(false); - }, cancellationToken)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); - } - private static Task GetIndexAsync( IChecksummedPersistentStorageService storageService, DocumentKey documentKey, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return SpecializedTasks.Null(); + // Retrieve the string table we use to dedupe strings. If we can't get it, that means the solution has // fully loaded and we've switched over to normal navto lookup. if (!ShouldSearchCachedDocuments(out var cachedIndexMap, out var stringTable)) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index ee18144bee5f8..90e55bc5a6137 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; @@ -21,20 +22,22 @@ public async Task SearchGeneratedDocumentsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + Contract.ThrowIfTrue(projects.IsEmpty); Contract.ThrowIfTrue(projects.Select(p => p.Language).Distinct().Count() != 1); - var onItemFound = GetOnItemFoundCallback(solution, activeDocument, onResultFound, cancellationToken); + var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound); var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false); if (client != null) { - var callback = new NavigateToSearchServiceCallback(onItemFound, onProjectCompleted); + var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted, cancellationToken); await client.TryInvokeAsync( // Sync and search the full solution snapshot. While this function is called serially per project, @@ -50,33 +53,35 @@ await client.TryInvokeAsync( } await SearchGeneratedDocumentsInCurrentProcessAsync( - projects, searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + projects, searchPattern, kinds, onItemsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); } public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( ImmutableArray projects, string pattern, IImmutableSet kinds, - Func onItemFound, + Func, VoidResult, CancellationToken, Task> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - // If the user created a dotted pattern then we'll grab the last part of the name var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(pattern); var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); - // Projects is already sorted in dependency order by the host. Process in that order so that prior - // compilations are available for later projects when needed. - foreach (var project in projects) + await ProducerConsumer.RunParallelAsync( + projects, ProcessSingleProjectAsync, onItemsFound, args: default, cancellationToken).ConfigureAwait(false); + return; + + async Task ProcessSingleProjectAsync( + Project project, Action onItemFound, VoidResult _, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); // First generate all the source-gen docs. Then handoff to the standard search routine to find matches in them. var sourceGeneratedDocs = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); - using var _ = GetPooledHashSet(sourceGeneratedDocs, out var documents); - await ProcessDocumentsAsync( - searchDocument: null, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, documents, cancellationToken).ConfigureAwait(false); + await RoslynParallel.ForEachAsync( + sourceGeneratedDocs, + cancellationToken, + (document, cancellationToken) => SearchSingleDocumentAsync( + document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); await onProjectCompleted().ConfigureAwait(false); } diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 5224895958359..64a90f344cdfd 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -37,93 +37,48 @@ internal abstract partial class AbstractNavigateToSearchService (PatternMatchKind.LowercaseSubstring, NavigateToMatchKind.Fuzzy), ]; - private static async Task SearchProjectInCurrentProcessAsync( - Project project, ImmutableArray priorityDocuments, - Document? searchDocument, string pattern, IImmutableSet kinds, - Func onResultFound, - Func onProjectCompleted, - CancellationToken cancellationToken) - { - try - { - await Task.Yield().ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - - // We're doing a real search over the fully loaded solution now. No need to hold onto the cached map - // of potentially stale indices. - ClearCachedData(); - - // If the user created a dotted pattern then we'll grab the last part of the name - var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(pattern); - - var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); - - // Prioritize the active documents if we have any. - using var _1 = GetPooledHashSet(priorityDocuments.Where(d => project.ContainsDocument(d.Id)), out var highPriDocs); - using var _2 = GetPooledHashSet(project.Documents.Where(d => !highPriDocs.Contains(d)), out var lowPriDocs); - - await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onResultFound, highPriDocs, cancellationToken).ConfigureAwait(false); - - // Then process non-priority documents. - await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onResultFound, lowPriDocs, cancellationToken).ConfigureAwait(false); - } - finally - { - await onProjectCompleted().ConfigureAwait(false); - } - } - - private static async Task ProcessDocumentsAsync( - Document? searchDocument, + private static async ValueTask SearchSingleDocumentAsync( + Document document, string patternName, string? patternContainer, DeclaredSymbolInfoKindSet kinds, - Func onItemFound, - HashSet documents, + Action onItemFound, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - using var _ = ArrayBuilder.GetInstance(out var tasks); + if (cancellationToken.IsCancellationRequested) + return; - foreach (var document in documents) - { - if (searchDocument != null && searchDocument != document) - continue; + // Get the index for the file we're searching, as well as for its linked siblings. We'll use the latter to add + // the information to a symbol about all the project TFMs is can be found in. + var index = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); + using var _ = ArrayBuilder<(TopLevelSyntaxTreeIndex, ProjectId)>.GetInstance(out var linkedIndices); - cancellationToken.ThrowIfCancellationRequested(); - tasks.Add(ProcessDocumentAsync(document, patternName, patternContainer, kinds, onItemFound, cancellationToken)); + foreach (var linkedDocumentId in document.GetLinkedDocumentIds()) + { + var linkedDocument = document.Project.Solution.GetRequiredDocument(linkedDocumentId); + var linkedIndex = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(linkedDocument, cancellationToken).ConfigureAwait(false); + linkedIndices.Add((linkedIndex, linkedDocumentId.ProjectId)); } - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - private static async Task ProcessDocumentAsync( - Document document, - string patternName, - string? patternContainer, - DeclaredSymbolInfoKindSet kinds, - Func onResultFound, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - await Task.Yield().ConfigureAwait(false); - var index = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); - - await ProcessIndexAsync( - DocumentKey.ToDocumentKey(document), document, patternName, patternContainer, kinds, onResultFound, index, cancellationToken).ConfigureAwait(false); + ProcessIndex( + DocumentKey.ToDocumentKey(document), document, patternName, patternContainer, kinds, + index, linkedIndices, onItemFound, cancellationToken); } - private static async Task ProcessIndexAsync( + private static void ProcessIndex( DocumentKey documentKey, Document? document, string patternName, string? patternContainer, DeclaredSymbolInfoKindSet kinds, - Func onResultFound, TopLevelSyntaxTreeIndex index, + ArrayBuilder<(TopLevelSyntaxTreeIndex, ProjectId)>? linkedIndices, + Action onItemFound, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + var containerMatcher = patternContainer != null ? PatternMatcher.CreateDotSeparatedContainerMatcher(patternContainer, includeMatchedSpans: true) : null; @@ -133,50 +88,38 @@ private static async Task ProcessIndexAsync( foreach (var declaredSymbolInfo in index.DeclaredSymbolInfos) { + if (cancellationToken.IsCancellationRequested) + return; + // Namespaces are never returned in nav-to as they're too common and have too many locations. if (declaredSymbolInfo.Kind == DeclaredSymbolInfoKind.Namespace) continue; - await AddResultIfMatchAsync( - documentKey, document, - declaredSymbolInfo, - nameMatcher, containerMatcher, - kinds, onResultFound, cancellationToken).ConfigureAwait(false); - } - } + using var nameMatches = TemporaryArray.Empty; + using var containerMatches = TemporaryArray.Empty; - private static async Task AddResultIfMatchAsync( - DocumentKey documentKey, - Document? document, - DeclaredSymbolInfo declaredSymbolInfo, - PatternMatcher nameMatcher, - PatternMatcher? containerMatcher, - DeclaredSymbolInfoKindSet kinds, - Func onResultFound, - CancellationToken cancellationToken) - { - using var nameMatches = TemporaryArray.Empty; - using var containerMatches = TemporaryArray.Empty; - - cancellationToken.ThrowIfCancellationRequested(); - if (kinds.Contains(declaredSymbolInfo.Kind) && - nameMatcher.AddMatches(declaredSymbolInfo.Name, ref nameMatches.AsRef()) && - containerMatcher?.AddMatches(declaredSymbolInfo.FullyQualifiedContainerName, ref containerMatches.AsRef()) != false) - { - // See if we have a match in a linked file. If so, see if we have the same match in - // other projects that this file is linked in. If so, include the full set of projects - // the match is in so we can display that well in the UI. - // - // We can only do this in the case where the solution is loaded and thus we can examine - // the relationship between this document and the other documents linked to it. In the - // case where the solution isn't fully loaded and we're just reading in cached data, we - // don't know what other files we're linked to and can't merge results in this fashion. - var additionalMatchingProjects = await GetAdditionalProjectsWithMatchAsync( - document, declaredSymbolInfo, cancellationToken).ConfigureAwait(false); - - var result = ConvertResult( - documentKey, document, declaredSymbolInfo, nameMatches, containerMatches, additionalMatchingProjects); - await onResultFound(result).ConfigureAwait(false); + if (kinds.Contains(declaredSymbolInfo.Kind) && + nameMatcher.AddMatches(declaredSymbolInfo.Name, ref nameMatches.AsRef()) && + containerMatcher?.AddMatches(declaredSymbolInfo.FullyQualifiedContainerName, ref containerMatches.AsRef()) != false) + { + if (cancellationToken.IsCancellationRequested) + return; + + // See if we have a match in a linked file. If so, see if we have the same match in + // other projects that this file is linked in. If so, include the full set of projects + // the match is in so we can display that well in the UI. + // + // We can only do this in the case where the solution is loaded and thus we can examine + // the relationship between this document and the other documents linked to it. In the + // case where the solution isn't fully loaded and we're just reading in cached data, we + // don't know what other files we're linked to and can't merge results in this fashion. + var additionalMatchingProjects = GetAdditionalProjectsWithMatch( + document, declaredSymbolInfo, linkedIndices); + + var result = ConvertResult( + documentKey, document, declaredSymbolInfo, nameMatches, containerMatches, additionalMatchingProjects); + onItemFound(result); + } } } @@ -217,28 +160,24 @@ private static RoslynNavigateToItem ConvertResult( allPatternMatches.ToImmutableAndClear()); } - private static async ValueTask> GetAdditionalProjectsWithMatchAsync( - Document? document, DeclaredSymbolInfo declaredSymbolInfo, CancellationToken cancellationToken) + private static ImmutableArray GetAdditionalProjectsWithMatch( + Document? document, + DeclaredSymbolInfo declaredSymbolInfo, + ArrayBuilder<(TopLevelSyntaxTreeIndex, ProjectId)>? linkedIndices) { - if (document == null) + if (document == null || linkedIndices is null || linkedIndices.Count == 0) return []; - using var _ = ArrayBuilder.GetInstance(out var result); + using var result = TemporaryArray.Empty; - var solution = document.Project.Solution; - var linkedDocumentIds = document.GetLinkedDocumentIds(); - foreach (var linkedDocumentId in linkedDocumentIds) + foreach (var (index, projectId) in linkedIndices) { - var linkedDocument = solution.GetRequiredDocument(linkedDocumentId); - var index = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(linkedDocument, cancellationToken).ConfigureAwait(false); - // See if the index for the other file also contains this same info. If so, merge the results so the // user only sees them as a single hit in the UI. if (index.DeclaredSymbolInfoSet.Contains(declaredSymbolInfo)) - result.Add(linkedDocument.Project.Id); + result.Add(projectId); } - result.RemoveDuplicates(); return result.ToImmutableAndClear(); } diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index d434504af767c..c974e24516d72 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -3,14 +3,14 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; @@ -21,16 +21,16 @@ public async Task SearchDocumentAsync( Document document, string searchPattern, IImmutableSet kinds, - Func onResultFound, + Func, Task> onResultsFound, CancellationToken cancellationToken) { var solution = document.Project.Solution; - var onItemFound = GetOnItemFoundCallback(solution, activeDocument: null, (_, i) => onResultFound(i), cancellationToken); + var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument: null, onResultsFound); var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false); if (client != null) { - var callback = new NavigateToSearchServiceCallback(onItemFound, onProjectCompleted: null); + var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted: null, cancellationToken); // Don't need to sync the full solution when searching a single document. Just sync the project that doc is in. await client.TryInvokeAsync( document.Project, @@ -41,14 +41,25 @@ await client.TryInvokeAsync( return; } - await SearchDocumentInCurrentProcessAsync(document, searchPattern, kinds, onItemFound, cancellationToken).ConfigureAwait(false); + await SearchDocumentInCurrentProcessAsync(document, searchPattern, kinds, onItemsFound, cancellationToken).ConfigureAwait(false); } - public static Task SearchDocumentInCurrentProcessAsync(Document document, string searchPattern, IImmutableSet kinds, Func onItemFound, CancellationToken cancellationToken) + public static async Task SearchDocumentInCurrentProcessAsync( + Document document, + string searchPattern, + IImmutableSet kinds, + Func, VoidResult, CancellationToken, Task> onItemsFound, + CancellationToken cancellationToken) { - return SearchProjectInCurrentProcessAsync( - document.Project, priorityDocuments: [], document, searchPattern, kinds, - onItemFound, () => Task.CompletedTask, cancellationToken); + var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); + var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); + + var results = new ConcurrentSet(); + await SearchSingleDocumentAsync( + document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, t => results.Add(t), cancellationToken).ConfigureAwait(false); + + if (results.Count > 0) + await onItemsFound(results.ToImmutableArray(), default, cancellationToken).ConfigureAwait(false); } public async Task SearchProjectsAsync( @@ -58,22 +69,24 @@ public async Task SearchProjectsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + Contract.ThrowIfTrue(projects.IsEmpty); Contract.ThrowIfTrue(projects.Select(p => p.Language).Distinct().Count() != 1); Debug.Assert(priorityDocuments.All(d => projects.Contains(d.Project))); - var onItemFound = GetOnItemFoundCallback(solution, activeDocument, onResultFound, cancellationToken); + var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound); var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false); if (client != null) { var priorityDocumentIds = priorityDocuments.SelectAsArray(d => d.Id); - var callback = new NavigateToSearchServiceCallback(onItemFound, onProjectCompleted); + var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted, cancellationToken); await client.TryInvokeAsync( // Intentionally sync the full solution. When SearchProjectAsync is called, we're searching all @@ -88,7 +101,7 @@ await client.TryInvokeAsync( } await SearchProjectsInCurrentProcessAsync( - projects, priorityDocuments, searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + projects, priorityDocuments, searchPattern, kinds, onItemsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); } public static async Task SearchProjectsInCurrentProcessAsync( @@ -96,34 +109,41 @@ public static async Task SearchProjectsInCurrentProcessAsync( ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, - Func onItemFound, + Func, VoidResult, CancellationToken, Task> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - using var _1 = GetPooledHashSet(priorityDocuments.Select(d => d.Project), out var highPriProjects); - using var _2 = GetPooledHashSet(projects.Where(p => !highPriProjects.Contains(p)), out var lowPriProjects); + // We're doing a real search over the fully loaded solution now. No need to hold onto the cached map + // of potentially stale indices. + ClearCachedData(); - Debug.Assert(projects.SetEquals(highPriProjects.Concat(lowPriProjects))); + var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); + var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); - await ProcessProjectsAsync(highPriProjects).ConfigureAwait(false); - await ProcessProjectsAsync(lowPriProjects).ConfigureAwait(false); + using var _ = GetPooledHashSet(priorityDocuments.Select(d => d.Project), out var highPriProjects); + // Process each project on its own. That way we can tell the client when we are done searching it. Put the + // projects with priority documents ahead of those without so we can get results for those faster. + await ProducerConsumer.RunParallelAsync( + Prioritize(projects, highPriProjects.Contains), + SearchSingleProjectAsync, onItemsFound, args: default, cancellationToken).ConfigureAwait(false); return; - async Task ProcessProjectsAsync(HashSet projects) + async Task SearchSingleProjectAsync( + Project project, + Action onItemFound, + VoidResult _, + CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var tasks); + using var _1 = GetPooledHashSet(priorityDocuments.Where(d => project == d.Project), out var highPriDocs); - foreach (var project in projects) - { - cancellationToken.ThrowIfCancellationRequested(); - tasks.Add(SearchProjectInCurrentProcessAsync( - project, priorityDocuments.WhereAsArray(d => d.Project == project), searchDocument: null, - searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken)); - } + await RoslynParallel.ForEachAsync( + Prioritize(project.Documents, highPriDocs.Contains), + cancellationToken, + (document, cancellationToken) => SearchSingleDocumentAsync( + document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); - await Task.WhenAll(tasks).ConfigureAwait(false); + await onProjectCompleted().ConfigureAwait(false); } } } diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index b9015db743520..b39340e6da61f 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -8,7 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; @@ -33,19 +33,22 @@ internal abstract partial class AbstractNavigateToSearchService : IAdvancedNavig public bool CanFilter => true; - private static Func GetOnItemFoundCallback( - Solution solution, Document? activeDocument, Func onResultFound, CancellationToken cancellationToken) + private static Func, VoidResult, CancellationToken, Task> GetOnItemsFoundCallback( + Solution solution, Document? activeDocument, Func, Task> onResultsFound) { - return async item => + return async (items, _, cancellationToken) => { - // This must succeed. We should always be searching for items that correspond to documents/projects in - // the host side solution. Note: this even includes 'cached' items. While those may correspond to - // stale versions of a document, it should still be for documents that the host has asked about. - var project = solution.GetRequiredProject(item.DocumentId.ProjectId); + using var _1 = ArrayBuilder.GetInstance(items.Length, out var results); - var result = await item.TryCreateSearchResultAsync(solution, activeDocument, cancellationToken).ConfigureAwait(false); - if (result != null) - await onResultFound(project, result).ConfigureAwait(false); + foreach (var item in items) + { + var result = await item.TryCreateSearchResultAsync(solution, activeDocument, cancellationToken).ConfigureAwait(false); + if (result != null) + results.Add(result); + } + + if (results.Count > 0) + await onResultsFound(results.ToImmutableAndClear()).ConfigureAwait(false); }; } @@ -62,4 +65,20 @@ private static PooledDisposer> GetPooledHashSet(ImmutableArr instance.AddRange(items); return disposer; } + + private static IEnumerable Prioritize(IEnumerable items, Func isPriority) + { + using var _ = ArrayBuilder.GetInstance(out var normalItems); + + foreach (var item in items) + { + if (isPriority(item)) + yield return item; + else + normalItems.Add(item); + } + + foreach (var item in normalItems) + yield return item; + } } diff --git a/src/Features/Core/Portable/NavigateTo/INavigateToSearchCallback.cs b/src/Features/Core/Portable/NavigateTo/INavigateToSearchCallback.cs index 281c9ffc89b56..4a2e95b4e31ba 100644 --- a/src/Features/Core/Portable/NavigateTo/INavigateToSearchCallback.cs +++ b/src/Features/Core/Portable/NavigateTo/INavigateToSearchCallback.cs @@ -2,6 +2,7 @@ // 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.Immutable; using System.Threading; using System.Threading.Tasks; @@ -12,7 +13,7 @@ internal interface INavigateToSearchCallback void Done(bool isFullyLoaded); void ReportIncomplete(); - Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken); + Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken); void ReportProgress(int current, int maximum); } diff --git a/src/Features/Core/Portable/NavigateTo/INavigateToSearchResult.cs b/src/Features/Core/Portable/NavigateTo/INavigateToSearchResult.cs index 761a370778a72..ded2ae3bca7f4 100644 --- a/src/Features/Core/Portable/NavigateTo/INavigateToSearchResult.cs +++ b/src/Features/Core/Portable/NavigateTo/INavigateToSearchResult.cs @@ -3,10 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.PatternMatching; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; diff --git a/src/Features/Core/Portable/NavigateTo/INavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/INavigateToSearchService.cs index 70365273bff52..2afcd69e24ece 100644 --- a/src/Features/Core/Portable/NavigateTo/INavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/INavigateToSearchService.cs @@ -19,7 +19,7 @@ Task SearchDocumentAsync( Document document, string searchPattern, IImmutableSet kinds, - Func onResultFound, + Func, Task> onResultsFound, CancellationToken cancellationToken); /// @@ -39,7 +39,7 @@ Task SearchProjectsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken); } @@ -66,7 +66,7 @@ Task SearchCachedDocumentsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken); @@ -84,7 +84,7 @@ Task SearchGeneratedDocumentsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken); } diff --git a/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs index 2f9811f0801b5..e50aa7fad8794 100644 --- a/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Storage; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; @@ -26,7 +27,7 @@ internal interface IRemoteNavigateToSearchService public interface ICallback { - ValueTask OnResultFoundAsync(RemoteServiceCallbackId callbackId, RoslynNavigateToItem result); + ValueTask OnItemsFoundAsync(RemoteServiceCallbackId callbackId, ImmutableArray items); ValueTask OnProjectCompletedAsync(RemoteServiceCallbackId callbackId); } } @@ -39,22 +40,23 @@ internal sealed class NavigateToSearchServiceServerCallbackDispatcher() : Remote private new NavigateToSearchServiceCallback GetCallback(RemoteServiceCallbackId callbackId) => (NavigateToSearchServiceCallback)base.GetCallback(callbackId); - public ValueTask OnResultFoundAsync(RemoteServiceCallbackId callbackId, RoslynNavigateToItem result) - => GetCallback(callbackId).OnResultFoundAsync(result); + public ValueTask OnItemsFoundAsync(RemoteServiceCallbackId callbackId, ImmutableArray items) + => GetCallback(callbackId).OnItemsFoundAsync(items); public ValueTask OnProjectCompletedAsync(RemoteServiceCallbackId callbackId) => GetCallback(callbackId).OnProjectCompletedAsync(); } internal sealed class NavigateToSearchServiceCallback( - Func onResultFound, - Func? onProjectCompleted) + Func, VoidResult, CancellationToken, Task> onItemsFound, + Func? onProjectCompleted, + CancellationToken cancellationToken) { - public async ValueTask OnResultFoundAsync(RoslynNavigateToItem result) + public async ValueTask OnItemsFoundAsync(ImmutableArray items) { try { - await onResultFound(result).ConfigureAwait(false); + await onItemsFound(items, default, cancellationToken).ConfigureAwait(false); } catch (Exception ex) when (FatalError.ReportAndPropagateUnlessCanceled(ex)) { diff --git a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs index a11f4ff35d6f6..0af4241b615a4 100644 --- a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs +++ b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs @@ -191,7 +191,7 @@ private async Task SearchCurrentDocumentAsync(CancellationToken cancellationToke await AddProgressItemsAsync(1, cancellationToken).ConfigureAwait(false); await service.SearchDocumentAsync( _activeDocument, _searchPattern, _kinds, - r => _callback.AddItemAsync(project, r, cancellationToken), + r => _callback.AddResultsAsync(r, cancellationToken), cancellationToken).ConfigureAwait(false); } @@ -345,8 +345,8 @@ private ImmutableArray GetPriorityDocuments(ImmutableArray pr private async Task ProcessOrderedProjectsAsync( bool parallel, ImmutableArray> orderedProjects, - HashSet seenItems, - Func, Func, Func, Task> processProjectAsync, + HashSet seenResults, + Func, Func, Task>, Func, Task> processProjectAsync, CancellationToken cancellationToken) { // Process each group one at a time. However, in each group process all projects in parallel to get results @@ -362,38 +362,47 @@ private async Task ProcessOrderedProjectsAsync( if (!parallel) { foreach (var group in groups) - await SearchCoreAsync(group).ConfigureAwait(false); + await SearchCoreAsync(group, cancellationToken).ConfigureAwait(false); } else { - var allTasks = groups.Select(SearchCoreAsync); - await Task.WhenAll(allTasks).ConfigureAwait(false); + await RoslynParallel.ForEachAsync( + source: groups, + cancellationToken, + SearchCoreAsync).ConfigureAwait(false); } } return; - async Task SearchCoreAsync(IGrouping grouping) + async ValueTask SearchCoreAsync(IGrouping grouping, CancellationToken cancellationToken) { - await Task.Yield().ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); var searchService = grouping.Key; await processProjectAsync( searchService, [.. grouping], - (project, result) => + results => { + using var _ = ArrayBuilder.GetInstance(results.Length, out var nonDuplicates); + // If we're seeing a dupe in another project, then filter it out here. The results from // the individual projects will already contain the information about all the projects // leading to a better condensed view that doesn't look like it contains duplicate info. - lock (seenItems) + lock (seenResults) { - if (!seenItems.Add(result)) - return Task.CompletedTask; + foreach (var result in results) + { + if (seenResults.Add(result)) + nonDuplicates.Add(result); + } } - return _callback.AddItemAsync(project, result, cancellationToken); + if (nonDuplicates.Count > 0) + _callback.AddResultsAsync(nonDuplicates.ToImmutableAndClear(), cancellationToken); + + return Task.CompletedTask; }, () => this.ProgressItemsCompletedAsync(count: 1, cancellationToken)).ConfigureAwait(false); } @@ -430,7 +439,7 @@ private Task SearchCachedDocumentsAsync( parallel: true, orderedProjects, seenItems, - async (service, projects, onItemFound, onProjectCompleted) => + async (service, projects, onResultsFound, onProjectCompleted) => { // if the language doesn't support searching cached docs, immediately transition the project to the // completed state. @@ -443,7 +452,7 @@ private Task SearchCachedDocumentsAsync( { await advancedService.SearchCachedDocumentsAsync( _solution, projects, GetPriorityDocuments(projects), _searchPattern, _kinds, _activeDocument, - onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + onResultsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); } }, cancellationToken); @@ -487,7 +496,7 @@ private Task SearchGeneratedDocumentsAsync( parallel: false, filteredProjects, seenItems, - async (service, projects, onItemFound, onProjectCompleted) => + async (service, projects, onResultsFound, onProjectCompleted) => { // if the language doesn't support searching generated docs, immediately transition the project to the // completed state. @@ -499,7 +508,7 @@ private Task SearchGeneratedDocumentsAsync( else { await advancedService.SearchGeneratedDocumentsAsync( - _solution, projects, _searchPattern, _kinds, _activeDocument, onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + _solution, projects, _searchPattern, _kinds, _activeDocument, onResultsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); } }, cancellationToken); @@ -519,10 +528,10 @@ public IImmutableSet KindsProvided public bool CanFilter => false; - public Task SearchDocumentAsync(Document document, string searchPattern, IImmutableSet kinds, Func onResultFound, CancellationToken cancellationToken) + public Task SearchDocumentAsync(Document document, string searchPattern, IImmutableSet kinds, Func, Task> onResultsFound, CancellationToken cancellationToken) => Task.CompletedTask; - public async Task SearchProjectsAsync(Solution solution, ImmutableArray projects, ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) + public async Task SearchProjectsAsync(Solution solution, ImmutableArray projects, ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { foreach (var _ in projects) await onProjectCompleted().ConfigureAwait(false); diff --git a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs index 7437657dc3fc5..325b107a6cf29 100644 --- a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs +++ b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs @@ -87,7 +87,7 @@ internal readonly struct RoslynNavigateToItem( } } - private class NavigateToSearchResult : INavigateToSearchResult, INavigableItem + private sealed class NavigateToSearchResult : INavigateToSearchResult, INavigableItem { private static readonly char[] s_dotArray = ['.']; diff --git a/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs b/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs index 73580d5c070a1..3f28908e0ee83 100644 --- a/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs +++ b/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs @@ -29,8 +29,9 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf var cancellationToken = context.CancellationToken; var semanticModel = await context.Document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var services = context.Document.Project.Solution.Services; + var onTheFlyDocsElement = await GetOnTheFlyDocsElementAsync(context, cancellationToken).ConfigureAwait(false); return await CreateContentAsync( - services, semanticModel, token, tokenInformation, supportedPlatforms, context.Options, cancellationToken).ConfigureAwait(false); + services, semanticModel, token, tokenInformation, supportedPlatforms, context.Options, onTheFlyDocsElement, cancellationToken).ConfigureAwait(false); } protected override async Task BuildQuickInfoAsync( @@ -40,8 +41,9 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf if (tokenInformation.Symbols.IsDefaultOrEmpty) return null; + // onTheFlyDocElement is null here since On-The-Fly Docs are being computed at the document level. return await CreateContentAsync( - context.Services, context.SemanticModel, token, tokenInformation, supportedPlatforms: null, context.Options, context.CancellationToken).ConfigureAwait(false); + context.Services, context.SemanticModel, token, tokenInformation, supportedPlatforms: null, context.Options, onTheFlyDocsElement: null, context.CancellationToken).ConfigureAwait(false); } private async Task<(TokenInformation tokenInformation, SupportedPlatformData? supportedPlatforms)> ComputeQuickInfoDataAsync( @@ -155,6 +157,7 @@ protected static Task CreateContentAsync( TokenInformation tokenInformation, SupportedPlatformData? supportedPlatforms, SymbolDescriptionOptions options, + OnTheFlyDocsElement? onTheFlyDocsElement, CancellationToken cancellationToken) { var syntaxFactsService = services.GetRequiredLanguageService(semanticModel.Language); @@ -172,13 +175,16 @@ protected static Task CreateContentAsync( return QuickInfoUtilities.CreateQuickInfoItemAsync( services, semanticModel, token.Span, symbols, supportedPlatforms, - tokenInformation.ShowAwaitReturn, tokenInformation.NullableFlowState, options, cancellationToken); + tokenInformation.ShowAwaitReturn, tokenInformation.NullableFlowState, options, onTheFlyDocsElement, cancellationToken); } protected abstract bool GetBindableNodeForTokenIndicatingLambda(SyntaxToken token, [NotNullWhen(returnValue: true)] out SyntaxNode? found); protected abstract bool GetBindableNodeForTokenIndicatingPossibleIndexerAccess(SyntaxToken token, [NotNullWhen(returnValue: true)] out SyntaxNode? found); protected abstract bool GetBindableNodeForTokenIndicatingMemberAccess(SyntaxToken token, out SyntaxToken found); + protected virtual Task GetOnTheFlyDocsElementAsync(QuickInfoContext context, CancellationToken cancellationToken) + => Task.FromResult(null); + protected virtual NullableFlowState GetNullabilityAnalysis(SemanticModel semanticModel, ISymbol symbol, SyntaxNode node, CancellationToken cancellationToken) => NullableFlowState.None; private TokenInformation BindToken( diff --git a/src/Features/Core/Portable/QuickInfo/OnTheFlyDocsElement.cs b/src/Features/Core/Portable/QuickInfo/OnTheFlyDocsElement.cs new file mode 100644 index 0000000000000..50ca692b162d2 --- /dev/null +++ b/src/Features/Core/Portable/QuickInfo/OnTheFlyDocsElement.cs @@ -0,0 +1,20 @@ +// 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.Immutable; + +namespace Microsoft.CodeAnalysis.QuickInfo; + +/// +/// Represents the data needed to provide on-the-fly documentation from the symbol. +/// +/// formatted string representation of a symbol/> +/// the symbol's declaration code +/// the language of the symbol +internal sealed class OnTheFlyDocsElement(string symbolSignature, ImmutableArray declarationCode, string language) +{ + public string SymbolSignature { get; } = symbolSignature; + public ImmutableArray DeclarationCode { get; } = declarationCode; + public string Language { get; } = language; +} diff --git a/src/Features/Core/Portable/QuickInfo/QuickInfoItem.cs b/src/Features/Core/Portable/QuickInfo/QuickInfoItem.cs index ad1e8ddf430e6..8c31c4d586d83 100644 --- a/src/Features/Core/Portable/QuickInfo/QuickInfoItem.cs +++ b/src/Features/Core/Portable/QuickInfo/QuickInfoItem.cs @@ -30,16 +30,20 @@ public sealed class QuickInfoItem /// public ImmutableArray RelatedSpans { get; } + internal OnTheFlyDocsElement? OnTheFlyDocsElement { get; } + private QuickInfoItem( TextSpan span, ImmutableArray tags, ImmutableArray sections, - ImmutableArray relatedSpans) + ImmutableArray relatedSpans, + OnTheFlyDocsElement? onTheFlyDocsElement) { Span = span; Tags = tags.IsDefault ? [] : tags; Sections = sections.IsDefault ? [] : sections; RelatedSpans = relatedSpans.IsDefault ? [] : relatedSpans; + OnTheFlyDocsElement = onTheFlyDocsElement; } public static QuickInfoItem Create( @@ -48,6 +52,16 @@ public static QuickInfoItem Create( ImmutableArray sections = default, ImmutableArray relatedSpans = default) { - return new QuickInfoItem(span, tags, sections, relatedSpans); + return Create(span, tags, sections, relatedSpans, onTheFlyDocsElement: null); + } + + internal static QuickInfoItem Create( + TextSpan span, + ImmutableArray tags, + ImmutableArray sections, + ImmutableArray relatedSpans, + OnTheFlyDocsElement? onTheFlyDocsElement) + { + return new QuickInfoItem(span, tags, sections, relatedSpans, onTheFlyDocsElement); } } diff --git a/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs b/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs index cb25aef91c6fc..9ca249e914acc 100644 --- a/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs +++ b/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs @@ -2,13 +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.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; @@ -22,7 +19,7 @@ namespace Microsoft.CodeAnalysis.QuickInfo; internal static class QuickInfoUtilities { public static Task CreateQuickInfoItemAsync(SolutionServices services, SemanticModel semanticModel, TextSpan span, ImmutableArray symbols, SymbolDescriptionOptions options, CancellationToken cancellationToken) - => CreateQuickInfoItemAsync(services, semanticModel, span, symbols, supportedPlatforms: null, showAwaitReturn: false, flowState: NullableFlowState.None, options, cancellationToken); + => CreateQuickInfoItemAsync(services, semanticModel, span, symbols, supportedPlatforms: null, showAwaitReturn: false, flowState: NullableFlowState.None, options, onTheFlyDocsElement: null, cancellationToken); public static async Task CreateQuickInfoItemAsync( SolutionServices services, @@ -33,6 +30,7 @@ public static async Task CreateQuickInfoItemAsync( bool showAwaitReturn, NullableFlowState flowState, SymbolDescriptionOptions options, + OnTheFlyDocsElement? onTheFlyDocsElement, CancellationToken cancellationToken) { var descriptionService = services.GetRequiredLanguageService(semanticModel.Language); @@ -152,7 +150,7 @@ public static async Task CreateQuickInfoItemAsync( if (supportedPlatforms?.HasValidAndInvalidProjects() == true) tags = tags.Add(WellKnownTags.Warning); - return QuickInfoItem.Create(span, tags, sections.ToImmutable()); + return QuickInfoItem.Create(span, tags, sections.ToImmutable(), relatedSpans: default, onTheFlyDocsElement); bool TryGetGroupText(SymbolDescriptionGroups group, out ImmutableArray taggedParts) => groups.TryGetValue(group, out taggedParts) && !taggedParts.IsDefaultOrEmpty; diff --git a/src/Features/Core/Portable/SymbolSearch/SymbolSearchUpdateNoOpEngine.cs b/src/Features/Core/Portable/SymbolSearch/SymbolSearchUpdateNoOpEngine.cs index 1646c549e47cb..ea2a0b7184685 100644 --- a/src/Features/Core/Portable/SymbolSearch/SymbolSearchUpdateNoOpEngine.cs +++ b/src/Features/Core/Portable/SymbolSearch/SymbolSearchUpdateNoOpEngine.cs @@ -13,6 +13,11 @@ internal sealed class SymbolSearchUpdateNoOpEngine : ISymbolSearchUpdateEngine { public static readonly SymbolSearchUpdateNoOpEngine Instance = new(); + public void Dispose() + { + // Nothing to do for the no-op version. + } + public ValueTask> FindPackagesWithAssemblyAsync(string source, string assemblyName, CancellationToken cancellationToken) => ValueTaskFactory.FromResult(ImmutableArray.Empty); diff --git a/src/Features/Core/Portable/SymbolSearch/Windows/SymbolSearchUpdateEngine.cs b/src/Features/Core/Portable/SymbolSearch/Windows/SymbolSearchUpdateEngine.cs index d827000bfa327..e5db69773fdd4 100644 --- a/src/Features/Core/Portable/SymbolSearch/Windows/SymbolSearchUpdateEngine.cs +++ b/src/Features/Core/Portable/SymbolSearch/Windows/SymbolSearchUpdateEngine.cs @@ -65,6 +65,11 @@ internal SymbolSearchUpdateEngine( _reportAndSwallowExceptionUnlessCanceled = reportAndSwallowExceptionUnlessCanceled; } + public void Dispose() + { + // Nothing to do for the core symbol search engine. + } + public ValueTask> FindPackagesWithTypeAsync( string source, string name, int arity, CancellationToken cancellationToken) { diff --git a/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs b/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs index 5baec78131adb..54fbfad723cda 100644 --- a/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs +++ b/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs @@ -41,7 +41,7 @@ public sealed override ImmutableArray FixableDiagnosticIds protected abstract TPropertyDeclaration GetPropertyDeclaration(SyntaxNode node); protected abstract SyntaxNode GetNodeToRemove(TVariableDeclarator declarator); - protected abstract IEnumerable GetFormattingRules(Document document); + protected abstract ImmutableArray GetFormattingRules(Document document); protected abstract Task UpdatePropertyAsync( Document propertyDocument, Compilation compilation, IFieldSymbol fieldSymbol, IPropertySymbol propertySymbol, @@ -281,10 +281,8 @@ private static bool CanEditDocument( private async Task FormatAsync(SyntaxNode newRoot, Document document, CodeCleanupOptionsProvider fallbackOptions, CancellationToken cancellationToken) { var formattingRules = GetFormattingRules(document); - if (formattingRules == null) - { + if (formattingRules.IsDefault) return newRoot; - } var options = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); return Formatter.Format(newRoot, SpecializedFormattingAnnotation, document.Project.Solution.Services, options, formattingRules, cancellationToken); diff --git a/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs b/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs index 91ae027a843b9..c730ff32c5eb8 100644 --- a/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs +++ b/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs @@ -2,6 +2,7 @@ // 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.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols; @@ -28,11 +29,16 @@ private class FindReferencesProgress(OperationCollector valueTrackingProgressCol public ValueTask OnDefinitionFoundAsync(SymbolGroup symbolGroup, CancellationToken _) => new(); - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken _) => new(); - - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken _) => new(); + public async ValueTask OnReferencesFoundAsync( + ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, + CancellationToken cancellationToken) + { + foreach (var (_, symbol, location) in references) + await OnReferenceFoundAsync(symbol, location, cancellationToken).ConfigureAwait(false); + } - public async ValueTask OnReferenceFoundAsync(SymbolGroup _, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) + private async ValueTask OnReferenceFoundAsync( + ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) { if (!location.Location.IsInSource) { diff --git a/src/Features/Core/Portable/ValueTracking/ValueTracker.OperationCollector.cs b/src/Features/Core/Portable/ValueTracking/ValueTracker.OperationCollector.cs index 24a0440e06b94..a62f976c6c69b 100644 --- a/src/Features/Core/Portable/ValueTracking/ValueTracker.OperationCollector.cs +++ b/src/Features/Core/Portable/ValueTracking/ValueTracker.OperationCollector.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; namespace Microsoft.CodeAnalysis.ValueTracking; @@ -186,10 +187,10 @@ private async Task TrackArgumentsAsync(ImmutableArray argume .Select(argument => (collector: Clone(), argument)) .ToImmutableArray(); - var tasks = collectorsAndArgumentMap - .Select(pair => Task.Run(() => pair.collector.VisitAsync(pair.argument, cancellationToken))); - - await Task.WhenAll(tasks).ConfigureAwait(false); + await RoslynParallel.ForEachAsync( + collectorsAndArgumentMap, + cancellationToken, + async (pair, cancellationToken) => await pair.collector.VisitAsync(pair.argument.Value, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); var items = collectorsAndArgumentMap .Select(pair => pair.collector.ProgressCollector) diff --git a/src/Features/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1+Test.cs b/src/Features/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1+Test.cs index 8898f89aaaf22..1ddb360748015 100644 --- a/src/Features/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1+Test.cs +++ b/src/Features/DiagnosticsTestUtilities/CodeActions/CSharpCodeRefactoringVerifier`1+Test.cs @@ -118,9 +118,9 @@ protected override CodeRefactoringContext CreateCodeRefactoringContext(Document => new CodeRefactoringContext(document, span, (action, textSpan) => registerRefactoring(action), _sharedState.CodeActionOptions, cancellationToken); /// - /// The we want this test to run in. Defaults to if unspecified. + /// The we want this test to run in. Defaults to if unspecified. /// - public TestHost TestHost { get; set; } = TestHost.InProcess; + public TestHost TestHost { get; set; } = TestHost.OutOfProcess; private static readonly TestComposition s_editorFeaturesOOPComposition = FeaturesTestCompositions.Features.WithTestHostParts(TestHost.OutOfProcess); diff --git a/src/Features/DiagnosticsTestUtilities/CodeActions/VisualBasicCodeRefactoringVerifier`1+Test.cs b/src/Features/DiagnosticsTestUtilities/CodeActions/VisualBasicCodeRefactoringVerifier`1+Test.cs index fb2c5a1637488..e8a1e4db8679a 100644 --- a/src/Features/DiagnosticsTestUtilities/CodeActions/VisualBasicCodeRefactoringVerifier`1+Test.cs +++ b/src/Features/DiagnosticsTestUtilities/CodeActions/VisualBasicCodeRefactoringVerifier`1+Test.cs @@ -104,9 +104,10 @@ protected override CodeRefactoringContext CreateCodeRefactoringContext(Document => new CodeRefactoringContext(document, span, (action, textSpan) => registerRefactoring(action), _sharedState.CodeActionOptions, cancellationToken); /// - /// The we want this test to run in. Defaults to if unspecified. + /// The we want this test to run in. Defaults to + /// if unspecified. /// - public TestHost TestHost { get; set; } = TestHost.InProcess; + public TestHost TestHost { get; set; } = TestHost.OutOfProcess; private static readonly TestComposition s_editorFeaturesOOPComposition = FeaturesTestCompositions.Features.WithTestHostParts(TestHost.OutOfProcess); diff --git a/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs b/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs index b0bdf4fd1c942..6742e113a7814 100644 --- a/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs +++ b/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs @@ -80,7 +80,7 @@ internal TestParameters( bool retainNonFixableDiagnostics = false, bool includeDiagnosticsOutsideSelection = false, string title = null, - TestHost testHost = TestHost.InProcess, + TestHost testHost = TestHost.OutOfProcess, string workspaceKind = null, bool includeNonLocalDocumentDiagnostics = false, bool treatPositionIndicatorsAsCode = false) @@ -262,7 +262,7 @@ private static void AddAnalyzerConfigDocumentWithOptions(TestWorkspace workspace Assert.True(applied); return; - string GenerateAnalyzerConfigText(OptionsCollectionAlias options) + static string GenerateAnalyzerConfigText(OptionsCollectionAlias options) { var textBuilder = new StringBuilder(); @@ -417,7 +417,7 @@ internal Task TestInRegularAndScriptAsync( object fixProviderData = null, ParseOptions parseOptions = null, string title = null, - TestHost testHost = TestHost.InProcess) + TestHost testHost = TestHost.OutOfProcess) { return TestInRegularAndScript1Async( initialMarkup, expectedMarkup, @@ -457,7 +457,7 @@ internal Task TestAsync( OptionsCollectionAlias globalOptions = null, object fixProviderData = null, CodeActionPriority? priority = null, - TestHost testHost = TestHost.InProcess) + TestHost testHost = TestHost.OutOfProcess) { return TestAsync( initialMarkup, diff --git a/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest_NoEditor.cs b/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest_NoEditor.cs index 1442d07a78fb9..a9197c7d2d880 100644 --- a/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest_NoEditor.cs +++ b/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest_NoEditor.cs @@ -152,7 +152,7 @@ internal override async Task> GetDiagnosticsAsync( TestWorkspace workspace, TestParameters parameters) { var (analyzer, _) = GetOrCreateDiagnosticProviderAndFixer(workspace, parameters); - AddAnalyzerToWorkspace(workspace, analyzer, parameters); + AddAnalyzerToWorkspace(workspace, analyzer); var document = GetDocumentAndSelectSpan(workspace, out var span); var allDiagnostics = await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(workspace, document, span, includeNonLocalDocumentDiagnostics: parameters.includeNonLocalDocumentDiagnostics); @@ -164,7 +164,7 @@ internal override async Task> GetDiagnosticsAsync( TestWorkspace workspace, TestParameters parameters) { var (analyzer, fixer) = GetOrCreateDiagnosticProviderAndFixer(workspace, parameters); - AddAnalyzerToWorkspace(workspace, analyzer, parameters); + AddAnalyzerToWorkspace(workspace, analyzer); GetDocumentAndSelectSpanOrAnnotatedSpan(workspace, out var document, out var span, out var annotation); diff --git a/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractSuppressionDiagnosticTest_NoEditor.cs b/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractSuppressionDiagnosticTest_NoEditor.cs index 8009dd36e2267..30aaae6f4be0a 100644 --- a/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractSuppressionDiagnosticTest_NoEditor.cs +++ b/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractSuppressionDiagnosticTest_NoEditor.cs @@ -68,7 +68,7 @@ internal override async Task> GetDiagnosticsAsync( TestWorkspace workspace, TestParameters parameters) { var (analyzer, _) = CreateDiagnosticProviderAndFixer(workspace); - AddAnalyzerToWorkspace(workspace, analyzer, parameters); + AddAnalyzerToWorkspace(workspace, analyzer); var document = GetDocumentAndSelectSpan(workspace, out var span); var diagnostics = await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(workspace, document, span, includeNonLocalDocumentDiagnostics: parameters.includeNonLocalDocumentDiagnostics); @@ -79,7 +79,7 @@ internal override async Task> GetDiagnosticsAsync( TestWorkspace workspace, TestParameters parameters) { var (analyzer, fixer) = CreateDiagnosticProviderAndFixer(workspace); - AddAnalyzerToWorkspace(workspace, analyzer, parameters); + AddAnalyzerToWorkspace(workspace, analyzer); GetDocumentAndSelectSpanOrAnnotatedSpan(workspace, out var document, out var span, out var annotation); diff --git a/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUnncessarySuppressionDiagnosticTest.cs b/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUnncessarySuppressionDiagnosticTest.cs index 90c53d22f1ead..52e1a2a1ff5a6 100644 --- a/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUnncessarySuppressionDiagnosticTest.cs +++ b/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUnncessarySuppressionDiagnosticTest.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.RemoveUnnecessarySuppressions; +using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.UnitTests.Diagnostics; using Xunit.Abstractions; @@ -32,7 +33,7 @@ protected AbstractUnncessarySuppressionDiagnosticTest(ITestOutputHelper logger) private void AddAnalyzersToWorkspace(TestWorkspace workspace) { var analyzerReference = new AnalyzerImageReference(OtherAnalyzers.Add(SuppressionAnalyzer)); - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); + workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference])); } internal override async Task> GetDiagnosticsAsync( diff --git a/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_NoEditor.cs b/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_NoEditor.cs index d19e283a4238c..803368302a6e4 100644 --- a/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_NoEditor.cs +++ b/src/Features/DiagnosticsTestUtilities/Diagnostics/AbstractUserDiagnosticTest_NoEditor.cs @@ -20,6 +20,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Remote.Testing; +using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; @@ -100,26 +101,23 @@ protected override async Task> GetDiagnosticsWorkerAs internal override Task GetCodeRefactoringAsync(TestWorkspace workspace, TestParameters parameters) => throw new NotImplementedException("No refactoring provided in diagnostic test"); - protected static void AddAnalyzerToWorkspace(Workspace workspace, DiagnosticAnalyzer analyzer, TestParameters parameters) + protected static void AddAnalyzerToWorkspace(Workspace workspace, DiagnosticAnalyzer analyzer) { - AnalyzerReference[] analyzeReferences; + AnalyzerReference[] analyzerReferences; if (analyzer != null) { - Contract.ThrowIfTrue(parameters.testHost == TestHost.OutOfProcess, $"Out-of-proc testing is not supported since {analyzer} can't be serialized."); - - analyzeReferences = new[] { new AnalyzerImageReference(ImmutableArray.Create(analyzer)) }; + var analyzerImageReference = new AnalyzerImageReference([analyzer]); + analyzerReferences = [analyzerImageReference]; } else { // create a serializable analyzer reference: - analyzeReferences = new[] - { + analyzerReferences = [ new AnalyzerFileReference(DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp).GetType().Assembly.Location, TestAnalyzerAssemblyLoader.LoadFromFile), - new AnalyzerFileReference(DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.VisualBasic).GetType().Assembly.Location, TestAnalyzerAssemblyLoader.LoadFromFile) - }; + new AnalyzerFileReference(DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.VisualBasic).GetType().Assembly.Location, TestAnalyzerAssemblyLoader.LoadFromFile)]; } - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(analyzeReferences)); + workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(analyzerReferences)); } protected static Document GetDocumentAndSelectSpan(TestWorkspace workspace, out TextSpan span) diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LspFileChangeWatcherTests.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LspFileChangeWatcherTests.cs index c7b1b6b940865..4be80e009e63a 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LspFileChangeWatcherTests.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/LspFileChangeWatcherTests.cs @@ -67,7 +67,7 @@ public async Task CreatingDirectoryWatchRequestsDirectoryWatch() var watcher = GetSingleFileWatcher(dynamicCapabilitiesRpcTarget); - Assert.Equal(tempDirectory.Path + Path.DirectorySeparatorChar, watcher.GlobPattern.BaseUri.LocalPath); + Assert.Equal(tempDirectory.Path, watcher.GlobPattern.BaseUri.LocalPath); Assert.Equal("**/*", watcher.GlobPattern.Pattern); // Get rid of the registration and it should be gone again diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/BrokeredServiceProxy.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/BrokeredServiceProxy.cs index bff88eebd4630..de5f1376515eb 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/BrokeredServiceProxy.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/BrokeredServiceProxy.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Nerdbank.Streams; +using Roslyn.Utilities; using StreamJsonRpc; namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests; @@ -25,8 +26,14 @@ public BrokeredServiceProxy(T service) { var (serverStream, clientStream) = FullDuplexStream.CreatePair(); - var serverTask = Task.Run(async () => + _createConnectionTask = Task.WhenAll(CreateServerAsync(), CreateClientAsync()); + return; + + async Task CreateServerAsync() { + // Ensure caller can proceed. + await Task.Yield().ConfigureAwait(false); + var serverMultiplexingStream = await MultiplexingStream.CreateAsync(serverStream); var serverChannel = await serverMultiplexingStream.AcceptChannelAsync(""); @@ -35,10 +42,13 @@ public BrokeredServiceProxy(T service) _serverRpc.AddLocalRpcTarget(service, options: null); _serverRpc.StartListening(); - }); + } - var clientTask = Task.Run(async () => + async Task CreateClientAsync() { + // Ensure caller can proceed. + await Task.Yield().ConfigureAwait(false); + var clientMultiplexingStream = await MultiplexingStream.CreateAsync(clientStream); var clientChannel = await clientMultiplexingStream.OfferChannelAsync(""); @@ -47,9 +57,7 @@ public BrokeredServiceProxy(T service) _clientFactoryProxy = _clientRpc.Attach(); _clientRpc.StartListening(); - }); - - _createConnectionTask = Task.WhenAll(serverTask, clientTask); + } } public async ValueTask DisposeAsync() diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/BrokeredServices/Services/Descriptors.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/BrokeredServices/Services/Descriptors.cs index dd0618831e79c..4fa57508b1b38 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/BrokeredServices/Services/Descriptors.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/BrokeredServices/Services/Descriptors.cs @@ -45,7 +45,9 @@ internal class Descriptors { BrokeredServiceDescriptors.DebuggerManagedHotReloadService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) }, { BrokeredServiceDescriptors.HotReloadSessionNotificationService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) }, { BrokeredServiceDescriptors.ManagedHotReloadAgentManagerService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) }, - { BrokeredServiceDescriptors.MauiLaunchCustomizerServiceDescriptor.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) }, + { BrokeredServiceDescriptors.HotReloadOptionService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) }, + { BrokeredServiceDescriptors.HotReloadLoggerService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) }, + { BrokeredServiceDescriptors.MauiLaunchCustomizerService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) }, }.ToImmutableDictionary(); public static ServiceJsonRpcDescriptor CreateDescriptor(ServiceMoniker serviceMoniker) => new( diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs index b2da22f5e8157..254707da542a7 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs @@ -65,8 +65,9 @@ private class FileChangeContext : IFileChangeContext /// The list of file paths we're watching manually that were outside the directories being watched. The count in this case counts /// the number of /// - private readonly Dictionary _watchedFiles = new Dictionary(_stringComparer); - private static readonly StringComparer _stringComparer = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + private readonly Dictionary _watchedFiles = new Dictionary(s_stringComparer); + private static readonly StringComparer s_stringComparer = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + private static readonly StringComparison s_stringComparison = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; public FileChangeContext(ImmutableArray watchedDirectories, LspFileChangeWatcher lspFileChangeWatcher) { @@ -80,7 +81,7 @@ public FileChangeContext(ImmutableArray watchedDirectories, Ls { GlobPattern = new RelativePattern { - BaseUri = ProtocolConversions.CreateAbsoluteUri(d.Path), + BaseUri = ProtocolConversions.CreateRelativePatternBaseUri(d.Path), Pattern = d.ExtensionFilter is not null ? "**/*" + d.ExtensionFilter : "**/*" } }).ToArray(); @@ -99,7 +100,7 @@ private void WatchedFilesHandler_OnNotificationRaised(object? sender, DidChangeW // Unfortunately the LSP protocol doesn't give us any hint of which of the file watches we might have sent to the client // was the one that registered for this change, so we have to check paths to see if this one we should respond to. - if (WatchedDirectory.FilePathCoveredByWatchedDirectories(_watchedDirectories, filePath, StringComparison.Ordinal)) + if (WatchedDirectory.FilePathCoveredByWatchedDirectories(_watchedDirectories, filePath, s_stringComparison)) { FileChanged?.Invoke(this, filePath); } @@ -128,7 +129,7 @@ public void Dispose() public IWatchedFile EnqueueWatchingFile(string filePath) { // If we already have this file under our path, we may not have to do additional watching - if (WatchedDirectory.FilePathCoveredByWatchedDirectories(_watchedDirectories, filePath, StringComparison.OrdinalIgnoreCase)) + if (WatchedDirectory.FilePathCoveredByWatchedDirectories(_watchedDirectories, filePath, s_stringComparison)) return NoOpWatchedFile.Instance; // Record that we're now watching this file diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs index f68f4a7e7d536..c920f014a8327 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs @@ -12,10 +12,10 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.ProjectTelemetry; using Microsoft.CodeAnalysis.MSBuild; -using Microsoft.CodeAnalysis.MSBuild.Logging; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.ProjectSystem; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.Composition; @@ -160,23 +160,18 @@ private async ValueTask LoadOrReloadProjectsAsync(ImmutableSegmentedList(); - - var projectsThatNeedRestore = new ConcurrentSet(); - foreach (var projectToLoad in projectPathsToLoadOrReload) - { - tasks.Add(Task.Run(async () => + var projectsThatNeedRestore = await ProducerConsumer.RunParallelAsync( + source: projectPathsToLoadOrReload, + produceItems: static async (projectToLoad, callback, args, cancellationToken) => { - var projectNeedsRestore = await LoadOrReloadProjectAsync(projectToLoad, toastErrorReporter, buildHostProcessManager, cancellationToken); + var projectNeedsRestore = await args.@this.LoadOrReloadProjectAsync( + projectToLoad, args.toastErrorReporter, args.buildHostProcessManager, cancellationToken); if (projectNeedsRestore) - { - projectsThatNeedRestore.Add(projectToLoad.Path); - } - }, cancellationToken)); - } - - await Task.WhenAll(tasks); + callback(projectToLoad.Path); + }, + args: (@this: this, toastErrorReporter, buildHostProcessManager), + cancellationToken).ConfigureAwait(false); if (_globalOptionService.GetOption(LanguageServerProjectSystemOptionsStorage.EnableAutomaticRestore) && projectsThatNeedRestore.Any()) { @@ -185,7 +180,7 @@ private async ValueTask LoadOrReloadProjectsAsync(ImmutableSegmentedList projectPaths, CancellationToken cancellationToken) + internal static async Task RestoreProjectsAsync(ImmutableArray projectPaths, CancellationToken cancellationToken) { if (projectPaths.IsEmpty) - { return; - } Contract.ThrowIfNull(LanguageServerHost.Instance, "We don't have an LSP channel yet to send this request through."); + var languageServerManager = LanguageServerHost.Instance.GetRequiredLspService(); - var unresolvedParams = new UnresolvedDependenciesParams([.. projectPaths]); + + // Ensure we only pass unique paths back to be restored. + var unresolvedParams = new UnresolvedDependenciesParams([.. projectPaths.Distinct()]); await languageServerManager.SendRequestAsync(ProjectNeedsRestoreName, unresolvedParams, cancellationToken); } diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/RazorDynamicDocumentSyncRegistration.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/RazorDynamicDocumentSyncRegistration.cs new file mode 100644 index 0000000000000..d255899e89c82 --- /dev/null +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/RazorDynamicDocumentSyncRegistration.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.Options; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.LanguageServer; + +[ExportCSharpVisualBasicLspServiceFactory(typeof(OnInitialized)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class RazorDynamicDocumentSyncRegistration(IGlobalOptionService globalOptionService) : ILspServiceFactory +{ + public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) + => new OnInitialized(globalOptionService); + + public class OnInitialized : IOnInitialized, ILspService + { + private readonly IGlobalOptionService _globalOptionService; + + public OnInitialized(IGlobalOptionService globalOptionService) + { + _globalOptionService = globalOptionService; + } + + public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) + { + // Hot reload only works in devkit scenarios. Without, there is no need to register for dynamic document sync. + if (!_globalOptionService.GetOption(LspOptionsStorage.LspUsingDevkitFeatures)) + { + return; + } + + // If dynamic registration for text document synchronization is supported, register for .razor and .cshtml files + // so that they are up to date for hot reload scenarios rather than depending on the file watchers to update + // the contents. + if (clientCapabilities.TextDocument?.Synchronization?.DynamicRegistration is true) + { + var languageServerManager = context.GetRequiredLspService(); + + var documentFilters = new[] { new DocumentFilter() { Pattern = "**/*.razor" }, new DocumentFilter() { Pattern = "**/*.cshtml" } }; + var registrationOptions = new TextDocumentRegistrationOptions() + { + DocumentSelector = documentFilters + }; + + await languageServerManager.SendRequestAsync(Methods.ClientRegisterCapabilityName, + new RegistrationParams() + { + Registrations = [ + new() + { + Id = Guid.NewGuid().ToString(), // No need to save this for unregistering + Method = Methods.TextDocumentDidOpenName, + RegisterOptions = registrationOptions + }, + new() + { + Id = Guid.NewGuid().ToString(), // No need to save this for unregistering + Method = Methods.TextDocumentDidChangeName, + RegisterOptions = registrationOptions + }, + new() + { + Id = Guid.NewGuid().ToString(), // No need to save this for unregistering + Method = Methods.TextDocumentDidCloseName, + RegisterOptions = registrationOptions + } + ] + }, + cancellationToken).ConfigureAwait(false); + } + } + } +} + diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj index fa42a2d113562..c3e8ab62d162f 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj @@ -62,7 +62,6 @@ - diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/HandlerProviderTests.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/HandlerProviderTests.cs index 18c12b0d365be..9bc86ff2fcfa9 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/HandlerProviderTests.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.UnitTests/HandlerProviderTests.cs @@ -11,8 +11,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework.UnitTests; public class HandlerProviderTests { - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void GetMethodHandler(bool supportsGetRegisteredServices) { var handlerProvider = GetHandlerProvider(supportsGetRegisteredServices); @@ -21,8 +20,7 @@ public void GetMethodHandler(bool supportsGetRegisteredServices) Assert.Same(TestMethodHandler.Instance, methodHandler); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void GetMethodHandler_Parameterless(bool supportsGetRegisteredServices) { var handlerProvider = GetHandlerProvider(supportsGetRegisteredServices); @@ -31,8 +29,7 @@ public void GetMethodHandler_Parameterless(bool supportsGetRegisteredServices) Assert.Same(TestParameterlessMethodHandler.Instance, methodHandler); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void GetMethodHandler_Notification(bool supportsGetRegisteredServices) { var handlerProvider = GetHandlerProvider(supportsGetRegisteredServices); @@ -41,8 +38,7 @@ public void GetMethodHandler_Notification(bool supportsGetRegisteredServices) Assert.Same(TestNotificationHandler.Instance, methodHandler); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void GetMethodHandler_ParameterlessNotification(bool supportsGetRegisteredServices) { var handlerProvider = GetHandlerProvider(supportsGetRegisteredServices); @@ -67,8 +63,7 @@ public void GetMethodHandler_WrongResponseType_Throws() Assert.Throws(() => handlerProvider.GetMethodHandler(TestMethodHandler.Name, TestMethodHandler.RequestType, responseType: typeof(long), LanguageServerConstants.DefaultLanguageName)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void GetRegisteredMethods(bool supportsGetRegisteredServices) { var handlerProvider = GetHandlerProvider(supportsGetRegisteredServices); diff --git a/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs index 8eb7e16616167..406ea9a4c2de0 100644 --- a/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs +++ b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs @@ -120,16 +120,6 @@ public ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities) // Using VS server capabilities because we have our own custom client. capabilities.OnAutoInsertProvider = new VSInternalDocumentOnAutoInsertOptions { TriggerCharacters = ["'", "/", "\n"] }; - if (!supportsVsExtensions) - { - capabilities.DiagnosticOptions = new DiagnosticOptions - { - InterFileDependencies = true, - WorkDoneProgress = true, - WorkspaceDiagnostics = true, - }; - } - return capabilities; } diff --git a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs index 890759ebb9ceb..d9b8e4078f89f 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs @@ -10,8 +10,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindUsages; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; diff --git a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs index a06cee02e928e..b46fbf4e76b96 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs @@ -199,6 +199,22 @@ public static Uri CreateAbsoluteUri(string absolutePath) } } + internal static Uri CreateRelativePatternBaseUri(string path) + { + // According to VSCode LSP RelativePattern spec, + // found at https://github.com/microsoft/vscode/blob/9e1974682eb84eebb073d4ae775bad1738c281f6/src/vscode-dts/vscode.d.ts#L2226 + // the baseUri should not end in a trailing separator, nor should it + // have any relative segmeents (., ..) + if (path[^1] == System.IO.Path.DirectorySeparatorChar) + { + path = path[..^1]; + } + + Debug.Assert(!path.Split(System.IO.Path.DirectorySeparatorChar).Any(p => p == "." || p == "..")); + + return CreateAbsoluteUri(path); + } + // Implements workaround for https://github.com/dotnet/runtime/issues/89538: internal static string GetAbsoluteUriString(string absolutePath) { diff --git a/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs index 52f031d5db3a5..3ea1ac7bbe425 100644 --- a/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs +++ b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs @@ -141,8 +141,8 @@ public async Task GetMostSevereFixAsync( } } - var errorFixTask = Task.Run(() => GetFirstFixAsync(spanToErrorDiagnostics, cancellationToken), cancellationToken); - var otherFixTask = Task.Run(() => GetFirstFixAsync(spanToOtherDiagnostics, linkedToken), linkedToken); + var errorFixTask = GetFirstFixAsync(spanToErrorDiagnostics, cancellationToken); + var otherFixTask = GetFirstFixAsync(spanToOtherDiagnostics, linkedToken); // If the error diagnostics task happens to complete with a non-null result before // the other diagnostics task, we can cancel the other task. @@ -156,6 +156,9 @@ public async Task GetMostSevereFixAsync( SortedDictionary> spanToDiagnostics, CancellationToken cancellationToken) { + // Ensure we yield here so the caller can continue on. + await AwaitExtensions.ConfigureAwait(Task.Yield(), false); + await foreach (var collection in StreamFixesAsync( document, spanToDiagnostics, fixAllForInSpan: false, priorityProvider, fallbackOptions, _ => null, cancellationToken).ConfigureAwait(false)) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs index be32623daf749..b7841f2bc4d43 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics @@ -68,7 +69,7 @@ public static bool IsGlobalOptionAffectingDiagnostics(IOption2 option) public void RequestDiagnosticRefresh() => _diagnosticsRefresher?.RequestWorkspaceRefresh(); - public Task<(ImmutableArray diagnostics, bool upToDate)> TryGetDiagnosticsForSpanAsync( + public async Task<(ImmutableArray diagnostics, bool upToDate)> TryGetDiagnosticsForSpanAsync( TextDocument document, TextSpan range, Func? shouldIncludeDiagnostic, @@ -81,20 +82,18 @@ public void RequestDiagnosticRefresh() var analyzer = CreateIncrementalAnalyzer(document.Project.Solution.Workspace); // always make sure that analyzer is called on background thread. - return Task.Run(async () => - { - priorityProvider ??= new DefaultCodeActionRequestPriorityProvider(); - - using var _ = ArrayBuilder.GetInstance(out var diagnostics); - var upToDate = await analyzer.TryAppendDiagnosticsForSpanAsync( - document, range, diagnostics, shouldIncludeDiagnostic, - includeSuppressedDiagnostics, true, priorityProvider, blockForData: false, - addOperationScope: null, diagnosticKinds, isExplicit, cancellationToken).ConfigureAwait(false); - return (diagnostics.ToImmutable(), upToDate); - }, cancellationToken); + await TaskScheduler.Default; + priorityProvider ??= new DefaultCodeActionRequestPriorityProvider(); + + using var _ = ArrayBuilder.GetInstance(out var diagnostics); + var upToDate = await analyzer.TryAppendDiagnosticsForSpanAsync( + document, range, diagnostics, shouldIncludeDiagnostic, + includeSuppressedDiagnostics, true, priorityProvider, blockForData: false, + addOperationScope: null, diagnosticKinds, isExplicit, cancellationToken).ConfigureAwait(false); + return (diagnostics.ToImmutable(), upToDate); } - public Task> GetDiagnosticsForSpanAsync( + public async Task> GetDiagnosticsForSpanAsync( TextDocument document, TextSpan? range, Func? shouldIncludeDiagnostic, @@ -110,9 +109,10 @@ public Task> GetDiagnosticsForSpanAsync( priorityProvider ??= new DefaultCodeActionRequestPriorityProvider(); // always make sure that analyzer is called on background thread. - return Task.Run(() => analyzer.GetDiagnosticsForSpanAsync( + await TaskScheduler.Default; + return await analyzer.GetDiagnosticsForSpanAsync( document, range, shouldIncludeDiagnostic, includeSuppressedDiagnostics, includeCompilerDiagnostics, - priorityProvider, blockForData: true, addOperationScope, diagnosticKinds, isExplicit, cancellationToken), cancellationToken); + priorityProvider, blockForData: true, addOperationScope, diagnosticKinds, isExplicit, cancellationToken).ConfigureAwait(false); } public Task> GetCachedDiagnosticsAsync(Workspace workspace, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index f24820a50f801..6675a828608cc 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 @@ -24,51 +24,33 @@ public Task> GetDiagnosticsForIdsAsync(Solution s public Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId: null, diagnosticIds, shouldIncludeAnalyzer, getDocuments: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics).GetProjectDiagnosticsAsync(cancellationToken); - private abstract class DiagnosticGetter + private abstract class DiagnosticGetter( + DiagnosticIncrementalAnalyzer owner, + Solution solution, + ProjectId? projectId, + DocumentId? documentId, + Func>? getDocuments, + bool includeSuppressedDiagnostics, + bool includeLocalDocumentDiagnostics, + bool includeNonLocalDocumentDiagnostics) { - protected readonly DiagnosticIncrementalAnalyzer Owner; - - protected readonly Solution Solution; - protected readonly ProjectId? ProjectId; - protected readonly DocumentId? DocumentId; - protected readonly bool IncludeSuppressedDiagnostics; - protected readonly bool IncludeLocalDocumentDiagnostics; - protected readonly bool IncludeNonLocalDocumentDiagnostics; - - private readonly Func> _getDocuments; - - private ImmutableArray.Builder? _lazyDataBuilder; - - public DiagnosticGetter( - DiagnosticIncrementalAnalyzer owner, - Solution solution, - ProjectId? projectId, - DocumentId? documentId, - Func>? getDocuments, - bool includeSuppressedDiagnostics, - bool includeLocalDocumentDiagnostics, - bool includeNonLocalDocumentDiagnostics) - { - Owner = owner; - Solution = solution; + protected readonly DiagnosticIncrementalAnalyzer Owner = owner; - DocumentId = documentId; - _getDocuments = getDocuments ?? (static (project, documentId) => documentId != null ? [documentId] : project.DocumentIds); - ProjectId = projectId ?? documentId?.ProjectId; + protected readonly Solution Solution = solution; + protected readonly ProjectId? ProjectId = projectId ?? documentId?.ProjectId; + protected readonly DocumentId? DocumentId = documentId; + protected readonly bool IncludeSuppressedDiagnostics = includeSuppressedDiagnostics; + protected readonly bool IncludeLocalDocumentDiagnostics = includeLocalDocumentDiagnostics; + protected readonly bool IncludeNonLocalDocumentDiagnostics = includeNonLocalDocumentDiagnostics; - IncludeSuppressedDiagnostics = includeSuppressedDiagnostics; - IncludeLocalDocumentDiagnostics = includeLocalDocumentDiagnostics; - IncludeNonLocalDocumentDiagnostics = includeNonLocalDocumentDiagnostics; - } + private readonly Func> _getDocuments = getDocuments ?? (static (project, documentId) => documentId != null ? [documentId] : project.DocumentIds); protected StateManager StateManager => Owner._stateManager; protected virtual bool ShouldIncludeDiagnostic(DiagnosticData diagnostic) => true; - protected ImmutableArray GetDiagnosticData() - => (_lazyDataBuilder != null) ? _lazyDataBuilder.ToImmutableArray() : []; - - protected abstract Task AppendDiagnosticsAsync(Project project, IEnumerable documentIds, bool includeProjectNonLocalResult, CancellationToken cancellationToken); + protected abstract Task ProduceDiagnosticsAsync( + Project project, IReadOnlyList documentIds, bool includeProjectNonLocalResult, Action callback, CancellationToken cancellationToken); public async Task> GetDiagnosticsAsync(CancellationToken cancellationToken) { @@ -76,53 +58,40 @@ public async Task> GetDiagnosticsAsync(Cancellati { var project = Solution.GetProject(ProjectId); if (project == null) - { - return GetDiagnosticData(); - } - - var documentIds = _getDocuments(project, DocumentId); + return []; // return diagnostics specific to one project or document var includeProjectNonLocalResult = DocumentId == null; - await AppendDiagnosticsAsync(project, documentIds, includeProjectNonLocalResult, cancellationToken).ConfigureAwait(false); - return GetDiagnosticData(); + return await ProduceProjectDiagnosticsAsync( + [project], project => _getDocuments(project, DocumentId), includeProjectNonLocalResult, cancellationToken).ConfigureAwait(false); } - await AppendDiagnosticsAsync(Solution, cancellationToken).ConfigureAwait(false); - return GetDiagnosticData(); + return await ProduceSolutionDiagnosticsAsync(Solution, cancellationToken).ConfigureAwait(false); } - protected async Task AppendDiagnosticsAsync(Solution solution, CancellationToken cancellationToken) + protected Task> ProduceSolutionDiagnosticsAsync(Solution solution, CancellationToken cancellationToken) + => ProduceProjectDiagnosticsAsync(solution.Projects, static project => project.DocumentIds, includeProjectNonLocalResult: true, cancellationToken); + + protected async Task> ProduceProjectDiagnosticsAsync( + IEnumerable projects, Func> getDocumentIds, + bool includeProjectNonLocalResult, CancellationToken cancellationToken) { // PERF: run projects in parallel rather than running CompilationWithAnalyzer with concurrency == true. // We do this to not get into thread starvation causing hundreds of threads to be spawned. - var includeProjectNonLocalResult = true; - - var tasks = new Task[solution.ProjectIds.Count]; - var index = 0; - foreach (var project in solution.Projects) - { - var localProject = project; - tasks[index++] = Task.Run( - () => AppendDiagnosticsAsync( - localProject, localProject.DocumentIds, includeProjectNonLocalResult, cancellationToken), cancellationToken); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); + return await ProducerConsumer.RunParallelAsync( + source: projects, + produceItems: static (project, callback, args, cancellationToken) => args.@this.ProduceDiagnosticsAsync( + project, args.getDocumentIds(project), args.includeProjectNonLocalResult, callback, cancellationToken), + args: (@this: this, getDocumentIds, includeProjectNonLocalResult), + cancellationToken).ConfigureAwait(false); } - protected void AppendDiagnostics(ImmutableArray items) + protected void InvokeCallback(Action callback, ImmutableArray diagnostics) { - Debug.Assert(!items.IsDefault); - - if (_lazyDataBuilder == null) - { - Interlocked.CompareExchange(ref _lazyDataBuilder, ImmutableArray.CreateBuilder(), null); - } - - lock (_lazyDataBuilder) + foreach (var diagnostic in diagnostics) { - _lazyDataBuilder.AddRange(items.Where(ShouldIncludeSuppressedDiagnostic).Where(ShouldIncludeDiagnostic)); + if (ShouldIncludeSuppressedDiagnostic(diagnostic) && ShouldIncludeDiagnostic(diagnostic)) + callback(diagnostic); } } @@ -130,14 +99,19 @@ private bool ShouldIncludeSuppressedDiagnostic(DiagnosticData diagnostic) => IncludeSuppressedDiagnostics || !diagnostic.IsSuppressed; } - private sealed class IdeCachedDiagnosticGetter : DiagnosticGetter + private sealed class IdeCachedDiagnosticGetter( + DiagnosticIncrementalAnalyzer owner, + Solution solution, + ProjectId? projectId, + DocumentId? documentId, + bool includeSuppressedDiagnostics, + bool includeLocalDocumentDiagnostics, + bool includeNonLocalDocumentDiagnostics) : DiagnosticGetter( + owner, solution, projectId, documentId, getDocuments: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) { - public IdeCachedDiagnosticGetter(DiagnosticIncrementalAnalyzer owner, Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) - : base(owner, solution, projectId, documentId, getDocuments: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) - { - } - - protected override async Task AppendDiagnosticsAsync(Project project, IEnumerable documentIds, bool includeProjectNonLocalResult, CancellationToken cancellationToken) + protected override async Task ProduceDiagnosticsAsync( + Project project, IReadOnlyList documentIds, bool includeProjectNonLocalResult, + Action callback, CancellationToken cancellationToken) { foreach (var stateSet in StateManager.GetStateSets(project.Id)) { @@ -145,18 +119,18 @@ protected override async Task AppendDiagnosticsAsync(Project project, IEnumerabl { if (IncludeLocalDocumentDiagnostics) { - AppendDiagnostics(await GetDiagnosticsAsync(stateSet, project, documentId, AnalysisKind.Syntax, cancellationToken).ConfigureAwait(false)); - AppendDiagnostics(await GetDiagnosticsAsync(stateSet, project, documentId, AnalysisKind.Semantic, cancellationToken).ConfigureAwait(false)); + InvokeCallback(callback, await GetDiagnosticsAsync(stateSet, project, documentId, AnalysisKind.Syntax, cancellationToken).ConfigureAwait(false)); + InvokeCallback(callback, await GetDiagnosticsAsync(stateSet, project, documentId, AnalysisKind.Semantic, cancellationToken).ConfigureAwait(false)); } if (IncludeNonLocalDocumentDiagnostics) - AppendDiagnostics(await GetDiagnosticsAsync(stateSet, project, documentId, AnalysisKind.NonLocal, cancellationToken).ConfigureAwait(false)); + InvokeCallback(callback, await GetDiagnosticsAsync(stateSet, project, documentId, AnalysisKind.NonLocal, cancellationToken).ConfigureAwait(false)); } if (includeProjectNonLocalResult) { // include project diagnostics if there is no target document - AppendDiagnostics(await GetProjectStateDiagnosticsAsync(stateSet, project, documentId: null, AnalysisKind.NonLocal, cancellationToken).ConfigureAwait(false)); + InvokeCallback(callback, await GetProjectStateDiagnosticsAsync(stateSet, project, documentId: null, AnalysisKind.NonLocal, cancellationToken).ConfigureAwait(false)); } } } @@ -225,43 +199,43 @@ private static async Task> GetProjectStateDiagnos } } - private sealed class IdeLatestDiagnosticGetter : DiagnosticGetter + private sealed class IdeLatestDiagnosticGetter( + DiagnosticIncrementalAnalyzer owner, + Solution solution, + ProjectId? projectId, + DocumentId? documentId, + ImmutableHashSet? diagnosticIds, + Func? shouldIncludeAnalyzer, + Func>? getDocuments, + bool includeSuppressedDiagnostics, + bool includeLocalDocumentDiagnostics, + bool includeNonLocalDocumentDiagnostics) : DiagnosticGetter( + owner, solution, projectId, documentId, getDocuments, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) { - private readonly ImmutableHashSet? _diagnosticIds; - private readonly Func? _shouldIncludeAnalyzer; - - public IdeLatestDiagnosticGetter( - DiagnosticIncrementalAnalyzer owner, Solution solution, ProjectId? projectId, - DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, - Func>? getDocuments, - bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) - : base(owner, solution, projectId, documentId, getDocuments, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) - { - _diagnosticIds = diagnosticIds; - _shouldIncludeAnalyzer = shouldIncludeAnalyzer; - } + private readonly ImmutableHashSet? _diagnosticIds = diagnosticIds; + private readonly Func? _shouldIncludeAnalyzer = shouldIncludeAnalyzer; public async Task> GetProjectDiagnosticsAsync(CancellationToken cancellationToken) { if (ProjectId != null) { var project = Solution.GetProject(ProjectId); - if (project != null) - { - await AppendDiagnosticsAsync(project, documentIds: [], includeProjectNonLocalResult: true, cancellationToken).ConfigureAwait(false); - } + if (project is null) + return []; - return GetDiagnosticData(); + return await ProduceProjectDiagnosticsAsync( + [project], static _ => [], includeProjectNonLocalResult: true, cancellationToken).ConfigureAwait(false); } - await AppendDiagnosticsAsync(Solution, cancellationToken).ConfigureAwait(false); - return GetDiagnosticData(); + return await ProduceSolutionDiagnosticsAsync(Solution, cancellationToken).ConfigureAwait(false); } protected override bool ShouldIncludeDiagnostic(DiagnosticData diagnostic) => _diagnosticIds == null || _diagnosticIds.Contains(diagnostic.Id); - protected override async Task AppendDiagnosticsAsync(Project project, IEnumerable documentIds, bool includeProjectNonLocalResult, CancellationToken cancellationToken) + protected override async Task ProduceDiagnosticsAsync( + Project project, IReadOnlyList documentIds, bool includeProjectNonLocalResult, + Action callback, CancellationToken cancellationToken) { // get analyzers that are not suppressed. var stateSets = StateManager.GetOrCreateStateSets(project).Where(s => ShouldIncludeStateSet(project, s)).ToImmutableArrayOrEmpty(); @@ -281,18 +255,18 @@ protected override async Task AppendDiagnosticsAsync(Project project, IEnumerabl { if (IncludeLocalDocumentDiagnostics) { - AppendDiagnostics(analysisResult.GetDocumentDiagnostics(documentId, AnalysisKind.Syntax)); - AppendDiagnostics(analysisResult.GetDocumentDiagnostics(documentId, AnalysisKind.Semantic)); + InvokeCallback(callback, analysisResult.GetDocumentDiagnostics(documentId, AnalysisKind.Syntax)); + InvokeCallback(callback, analysisResult.GetDocumentDiagnostics(documentId, AnalysisKind.Semantic)); } if (IncludeNonLocalDocumentDiagnostics) - AppendDiagnostics(analysisResult.GetDocumentDiagnostics(documentId, AnalysisKind.NonLocal)); + InvokeCallback(callback, analysisResult.GetDocumentDiagnostics(documentId, AnalysisKind.NonLocal)); } if (includeProjectNonLocalResult) { // include project diagnostics if there is no target document - AppendDiagnostics(analysisResult.GetOtherDiagnostics()); + InvokeCallback(callback, analysisResult.GetOtherDiagnostics()); } } } diff --git a/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs index 4f6b49fea36b6..deb7877c1b234 100644 --- a/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs +++ b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs @@ -21,7 +21,7 @@ private sealed class OpenDocumentSource(Document document) : AbstractDocumentDia public override bool IsLiveSource() => true; - public override async Task> GetDiagnosticsAsync(IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { var designTimeDocument = Document; var designTimeSolution = designTimeDocument.Project.Solution; diff --git a/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs index d74ca12db6421..b84fee77d2030 100644 --- a/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs +++ b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs @@ -22,7 +22,7 @@ private sealed class ProjectSource(Project project, ImmutableArray true; - public override Task> GetDiagnosticsAsync(IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + public override Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) => Task.FromResult(diagnostics); } @@ -31,7 +31,7 @@ private sealed class ClosedDocumentSource(TextDocument document, ImmutableArray< public override bool IsLiveSource() => true; - public override Task> GetDiagnosticsAsync(IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + public override Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) => Task.FromResult(diagnostics); } diff --git a/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs index 8aacbe48a42fe..cc7fc24bc3875 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs @@ -2,7 +2,6 @@ // 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.Diagnostics; using Microsoft.CodeAnalysis.Options; diff --git a/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs b/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs index cf1b8f407a589..b37ddde7869e0 100644 --- a/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs +++ b/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; using static Microsoft.CodeAnalysis.CodeActions.CodeAction; using CodeFixGroupKey = System.Tuple; @@ -43,15 +44,16 @@ public static async ValueTask> GetFilt { var originalSolution = document.Project.Solution; - // Intentionally switch to a threadpool thread to compute fixes. We do not want to accidentally - // run any of this on the UI thread and potentially allow any code to take a dependency on that. - var fixes = await Task.Run(() => codeFixService.GetFixesAsync( + // Intentionally switch to a threadpool thread to compute fixes. We do not want to accidentally run any of + // this on the UI thread and potentially allow any code to take a dependency on that. + await TaskScheduler.Default; + var fixes = await codeFixService.GetFixesAsync( document, selection, priorityProvider, fallbackOptions, addOperationScope, - cancellationToken), cancellationToken).ConfigureAwait(false); + cancellationToken).ConfigureAwait(false); var filteredFixes = fixes.WhereAsArray(c => c.Fixes.Length > 0); var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); @@ -443,14 +445,12 @@ public static async Task> GetFilterAnd bool filterOutsideSelection, CancellationToken cancellationToken) { - // It may seem strange that we kick off a task, but then immediately 'Wait' on - // it. However, it's deliberate. We want to make sure that the code runs on - // the background so that no one takes an accidentally dependency on running on - // the UI thread. - var refactorings = await Task.Run( - () => codeRefactoringService.GetRefactoringsAsync( - document, selection, priority, options, addOperationScope, - cancellationToken), cancellationToken).ConfigureAwait(false); + // Intentionally switch to a threadpool thread to compute fixes. We do not want to accidentally run any of + // this on the UI thread and potentially allow any code to take a dependency on that. + await TaskScheduler.Default; + var refactorings = await codeRefactoringService.GetRefactoringsAsync( + document, selection, priority, options, addOperationScope, + cancellationToken).ConfigureAwait(false); var filteredRefactorings = FilterOnAnyThread(refactorings, selection, filterOutsideSelection); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs index dc20144b584f1..106d2dc6bfc78 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs @@ -2,17 +2,21 @@ // 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.Immutable; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Microsoft.CommonLanguageServerProtocol.Framework; using Roslyn.LanguageServer.Protocol; -using LSP = Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractDocumentPullDiagnosticHandler( IDiagnosticAnalyzerService diagnosticAnalyzerService, IDiagnosticsRefresher diagnosticRefresher, + IDiagnosticSourceManager diagnosticSourceManager, IGlobalOptionService globalOptions) : AbstractPullDiagnosticHandler( diagnosticAnalyzerService, @@ -20,5 +24,31 @@ internal abstract class AbstractDocumentPullDiagnosticHandler where TDiagnosticsParams : IPartialResultParams { - public abstract LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(TDiagnosticsParams diagnosticsParams); + protected readonly IDiagnosticSourceManager DiagnosticSourceManager = diagnosticSourceManager; + + public abstract TextDocumentIdentifier? GetTextDocumentIdentifier(TDiagnosticsParams diagnosticsParams); + + protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) + { + // Note: context.Document may be null in the case where the client is asking about a document that we have + // since removed from the workspace. In this case, we don't really have anything to process. + // GetPreviousResults will be used to properly realize this and notify the client that the doc is gone. + // + // Only consider open documents here (and only closed ones in the WorkspacePullDiagnosticHandler). Each + // handler treats those as separate worlds that they are responsible for. + var textDocument = context.TextDocument; + if (textDocument is null) + { + context.TraceInformation("Ignoring diagnostics request because no text document was provided"); + return new([]); + } + + if (!context.IsTracking(textDocument.GetURI())) + { + context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); + return new([]); + } + + return DiagnosticSourceManager.CreateDocumentDiagnosticSourcesAsync(context, requestDiagnosticCategory, cancellationToken); + } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index 755e5d771299d..456a14bd40089 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -2,7 +2,6 @@ // 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.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -38,8 +37,6 @@ internal abstract partial class AbstractPullDiagnosticHandler protected const int WorkspaceDiagnosticIdentifier = 1; protected const int DocumentDiagnosticIdentifier = 2; - // internal for testing purposes - internal const int DocumentNonLocalDiagnosticIdentifier = 3; private readonly IDiagnosticsRefresher _diagnosticRefresher; protected readonly IGlobalOptionService GlobalOptions; @@ -68,8 +65,6 @@ protected AbstractPullDiagnosticHandler( GlobalOptions = globalOptions; } - protected virtual string? GetDiagnosticSourceIdentifier(TDiagnosticsParams diagnosticsParams) => null; - /// /// Retrieve the previous results we reported. Used so we can avoid resending data for unchanged files. Also /// used so we can report which documents were removed and can have all their diagnostics cleared. @@ -80,7 +75,7 @@ protected AbstractPullDiagnosticHandler( /// Returns all the documents that should be processed in the desired order to process them in. /// protected abstract ValueTask> GetOrderedDiagnosticSourcesAsync( - TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken); + TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken); /// /// Creates the appropriate LSP type to report a new set of diagnostics and resultId. @@ -106,7 +101,7 @@ protected abstract ValueTask> GetOrderedDiagno /// protected abstract DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource); - protected abstract string? GetDiagnosticCategory(TDiagnosticsParams diagnosticsParams); + protected abstract string? GetRequestDiagnosticCategory(TDiagnosticsParams diagnosticsParams); /// /// Used by public workspace pull diagnostics to allow it to keep the connection open until @@ -134,9 +129,8 @@ protected virtual Task WaitForChangesAsync(RequestContext context, CancellationT Contract.ThrowIfNull(context.Solution); var clientCapabilities = context.GetRequiredClientCapabilities(); - var category = GetDiagnosticCategory(diagnosticsParams) ?? ""; - var sourceIdentifier = GetDiagnosticSourceIdentifier(diagnosticsParams) ?? ""; - var handlerName = $"{this.GetType().Name}(category: {category}, source: {sourceIdentifier})"; + var category = GetRequestDiagnosticCategory(diagnosticsParams); + var handlerName = $"{this.GetType().Name}(category: {category})"; context.TraceInformation($"{handlerName} started getting diagnostics"); var versionedCache = _categoryToVersionedCache.GetOrAdd(handlerName, static handlerName => new(handlerName)); @@ -160,7 +154,7 @@ protected virtual Task WaitForChangesAsync(RequestContext context, CancellationT // Next process each file in priority order. Determine if diagnostics are changed or unchanged since the // last time we notified the client. Report back either to the client so they can update accordingly. var orderedSources = await GetOrderedDiagnosticSourcesAsync( - diagnosticsParams, context, cancellationToken).ConfigureAwait(false); + diagnosticsParams, category, context, cancellationToken).ConfigureAwait(false); context.TraceInformation($"Processing {orderedSources.Length} documents"); @@ -295,7 +289,7 @@ private async Task ComputeAndReportCurrentDiagnosticsAsync( CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var result); - var diagnostics = await diagnosticSource.GetDiagnosticsAsync(DiagnosticAnalyzerService, context, cancellationToken).ConfigureAwait(false); + var diagnostics = await diagnosticSource.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); // If we can't get a text document identifier we can't report diagnostics for this source. // This can happen for 'fake' projects (e.g. used for TS script blocks). @@ -417,8 +411,11 @@ LSP.VSDiagnostic CreateLspDiagnostic( if (capabilities.HasVisualStudioLspCapability()) { + // The client expects us to return null if there is no message (not an empty string). + var expandedMessage = string.IsNullOrEmpty(diagnosticData.Description) ? null : diagnosticData.Description; + diagnostic.DiagnosticType = diagnosticData.Category; - diagnostic.ExpandedMessage = diagnosticData.Description; + diagnostic.ExpandedMessage = expandedMessage; diagnostic.Projects = [ new VSDiagnosticProjectInformation diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index f3911586aad23..7e3d6e4619791 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -3,21 +3,13 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; -using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Collections; -using Microsoft.CodeAnalysis.SolutionCrawler; -using Microsoft.CodeAnalysis.TaskList; using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -27,6 +19,7 @@ internal abstract class AbstractWorkspacePullDiagnosticsHandler /// Flag that represents whether the LSP view of the world has changed. @@ -40,9 +33,11 @@ protected AbstractWorkspacePullDiagnosticsHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService diagnosticAnalyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) : base(diagnosticAnalyzerService, diagnosticRefresher, globalOptions) { + DiagnosticSourceManager = diagnosticSourceManager; _workspaceManager = workspaceManager; _workspaceRegistrationService = registrationService; @@ -56,32 +51,17 @@ public void Dispose() _workspaceRegistrationService.LspSolutionChanged -= OnLspSolutionChanged; } - protected override async ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) + protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) { - // If we're being called from razor, we do not support WorkspaceDiagnostics at all. For razor, workspace - // diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for - // document-diagnostics instead. if (context.ServerKind == WellKnownLspServerKinds.RazorLspServer) - return []; - - Contract.ThrowIfNull(context.Solution); - - var category = GetDiagnosticCategory(diagnosticsParams); - - // TODO: Implement as extensibility point. https://github.com/dotnet/roslyn/issues/72896 - - if (category == PullDiagnosticCategories.Task) - return GetTaskListDiagnosticSources(context, GlobalOptions); - - if (category == PullDiagnosticCategories.EditAndContinue) - return await EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution, document => context.IsTracking(document.GetURI()), cancellationToken).ConfigureAwait(false); - - // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). - if (category == null || category == PullDiagnosticCategories.WorkspaceDocumentsAndProject) - return await GetDiagnosticSourcesAsync(context, GlobalOptions, cancellationToken).ConfigureAwait(false); + { + // If we're being called from razor, we do not support WorkspaceDiagnostics at all. For razor, workspace + // diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for + // document-diagnostics instead. + return new([]); + } - // if it's a category we don't recognize, return nothing. - return []; + return DiagnosticSourceManager.CreateWorkspaceDiagnosticSourcesAsync(context, requestDiagnosticCategory, cancellationToken); } private void OnLspSolutionChanged(object? sender, WorkspaceChangeEventArgs e) @@ -115,159 +95,6 @@ protected override async Task WaitForChangesAsync(RequestContext context, Cancel return; } - private static ImmutableArray GetTaskListDiagnosticSources( - RequestContext context, IGlobalOptionService globalOptions) - { - Contract.ThrowIfNull(context.Solution); - - // Only compute task list items for closed files if the option is on for it. - var taskListEnabled = globalOptions.GetTaskListOptions().ComputeForClosedFiles; - if (!taskListEnabled) - return []; - - using var _ = ArrayBuilder.GetInstance(out var result); - - foreach (var project in GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) - { - foreach (var document in project.Documents) - { - if (!ShouldSkipDocument(context, document)) - result.Add(new TaskListDiagnosticSource(document, globalOptions)); - } - } - - return result.ToImmutableAndClear(); - } - - /// - /// There are three potential sources for reporting workspace diagnostics: - /// - /// 1. Full solution analysis: If the user has enabled Full solution analysis, we always run analysis on the latest - /// project snapshot and return up-to-date diagnostics computed from this analysis. - /// - /// 2. Code analysis service: Otherwise, if full solution analysis is disabled, and if we have diagnostics from an explicitly - /// triggered code analysis execution on either the current or a prior project snapshot, we return - /// diagnostics from this execution. These diagnostics may be stale with respect to the current - /// project snapshot, but they match user's intent of not enabling continuous background analysis - /// for always having up-to-date workspace diagnostics, but instead computing them explicitly on - /// specific project snapshots by manually running the "Run Code Analysis" command on a project or solution. - /// - /// 3. EnC analysis: Emit and debugger diagnostics associated with a closed document or not associated with any document. - /// - /// If full solution analysis is disabled AND code analysis was never executed for the given project, - /// we have no workspace diagnostics to report and bail out. - /// - public static async ValueTask> GetDiagnosticSourcesAsync( - RequestContext context, IGlobalOptionService globalOptions, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(context.Solution); - - using var _ = ArrayBuilder.GetInstance(out var result); - - var solution = context.Solution; - var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; - var codeAnalysisService = solution.Services.GetRequiredService(); - - foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) - await AddDocumentsAndProjectAsync(project, cancellationToken).ConfigureAwait(false); - - return result.ToImmutableAndClear(); - - async Task AddDocumentsAndProjectAsync(Project project, CancellationToken cancellationToken) - { - var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); - if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) - return; - - Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled - ? ShouldIncludeAnalyzer : null; - - AddDocumentSources(project.Documents); - AddDocumentSources(project.AdditionalDocuments); - - // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. - if (enableDiagnosticsInSourceGeneratedFiles) - { - var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); - AddDocumentSources(sourceGeneratedDocuments); - } - - // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. - AddProjectSource(); - - return; - - void AddDocumentSources(IEnumerable documents) - { - foreach (var document in documents) - { - if (!ShouldSkipDocument(context, document)) - { - // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. - var documentDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, shouldIncludeAnalyzer) - : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); - result.Add(documentDiagnosticSource); - } - } - } - - void AddProjectSource() - { - var projectDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, shouldIncludeAnalyzer) - : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); - result.Add(projectDiagnosticSource); - } - - bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) - => analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; - } - } - - private static IEnumerable GetProjectsInPriorityOrder( - Solution solution, ImmutableArray supportedLanguages) - { - return GetProjectsInPriorityOrderWorker(solution) - .WhereNotNull() - .Distinct() - .Where(p => supportedLanguages.Contains(p.Language)); - - static IEnumerable GetProjectsInPriorityOrderWorker(Solution solution) - { - var documentTrackingService = solution.Services.GetRequiredService(); - - // Collect all the documents from the solution in the order we'd like to get diagnostics for. This will - // prioritize the files from currently active projects, but then also include all other docs in all projects - // (depending on current FSA settings). - - var activeDocument = documentTrackingService.GetActiveDocument(solution); - var visibleDocuments = documentTrackingService.GetVisibleDocuments(solution); - - yield return activeDocument?.Project; - foreach (var doc in visibleDocuments) - yield return doc.Project; - - foreach (var project in solution.Projects) - yield return project; - } - } - - private static bool ShouldSkipDocument(RequestContext context, TextDocument document) - { - // Only consider closed documents here (and only open ones in the DocumentPullDiagnosticHandler). - // Each handler treats those as separate worlds that they are responsible for. - if (context.IsTracking(document.GetURI())) - { - context.TraceInformation($"Skipping tracked document: {document.GetURI()}"); - return true; - } - - // Do not attempt to get workspace diagnostics for Razor files, Razor will directly ask us for document diagnostics - // for any razor file they are interested in. - return document.IsRazorDocument(); - } - internal abstract TestAccessor GetTestAccessor(); internal readonly struct TestAccessor(AbstractWorkspacePullDiagnosticsHandler handler) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs new file mode 100644 index 0000000000000..2af9fa8710706 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs @@ -0,0 +1,164 @@ +// 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.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[Export(typeof(IDiagnosticSourceManager)), Shared] +internal sealed class DiagnosticSourceManager : IDiagnosticSourceManager +{ + /// + /// Document level providers ordered by name. + /// + private readonly ImmutableDictionary _nameToDocumentProviderMap; + + /// + /// Workspace level providers ordered by name. + /// + private readonly ImmutableDictionary _nameToWorkspaceProviderMap; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DiagnosticSourceManager([ImportMany] IEnumerable sourceProviders) + { + _nameToDocumentProviderMap = sourceProviders + .Where(p => p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); + + _nameToWorkspaceProviderMap = sourceProviders + .Where(p => !p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); + } + + public ImmutableArray GetDocumentSourceProviderNames(ClientCapabilities clientCapabilities) + => _nameToDocumentProviderMap.Where(kvp => kvp.Value.IsEnabled(clientCapabilities)).SelectAsArray(kvp => kvp.Key); + + public ImmutableArray GetWorkspaceSourceProviderNames(ClientCapabilities clientCapabilities) + => _nameToWorkspaceProviderMap.Where(kvp => kvp.Value.IsEnabled(clientCapabilities)).SelectAsArray(kvp => kvp.Key); + + public ValueTask> CreateDocumentDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken) + => CreateDiagnosticSourcesAsync(context, providerName, _nameToDocumentProviderMap, isDocument: true, cancellationToken); + + public ValueTask> CreateWorkspaceDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken) + => CreateDiagnosticSourcesAsync(context, providerName, _nameToWorkspaceProviderMap, isDocument: false, cancellationToken); + + private static async ValueTask> CreateDiagnosticSourcesAsync( + RequestContext context, + string? providerName, + ImmutableDictionary nameToProviderMap, + bool isDocument, + CancellationToken cancellationToken) + { + if (providerName != null) + { + // VS does not distinguish between document and workspace sources. Thus it can request + // document diagnostics with workspace source name. We need to handle this case. + if (nameToProviderMap.TryGetValue(providerName, out var provider)) + { + Contract.ThrowIfFalse(provider.IsEnabled(context.GetRequiredClientCapabilities())); + return await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); + } + + return []; + } + else + { + // VS Code (and legacy VS ?) pass null sourceName when requesting all sources. + using var _ = ArrayBuilder.GetInstance(out var sourcesBuilder); + foreach (var (name, provider) in nameToProviderMap) + { + if (!provider.IsEnabled(context.GetRequiredClientCapabilities())) + { + continue; + } + + var namedSources = await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); + sourcesBuilder.AddRange(namedSources); + } + + var sources = sourcesBuilder.ToImmutableAndClear(); + return AggregateSourcesIfNeeded(sources, isDocument); + } + } + + public static ImmutableArray AggregateSourcesIfNeeded(ImmutableArray sources, bool isDocument) + { + if (sources.Length <= 1) + { + return sources; + } + + if (isDocument) + { + // Group all document sources into a single source. + Debug.Assert(sources.All(s => s.IsLiveSource()), "All document sources should be live"); + sources = [new AggregatedDocumentDiagnosticSource(sources)]; + } + else + { + // We ASSUME that all sources with the same ProjectOrDocumentID and IsLiveSource + // will have same value for GetDocumentIdentifier and GetProject(). Thus can be + // aggregated in a single source which will return same values. See + // AggregatedDocumentDiagnosticSource implementation for more details. + sources = sources.GroupBy(s => (s.GetId(), s.IsLiveSource()), s => s) + .SelectMany(g => AggregatedDocumentDiagnosticSource.AggregateIfNeeded(g)) + .ToImmutableArray(); + } + + return sources; + } + + /// + /// Aggregates multiple s into a single source. + /// + /// Sources to aggregate + /// + /// Aggregation is usually needed for clients like VS Code which supports single source per request. + /// + private sealed class AggregatedDocumentDiagnosticSource(ImmutableArray sources) : IDiagnosticSource + { + public static ImmutableArray AggregateIfNeeded(IEnumerable sources) + { + var result = sources.ToImmutableArray(); + if (result.Length > 1) + { + result = [new AggregatedDocumentDiagnosticSource(result)]; + } + + return result; + } + + public bool IsLiveSource() => true; + public Project GetProject() => sources[0].GetProject(); + public ProjectOrDocumentId GetId() => sources[0].GetId(); + public TextDocumentIdentifier? GetDocumentIdentifier() => sources[0].GetDocumentIdentifier(); + public string ToDisplayString() => $"{this.GetType().Name}: count={sources.Length}"; + + public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var diagnostics); + foreach (var source in sources) + { + var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); + diagnostics.AddRange(namedDiagnostics); + } + + return diagnostics.ToImmutableAndClear(); + } + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..85fca7423bdd3 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs @@ -0,0 +1,70 @@ +// 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 System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +internal abstract class AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider( + IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind kind, string sourceName) + : IDiagnosticSourceProvider +{ + public bool IsDocument => true; + public string Name => sourceName; + + public bool IsEnabled(ClientCapabilities clientCapabilities) => true; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (context.GetTrackedDocument() is { } document) + { + return new([new DocumentDiagnosticSource(diagnosticAnalyzerService, kind, document)]); + } + + return new([]); + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + private sealed class DocumentCompilerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + DiagnosticKind.CompilerSyntax, PullDiagnosticCategories.DocumentCompilerSyntax) + { + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + private sealed class DocumentCompilerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider( + diagnosticAnalyzerService, DiagnosticKind.CompilerSemantic, PullDiagnosticCategories.DocumentCompilerSemantic) + { + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + private sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + DiagnosticKind.AnalyzerSyntax, PullDiagnosticCategories.DocumentAnalyzerSyntax) + { + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + private sealed class DocumentAnalyzerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + DiagnosticKind.AnalyzerSemantic, PullDiagnosticCategories.DocumentAnalyzerSemantic) + { + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs new file mode 100644 index 0000000000000..25fc19acaca03 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; + +/// +/// Provides centralized/singleton management of MEF based s. +/// Consumers - like diagnostic handlers - use it to get diagnostics from one or more providers. +/// +internal interface IDiagnosticSourceManager +{ + /// + /// Returns the names of document level s. + /// + ImmutableArray GetDocumentSourceProviderNames(ClientCapabilities clientCapabilities); + + /// + /// Returns the names of workspace level s. + /// + ImmutableArray GetWorkspaceSourceProviderNames(ClientCapabilities clientCapabilities); + + /// + /// Creates document diagnostic sources for the given . + /// + /// + /// Optional provider name. If then diagnostics from all providers are used. + /// A cancellation token that can be used to cancel the request processing. + ValueTask> CreateDocumentDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken); + + /// + /// Creates workspace diagnostic sources for the given . + /// + /// + /// Optional provider name. If not specified then diagnostics from all providers are used. + /// A cancellation token that can be used to cancel the request processing. + ValueTask> CreateWorkspaceDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken); +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..773f3a309c94e --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs @@ -0,0 +1,36 @@ +// 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.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +/// +/// Provides diagnostic sources. +/// +internal interface IDiagnosticSourceProvider +{ + /// + /// if this provider is for documents, if it is for workspace. + /// + bool IsDocument { get; } + + /// + /// Provider's name. Each should have a unique name within scope. + /// + string Name { get; } + + bool IsEnabled(ClientCapabilities clientCapabilities); + + /// + /// Creates the diagnostic sources. + /// + /// + /// A cancellation token that can be used to cancel the request processing. + ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); +} + diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDiagnosticSourceHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDiagnosticSourceHelpers.cs new file mode 100644 index 0000000000000..8956840dbbfa2 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDiagnosticSourceHelpers.cs @@ -0,0 +1,56 @@ +// 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.Linq; +using Microsoft.CodeAnalysis.Host; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +internal static class WorkspaceDiagnosticSourceHelpers +{ + public static IEnumerable GetProjectsInPriorityOrder(Solution solution, ImmutableArray supportedLanguages) + { + return GetProjectsInPriorityOrderWorker(solution) + .WhereNotNull() + .Distinct() + .Where(p => supportedLanguages.Contains(p.Language)); + + static IEnumerable GetProjectsInPriorityOrderWorker(Solution solution) + { + var documentTrackingService = solution.Services.GetRequiredService(); + + // Collect all the documents from the solution in the order we'd like to get diagnostics for. This will + // prioritize the files from currently active projects, but then also include all other docs in all projects + // (depending on current FSA settings). + + var activeDocument = documentTrackingService.GetActiveDocument(solution); + var visibleDocuments = documentTrackingService.GetVisibleDocuments(solution); + + yield return activeDocument?.Project; + foreach (var doc in visibleDocuments) + yield return doc.Project; + + foreach (var project in solution.Projects) + yield return project; + } + } + + public static bool ShouldSkipDocument(RequestContext context, TextDocument document) + { + // Only consider closed documents here (and only open ones in the DocumentPullDiagnosticHandler). + // Each handler treats those as separate worlds that they are responsible for. + if (context.IsTracking(document.GetURI())) + { + context.TraceInformation($"Skipping tracked document: {document.GetURI()}"); + return true; + } + + // Do not attempt to get workspace diagnostics for Razor files, Razor will directly ask us for document diagnostics + // for any razor file they are interested in. + return document.IsRazorDocument(); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..58cca636c91f5 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs @@ -0,0 +1,118 @@ +// 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 System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.SolutionCrawler; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[Export(typeof(IDiagnosticSourceProvider)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class WorkspaceDocumentsAndProjectDiagnosticSourceProvider( + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, + [Import] IGlobalOptionService globalOptions) + : IDiagnosticSourceProvider +{ + public bool IsDocument => false; + public string Name => PullDiagnosticCategories.WorkspaceDocumentsAndProject; + + public bool IsEnabled(ClientCapabilities clientCapabilities) => true; + + /// + /// There are three potential sources for reporting workspace diagnostics: + /// + /// 1. Full solution analysis: If the user has enabled Full solution analysis, we always run analysis on the latest + /// project snapshot and return up-to-date diagnostics computed from this analysis. + /// + /// 2. Code analysis service: Otherwise, if full solution analysis is disabled, and if we have diagnostics from an explicitly + /// triggered code analysis execution on either the current or a prior project snapshot, we return + /// diagnostics from this execution. These diagnostics may be stale with respect to the current + /// project snapshot, but they match user's intent of not enabling continuous background analysis + /// for always having up-to-date workspace diagnostics, but instead computing them explicitly on + /// specific project snapshots by manually running the "Run Code Analysis" command on a project or solution. + /// + /// 3. EnC analysis: Emit and debugger diagnostics associated with a closed document or not associated with any document. + /// + /// If full solution analysis is disabled AND code analysis was never executed for the given project, + /// we have no workspace diagnostics to report and bail out. + /// + public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(context.Solution); + + using var _ = ArrayBuilder.GetInstance(out var result); + + var solution = context.Solution; + var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; + var codeAnalysisService = solution.Services.GetRequiredService(); + + foreach (var project in WorkspaceDiagnosticSourceHelpers.GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) + await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + + return result.ToImmutableAndClear(); + + async Task AddDocumentsAndProjectAsync(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + { + var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); + if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) + return; + + Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled + ? ShouldIncludeAnalyzer : null; + + AddDocumentSources(project.Documents); + AddDocumentSources(project.AdditionalDocuments); + + // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. + if (enableDiagnosticsInSourceGeneratedFiles) + { + var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); + AddDocumentSources(sourceGeneratedDocuments); + } + + // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. + AddProjectSource(); + + return; + + void AddDocumentSources(IEnumerable documents) + { + foreach (var document in documents) + { + if (!WorkspaceDiagnosticSourceHelpers.ShouldSkipDocument(context, document)) + { + // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. + var documentDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); + result.Add(documentDiagnosticSource); + } + } + } + + void AddProjectSource() + { + var projectDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); + result.Add(projectDiagnosticSource); + } + + bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) + => analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; + } + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs index 0de186f4e9ac1..f88810864a7bb 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs @@ -18,7 +18,7 @@ internal abstract class AbstractDocumentDiagnosticSource(TDocument do public abstract bool IsLiveSource(); public abstract Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken); + RequestContext context, CancellationToken cancellationToken); public ProjectOrDocumentId GetId() => new(Document.Id); public Project GetProject() => Document.Project; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs index d34ef1d906826..ac49b0a9da7c6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs @@ -16,14 +16,14 @@ internal abstract class AbstractProjectDiagnosticSource(Project project) { protected Project Project => project; - public static AbstractProjectDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(Project project, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(project, shouldIncludeAnalyzer); + public static AbstractProjectDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(project, diagnosticAnalyzerService, shouldIncludeAnalyzer); public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(project, codeAnalysisService); public abstract bool IsLiveSource(); - public abstract Task> GetDiagnosticsAsync(IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken); + public abstract Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken); public ProjectOrDocumentId GetId() => new(Project.Id); public Project GetProject() => Project; @@ -33,7 +33,8 @@ public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(P : null; public string ToDisplayString() => Project.Name; - private sealed class FullSolutionAnalysisDiagnosticSource(Project project, Func? shouldIncludeAnalyzer) : AbstractProjectDiagnosticSource(project) + private sealed class FullSolutionAnalysisDiagnosticSource(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + : AbstractProjectDiagnosticSource(project) { /// /// This is a normal project source that represents live/fresh diagnostics that should supersede everything else. @@ -42,7 +43,6 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { @@ -55,7 +55,8 @@ public override async Task> GetDiagnosticsAsync( } } - private sealed class CodeAnalysisDiagnosticSource(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) : AbstractProjectDiagnosticSource(project) + private sealed class CodeAnalysisDiagnosticSource(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) + : AbstractProjectDiagnosticSource(project) { /// /// This source provides the results of the *last* explicitly kicked off "run code analysis" command from the @@ -66,7 +67,6 @@ public override bool IsLiveSource() => false; public override Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index cc301aa60d8b3..6777d956c2c88 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -17,13 +17,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractWorkspaceDocumentDiagnosticSource(TextDocument document) : AbstractDocumentDiagnosticSource(document) { - public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(document, shouldIncludeAnalyzer); + public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(document, diagnosticAnalyzerService, shouldIncludeAnalyzer); public static AbstractWorkspaceDocumentDiagnosticSource CreateForCodeAnalysisDiagnostics(TextDocument document, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(document, codeAnalysisService); - private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, Func? shouldIncludeAnalyzer) + private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource(document) { /// @@ -31,7 +31,7 @@ private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, /// once we compute the diagnostics once for a particular project, we don't need to recompute them again as we /// walk every document within it. /// - private static readonly ConditionalWeakTable>> s_projectToDiagnostics = new(); + private static readonly ConditionalWeakTable>> s_projectToDiagnostics = new(); /// /// This is a normal document source that represents live/fresh diagnostics that should supersede everything else. @@ -40,7 +40,6 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { @@ -67,20 +66,24 @@ private async ValueTask> GetProjectDiagnosticsAsy } var result = await lazyDiagnostics.GetValueAsync(cancellationToken).ConfigureAwait(false); - return (ImmutableArray)result; + return result[Document.Id].ToImmutableArray(); - AsyncLazy> GetLazyDiagnostics() + AsyncLazy> GetLazyDiagnostics() { return s_projectToDiagnostics.GetValue( Document.Project, - _ => AsyncLazy.Create>( - async cancellationToken => await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( - Document.Project.Solution, Document.Project.Id, documentId: null, - diagnosticIds: null, shouldIncludeAnalyzer, - // Ensure we compute and return diagnostics for both the normal docs and the additional docs in this project. - static (project, _) => [.. project.DocumentIds.Concat(project.AdditionalDocumentIds)], - includeSuppressedDiagnostics: false, - includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false))); + _ => AsyncLazy.Create( + async cancellationToken => + { + var allDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + Document.Project.Solution, Document.Project.Id, documentId: null, + diagnosticIds: null, shouldIncludeAnalyzer, + // Ensure we compute and return diagnostics for both the normal docs and the additional docs in this project. + static (project, _) => [.. project.DocumentIds.Concat(project.AdditionalDocumentIds)], + includeSuppressedDiagnostics: false, + includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); + return allDiagnostics.Where(d => d.DocumentId != null).ToLookup(d => d.DocumentId!); + })); } } } @@ -97,7 +100,6 @@ public override bool IsLiveSource() => false; public override Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 54f5c6ce5deb2..e9a5ad49650bf 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -7,11 +7,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Copilot; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal sealed class DocumentDiagnosticSource(DiagnosticKind diagnosticKind, TextDocument document) +internal sealed class DocumentDiagnosticSource(IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind diagnosticKind, TextDocument document) : AbstractDocumentDiagnosticSource(document) { public DiagnosticKind DiagnosticKind { get; } = diagnosticKind; @@ -23,14 +22,14 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + RequestContext context, CancellationToken cancellationToken) { // We call GetDiagnosticsForSpanAsync here instead of GetDiagnosticsForIdsAsync as it has faster perf // characteristics. GetDiagnosticsForIdsAsync runs analyzers against the entire compilation whereas // GetDiagnosticsForSpanAsync will only run analyzers against the request document. // Also ensure we pass in "includeSuppressedDiagnostics = true" for unnecessary suppressions to be reported. var allSpanDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( - Document, range: null, diagnosticKind: this.DiagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); + Document, range: null, diagnosticKind: this.DiagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); // Add cached Copilot diagnostics when computing analyzer semantic diagnostics. // TODO: move to a separate diagnostic source. https://github.com/dotnet/roslyn/issues/72896 diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs index 34d110e4adb4c..e8cb70bc1de4c 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs @@ -2,7 +2,6 @@ // 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.Threading; using System.Threading.Tasks; @@ -33,7 +32,6 @@ internal interface IDiagnosticSource bool IsLiveSource(); Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs index 8ffd2d9b75fb9..d370f15240a60 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal sealed class NonLocalDocumentDiagnosticSource(TextDocument document, Func? shouldIncludeAnalyzer) +internal sealed class NonLocalDocumentDiagnosticSource(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) : AbstractDocumentDiagnosticSource(document) { private readonly Func? _shouldIncludeAnalyzer = shouldIncludeAnalyzer; @@ -19,7 +19,6 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs index df8a3491ccdcc..fe6ebc182c6f9 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs @@ -33,7 +33,7 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + RequestContext context, CancellationToken cancellationToken) { var service = this.Document.GetLanguageService(); if (service == null) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index eace41f6259c5..16ee2f87d2bf2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -3,15 +3,10 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -21,13 +16,14 @@ internal partial class DocumentPullDiagnosticHandler { public DocumentPullDiagnosticHandler( IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticRefresher, globalOptions) + : base(analyzerService, diagnosticRefresher, diagnosticSourceManager, globalOptions) { } - protected override string? GetDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) + protected override string? GetRequestDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) => diagnosticsParams.QueryingDiagnosticKind?.Value; public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) @@ -72,59 +68,7 @@ protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bo => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) - => progress.GetFlattenedValues(); - - protected override ValueTask> GetOrderedDiagnosticSourcesAsync( - VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) - => new(GetDiagnosticSource(diagnosticsParams, context) is { } diagnosticSource ? [diagnosticSource] : []); - - private IDiagnosticSource? GetDiagnosticSource(VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context) - { - var category = diagnosticsParams.QueryingDiagnosticKind?.Value; - - // TODO: Implement as extensibility point. https://github.com/dotnet/roslyn/issues/72896 - - if (category == PullDiagnosticCategories.Task) - return context.GetTrackedDocument() is { } document ? new TaskListDiagnosticSource(document, GlobalOptions) : null; - - if (category == PullDiagnosticCategories.EditAndContinue) - return GetEditAndContinueDiagnosticSource(context); - - var diagnosticKind = category switch - { - PullDiagnosticCategories.DocumentCompilerSyntax => DiagnosticKind.CompilerSyntax, - PullDiagnosticCategories.DocumentCompilerSemantic => DiagnosticKind.CompilerSemantic, - PullDiagnosticCategories.DocumentAnalyzerSyntax => DiagnosticKind.AnalyzerSyntax, - PullDiagnosticCategories.DocumentAnalyzerSemantic => DiagnosticKind.AnalyzerSemantic, - // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). - null => DiagnosticKind.All, - // if it's a category we don't recognize, return nothing. - _ => (DiagnosticKind?)null, - }; - - if (diagnosticKind is null) - return null; - - return GetDiagnosticSource(diagnosticKind.Value, context); - } - - internal static IDiagnosticSource? GetEditAndContinueDiagnosticSource(RequestContext context) - => context.GetTrackedDocument() is { } document ? EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document) : null; - - internal static IDiagnosticSource? GetDiagnosticSource(DiagnosticKind diagnosticKind, RequestContext context) - => context.GetTrackedDocument() is { } textDocument ? new DocumentDiagnosticSource(diagnosticKind, textDocument) : null; - - internal static IDiagnosticSource? GetNonLocalDiagnosticSource(RequestContext context, IGlobalOptionService globalOptions) { - var textDocument = context.GetTrackedDocument(); - if (textDocument == null) - return null; - - // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. - if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) - return null; - - // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. - return new NonLocalDocumentDiagnosticSource(textDocument, shouldIncludeAnalyzer: static analyzer => !analyzer.IsCompilerAnalyzer()); + return progress.GetFlattenedValues(); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs index d731fe70276cd..07a6cfcaabc17 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs @@ -6,6 +6,7 @@ using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -14,6 +15,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal class DocumentPullDiagnosticHandlerFactory : ILspServiceFactory { private readonly IDiagnosticAnalyzerService _analyzerService; + private readonly IDiagnosticSourceManager _diagnosticSourceManager; private readonly IDiagnosticsRefresher _diagnosticsRefresher; private readonly IGlobalOptionService _globalOptions; @@ -21,14 +23,16 @@ internal class DocumentPullDiagnosticHandlerFactory : ILspServiceFactory [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public DocumentPullDiagnosticHandlerFactory( IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) { _analyzerService = analyzerService; + _diagnosticSourceManager = diagnosticSourceManager; _diagnosticsRefresher = diagnosticsRefresher; _globalOptions = globalOptions; } public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) - => new DocumentPullDiagnosticHandler(_analyzerService, _diagnosticsRefresher, _globalOptions); + => new DocumentPullDiagnosticHandler(_analyzerService, _diagnosticSourceManager, _diagnosticsRefresher, _globalOptions); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..5ac98fefc7389 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs @@ -0,0 +1,44 @@ +// 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 System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.SolutionCrawler; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; + +[Export(typeof(IDiagnosticSourceProvider)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class PublicDocumentNonLocalDiagnosticSourceProvider( + [Import] IGlobalOptionService globalOptions, + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : IDiagnosticSourceProvider +{ + public const string NonLocal = nameof(NonLocal); + public bool IsDocument => true; + public string Name => NonLocal; + + public bool IsEnabled(ClientCapabilities clientCapabilities) => true; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. + if (context.GetTrackedDocument() is { } textDocument && + globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) == BackgroundAnalysisScope.FullSolution) + { + // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. + return new([new NonLocalDocumentDiagnosticSource(textDocument, diagnosticAnalyzerService, a => !a.IsCompilerAnalyzer())]); + } + + return new([]); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs index e369e4180ed43..3ae460c780e84 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs @@ -5,10 +5,9 @@ using System; using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; -using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; @@ -19,6 +18,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; internal sealed class PublicDocumentPullDiagnosticHandlerFactory : ILspServiceFactory { private readonly IDiagnosticAnalyzerService _analyzerService; + private readonly IDiagnosticSourceManager _diagnosticSourceManager; private readonly IDiagnosticsRefresher _diagnosticRefresher; private readonly IGlobalOptionService _globalOptions; @@ -26,10 +26,12 @@ internal sealed class PublicDocumentPullDiagnosticHandlerFactory : ILspServiceFa [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public PublicDocumentPullDiagnosticHandlerFactory( IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) { _analyzerService = analyzerService; + _diagnosticSourceManager = diagnosticSourceManager; _diagnosticRefresher = diagnosticRefresher; _globalOptions = globalOptions; } @@ -37,6 +39,6 @@ public PublicDocumentPullDiagnosticHandlerFactory( public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var clientLanguageServerManager = lspServices.GetRequiredService(); - return new PublicDocumentPullDiagnosticsHandler(clientLanguageServerManager, _analyzerService, _diagnosticRefresher, _globalOptions); + return new PublicDocumentPullDiagnosticsHandler(clientLanguageServerManager, _analyzerService, _diagnosticSourceManager, _diagnosticRefresher, _globalOptions); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 400087cd41ef7..426148bed2f11 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -2,52 +2,42 @@ // 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.Linq; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; -using DocumentDiagnosticReport = SumType; - // A document diagnostic partial report is defined as having the first literal send = DocumentDiagnosticReport (aka changed / unchanged) followed // by n DocumentDiagnosticPartialResult literals. -// See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#documentDiagnosticParams using DocumentDiagnosticPartialReport = SumType; +using DocumentDiagnosticReport = SumType; [Method(Methods.TextDocumentDiagnosticName)] internal sealed partial class PublicDocumentPullDiagnosticsHandler : AbstractDocumentPullDiagnosticHandler { - private readonly string _nonLocalDiagnosticsSourceRegistrationId; private readonly IClientLanguageServerManager _clientLanguageServerManager; public PublicDocumentPullDiagnosticsHandler( IClientLanguageServerManager clientLanguageServerManager, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticsRefresher, globalOptions) + : base(analyzerService, diagnosticsRefresher, diagnosticSourceManager, globalOptions) { - _nonLocalDiagnosticsSourceRegistrationId = Guid.NewGuid().ToString(); _clientLanguageServerManager = clientLanguageServerManager; } - /// - /// Public API doesn't support categories (yet). - /// - protected override string? GetDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) - => null; + protected override string? GetRequestDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) + => diagnosticsParams.Identifier; - public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.TextDocument; - - protected override string? GetDiagnosticSourceIdentifier(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.Identifier; + public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) + => diagnosticsParams.TextDocument; protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); @@ -92,18 +82,6 @@ protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifi return null; } - protected override ValueTask> GetOrderedDiagnosticSourcesAsync(DocumentDiagnosticParams diagnosticParams, RequestContext context, CancellationToken cancellationToken) - { - var nonLocalDocumentDiagnostics = diagnosticParams.Identifier == DocumentNonLocalDiagnosticIdentifier.ToString(); - - // Task list items are not reported through the public LSP diagnostic API. - var source = nonLocalDocumentDiagnostics - ? DocumentPullDiagnosticHandler.GetNonLocalDiagnosticSource(context, GlobalOptions) - : DocumentPullDiagnosticHandler.GetDiagnosticSource(DiagnosticKind.All, context); - - return new(source != null ? [source] : []); - } - protected override ImmutableArray? GetPreviousResults(DocumentDiagnosticParams diagnosticsParams) { if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs index 940723c0e652a..539100d7a23b2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs @@ -2,61 +2,62 @@ // 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.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; - -using DocumentDiagnosticReport = SumType; - // A document diagnostic partial report is defined as having the first literal send = DocumentDiagnosticReport (aka changed / unchanged) followed // by n DocumentDiagnosticPartialResult literals. // See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic -using DocumentDiagnosticPartialReport = SumType; -internal sealed partial class PublicDocumentPullDiagnosticsHandler : AbstractDocumentPullDiagnosticHandler, IOnInitialized +internal sealed partial class PublicDocumentPullDiagnosticsHandler : IOnInitialized { public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { - // Dynamically register a non-local document diagnostic source if Full solution background analysis is enabled - // for analyzer execution. This diagnostic source reports diagnostics in open documents that are reported - // when analyzing other documents or at compilation end. - if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true && IsFsaEnabled()) + // Dynamically register for all relevant diagnostic sources. + if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true) { // TODO: Hookup an option changed handler for changes to BackgroundAnalysisScopeOption // to dynamically register/unregister the non-local document diagnostic source. + var documentSources = DiagnosticSourceManager.GetDocumentSourceProviderNames(clientCapabilities); + var workspaceSources = DiagnosticSourceManager.GetWorkspaceSourceProviderNames(clientCapabilities); + + // All diagnostic sources have to be registered under the document pull method name, + // See https://github.com/microsoft/language-server-protocol/issues/1723 + // + // Additionally if a source name is used by both document and workspace pull (e.g. enc) + // we don't want to send two registrations, instead we should send a single registration + // that also sets the workspace pull option. + // + // So we build up a unique set of source names and mark if each one is also a workspace source. + var allSources = documentSources + .AddRange(workspaceSources) + .ToSet() + .Select(name => (Name: name, IsWorkspaceSource: workspaceSources.Contains(name))); + + var registrations = allSources.Select(FromSourceName).ToArray(); await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @params: new RegistrationParams() { - Registrations = - [ - new Registration - { - Id = _nonLocalDiagnosticsSourceRegistrationId, - Method = Methods.TextDocumentDiagnosticName, - RegisterOptions = new DiagnosticRegistrationOptions - { - Identifier = DocumentNonLocalDiagnosticIdentifier.ToString() - } - } - ] + Registrations = registrations }, cancellationToken).ConfigureAwait(false); } - bool IsFsaEnabled() + static Registration FromSourceName((string Name, bool IsWorkspaceSource) source) { - foreach (var language in context.SupportedLanguages) + return new() { - if (GlobalOptions.GetBackgroundAnalysisScope(language) == BackgroundAnalysisScope.FullSolution) - return true; - } - - return false; + Id = Guid.NewGuid().ToString(), + Method = Methods.TextDocumentDiagnosticName, + RegisterOptions = new DiagnosticRegistrationOptions { Identifier = source.Name, InterFileDependencies = true, WorkspaceDiagnostics = source.IsWorkspaceSource, WorkDoneProgress = source.IsWorkspaceSource } + }; } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs index 90970a521fe53..ea0814316fbfc 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs @@ -6,6 +6,7 @@ using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; @@ -16,12 +17,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; internal sealed class PublicWorkspacePullDiagnosticHandlerFactory( LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : ILspServiceFactory { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var workspaceManager = lspServices.GetRequiredService(); - return new PublicWorkspacePullDiagnosticsHandler(workspaceManager, registrationService, analyzerService, diagnosticsRefresher, globalOptions); + return new PublicWorkspacePullDiagnosticsHandler(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index 6c2beb5517041..71f78c36902a0 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -7,6 +7,7 @@ using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; @@ -19,21 +20,21 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; using WorkspaceDiagnosticPartialReport = SumType; [Method(Methods.WorkspaceDiagnosticName)] -internal sealed class PublicWorkspacePullDiagnosticsHandler( - LspWorkspaceManager workspaceManager, - LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, - IDiagnosticsRefresher diagnosticRefresher, - IGlobalOptionService globalOptions) - : AbstractWorkspacePullDiagnosticsHandler( - workspaceManager, registrationService, analyzerService, diagnosticRefresher, globalOptions), IDisposable +internal sealed partial class PublicWorkspacePullDiagnosticsHandler : AbstractWorkspacePullDiagnosticsHandler, IDisposable { + public PublicWorkspacePullDiagnosticsHandler( + LspWorkspaceManager workspaceManager, + LspWorkspaceRegistrationService registrationService, + IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) + : base(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticRefresher, globalOptions) + { + } - /// - /// Public API doesn't support categories (yet). - /// - protected override string? GetDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) - => null; + protected override string? GetRequestDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) + => diagnosticsParams.Identifier; protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index 66893cacf32fb..62f6e9b5b8332 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; @@ -17,26 +18,27 @@ internal sealed partial class WorkspacePullDiagnosticHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : AbstractWorkspacePullDiagnosticsHandler( - workspaceManager, registrationService, analyzerService, diagnosticsRefresher, globalOptions) + workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { - protected override string? GetDiagnosticCategory(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) + protected override string? GetRequestDiagnosticCategory(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) => diagnosticsParams.QueryingDiagnosticKind?.Value; protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) - => [ - new VSInternalWorkspaceDiagnosticReport - { - TextDocument = identifier, - Diagnostics = diagnostics, - ResultId = resultId, - // Mark these diagnostics as having come from us. They will be superseded by any diagnostics for the - // same file produced by the DocumentPullDiagnosticHandler. - Identifier = WorkspaceDiagnosticIdentifier, - } - ]; + => [ + new VSInternalWorkspaceDiagnosticReport + { + TextDocument = identifier, + Diagnostics = diagnostics, + ResultId = resultId, + // Mark these diagnostics as having come from us. They will be superseded by any diagnostics for the + // same file produced by the DocumentPullDiagnosticHandler. + Identifier = WorkspaceDiagnosticIdentifier, + } + ]; protected override VSInternalWorkspaceDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) => CreateReport(identifier, diagnostics: null, resultId: null); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs index b928cf51f3910..7c13f4d46b74a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs @@ -6,6 +6,7 @@ using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -16,12 +17,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal class WorkspacePullDiagnosticHandlerFactory( LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : ILspServiceFactory { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var workspaceManager = lspServices.GetRequiredService(); - return new WorkspacePullDiagnosticHandler(workspaceManager, registrationService, analyzerService, diagnosticsRefresher, globalOptions); + return new WorkspacePullDiagnosticHandler(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/DocumentEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/DocumentEditAndContinueDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..5f66ca41cdddb --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/DocumentEditAndContinueDiagnosticSourceProvider.cs @@ -0,0 +1,35 @@ +// 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 System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Host.Mef; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[Export(typeof(IDiagnosticSourceProvider)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DocumentEditAndContinueDiagnosticSourceProvider() : IDiagnosticSourceProvider +{ + public bool IsDocument => true; + public string Name => PullDiagnosticCategories.EditAndContinue; + + public bool IsEnabled(ClientCapabilities capabilities) => true; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (context.GetTrackedDocument() is { } document) + { + return new([EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document)]); + } + + return new([]); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/WorkspaceEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/WorkspaceEditAndContinueDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..af92108b86cae --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/WorkspaceEditAndContinueDiagnosticSourceProvider.cs @@ -0,0 +1,32 @@ +// 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 System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Host.Mef; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[Export(typeof(IDiagnosticSourceProvider)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class WorkspaceEditAndContinueDiagnosticSourceProvider() : IDiagnosticSourceProvider +{ + public bool IsDocument => false; + public string Name => PullDiagnosticCategories.EditAndContinue; + + public bool IsEnabled(ClientCapabilities capabilities) => true; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(context.Solution); + return EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution!, document => context.IsTracking(document.GetURI()), cancellationToken); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs b/src/Features/LanguageServer/Protocol/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs index 806e79597b426..778a3dd2ac3ea 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs @@ -42,7 +42,7 @@ internal abstract class AbstractFormatDocumentHandlerBase(); edits.AddRange(textChanges.Select(change => ProtocolConversions.TextChangeToTextEdit(change, text))); diff --git a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs index 852a0d777a22a..f0ff5255f831b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs @@ -70,20 +70,26 @@ private sealed class LSPNavigateToCallback( BufferedProgress progress) : INavigateToSearchCallback { - public async Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) + public async Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { - var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(project.Solution, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(context.Solution); + var solution = context.Solution; - var location = await ProtocolConversions.TextSpanToLocationAsync( - document, result.NavigableItem.SourceSpan, result.NavigableItem.IsStale, context, cancellationToken).ConfigureAwait(false); - if (location == null) - return; + foreach (var result in results) + { + var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(solution, cancellationToken).ConfigureAwait(false); - var service = project.Solution.Services.GetRequiredService(); - var symbolInfo = service.Create( - result.Name, result.AdditionalInformation, ProtocolConversions.NavigateToKindToSymbolKind(result.Kind), location, result.NavigableItem.Glyph); + var location = await ProtocolConversions.TextSpanToLocationAsync( + document, result.NavigableItem.SourceSpan, result.NavigableItem.IsStale, context, cancellationToken).ConfigureAwait(false); + if (location == null) + return; - progress.Report(symbolInfo); + var service = solution.Services.GetRequiredService(); + var symbolInfo = service.Create( + result.Name, result.AdditionalInformation, ProtocolConversions.NavigateToKindToSymbolKind(result.Kind), location, result.NavigableItem.Glyph); + + progress.Report(symbolInfo); + } } public void Done(bool isFullyLoaded) diff --git a/src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..5e78b219c22f3 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs @@ -0,0 +1,36 @@ +// 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 System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Tasks; + +[Export(typeof(IDiagnosticSourceProvider)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DocumentTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) : IDiagnosticSourceProvider +{ + public bool IsDocument => true; + public string Name => PullDiagnosticCategories.Task; + + public bool IsEnabled(ClientCapabilities capabilities) => capabilities.HasVisualStudioLspCapability(); + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (context.GetTrackedDocument() is { } document) + { + return new([new TaskListDiagnosticSource(document, globalOptions)]); + } + + return new([]); + } +} + diff --git a/src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..2a273d85d398a --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs @@ -0,0 +1,51 @@ +// 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 System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.TaskList; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Tasks; + +[Export(typeof(IDiagnosticSourceProvider)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class WorkspaceTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) : IDiagnosticSourceProvider +{ + public bool IsDocument => false; + public string Name => PullDiagnosticCategories.Task; + + public bool IsEnabled(ClientCapabilities capabilities) => capabilities.HasVisualStudioLspCapability(); + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(context.Solution); + + // Only compute task list items for closed files if the option is on for it. + if (globalOptions.GetTaskListOptions().ComputeForClosedFiles) + { + using var _ = ArrayBuilder.GetInstance(out var result); + foreach (var project in WorkspaceDiagnosticSourceHelpers.GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) + { + foreach (var document in project.Documents) + { + if (!WorkspaceDiagnosticSourceHelpers.ShouldSkipDocument(context, document)) + result.Add(new TaskListDiagnosticSource(document, globalOptions)); + } + } + + return new(result.ToImmutableAndClear()); + } + + return new([]); + } +} diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalHover.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalHover.cs index ea9a5ec9094f6..de87ad88193bc 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalHover.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalHover.cs @@ -10,6 +10,7 @@ namespace Roslyn.LanguageServer.Protocol /// /// Extension to Hover which adds additional data for colorization. /// + [DataContract] internal class VSInternalHover : Hover { /// diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionContext.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionContext.cs index 284ffa5d505ae..a3ab7943d4821 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionContext.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionContext.cs @@ -12,6 +12,7 @@ namespace Roslyn.LanguageServer.Protocol /// Context for inline completion request. /// See https://github.com/microsoft/vscode/blob/075ba020e8493f40dba89891b1a08453f2c067e9/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts#L27. /// + [DataContract] internal class VSInternalInlineCompletionContext { /// diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionItem.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionItem.cs index 73fb5c5285852..740c6799c26d9 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionItem.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionItem.cs @@ -13,6 +13,7 @@ namespace Roslyn.LanguageServer.Protocol /// /// See https://github.com/microsoft/vscode/blob/075ba020e8493f40dba89891b1a08453f2c067e9/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts#L78. /// + [DataContract] internal class VSInternalInlineCompletionItem { /// @@ -45,4 +46,4 @@ internal class VSInternalInlineCompletionItem [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public InsertTextFormat? TextFormat { get; set; } = InsertTextFormat.Plaintext; } -} \ No newline at end of file +} diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionList.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionList.cs index 62f4b20ba0d07..57c7957df661b 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionList.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionList.cs @@ -12,6 +12,7 @@ namespace Roslyn.LanguageServer.Protocol /// /// See https://github.com/microsoft/vscode/blob/075ba020e8493f40dba89891b1a08453f2c067e9/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts#L72. /// + [DataContract] internal class VSInternalInlineCompletionList { /// @@ -21,4 +22,4 @@ internal class VSInternalInlineCompletionList [JsonProperty(Required = Required.Always)] public VSInternalInlineCompletionItem[] Items { get; set; } } -} \ No newline at end of file +} diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionRequest.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionRequest.cs index fe3840400b762..a57c4f294d4db 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionRequest.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalInlineCompletionRequest.cs @@ -12,6 +12,7 @@ namespace Roslyn.LanguageServer.Protocol /// /// See https://github.com/microsoft/vscode/blob/075ba020e8493f40dba89891b1a08453f2c067e9/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts#L24. /// + [DataContract] internal class VSInternalInlineCompletionRequest : ITextDocumentParams { /// diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalReferenceParams.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalReferenceParams.cs index a302b2baeb3ce..ef0a031bbb6dd 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalReferenceParams.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalReferenceParams.cs @@ -10,6 +10,7 @@ namespace Roslyn.LanguageServer.Protocol /// /// Class which represents extensions of passed as parameter of find reference requests. /// + [DataContract] internal class VSInternalReferenceParams : ReferenceParams { /// diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalSelectedCompletionInfo.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalSelectedCompletionInfo.cs index 566acebde174c..963cc264e0acf 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalSelectedCompletionInfo.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalSelectedCompletionInfo.cs @@ -12,6 +12,7 @@ namespace Roslyn.LanguageServer.Protocol /// /// See https://github.com/microsoft/vscode/blob/075ba020e8493f40dba89891b1a08453f2c067e9/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts#L48. /// + [DataContract] internal class VSInternalSelectedCompletionInfo { /// @@ -42,4 +43,4 @@ internal class VSInternalSelectedCompletionInfo [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public bool IsSnippetText { get; set; } } -} \ No newline at end of file +} diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalTextDocumentClientCapabilities.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalTextDocumentClientCapabilities.cs index 1ae592269a85e..abc8b2b3749ed 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalTextDocumentClientCapabilities.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/VSInternalTextDocumentClientCapabilities.cs @@ -10,6 +10,7 @@ namespace Roslyn.LanguageServer.Protocol /// /// Text document capabilities specific to Visual Studio. /// + [DataContract] internal class VSInternalTextDocumentClientCapabilities : TextDocumentClientCapabilities { /// diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs index b2b813d4c07ed..cfa16156b82ce 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs @@ -496,8 +496,7 @@ public async Task TestUsingServerDefaultCommitCharacters(bool mutatingLspWorkspa } } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://github.com/dotnet/roslyn/issues/26488")] public async Task TestCompletionForObsoleteSymbol(bool mutatingLspWorkspace) { diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs index 201f98f4233cf..556c9a4cc1282 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs @@ -72,7 +72,7 @@ private protected static async Task> RunGet } else { - return await RunPublicGetWorkspacePullDiagnosticsAsync(testLspServer, previousResults, useProgress, triggerConnectionClose); + return await RunPublicGetWorkspacePullDiagnosticsAsync(testLspServer, previousResults, useProgress, category, triggerConnectionClose); } } @@ -114,6 +114,7 @@ private protected static async Task> RunPub TestLspServer testLspServer, ImmutableArray<(string resultId, TextDocumentIdentifier identifier)>? previousResults, bool useProgress, + string? category, bool triggerConnectionClose) { await testLspServer.WaitForDiagnosticsAsync(); @@ -121,7 +122,7 @@ private protected static async Task> RunPub BufferedProgress? progress = useProgress ? BufferedProgress.Create(null) : null; var diagnosticsTask = testLspServer.ExecuteRequestAsync( Methods.WorkspaceDiagnosticName, - CreateProposedWorkspaceDiagnosticParams(previousResults, progress), + CreateProposedWorkspaceDiagnosticParams(previousResults, progress, category), CancellationToken.None).ConfigureAwait(false); if (triggerConnectionClose) @@ -147,8 +148,9 @@ private protected static async Task> RunPub } private static WorkspaceDiagnosticParams CreateProposedWorkspaceDiagnosticParams( - ImmutableArray<(string resultId, TextDocumentIdentifier identifier)>? previousResults = null, - IProgress? progress = null) + ImmutableArray<(string resultId, TextDocumentIdentifier identifier)>? previousResults, + IProgress? progress, + string? category) { var previousResultsLsp = previousResults?.Select(r => new PreviousResultId { @@ -158,7 +160,8 @@ private static WorkspaceDiagnosticParams CreateProposedWorkspaceDiagnosticParams return new WorkspaceDiagnosticParams { PreviousResultId = previousResultsLsp, - PartialResultToken = progress + PartialResultToken = progress, + Identifier = category }; } @@ -166,12 +169,12 @@ private static TestDiagnosticResult ConvertWorkspaceDiagnosticResult(SumType CreateDiagnosticParamsFromPreviousReports(ImmutableArray results) { - - return results.Select(r => (r.ResultId, r.TextDocument)).ToImmutableArray(); + // If there was no resultId provided in the response, we cannot create previous results for it. + return results.Where(r => r.ResultId != null).Select(r => (r.ResultId!, r.TextDocument)).ToImmutableArray(); } private protected static VSInternalDocumentDiagnosticsParams CreateDocumentDiagnosticParams( @@ -231,10 +234,9 @@ private protected static Task> RunGetDocume bool useVSDiagnostics, string? previousResultId = null, bool useProgress = false, - string? category = null, - bool testNonLocalDiagnostics = false) + string? category = null) { - return RunGetDocumentPullDiagnosticsAsync(testLspServer, new VSTextDocumentIdentifier { Uri = uri }, useVSDiagnostics, previousResultId, useProgress, category, testNonLocalDiagnostics); + return RunGetDocumentPullDiagnosticsAsync(testLspServer, new VSTextDocumentIdentifier { Uri = uri }, useVSDiagnostics, previousResultId, useProgress, category); } private protected static async Task> RunGetDocumentPullDiagnosticsAsync( @@ -243,14 +245,13 @@ private protected static async Task> RunGet bool useVSDiagnostics, string? previousResultId = null, bool useProgress = false, - string? category = null, - bool testNonLocalDiagnostics = false) + string? category = null) { await testLspServer.WaitForDiagnosticsAsync(); if (useVSDiagnostics) { - Assert.False(testNonLocalDiagnostics, "NonLocalDiagnostics are only supported for public DocumentPullHandler"); + Assert.False(category == PublicDocumentNonLocalDiagnosticSourceProvider.NonLocal, "NonLocalDiagnostics are only supported for public DocumentPullHandler"); BufferedProgress? progress = useProgress ? BufferedProgress.Create(null) : null; var diagnostics = await testLspServer.ExecuteRequestAsync( VSInternalMethods.DocumentPullDiagnosticName, @@ -271,7 +272,7 @@ private protected static async Task> RunGet BufferedProgress? progress = useProgress ? BufferedProgress.Create(null) : null; var diagnostics = await testLspServer.ExecuteRequestAsync?>( Methods.TextDocumentDiagnosticName, - CreateProposedDocumentDiagnosticParams(vsTextDocumentIdentifier, previousResultId, progress, testNonLocalDiagnostics), + CreateProposedDocumentDiagnosticParams(vsTextDocumentIdentifier, previousResultId, category, progress), CancellationToken.None).ConfigureAwait(false); if (useProgress) { @@ -298,12 +299,12 @@ private protected static async Task> RunGet static DocumentDiagnosticParams CreateProposedDocumentDiagnosticParams( VSTextDocumentIdentifier vsTextDocumentIdentifier, string? previousResultId, - IProgress? progress, - bool testNonLocalDiagnostics) + string? category, + IProgress? progress) { return new DocumentDiagnosticParams { - Identifier = testNonLocalDiagnostics ? DocumentPullDiagnosticHandler.DocumentNonLocalDiagnosticIdentifier.ToString() : null, + Identifier = category, PreviousResultId = previousResultId, PartialResultToken = progress, TextDocument = vsTextDocumentIdentifier, @@ -361,7 +362,7 @@ private protected static InitializationOptions GetInitializationOptions( /// Helper type to store unified LSP diagnostic results. /// Diagnostics are null when unchanged. /// - private protected record TestDiagnosticResult(TextDocumentIdentifier TextDocument, string ResultId, LSP.Diagnostic[]? Diagnostics) + private protected record TestDiagnosticResult(TextDocumentIdentifier TextDocument, string? ResultId, LSP.Diagnostic[]? Diagnostics) { public Uri Uri { get; } = TextDocument.Uri; } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs new file mode 100644 index 0000000000000..cb85dd18a0c98 --- /dev/null +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs @@ -0,0 +1,117 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; +using Newtonsoft.Json.Linq; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Test.Utilities; +using StreamJsonRpc; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Diagnostics; + +public class DiagnosticRegistrationTests : AbstractLanguageServerProtocolTests +{ + public DiagnosticRegistrationTests(ITestOutputHelper? testOutputHelper) : base(testOutputHelper) + { + } + + [Theory, CombinatorialData] + public async Task TestPublicDiagnosticSourcesAreRegisteredWhenSupported(bool mutatingLspWorkspace) + { + var clientCapabilities = new ClientCapabilities + { + TextDocument = new TextDocumentClientCapabilities + { + Diagnostic = new DiagnosticSetting + { + DynamicRegistration = true, + } + } + }; + var clientCallbackTarget = new ClientCallbackTarget(); + var initializationOptions = new InitializationOptions() + { + CallInitialized = true, + ClientCapabilities = clientCapabilities, + ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer, + ClientTarget = clientCallbackTarget, + }; + + await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, initializationOptions); + + var registrations = clientCallbackTarget.GetRegistrations(); + + // Get all registrations for diagnostics (note that workspace registrations are registered against document method name). + var diagnosticRegistrations = registrations + .Where(r => r.Method == Methods.TextDocumentDiagnosticName) + .Select(r => ((JObject)r.RegisterOptions!).ToObject()!); + + Assert.NotEmpty(diagnosticRegistrations); + + string[] documentSources = [ + PullDiagnosticCategories.DocumentCompilerSyntax, + PullDiagnosticCategories.DocumentCompilerSemantic, + PullDiagnosticCategories.DocumentAnalyzerSyntax, + PullDiagnosticCategories.DocumentAnalyzerSemantic, + PublicDocumentNonLocalDiagnosticSourceProvider.NonLocal + ]; + + string[] documentAndWorkspaceSources = [ + PullDiagnosticCategories.EditAndContinue, + PullDiagnosticCategories.WorkspaceDocumentsAndProject + ]; + + // Verify document only sources are present (and do not set the workspace diagnostic option). + foreach (var documentSource in documentSources) + { + var options = Assert.Single(diagnosticRegistrations, (r) => r.Identifier == documentSource); + Assert.False(options.WorkspaceDiagnostics); + Assert.True(options.InterFileDependencies); + } + + // Verify workspace sources are present (and do set the workspace diagnostic option). + foreach (var workspaceSource in documentAndWorkspaceSources) + { + var options = Assert.Single(diagnosticRegistrations, (r) => r.Identifier == workspaceSource); + Assert.True(options.WorkspaceDiagnostics); + Assert.True(options.InterFileDependencies); + Assert.True(options.WorkDoneProgress); + } + + // Verify task diagnostics are not present. + Assert.DoesNotContain(diagnosticRegistrations, (r) => r.Identifier == PullDiagnosticCategories.Task); + } + + /// + /// Implements a client side callback target for client/registerCapability to inspect what was registered. + /// + private class ClientCallbackTarget() + { + private readonly List _registrations = new(); + + [JsonRpcMethod(Methods.ClientRegisterCapabilityName, UseSingleObjectParameterDeserialization = true)] + public void ClientRegisterCapability(RegistrationParams registrationParams, CancellationToken _) + { + _registrations.AddRange(registrationParams.Registrations); + } + + /// + /// This is safe to call after 'initialized' has completed because capabilties are dynamically registered in the + /// implementation of the initialized request. Additionally, client/registerCapability is a request (not a notification) + /// which means the server will wait for the client to finish handling it before the server returns from 'initialized'. + /// + public ImmutableArray GetRegistrations() + { + return _registrations.ToImmutableArray(); + } + } +} diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs index 5f9e9694c4ec4..24b0be51a26dc 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; @@ -39,7 +40,7 @@ internal async Task TestNonLocalDocumentDiagnosticsAreReportedWhenFSAEnabled(boo // and not reported here. await OpenDocumentAsync(testLspServer, document); - var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics: false, testNonLocalDiagnostics: true); + var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics: false, category: PublicDocumentNonLocalDiagnosticSourceProvider.NonLocal); if (fsaEnabled) { Assert.Equal(1, results.Length); @@ -50,7 +51,7 @@ internal async Task TestNonLocalDocumentDiagnosticsAreReportedWhenFSAEnabled(boo Assert.Equal(document.GetURI(), results[0].Uri); // Asking again should give us back unchanged diagnostics. - var results2 = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics: false, previousResultId: results.Single().ResultId, testNonLocalDiagnostics: true); + var results2 = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics: false, previousResultId: results.Single().ResultId, category: PublicDocumentNonLocalDiagnosticSourceProvider.NonLocal); Assert.Null(results2[0].Diagnostics); Assert.Equal(results[0].ResultId, results2[0].ResultId); } @@ -59,7 +60,7 @@ internal async Task TestNonLocalDocumentDiagnosticsAreReportedWhenFSAEnabled(boo Assert.Empty(results); // Asking again should give us back unchanged diagnostics. - var results2 = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics: false, testNonLocalDiagnostics: true); + var results2 = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics: false, category: PublicDocumentNonLocalDiagnosticSourceProvider.NonLocal); Assert.Empty(results2); } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs index 6f32ed5d1bb8a..d575909fc21b9 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs @@ -73,14 +73,14 @@ public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff(bool useVSDiagno } [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/fsharp/issues/15972")] - public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff_Categories(bool mutatingLspWorkspace) + public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff_Categories(bool useVSDiagnostics, bool mutatingLspWorkspace) { var markup = @"class A : B {"; var additionalAnalyzers = new DiagnosticAnalyzer[] { new CSharpSyntaxAnalyzer(), new CSharpSemanticAnalyzer() }; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true, additionalAnalyzers: additionalAnalyzers); + markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics, additionalAnalyzers: additionalAnalyzers); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -90,35 +90,35 @@ public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff_Categories(bool await OpenDocumentAsync(testLspServer, document); var syntaxResults = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.DocumentCompilerSyntax); + testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.DocumentCompilerSyntax); var semanticResults = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.DocumentCompilerSemantic); + testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.DocumentCompilerSemantic); Assert.Equal("CS1513", syntaxResults.Single().Diagnostics.Single().Code); Assert.Equal("CS0246", semanticResults.Single().Diagnostics.Single().Code); var syntaxResults2 = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, previousResultId: syntaxResults.Single().ResultId, category: PullDiagnosticCategories.DocumentCompilerSyntax); + testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: syntaxResults.Single().ResultId, category: PullDiagnosticCategories.DocumentCompilerSyntax); var semanticResults2 = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, previousResultId: semanticResults.Single().ResultId, category: PullDiagnosticCategories.DocumentCompilerSemantic); + testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: semanticResults.Single().ResultId, category: PullDiagnosticCategories.DocumentCompilerSemantic); Assert.Equal(syntaxResults.Single().ResultId, syntaxResults2.Single().ResultId); Assert.Equal(semanticResults.Single().ResultId, semanticResults2.Single().ResultId); var syntaxAnalyzerResults = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.DocumentAnalyzerSyntax); + testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.DocumentAnalyzerSyntax); var semanticAnalyzerResults = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.DocumentAnalyzerSemantic); + testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.DocumentAnalyzerSemantic); Assert.Equal(CSharpSyntaxAnalyzer.RuleId, syntaxAnalyzerResults.Single().Diagnostics.Single().Code); Assert.Equal(CSharpSemanticAnalyzer.RuleId, semanticAnalyzerResults.Single().Diagnostics.Single().Code); var syntaxAnalyzerResults2 = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, previousResultId: syntaxAnalyzerResults.Single().ResultId, category: PullDiagnosticCategories.DocumentAnalyzerSyntax); + testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: syntaxAnalyzerResults.Single().ResultId, category: PullDiagnosticCategories.DocumentAnalyzerSyntax); var semanticAnalyzerResults2 = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, previousResultId: semanticAnalyzerResults.Single().ResultId, category: PullDiagnosticCategories.DocumentAnalyzerSemantic); + testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: semanticAnalyzerResults.Single().ResultId, category: PullDiagnosticCategories.DocumentAnalyzerSemantic); Assert.Equal(syntaxAnalyzerResults.Single().ResultId, syntaxAnalyzerResults2.Single().ResultId); Assert.Equal(semanticAnalyzerResults.Single().ResultId, semanticAnalyzerResults2.Single().ResultId); @@ -180,15 +180,12 @@ static void Main(string[] args) Assert.Equal(vsDiagnostic.ExpandedMessage, AnalyzersResources.Avoid_unused_parameters_in_your_code_If_the_parameter_cannot_be_removed_then_change_its_name_so_it_starts_with_an_underscore_and_is_optionally_followed_by_an_integer_such_as__comma__1_comma__2_etc_These_are_treated_as_special_discard_symbol_names); } - [Theory, CombinatorialData] - public async Task TestDocumentTodoCommentsDiagnosticsForOpenFile_NoCategory(bool useVSDiagnostics, bool mutatingLspWorkspace) + [Theory, CombinatorialData, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2050705")] + public async Task TestDocumentDiagnosticsUsesNullForExpandedMessage(bool mutatingLspWorkspace) { var markup = -@" -// todo: goo -class A { -}"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); +@"class A {"; + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -198,20 +195,22 @@ class A { await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics); + testLspServer, document.GetURI(), useVSDiagnostics: true); - Assert.Empty(results.Single().Diagnostics); + Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + var vsDiagnostic = (VSDiagnostic)results.Single().Diagnostics.Single(); + Assert.Null(vsDiagnostic.ExpandedMessage); } [Theory, CombinatorialData] - public async Task TestDocumentTodoCommentsDiagnosticsForOpenFile_Category(bool useVSDiagnostics, bool mutatingLspWorkspace) + public async Task TestDocumentTodoCommentsDiagnosticsForOpenFile_Category(bool mutatingLspWorkspace) { var markup = @" // todo: goo class A { }"; - await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -221,17 +220,10 @@ class A { await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.Task); + testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.Task); - if (useVSDiagnostics) - { - Assert.Equal("TODO", results.Single().Diagnostics.Single().Code); - Assert.Equal("todo: goo", results.Single().Diagnostics.Single().Message); - } - else - { - Assert.Empty(results.Single().Diagnostics); - } + Assert.Equal("TODO", results.Single().Diagnostics.Single().Code); + Assert.Equal("todo: goo", results.Single().Diagnostics.Single().Message); } [Theory, CombinatorialData] @@ -1035,7 +1027,7 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesWithWithRunCodeAnalysisF } [Theory, CombinatorialData] - public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOff(bool useVSDiagnostics, bool mutatingLspWorkspace) + public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOff(bool mutatingLspWorkspace) { var markup1 = @" @@ -1043,15 +1035,15 @@ public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOff(bool useVS class A { }"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTaskListItems: false, category: PullDiagnosticCategories.Task); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: false, category: PullDiagnosticCategories.Task); Assert.Equal(0, results.Length); } [Theory, CombinatorialData] - public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOn(bool useVSDiagnostics, bool mutatingLspWorkspace) + public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOn(bool mutatingLspWorkspace) { var markup1 = @" @@ -1059,21 +1051,14 @@ public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOn(bool useVSD class A { }"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTaskListItems: true, category: PullDiagnosticCategories.Task); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: true, category: PullDiagnosticCategories.Task); - if (useVSDiagnostics) - { - Assert.Equal(1, results.Length); - Assert.Equal("TODO", results[0].Diagnostics.Single().Code); - Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); - Assert.Equal(VSDiagnosticRank.Default, ((VSDiagnostic)results[0].Diagnostics.Single()).DiagnosticRank); - } - else - { - Assert.Empty(results); - } + Assert.Equal(1, results.Length); + Assert.Equal("TODO", results[0].Diagnostics.Single().Code); + Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); + Assert.Equal(VSDiagnosticRank.Default, ((VSDiagnostic)results[0].Diagnostics.Single()).DiagnosticRank); } [Theory] @@ -1108,7 +1093,7 @@ class A { } [Theory, CombinatorialData] - public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOff(bool useVSDiagnostics, bool mutatingLspWorkspace) + public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOff(bool mutatingLspWorkspace) { var markup1 = @" @@ -1116,24 +1101,15 @@ public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOff(bool useVSD class A { }"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics: true); - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTaskListItems: false, category: PullDiagnosticCategories.Task); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: false, category: PullDiagnosticCategories.Task); - if (useVSDiagnostics) - { - Assert.Equal(0, results.Length); - } - else - { - Assert.Equal(2, results.Length); - Assert.Empty(results[0].Diagnostics); - Assert.Empty(results[1].Diagnostics); - } + Assert.Equal(0, results.Length); } [Theory, CombinatorialData] - public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOn(bool useVSDiagnostics, bool mutatingLspWorkspace) + public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOn(bool mutatingLspWorkspace) { var markup1 = @" @@ -1141,28 +1117,18 @@ public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOn(bool useVSDi class A { }"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); - - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTaskListItems: true, category: PullDiagnosticCategories.Task); + [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics: true); - if (useVSDiagnostics) - { - Assert.Equal(1, results.Length); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: true, category: PullDiagnosticCategories.Task); - Assert.Equal("TODO", results[0].Diagnostics.Single().Code); - Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); - } - else - { - Assert.Equal(2, results.Length); + Assert.Equal(1, results.Length); - Assert.Empty(results[0].Diagnostics); - Assert.Empty(results[1].Diagnostics); - } + Assert.Equal("TODO", results[0].Diagnostics.Single().Code); + Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); } [Theory, CombinatorialData] - public async Task TestWorkspaceTodoAndDiagnosticForClosedFilesWithFSAOnAndTodoOn(bool useVSDiagnostics, bool mutatingLspWorkspace) + public async Task TestWorkspaceTodoAndDiagnosticForClosedFilesWithFSAOnAndTodoOn(bool mutatingLspWorkspace) { var markup1 = @" @@ -1170,24 +1136,12 @@ public async Task TestWorkspaceTodoAndDiagnosticForClosedFilesWithFSAOnAndTodoOn class A { "; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); - - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTaskListItems: true, category: PullDiagnosticCategories.Task); - - if (useVSDiagnostics) - { - Assert.Equal(1, results.Length); - - Assert.Equal("TODO", results[0].Diagnostics![0].Code); - } - else - { - Assert.Equal(2, results.Length); + [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics: true); - Assert.Equal("CS1513", results[0].Diagnostics![0].Code); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: true, category: PullDiagnosticCategories.Task); - Assert.Empty(results[1].Diagnostics); - } + Assert.Equal(1, results.Length); + Assert.Equal("TODO", results[0].Diagnostics![0].Code); } [Theory, CombinatorialData] @@ -1206,9 +1160,9 @@ public async Task EditAndContinue_NoActiveSession(bool mutatingLspWorkspace) } [Theory, CombinatorialData] - public async Task EditAndContinue(bool mutatingLspWorkspace) + public async Task EditAndContinue(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var options = GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, compilerDiagnosticsScope: null, useVSDiagnostics: true); + var options = GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, compilerDiagnosticsScope: null, useVSDiagnostics); var composition = Composition .AddExcludedPartTypes(typeof(EditAndContinueService)) .AddParts(typeof(MockEditAndContinueService)); @@ -1234,7 +1188,7 @@ public async Task EditAndContinue(bool mutatingLspWorkspace) encSessionState.ApplyChangesDiagnostics = [projectDiagnostic, openDocumentDiagnostic1, closedDocumentDiagnostic]; encService.GetDocumentDiagnosticsImpl = (_, _) => [openDocumentDiagnostic2]; - var documentResults1 = await RunGetDocumentPullDiagnosticsAsync(testLspServer, openDocument.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.EditAndContinue); + var documentResults1 = await RunGetDocumentPullDiagnosticsAsync(testLspServer, openDocument.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.EditAndContinue); // both diagnostics located in the open document are reported: AssertEx.Equal( @@ -1242,7 +1196,7 @@ public async Task EditAndContinue(bool mutatingLspWorkspace) "file:///C:/test1.cs -> [ENC_OPEN_DOC1,ENC_OPEN_DOC2]", ], documentResults1.Select(Inspect)); - var workspaceResults1 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: false, category: PullDiagnosticCategories.EditAndContinue); + var workspaceResults1 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTaskListItems: false, category: PullDiagnosticCategories.EditAndContinue); AssertEx.Equal( [ @@ -1256,7 +1210,7 @@ public async Task EditAndContinue(bool mutatingLspWorkspace) diagnosticsRefresher.RequestWorkspaceRefresh(); var documentResults2 = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, openDocument.GetURI(), previousResultId: documentResults1.Single().ResultId, useVSDiagnostics: true, category: PullDiagnosticCategories.EditAndContinue); + testLspServer, openDocument.GetURI(), previousResultId: documentResults1.Single().ResultId, useVSDiagnostics: useVSDiagnostics, category: PullDiagnosticCategories.EditAndContinue); AssertEx.Equal( [ @@ -1264,7 +1218,7 @@ public async Task EditAndContinue(bool mutatingLspWorkspace) ], documentResults2.Select(Inspect)); var workspaceResults2 = await RunGetWorkspacePullDiagnosticsAsync( - testLspServer, useVSDiagnostics: true, previousResults: workspaceResults1.SelectAsArray(r => (r.ResultId, r.TextDocument)), includeTaskListItems: false, category: PullDiagnosticCategories.EditAndContinue); + testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(workspaceResults1), includeTaskListItems: false, category: PullDiagnosticCategories.EditAndContinue); AssertEx.Equal( [ "file:///C:/test2.cs -> []", @@ -1277,14 +1231,14 @@ public async Task EditAndContinue(bool mutatingLspWorkspace) diagnosticsRefresher.RequestWorkspaceRefresh(); var documentResults3 = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, openDocument.GetURI(), previousResultId: documentResults2.Single().ResultId, useVSDiagnostics: true, category: PullDiagnosticCategories.EditAndContinue); + testLspServer, openDocument.GetURI(), previousResultId: documentResults2.Single().ResultId, useVSDiagnostics: useVSDiagnostics, category: PullDiagnosticCategories.EditAndContinue); AssertEx.Equal( [ "file:///C:/test1.cs -> []", ], documentResults3.Select(Inspect)); var workspaceResults3 = await RunGetWorkspacePullDiagnosticsAsync( - testLspServer, useVSDiagnostics: true, previousResults: workspaceResults2.SelectAsArray(r => (r.ResultId, r.TextDocument)), includeTaskListItems: false, category: PullDiagnosticCategories.EditAndContinue); + testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(workspaceResults2), includeTaskListItems: false, category: PullDiagnosticCategories.EditAndContinue); AssertEx.Equal([], workspaceResults3.Select(Inspect)); static DiagnosticData CreateDiagnostic(string id, Document? document = null, Project? project = null) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/ProtocolConversionsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/ProtocolConversionsTests.cs index 8685d125fc2a5..a9d20fa0ccee6 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/ProtocolConversionsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/ProtocolConversionsTests.cs @@ -103,6 +103,47 @@ public void CreateAbsoluteUri_LocalPaths_Unix(string filePath, string expectedAb Assert.Equal(filePath, uri.LocalPath); } + [ConditionalTheory(typeof(WindowsOnly))] + [InlineData("C:\\a\\b", "file:///C:/a/b")] + [InlineData("C:\\a\\b\\", "file:///C:/a/b")] + [InlineData("C:\\a\\\\b", "file:///C:/a//b")] + [InlineData("C:\\%25\ue25b/a\\b", "file:///C:/%2525%EE%89%9B/a/b")] + [InlineData("C:\\%25\ue25b/a\\\\b", "file:///C:/%2525%EE%89%9B/a//b")] + [InlineData("C:\\\u0089\uC7BD", "file:///C:/%C2%89%EC%9E%BD")] + [InlineData("/\\server\ue25b\\%25\ue25b\\b", "file://server/%2525%EE%89%9B/b")] + [InlineData("\\\\server\ue25b\\%25\ue25b\\b", "file://server/%2525%EE%89%9B/b")] + [InlineData("\\\\server\ue25b\\%25\ue25b\\b\\", "file://server/%2525%EE%89%9B/b")] + [InlineData("C:\\ !$&'()+,-;=@[]_~#", "file:///C:/%20!$&'()+,-;=@[]_~%23")] + [InlineData("C:\\ !$&'()+,-;=@[]_~#\ue25b", "file:///C:/%20!$&'()+,-;=@[]_~%23%EE%89%9B")] + [InlineData("C:\\\u0073\u0323\u0307", "file:///C:/s%CC%A3%CC%87")] // combining marks + [InlineData("A:/\\\u200e//", "file:///A://%E2%80%8E//")] // cases from https://github.com/dotnet/runtime/issues/1487 + [InlineData("B:\\/\u200e", "file:///B://%E2%80%8E")] + [InlineData("C:/\\\\-Ā\r", "file:///C:///-%C4%80%0D")] + [InlineData("D:\\\\\\\\\\\u200e", "file:///D://///%E2%80%8E")] + public void CreateRelativePatternBaseUri_LocalPaths_Windows(string filePath, string expectedUri) + { + var uri = ProtocolConversions.CreateRelativePatternBaseUri(filePath); + Assert.Equal(expectedUri, uri.AbsoluteUri); + } + + [ConditionalTheory(typeof(UnixLikeOnly))] + [InlineData("/", "file://")] + [InlineData("/u", "file:///u")] + [InlineData("/unix/", "file:///unix")] + [InlineData("/unix/path", "file:///unix/path")] + [InlineData("/%25\ue25b/\u0089\uC7BD", "file:///%2525%EE%89%9B/%C2%89%EC%9E%BD")] + [InlineData("/!$&'()+,-;=@[]_~#", "file:///!$&'()+,-;=@[]_~%23")] + [InlineData("/!$&'()+,-;=@[]_~#", "file:///!$&'()+,-;=@[]_~%23%EE%89%9B")] + [InlineData("/\\\u200e//", "file:////%E2%80%8E//")] // cases from https://github.com/dotnet/runtime/issues/1487 + [InlineData("\\/\u200e", "file:////%E2%80%8E")] + [InlineData("/\\\\-Ā\r", "file://///-%C4%80%0D")] + [InlineData("\\\\\\\\\\\u200e", "file:///////%E2%80%8E")] + public void CreateRelativePatternBaseUri_LocalPaths_Unix(string filePath, string expectedRelativeUri) + { + var uri = ProtocolConversions.CreateRelativePatternBaseUri(filePath); + Assert.Equal(expectedRelativeUri, uri.AbsoluteUri); + } + [ConditionalTheory(typeof(UnixLikeOnly))] [InlineData("/a/./b", "file:///a/./b", "file:///a/b")] [InlineData("/a/../b", "file:///a/../b", "file:///b")] diff --git a/src/Features/Lsif/Generator/Generator.cs b/src/Features/Lsif/Generator/Generator.cs index 87db8cfbcc8f6..57dfd14b41197 100644 --- a/src/Features/Lsif/Generator/Generator.cs +++ b/src/Features/Lsif/Generator/Generator.cs @@ -130,7 +130,10 @@ public async Task GenerateForProjectAsync( } }; - var documents = (await project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken)).ToList(); + var documents = new List(); + await foreach (var document in project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken)) + documents.Add(document); + var tasks = new List(); foreach (var document in documents) { diff --git a/src/Features/Lsif/Generator/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.csproj b/src/Features/Lsif/Generator/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.csproj index 20ba57b5f5573..6aefd270a5757 100644 --- a/src/Features/Lsif/Generator/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.csproj +++ b/src/Features/Lsif/Generator/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.csproj @@ -81,19 +81,9 @@ - - - - - - - - - diff --git a/src/Features/Test/EditAndContinue/ActiveStatementsMapTests.cs b/src/Features/Test/EditAndContinue/ActiveStatementsMapTests.cs index d3bea48ce86cc..8f2b12dc7db8c 100644 --- a/src/Features/Test/EditAndContinue/ActiveStatementsMapTests.cs +++ b/src/Features/Test/EditAndContinue/ActiveStatementsMapTests.cs @@ -262,8 +262,7 @@ SourceFileSpan Span(int startLine, int startColumn, int endLine, int endColumn) => new("a.cs", new(new(startLine, startColumn), new(endLine, endColumn))); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void NonRemappableRegionOrdering(bool reverse) { var source1 = @@ -312,8 +311,7 @@ static void M() Assert.Equal(unmappedActiveStatements[0].Statement.Span, activeStatement.FileSpan.Span); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void SubSpan(bool reverse) { var source1 = diff --git a/src/Features/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/Features/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index 7b64d9003233a..df6d379295bcf 100644 --- a/src/Features/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/Features/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -31,7 +31,8 @@ internal abstract class EditAndContinueTestHelpers EditAndContinueCapabilities.AddInstanceFieldToExistingType | EditAndContinueCapabilities.AddStaticFieldToExistingType | EditAndContinueCapabilities.AddMethodToExistingType | - EditAndContinueCapabilities.NewTypeDefinition; + EditAndContinueCapabilities.NewTypeDefinition | + EditAndContinueCapabilities.AddExplicitInterfaceImplementation; public const EditAndContinueCapabilities Net6RuntimeCapabilities = Net5RuntimeCapabilities | diff --git a/src/Features/VisualBasic/Portable/ChangeSignature/VisualBasicChangeSignatureService.vb b/src/Features/VisualBasic/Portable/ChangeSignature/VisualBasicChangeSignatureService.vb index 94694f171c585..e4144cbdb1143 100644 --- a/src/Features/VisualBasic/Portable/ChangeSignature/VisualBasicChangeSignatureService.vb +++ b/src/Features/VisualBasic/Portable/ChangeSignature/VisualBasicChangeSignatureService.vb @@ -7,6 +7,7 @@ Imports System.Composition Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.ChangeSignature +Imports Microsoft.CodeAnalysis.EditAndContinue Imports Microsoft.CodeAnalysis.Editing Imports Microsoft.CodeAnalysis.FindSymbols Imports Microsoft.CodeAnalysis.Formatting @@ -734,8 +735,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ChangeSignature Return results.ToImmutableAndFree() End Function - Protected Overrides Function GetFormattingRules(document As Document) As IEnumerable(Of AbstractFormattingRule) - Return SpecializedCollections.SingletonEnumerable(Of AbstractFormattingRule)(New ChangeSignatureFormattingRule()).Concat(Formatter.GetDefaultFormattingRules(document)) + Protected Overrides Function GetFormattingRules(document As Document) As ImmutableArray(Of AbstractFormattingRule) + Dim coreRules = Formatter.GetDefaultFormattingRules(document) + Dim result = New FixedSizeArrayBuilder(Of AbstractFormattingRule)(1 + coreRules.Length) + result.Add(New ChangeSignatureFormattingRule()) + result.AddRange(coreRules) + Return result.MoveToImmutable() End Function Protected Overrides Function TransferLeadingWhitespaceTrivia(Of T As SyntaxNode)(newArgument As T, oldArgument As SyntaxNode) As T diff --git a/src/Features/VisualBasic/Portable/EncapsulateField/VisualBasicEncapsulateFieldService.vb b/src/Features/VisualBasic/Portable/EncapsulateField/VisualBasicEncapsulateFieldService.vb index d34283af4824e..2c494217af6cb 100644 --- a/src/Features/VisualBasic/Portable/EncapsulateField/VisualBasicEncapsulateFieldService.vb +++ b/src/Features/VisualBasic/Portable/EncapsulateField/VisualBasicEncapsulateFieldService.vb @@ -130,11 +130,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EncapsulateField Return NameGenerator.GenerateUniqueName(propertyName, containingTypeMemberNames.ToSet(), StringComparer.OrdinalIgnoreCase) End Function - Friend Overrides Function GetConstructorNodes(containingType As INamedTypeSymbol) As IEnumerable(Of SyntaxNode) + Protected Overrides Function GetConstructorNodes(containingType As INamedTypeSymbol) As IEnumerable(Of SyntaxNode) Return containingType.Constructors.SelectMany(Function(c As IMethodSymbol) Return c.DeclaringSyntaxReferences.Select(Function(d) d.GetSyntax().Parent) End Function) End Function - End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/GoToDefinition/VisualBasicGoToDefinitionSymbolService.vb b/src/Features/VisualBasic/Portable/GoToDefinition/VisualBasicGoToDefinitionSymbolService.vb index 6af12e705418b..277e3494c07c8 100644 --- a/src/Features/VisualBasic/Portable/GoToDefinition/VisualBasicGoToDefinitionSymbolService.vb +++ b/src/Features/VisualBasic/Portable/GoToDefinition/VisualBasicGoToDefinitionSymbolService.vb @@ -3,6 +3,7 @@ ' See the LICENSE file in the project root for more information. Imports System.Composition +Imports System.Threading Imports Microsoft.CodeAnalysis.GoToDefinition Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Operations @@ -12,7 +13,7 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.GoToDefinition - Friend Class VisualBasicGoToDefinitionSymbolService + Friend NotInheritable Class VisualBasicGoToDefinitionSymbolService Inherits AbstractGoToDefinitionSymbolService @@ -20,7 +21,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.GoToDefinition Public Sub New() End Sub - Protected Overrides Function FindRelatedExplicitlyDeclaredSymbol(symbol As ISymbol, compilation As Compilation) As ISymbol + Protected Overrides Async Function FindRelatedExplicitlyDeclaredSymbolAsync(project As Project, symbol As ISymbol, cancellationToken As CancellationToken) As Task(Of ISymbol) + Dim compilation = Await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(False) Return symbol.FindRelatedExplicitlyDeclaredSymbol(compilation) End Function diff --git a/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.MultiLine.vb b/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.MultiLine.vb index 2cadad82d13ef..b91b722459024 100644 --- a/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.MultiLine.vb +++ b/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.MultiLine.vb @@ -3,8 +3,8 @@ ' See the LICENSE file in the project root for more information. Imports System.Composition -Imports System.Diagnostics.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -14,7 +14,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.InvertIf Inherits VisualBasicInvertIfCodeRefactoringProvider(Of MultiLineIfBlockSyntax) - + Public Sub New() End Sub diff --git a/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.SingleLine.vb b/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.SingleLine.vb index 2c2325b37ca3a..a83e71dd19c96 100644 --- a/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.SingleLine.vb +++ b/src/Features/VisualBasic/Portable/InvertIf/VisualBasicInvertIfCodeRefactoringProvider.SingleLine.vb @@ -3,8 +3,8 @@ ' See the LICENSE file in the project root for more information. Imports System.Composition -Imports System.Diagnostics.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -14,7 +14,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.InvertIf Inherits VisualBasicInvertIfCodeRefactoringProvider(Of SingleLineIfStatementSyntax) - + Public Sub New() End Sub diff --git a/src/Features/VisualBasic/Portable/MetadataAsSource/VisualBasicMetadataAsSourceService.vb b/src/Features/VisualBasic/Portable/MetadataAsSource/VisualBasicMetadataAsSourceService.vb index b7bbdec9277cd..0d6a04128eb5a 100644 --- a/src/Features/VisualBasic/Portable/MetadataAsSource/VisualBasicMetadataAsSourceService.vb +++ b/src/Features/VisualBasic/Portable/MetadataAsSource/VisualBasicMetadataAsSourceService.vb @@ -11,6 +11,7 @@ Imports Microsoft.CodeAnalysis.Formatting.Rules Imports Microsoft.CodeAnalysis.MetadataAsSource Imports Microsoft.CodeAnalysis.Simplification Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.ChangeSignature Imports Microsoft.CodeAnalysis.VisualBasic.Simplification Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -63,8 +64,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MetadataAsSource Return document.WithSyntaxRoot(newSyntaxRoot) End Function - Protected Overrides Function GetFormattingRules(document As Document) As IEnumerable(Of AbstractFormattingRule) - Return _memberSeparationRule.Concat(Formatter.GetDefaultFormattingRules(document)) + Protected Overrides Function GetFormattingRules(document As Document) As ImmutableArray(Of AbstractFormattingRule) + Dim coreRules = Formatter.GetDefaultFormattingRules(document) + Dim result = New FixedSizeArrayBuilder(Of AbstractFormattingRule)(1 + coreRules.Length) + result.Add(_memberSeparationRule) + result.AddRange(coreRules) + Return result.MoveToImmutable() End Function Protected Overrides Function GetReducers() As ImmutableArray(Of AbstractReducer) diff --git a/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicSemanticQuickInfoProvider.vb b/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicSemanticQuickInfoProvider.vb index 77f2abd72d4b7..695f312588b15 100644 --- a/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicSemanticQuickInfoProvider.vb +++ b/src/Features/VisualBasic/Portable/QuickInfo/VisualBasicSemanticQuickInfoProvider.vb @@ -197,7 +197,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.QuickInfo Return QuickInfoItem.Create(token.Span, sections:=ImmutableArray.Create(QuickInfoSection.Create(QuickInfoSectionKinds.Description, ImmutableArray.Create(New TaggedText(TextTags.Text, VBFeaturesResources.Multiple_Types))))) End If - Return Await CreateContentAsync(services, semanticModel, token, New TokenInformation(types), supportedPlatforms:=Nothing, options, cancellationToken).ConfigureAwait(False) + Return Await CreateContentAsync(services, semanticModel, token, New TokenInformation(types), supportedPlatforms:=Nothing, options, onTheFlyDocsElement:=Nothing, cancellationToken).ConfigureAwait(False) End Function Private Shared Function BuildContentForIntrinsicOperator( diff --git a/src/Features/VisualBasic/Portable/UseAutoProperty/VisualBasicUseAutoPropertyCodeFixProvider.vb b/src/Features/VisualBasic/Portable/UseAutoProperty/VisualBasicUseAutoPropertyCodeFixProvider.vb index 32aae58938712..244c4a39c372c 100644 --- a/src/Features/VisualBasic/Portable/UseAutoProperty/VisualBasicUseAutoPropertyCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/UseAutoProperty/VisualBasicUseAutoPropertyCodeFixProvider.vb @@ -2,6 +2,7 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports System.Collections.Immutable Imports System.Composition Imports System.Diagnostics.CodeAnalysis Imports System.Threading @@ -33,7 +34,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseAutoProperty Return Utilities.GetNodeToRemove(identifier) End Function - Protected Overrides Function GetFormattingRules(document As Document) As IEnumerable(Of AbstractFormattingRule) + Protected Overrides Function GetFormattingRules(document As Document) As ImmutableArray(Of AbstractFormattingRule) Return Nothing End Function diff --git a/src/Features/VisualBasicTest/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersTests.vb b/src/Features/VisualBasicTest/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersTests.vb index bed592676cd94..ab1ada5e8801e 100644 --- a/src/Features/VisualBasicTest/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersTests.vb +++ b/src/Features/VisualBasicTest/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersTests.vb @@ -8,6 +8,7 @@ Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBas Microsoft.CodeAnalysis.AddConstructorParametersFromMembers.AddConstructorParametersFromMembersCodeRefactoringProvider) Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.AddConstructorParametersFromMembers + Public Class AddConstructorParametersFromMembersTests diff --git a/src/Features/VisualBasicTest/AddDebuggerDisplay/AddDebuggerDisplayTests.vb b/src/Features/VisualBasicTest/AddDebuggerDisplay/AddDebuggerDisplayTests.vb index 7c104952fd9aa..7fc1ca8f6de43 100644 --- a/src/Features/VisualBasicTest/AddDebuggerDisplay/AddDebuggerDisplayTests.vb +++ b/src/Features/VisualBasicTest/AddDebuggerDisplay/AddDebuggerDisplayTests.vb @@ -6,7 +6,7 @@ Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBas Microsoft.CodeAnalysis.VisualBasic.AddDebuggerDisplay.VisualBasicAddDebuggerDisplayCodeRefactoringProvider) Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.AddDebuggerDisplay - + Public NotInheritable Class AddDebuggerDisplayTests diff --git a/src/Features/VisualBasicTest/ConvertCast/ConvertDirectCastToTryCastTests.vb b/src/Features/VisualBasicTest/ConvertCast/ConvertDirectCastToTryCastTests.vb index 41532d1e5d868..7e453bdcef496 100644 --- a/src/Features/VisualBasicTest/ConvertCast/ConvertDirectCastToTryCastTests.vb +++ b/src/Features/VisualBasicTest/ConvertCast/ConvertDirectCastToTryCastTests.vb @@ -7,9 +7,9 @@ Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBas Microsoft.CodeAnalysis.VisualBasic.ConvertCast.VisualBasicConvertDirectCastToTryCastCodeRefactoringProvider) Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.ConvertCast + Public Class ConvertDirectCastToTryCastTests - Public Async Function ConvertFromDirectCastToTryCast() As Task Dim markup = " diff --git a/src/Features/VisualBasicTest/ConvertCast/ConvertTryCastToDirectCastTests.vb b/src/Features/VisualBasicTest/ConvertCast/ConvertTryCastToDirectCastTests.vb index 93c6379de90eb..3bb8bd62e4d8c 100644 --- a/src/Features/VisualBasicTest/ConvertCast/ConvertTryCastToDirectCastTests.vb +++ b/src/Features/VisualBasicTest/ConvertCast/ConvertTryCastToDirectCastTests.vb @@ -6,9 +6,9 @@ Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBas Microsoft.CodeAnalysis.VisualBasic.ConvertConversionOperators.VisualBasicConvertTryCastToDirectCastCodeRefactoringProvider) Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.ConvertCast + Public Class ConvertTryCastToDirectCastTests - Public Async Function ConvertFromTryCastToDirectCast() As Task Dim markup = diff --git a/src/Features/VisualBasicTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.vb b/src/Features/VisualBasicTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.vb index 24324c5072dc5..c05675cb2265a 100644 --- a/src/Features/VisualBasicTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.vb +++ b/src/Features/VisualBasicTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.vb @@ -6,6 +6,7 @@ Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBas Microsoft.CodeAnalysis.VisualBasic.ConvertToInterpolatedString.VisualBasicConvertConcatenationToInterpolatedStringRefactoringProvider) Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.ConvertToInterpolatedString + Public Class ConvertConcatenationToInterpolatedStringTests diff --git a/src/Features/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb b/src/Features/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb index 95b726d40e429..e00b01e5dd200 100644 --- a/src/Features/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb +++ b/src/Features/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb @@ -5137,7 +5137,7 @@ End Class edits.VerifySemanticDiagnostics( active, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub @@ -5167,7 +5167,7 @@ End Class edits.VerifySemanticDiagnostics( active, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub @@ -5284,7 +5284,7 @@ End Class edits.VerifySemanticDiagnostics( active, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub @@ -5403,7 +5403,7 @@ End Class edits.VerifySemanticDiagnostics( active, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub @@ -5432,7 +5432,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifySemanticDiagnostics(active, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub @@ -5515,7 +5515,7 @@ End Class ' No rude edit since the AS is within the nested function. edits.VerifySemanticDiagnostics(active, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub diff --git a/src/Features/VisualBasicTest/EditAndContinue/StatementEditingTests.vb b/src/Features/VisualBasicTest/EditAndContinue/StatementEditingTests.vb index d31fbfda9c867..a6550aca7c311 100644 --- a/src/Features/VisualBasicTest/EditAndContinue/StatementEditingTests.vb +++ b/src/Features/VisualBasicTest/EditAndContinue/StatementEditingTests.vb @@ -5309,7 +5309,7 @@ End Class VerifySemanticDiagnostics( editScript:=edits, targetFrameworks:={TargetFramework.Mscorlib40AndSystemCore}, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub #End Region @@ -5419,7 +5419,7 @@ End Class VerifySemanticDiagnostics( edits, targetFrameworks:={TargetFramework.MinimalAsync}, - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub diff --git a/src/Features/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb b/src/Features/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb index 3eaf69d316e74..b1dd74eafa024 100644 --- a/src/Features/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb +++ b/src/Features/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb @@ -4789,6 +4789,10 @@ End Structure "Update [Function F() As Task(Of String)]@11 -> [Async Function F() As Task(Of String)]@11") edits.VerifySemanticDiagnostics( + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) + + edits.VerifySemanticDiagnostics( + {Diagnostic(RudeEditKind.MakeMethodAsyncNotSupportedByRuntime, "Async Function F()")}, capabilities:=EditAndContinueCapabilities.NewTypeDefinition) End Sub @@ -5268,7 +5272,7 @@ End Class" "Update [Function F() As Task(Of String)]@11 -> [Iterator Function F() As Task(Of String)]@11") edits.VerifySemanticDiagnostics( - capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + capabilities:=EditAndContinueCapabilities.NewTypeDefinition Or EditAndContinueCapabilities.AddExplicitInterfaceImplementation) End Sub diff --git a/src/Features/VisualBasicTest/GenerateDefaultConstructors/GenerateDefaultConstructorsTests.vb b/src/Features/VisualBasicTest/GenerateDefaultConstructors/GenerateDefaultConstructorsTests.vb index 7509c513e45bc..e1cfe17d143e7 100644 --- a/src/Features/VisualBasicTest/GenerateDefaultConstructors/GenerateDefaultConstructorsTests.vb +++ b/src/Features/VisualBasicTest/GenerateDefaultConstructors/GenerateDefaultConstructorsTests.vb @@ -13,11 +13,12 @@ Imports VerifyRefactoring = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions. Microsoft.CodeAnalysis.GenerateDefaultConstructors.GenerateDefaultConstructorsCodeRefactoringProvider) Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.GenerateDefaultConstructors + Public Class GenerateDefaultConstructorsTests Private Shared Async Function TestRefactoringAsync(source As String, fixedSource As String, Optional index As Integer = 0) As Task Await TestRefactoringOnlyAsync(source, fixedSource, index) - await TestCodeFixMissingAsync(source) + Await TestCodeFixMissingAsync(source) End Function Private Shared Async Function TestRefactoringOnlyAsync(source As String, fixedSource As String, Optional index As Integer = 0) As Task diff --git a/src/Features/VisualBasicTest/IntroduceParameter/IntroduceParameterTests.vb b/src/Features/VisualBasicTest/IntroduceParameter/IntroduceParameterTests.vb index b4ade51d6f561..634897519851b 100644 --- a/src/Features/VisualBasicTest/IntroduceParameter/IntroduceParameterTests.vb +++ b/src/Features/VisualBasicTest/IntroduceParameter/IntroduceParameterTests.vb @@ -11,6 +11,7 @@ Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBas Microsoft.CodeAnalysis.VisualBasic.IntroduceParameter.VisualBasicIntroduceParameterCodeRefactoringProvider) Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.IntroduceParameter + Public Class IntroduceParameterTests diff --git a/src/Features/VisualBasicTest/InvertIf/InvertMultiLineIfTests.vb b/src/Features/VisualBasicTest/InvertIf/InvertMultiLineIfTests.vb index 03cadc892d57f..de65c9f26ff25 100644 --- a/src/Features/VisualBasicTest/InvertIf/InvertMultiLineIfTests.vb +++ b/src/Features/VisualBasicTest/InvertIf/InvertMultiLineIfTests.vb @@ -2,22 +2,25 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports Microsoft.CodeAnalysis.CodeRefactorings -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces -Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings -Imports Microsoft.CodeAnalysis.VisualBasic.InvertIf +Imports Microsoft.CodeAnalysis.Testing +Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBasicCodeRefactoringVerifier(Of + Microsoft.CodeAnalysis.VisualBasic.InvertIf.VisualBasicInvertMultiLineIfCodeRefactoringProvider) Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.InvertIf - + Public Class InvertMultiLineIfTests - Inherits AbstractVisualBasicCodeActionTest_NoEditor - - Protected Overrides Function CreateCodeRefactoringProvider(workspace As TestWorkspace, parameters As TestParameters) As CodeRefactoringProvider - Return New VisualBasicInvertMultiLineIfCodeRefactoringProvider() + Private Shared Async Function TestInsideSubAsync(initial As String, expected As String, Optional languageVersion As LanguageVersion = LanguageVersion.Latest) As Task + Await TestAsync(CreateTreeText(initial), CreateTreeText(expected), languageVersion) End Function - Public Async Function TestFixOneAsync(initial As String, expected As String, Optional parseOptions As ParseOptions = Nothing) As Task - Await TestInRegularAndScriptAsync(CreateTreeText(initial), CreateTreeText(expected), parseOptions:=parseOptions) + Private Shared Async Function TestAsync(initial As String, expected As String, Optional languageVersion As LanguageVersion = LanguageVersion.Latest) As Task + Await New VerifyVB.Test With + { + .TestCode = initial, + .FixedCode = expected, + .LanguageVersion = languageVersion, + .CompilerDiagnostics = CompilerDiagnostics.None + }.RunAsync() End Function Public Shared Function CreateTreeText(initial As String) As String @@ -55,7 +58,7 @@ End Module Public Async Function TestMultiLineIdentifier() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If a Then aMethod() @@ -74,7 +77,7 @@ End Module Public Async Function TestElseIf() As Task - Await TestMissingInRegularAndScriptAsync( + Dim markup = " Sub Main() If a Then @@ -85,12 +88,14 @@ Sub Main() cMethod() End If End Sub -End Module") +End Module" + + Await TestAsync(markup, markup) End Function Public Async Function TestKeepElseIfKeyword() As Task - Await TestMissingInRegularAndScriptAsync( + Dim markup = "Module Program Sub Main() If a Then @@ -101,12 +106,14 @@ End Module") cMethod() End If End Sub -End Module") +End Module" + + Await TestAsync(markup, markup) End Function Public Async Function TestMissingOnIfElseIfElse() As Task - Await TestMissingInRegularAndScriptAsync( + Dim markup = "Module Program Sub Main() I[||]f a Then @@ -117,12 +124,14 @@ End Module") cMethod() End If End Sub -End Module") +End Module" + + Await TestAsync(markup, markup) End Function Public Async Function TestSelection() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [|If a Then aMethod() @@ -141,7 +150,7 @@ End Module") Public Async Function TestDoesNotOverlapHiddenPosition1() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main() #End ExternalSource @@ -170,7 +179,7 @@ End Module") Public Async Function TestDoesNotOverlapHiddenPosition2() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main() #ExternalSource File.vb 1 @@ -197,7 +206,7 @@ End Module") Public Async Function TestMissingOnOverlapsHiddenPosition1() As Task - Await TestMissingInRegularAndScriptAsync( + Dim markup = "Module Program Sub Main() [||]If a Then @@ -208,12 +217,14 @@ End Module") bMethod() End If End Sub -End Module") +End Module" + + Await TestAsync(markup, markup) End Function Public Async Function TestMissingOnOverlapsHiddenPosition2() As Task - Await TestMissingInRegularAndScriptAsync( + Dim markup = "Module Program Sub Main() If a Then @@ -226,12 +237,14 @@ End Module") cMethod() End If End Sub -End Module") +End Module" + + Await TestAsync(markup, markup) End Function Public Async Function TestMissingOnOverlapsHiddenPosition3() As Task - Await TestMissingInRegularAndScriptAsync( + Dim markup = "Module Program Sub Main() [||]If a Then @@ -244,12 +257,14 @@ End Module") cMethod() End If End Sub -End Module") +End Module" + + Await TestAsync(markup, markup) End Function Public Async Function TestMissingOnOverlapsHiddenPosition4() As Task - Await TestMissingInRegularAndScriptAsync( + Dim markup = "Module Program Sub Main() [||]If a Then @@ -260,12 +275,14 @@ End Module") #End ExternalSource End If End Sub -End Module") +End Module" + + Await TestAsync(markup, markup) End Function Public Async Function TestMissingOnOverlapsHiddenPosition5() As Task - Await TestMissingInRegularAndScriptAsync( + Dim markup = "Module Program Sub Main() [||]If a Then @@ -276,12 +293,14 @@ End Module") #End ExternalSource End If End Sub -End Module") +End Module" + + Await TestAsync(markup, markup) End Function Public Async Function TestMissingOnOverlapsHiddenPosition6() As Task - Await TestMissingInRegularAndScriptAsync( + Dim markup = "Module Program Sub Main() [||]If a Then @@ -292,12 +311,14 @@ End Module") bMethod() End If End Sub -End Module") +End Module" + + Await TestAsync(markup, markup) End Function Public Async Function TestMultipleStatementsMultiLineIfBlock() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main() [||]If a Then @@ -324,7 +345,7 @@ End Module") Public Async Function TestTriviaAfterMultiLineIfBlock() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main() [||]If a Then @@ -347,7 +368,7 @@ End Module") Public Async Function TestKeepExplicitLineContinuationTriviaMethod() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main() I[||]f a And b _ @@ -372,7 +393,7 @@ End Module") Public Async Function TestKeepTriviaInStatementsInMultiLineIfBlock() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main() [||]If a Then @@ -400,7 +421,7 @@ End Module") Public Async Function TestSimplifyToLengthEqualsZero() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main(args As String()) Dim x As String @@ -425,7 +446,7 @@ End Module") Public Async Function TestSimplifyToLengthEqualsZero2() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main(args As String()) Dim x() As String @@ -450,7 +471,7 @@ End Module") Public Async Function TestSimplifyToLengthEqualsZero4() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main(args As String()) Dim x() As String @@ -475,7 +496,7 @@ End Module") Public Async Function TestSimplifyToLengthEqualsZero5() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main(args As String()) Dim x As String @@ -500,7 +521,7 @@ End Module") Public Async Function TestDoesNotSimplifyToLengthEqualsZero() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main(args As String()) Dim x As String @@ -525,7 +546,7 @@ End Module") Public Async Function TestDoesNotSimplifyToLengthEqualsZero2() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main(args As String()) Dim x As String @@ -552,7 +573,7 @@ End Module") Public Async Function TestColonAfterSingleLineIfWithEmptyElse() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main() ' Invert If @@ -570,7 +591,7 @@ End Module") Public Async Function TestOnlyOnElseIf() As Task - Await TestMissingInRegularAndScriptAsync( + Dim markup = "Module Program Sub Main(args As String()) If False Then @@ -581,12 +602,14 @@ End Module") Console.WriteLine(""a"") End If End Sub -End Module") +End Module" + + Await TestAsync(markup, markup) End Function Public Async Function TestOnConditionOfMultiLineIfStatement() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main(args As String()) If [||]False Then @@ -611,12 +634,12 @@ End Module") Public Async Function TestDoNotRemoveTypeCharactersDuringComplexification() As Task Dim markup = - +" Imports System Module Program Sub Main() Goo(Function(take) - [||]If True Then Console.WriteLine("true") Else Console.WriteLine("false") + [||]If True Then Console.WriteLine(""true"") Else Console.WriteLine(""false"") take$.ToString() Return Function() 1 End Function) @@ -626,15 +649,15 @@ Imports System Sub Goo(Of T)(x As Func(Of Integer, T)) End Sub End Module - +" Dim expected = - +" Imports System Module Program Sub Main() Goo(Function(take) - If False Then Console.WriteLine("false") Else Console.WriteLine("true") + If False Then Console.WriteLine(""false"") Else Console.WriteLine(""true"") take$.ToString() Return Function() 1 End Function) @@ -644,14 +667,14 @@ Imports System Sub Goo(Of T)(x As Func(Of Integer, T)) End Sub End Module - +" Await TestAsync(markup, expected) End Function Public Async Function InvertIfWithoutStatements() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "class C sub M(x as String) [||]If x = ""a"" Then @@ -677,7 +700,7 @@ end class") Public Async Function InvertIfWithOnlyComment() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "class C sub M(x as String) [||]If x = ""a"" Then @@ -706,7 +729,7 @@ end class") Public Async Function InvertIfWithoutElse() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "class C sub M(x as String) [||]If x = ""a"" Then @@ -730,7 +753,7 @@ end class") Public Async Function TestMultiLineTypeOfIs_VB12() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If TypeOf a Is String Then aMethod() @@ -744,12 +767,12 @@ end class") Else aMethod() End If -", VisualBasicParseOptions.Default.WithLanguageVersion(LanguageVersion.VisualBasic12)) +", LanguageVersion.VisualBasic12) End Function Public Async Function TestMultiLineTypeOfIs_VB14() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If TypeOf a Is String Then aMethod() @@ -763,12 +786,12 @@ end class") Else aMethod() End If -", VisualBasicParseOptions.Default.WithLanguageVersion(LanguageVersion.VisualBasic14)) +", LanguageVersion.VisualBasic14) End Function Public Async Function TestMultiLineTypeOfIsNot() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If TypeOf a IsNot String Then aMethod() @@ -788,7 +811,7 @@ end class") Public Async Function PreserveSpace() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( " class C sub M(s as string) @@ -819,7 +842,7 @@ end class") Public Async Function PreserveSpace_WithComments() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( " class C sub M(s as string) @@ -858,7 +881,7 @@ end class") Public Async Function PreserveSpace_NoTrivia() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( " class C sub M(s as string) diff --git a/src/Features/VisualBasicTest/InvertIf/InvertSingleLineIfTests.vb b/src/Features/VisualBasicTest/InvertIf/InvertSingleLineIfTests.vb index cb49815aa7f34..41ad87bbb660e 100644 --- a/src/Features/VisualBasicTest/InvertIf/InvertSingleLineIfTests.vb +++ b/src/Features/VisualBasicTest/InvertIf/InvertSingleLineIfTests.vb @@ -2,22 +2,24 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports Microsoft.CodeAnalysis.CodeRefactorings -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces -Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings -Imports Microsoft.CodeAnalysis.VisualBasic.InvertIf +Imports Microsoft.CodeAnalysis.Testing +Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBasicCodeRefactoringVerifier(Of + Microsoft.CodeAnalysis.VisualBasic.InvertIf.VisualBasicInvertSingleLineIfCodeRefactoringProvider) Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.InvertIf - + Public Class InvertSingleLineIfTests - Inherits AbstractVisualBasicCodeActionTest_NoEditor - - Protected Overrides Function CreateCodeRefactoringProvider(workspace As TestWorkspace, parameters As TestParameters) As CodeRefactoringProvider - Return New VisualBasicInvertSingleLineIfCodeRefactoringProvider() + Private Shared Async Function TestInsideSubAsync(initial As String, expected As String) As Task + Await TestAsync(CreateTreeText(initial), CreateTreeText(expected)) End Function - Public Async Function TestFixOneAsync(initial As String, expected As String) As Task - Await TestInRegularAndScriptAsync(CreateTreeText(initial), CreateTreeText(expected)) + Private Shared Async Function TestAsync(initial As String, expected As String) As Task + Await New VerifyVB.Test With + { + .TestCode = initial, + .FixedCode = expected, + .CompilerDiagnostics = CompilerDiagnostics.None + }.RunAsync() End Function Public Shared Function CreateTreeText(initial As String) As String @@ -55,7 +57,7 @@ End Module Public Async Function TestAnd() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If a And b Then aMethod() Else bMethod() ", @@ -67,7 +69,7 @@ End Module Public Async Function TestAddEmptyArgumentListIfNeeded() As Task Dim markup = - +" Module A Sub Main() [||]If True Then : Goo : Goo @@ -77,14 +79,14 @@ Module A Sub Goo() End Sub End Module - +" - Await TestMissingAsync(markup) + Await TestAsync(markup, markup) End Function Public Async Function TestAndAlso() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If a AndAlso b Then aMethod() Else bMethod() ", @@ -95,7 +97,7 @@ End Module Public Async Function TestCall() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If a.Goo() Then aMethod() Else bMethod() ", @@ -106,7 +108,7 @@ End Module Public Async Function TestNotIdentifier() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If Not a Then aMethod() Else bMethod() ", @@ -117,7 +119,7 @@ End Module Public Async Function TestTrueLiteral() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If True Then aMethod() Else bMethod() ", @@ -128,7 +130,7 @@ End Module Public Async Function TestFalseLiteral() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If False Then aMethod() Else bMethod() ", @@ -139,7 +141,7 @@ End Module Public Async Function TestEquals() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If a = b Then aMethod() Else bMethod() ", @@ -150,7 +152,7 @@ End Module Public Async Function TestNotEquals() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If a <> b Then aMethod() Else bMethod() ", @@ -161,7 +163,7 @@ End Module Public Async Function TestLessThan() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If a < b Then aMethod() Else bMethod() ", @@ -172,7 +174,7 @@ End Module Public Async Function TestLessThanOrEqual() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If a <= b Then aMethod() Else bMethod() ", @@ -183,7 +185,7 @@ End Module Public Async Function TestGreaterThan() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If a > b Then aMethod() Else bMethod() ", @@ -194,7 +196,7 @@ End Module Public Async Function TestGreaterThanOrEqual() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If a >= b Then aMethod() Else bMethod() ", @@ -205,7 +207,7 @@ End Module Public Async Function TestIs() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " Dim myObject As New Object Dim thisObject = myObject @@ -222,7 +224,7 @@ End Module Public Async Function TestIsNot() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " Dim myObject As New Object Dim thisObject = myObject @@ -239,7 +241,7 @@ End Module Public Async Function TestOr() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If a Or b Then aMethod() Else bMethod() ", @@ -250,7 +252,7 @@ End Module Public Async Function TestOrElse() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If a OrElse b Then aMethod() Else bMethod() ", @@ -261,7 +263,7 @@ End Module Public Async Function TestOr2() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " I[||]f Not a Or Not b Then aMethod() Else bMethod() ", @@ -272,7 +274,7 @@ End Module Public Async Function TestOrElse2() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " I[||]f Not a OrElse Not b Then aMethod() Else bMethod() ", @@ -283,7 +285,7 @@ End Module Public Async Function TestAnd2() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If Not a And Not b Then aMethod() Else bMethod() ", @@ -294,7 +296,7 @@ End Module Public Async Function TestAndAlso2() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If Not a AndAlso Not b Then aMethod() Else bMethod() ", @@ -305,7 +307,7 @@ End Module Public Async Function TestXor() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " I[||]f a Xor b Then aMethod() Else bMethod() ", @@ -317,7 +319,7 @@ End Module Public Async Function TestXor2() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " I[||]f Not (a Xor b) Then aMethod() Else bMethod() ", @@ -328,7 +330,7 @@ End Module Public Async Function TestNested() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If (((a = b) AndAlso (c <> d)) OrElse ((e < f) AndAlso (Not g))) Then aMethod() Else bMethod() ", @@ -340,7 +342,7 @@ End Module Public Async Function TestEscapeKeywordsIfNeeded1() As Task Dim markup = - +" Imports System.Linq Module Program Sub Main() @@ -350,15 +352,15 @@ Module Program Sub Take() End Sub End Module - +" - Await TestMissingAsync(markup) + Await TestAsync(markup, markup) End Function Public Async Function TestEscapeKeywordsIfNeeded2() As Task Dim markup = - +" Imports System.Linq Module Program Sub Main() @@ -368,15 +370,15 @@ Module Program Sub Ascending() End Sub End Module - +" - Await TestMissingAsync(markup) + Await TestAsync(markup, markup) End Function Public Async Function TestEscapeKeywordsIfNeeded3() As Task Dim markup = - +" Imports System.Linq Module Program Sub Main() @@ -386,15 +388,15 @@ Module Program Sub Ascending() End Sub End Module - +" - Await TestMissingAsync(markup) + Await TestAsync(markup, markup) End Function Public Async Function TestEscapeKeywordsIfNeeded4() As Task Dim markup = - +" Imports System.Linq Module Program Sub Main() @@ -402,26 +404,15 @@ Module Program Take: Return End Sub End Module - - - Dim expected = - -Imports System.Linq -Module Program - Sub Main() - If False Then Console.WriteLine() Else Dim q = From x In "" -[Take]: Return - End Sub -End Module - +" - Await TestMissingAsync(markup) + Await TestAsync(markup, markup) End Function Public Async Function TestEscapeKeywordsIfNeeded5() As Task Dim markup = - +" Imports System.Linq Module Program Sub Main() @@ -431,14 +422,14 @@ Module Program Sub Take() End Sub End Module - +" - Await TestMissingAsync(markup) + Await TestAsync(markup, markup) End Function Public Async Function TestSelection() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [|If a And b Then aMethod() Else bMethod()|] ", @@ -449,7 +440,7 @@ End Module Public Async Function TestMultipleStatementsSingleLineIfStatement() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " If[||] a Then aMethod() : bMethod() Else cMethod() : d() ", @@ -460,7 +451,7 @@ End Module Public Async Function TestTriviaAfterSingleLineIfStatement() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If a Then aMethod() Else bMethod() ' I will stay put ", @@ -470,7 +461,7 @@ End Module End Function Public Async Function TestParenthesizeForLogicalExpressionPrecedence() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Sub Main() I[||]f a AndAlso b Or c Then aMethod() Else bMethod() End Sub @@ -483,7 +474,7 @@ End Module") Public Async Function TestParenthesizeComparisonOperands() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If 0 <= .GetHashCode Then aMethod() Else bMethod() ", @@ -496,7 +487,7 @@ End Module") Public Async Function TestNestedSingleLineIfs() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main() ' Invert the 1st If @@ -513,18 +504,20 @@ End Module") Public Async Function TestTryToParenthesizeAwkwardSyntaxInsideSingleLineLambdaMethod() As Task - Await TestMissingAsync( + Dim markup = "Module Program Sub Main() ' Invert If Dim x = Sub() I[||]f True Then Dim y Else Console.WriteLine(), z = 1 End Sub -End Module") +End Module" + + Await TestAsync(markup, markup) End Function Public Async Function TestOnConditionOfSingleLineIf() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub Main(args As String()) If T[||]rue Then Return Else Console.WriteLine(""a"") @@ -541,42 +534,42 @@ End Module") Public Async Function TestImplicitLineContinuationBeforeClosingParenIsRemoved() As Task Dim markup = - +" [||]If (True OrElse True ) Then Else End If - +" Dim expected = - +" If False AndAlso False Then Else End If - +" - Await TestAsync(markup, expected) + Await TestInsideSubAsync(markup, expected) End Function Public Async Function TestParenthesizeToKeepParseTheSame1() As Task Dim markup = - +" Module Program Sub Main() - [||]If 0 >= <x/>.GetHashCode Then Console.WriteLine(1) Else Console.WriteLine(2) + [||]If 0 >= .GetHashCode Then Console.WriteLine(1) Else Console.WriteLine(2) End Sub End Module - +" Dim expected = - +" Module Program Sub Main() - If 0 < (<x/>.GetHashCode) Then Console.WriteLine(2) Else Console.WriteLine(1) + If 0 < (.GetHashCode) Then Console.WriteLine(2) Else Console.WriteLine(1) End Sub End Module - +" Await TestAsync(markup, expected) End Function @@ -584,7 +577,7 @@ End Module Public Async Function TestParenthesizeToKeepParseTheSame2() As Task Dim markup = - +" Module Program Sub Main() Select Nothing @@ -592,14 +585,14 @@ Module Program End Select End Sub End Module - +" - Await TestMissingAsync(markup) + Await TestAsync(markup, markup) End Function Public Async Function TestSingleLineIdentifier() As Task - Await TestFixOneAsync( + Await TestInsideSubAsync( " [||]If a Then aMethod() Else bMethod() ", @@ -610,7 +603,7 @@ End Module Public Async Function TestWithMissingTrueStatementWithinUsing() As Task - Await TestInRegularAndScriptAsync( + Await TestAsync( "Module Program Sub M(Disposable As IDisposable) Dim x = True diff --git a/src/Features/VisualBasicTest/ReplaceConditionalWithStatements/ReplaceConditionalWithStatementsTests.vb b/src/Features/VisualBasicTest/ReplaceConditionalWithStatements/ReplaceConditionalWithStatementsTests.vb index 98c7f2af1f35a..c94569a532656 100644 --- a/src/Features/VisualBasicTest/ReplaceConditionalWithStatements/ReplaceConditionalWithStatementsTests.vb +++ b/src/Features/VisualBasicTest/ReplaceConditionalWithStatements/ReplaceConditionalWithStatementsTests.vb @@ -15,7 +15,7 @@ Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBas Microsoft.CodeAnalysis.VisualBasic.ReplaceConditionalWithStatements.VisualBasicReplaceConditionalWithStatementsCodeRefactoringProvider) Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.ReplaceConditionalWithStatements - + Public Class ReplaceConditionalWithStatementsTests Public Async Function TestAssignment_ObjectType() As Task diff --git a/src/Features/VisualBasicTest/SimplifyTypeNames/SimplifyTypeNamesTests.vb b/src/Features/VisualBasicTest/SimplifyTypeNames/SimplifyTypeNamesTests.vb index 67e10d591bdcc..9ee02703f1680 100644 --- a/src/Features/VisualBasicTest/SimplifyTypeNames/SimplifyTypeNamesTests.vb +++ b/src/Features/VisualBasicTest/SimplifyTypeNames/SimplifyTypeNamesTests.vb @@ -9,6 +9,7 @@ Imports Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics +Imports Microsoft.CodeAnalysis.Remote.Testing Imports Microsoft.CodeAnalysis.VisualBasic.CodeFixes.SimplifyTypeNames Imports Microsoft.CodeAnalysis.VisualBasic.SimplifyTypeNames @@ -1821,7 +1822,7 @@ End Module Await TestInRegularAndScriptAsync(source.Value, expected.Value) - Using workspace = TestWorkspace.CreateVisualBasic(source.Value, composition:=GetComposition()) + Using workspace = TestWorkspace.CreateVisualBasic(source.Value, composition:=GetComposition().WithTestHostParts(TestHost.OutOfProcess)) Dim diagnosticAndFixes = Await GetDiagnosticAndFixesAsync(workspace, New TestParameters()) Dim span = diagnosticAndFixes.Item1.First().Location.SourceSpan Assert.NotEqual(span.Start, 0) @@ -1869,7 +1870,7 @@ End Namespace Await TestInRegularAndScriptAsync(source.Value, expected.Value) - Using workspace = TestWorkspace.CreateVisualBasic(source.Value, composition:=GetComposition()) + Using workspace = TestWorkspace.CreateVisualBasic(source.Value, composition:=GetComposition().WithTestHostParts(TestHost.OutOfProcess)) Dim diagnosticAndFixes = Await GetDiagnosticAndFixesAsync(workspace, New TestParameters()) Dim span = diagnosticAndFixes.Item1.First().Location.SourceSpan Assert.Equal(span.Start, expected.Value.ReplaceLineEndings(vbLf).IndexOf("new C", StringComparison.Ordinal) + 4) @@ -1903,7 +1904,7 @@ End Module Await TestInRegularAndScriptAsync(source.Value, expected.Value) - Using workspace = TestWorkspace.CreateVisualBasic(source.Value, composition:=GetComposition()) + Using workspace = TestWorkspace.CreateVisualBasic(source.Value, composition:=GetComposition().WithTestHostParts(TestHost.OutOfProcess)) Dim diagnosticAndFixes = Await GetDiagnosticAndFixesAsync(workspace, New TestParameters()) Dim span = diagnosticAndFixes.Item1.First().Location.SourceSpan Assert.Equal(span.Start, expected.Value.ReplaceLineEndings(vbLf).IndexOf("Console.WriteLine(""goo"")", StringComparison.Ordinal)) diff --git a/src/NuGet/VS.ExternalAPIs.Roslyn.Package/VS.ExternalAPIs.Roslyn.Package.csproj b/src/NuGet/VS.ExternalAPIs.Roslyn.Package/VS.ExternalAPIs.Roslyn.Package.csproj index d62e1c2961bef..6ec1177df567e 100644 --- a/src/NuGet/VS.ExternalAPIs.Roslyn.Package/VS.ExternalAPIs.Roslyn.Package.csproj +++ b/src/NuGet/VS.ExternalAPIs.Roslyn.Package/VS.ExternalAPIs.Roslyn.Package.csproj @@ -49,7 +49,6 @@ - diff --git a/src/Scripting/CSharpTest/InteractiveSessionTests.cs b/src/Scripting/CSharpTest/InteractiveSessionTests.cs index 10c6016032737..24e7362698c70 100644 --- a/src/Scripting/CSharpTest/InteractiveSessionTests.cs +++ b/src/Scripting/CSharpTest/InteractiveSessionTests.cs @@ -1215,7 +1215,7 @@ public interface I public class C : I { public int F() => 1; -}", new MetadataReference[] { NetStandard13.SystemRuntime, lib1.ToMetadataReference() }); +}", new MetadataReference[] { NetStandard13.References.SystemRuntime, lib1.ToMetadataReference() }); lib2.Emit(file2.Path); @@ -1277,7 +1277,7 @@ public void ExtensionPriority1() var main = CreateCSharpCompilation( @"public static class M { public static readonly C X = new C(); }", - new MetadataReference[] { NetStandard13.SystemRuntime, libExe.ToMetadataReference() }, + new MetadataReference[] { NetStandard13.References.SystemRuntime, libExe.ToMetadataReference() }, mainName); var exeImage = libExe.EmitToArray(); @@ -1307,7 +1307,7 @@ public void ExtensionPriority2() var main = CreateCSharpCompilation( @"public static class M { public static readonly C X = new C(); }", - new MetadataReference[] { NetStandard13.SystemRuntime, libExe.ToMetadataReference() }, + new MetadataReference[] { NetStandard13.References.SystemRuntime, libExe.ToMetadataReference() }, mainName); var exeImage = libExe.EmitToArray(); diff --git a/src/Scripting/CoreTestUtilities/TestCompilationFactory.cs b/src/Scripting/CoreTestUtilities/TestCompilationFactory.cs index b48f3ddb82ca7..eb729b278204d 100644 --- a/src/Scripting/CoreTestUtilities/TestCompilationFactory.cs +++ b/src/Scripting/CoreTestUtilities/TestCompilationFactory.cs @@ -22,7 +22,7 @@ internal static Compilation CreateCSharpCompilationWithCorlib(string source, str return CSharpCompilation.Create( assemblyName ?? Guid.NewGuid().ToString(), new[] { CSharp.SyntaxFactory.ParseSyntaxTree(SourceText.From(source, encoding: null, SourceHashAlgorithms.Default)) }, - new[] { NetStandard13.SystemRuntime }, + new[] { NetStandard13.References.SystemRuntime }, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); } @@ -31,7 +31,7 @@ internal static Compilation CreateVisualBasicCompilationWithCorlib(string source return VisualBasicCompilation.Create( assemblyName ?? Guid.NewGuid().ToString(), new[] { VisualBasic.SyntaxFactory.ParseSyntaxTree(SourceText.From(source, encoding: null, SourceHashAlgorithms.Default)) }, - new[] { NetStandard13.SystemRuntime }, + new[] { NetStandard13.References.SystemRuntime }, new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); } diff --git a/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj b/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj index d3e2de0ff5753..e76457a1358fc 100644 --- a/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj +++ b/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj @@ -22,7 +22,6 @@ - diff --git a/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs b/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs index 400df749f61f7..a501677df6270 100644 --- a/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs +++ b/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs @@ -53,7 +53,7 @@ public async Task RunAsync(CancellationToken cancellationToken) if (usePersistentStorage) { var persistentStorageService = _workspace.Services.SolutionServices.GetPersistentStorageService(); - await using var persistentStorage = await persistentStorageService.GetStorageAsync(SolutionKey.ToSolutionKey(_workspace.CurrentSolution), cancellationToken).ConfigureAwait(false); + var persistentStorage = await persistentStorageService.GetStorageAsync(SolutionKey.ToSolutionKey(_workspace.CurrentSolution), cancellationToken).ConfigureAwait(false); if (persistentStorage is NoOpPersistentStorage) { throw new InvalidOperationException("Benchmark is not configured to use persistent storage."); diff --git a/src/Tools/BuildBoss/SolutionCheckerUtil.cs b/src/Tools/BuildBoss/SolutionCheckerUtil.cs index 5b63e86c46a2f..92256e6695dc3 100644 --- a/src/Tools/BuildBoss/SolutionCheckerUtil.cs +++ b/src/Tools/BuildBoss/SolutionCheckerUtil.cs @@ -109,7 +109,7 @@ private bool CheckDuplicate(TextWriter textWriter, out Dictionary private bool CheckProjectSystemGuid(TextWriter textWriter, IEnumerable dataList) { - Guid getExpectedGuid(ProjectData data) + static Guid getExpectedGuid(ProjectData data) { var util = data.ProjectUtil; switch (ProjectEntryUtil.GetProjectFileType(data.FilePath)) diff --git a/src/Tools/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs b/src/Tools/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs index 0314ebdb89ff3..e2e5d1d11dffb 100644 --- a/src/Tools/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs +++ b/src/Tools/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs @@ -17,4 +17,5 @@ internal interface IExternalCSharpCopilotCodeAnalysisService Task> AnalyzeDocumentAsync(Document document, TextSpan? span, string promptTitle, CancellationToken cancellationToken); Task> GetCachedDiagnosticsAsync(Document document, string promptTitle, CancellationToken cancellationToken); Task StartRefinementSessionAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken); + Task GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken); } diff --git a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs b/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs index aae8ad275f898..0f65dd06ac618 100644 --- a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs +++ b/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs @@ -28,7 +28,7 @@ internal abstract class AbstractCopilotCodeAnalysisService(IDiagnosticsRefresher { // The _diagnosticsCache is a cache for computed diagnostics via `AnalyzeDocumentAsync`. // Each document maps to a dictionary, which in tern maps a prompt title to a list of existing Diagnostics and a boolean flag. - // The list of diangostics represents the diagnostics computed for the document under the given prompt title, + // The list of diagnostics represents the diagnostics computed for the document under the given prompt title, // the boolean flag indicates whether the diagnostics result is for the entire document. // This cache is used to avoid duplicate analysis calls by storing the computed diagnostics for each document and prompt title. private readonly ConditionalWeakTable Diagnostics, bool IsCompleteResult)>> _diagnosticsCache = new(); @@ -38,6 +38,7 @@ internal abstract class AbstractCopilotCodeAnalysisService(IDiagnosticsRefresher protected abstract Task> AnalyzeDocumentCoreAsync(Document document, TextSpan? span, string promptTitle, CancellationToken cancellationToken); protected abstract Task> GetCachedDiagnosticsCoreAsync(Document document, string promptTitle, CancellationToken cancellationToken); protected abstract Task StartRefinementSessionCoreAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken); + protected abstract Task GetOnTheFlyDocsCoreAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken); public Task IsAvailableAsync(CancellationToken cancellationToken) => IsAvailableCoreAsync(cancellationToken); @@ -169,4 +170,12 @@ public async Task StartRefinementSessionAsync(Document oldDocument, Document new if (await service.IsRefineOptionEnabledAsync().ConfigureAwait(false)) await StartRefinementSessionCoreAsync(oldDocument, newDocument, primaryDiagnostic, cancellationToken).ConfigureAwait(false); } + + public async Task GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken) + { + if (!await IsAvailableAsync(cancellationToken).ConfigureAwait(false)) + return string.Empty; + + return await GetOnTheFlyDocsCoreAsync(symbolSignature, declarationCode, language, cancellationToken).ConfigureAwait(false); + } } diff --git a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.ReflectionWrapper.cs b/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.ReflectionWrapper.cs index adb3f8264ab57..425f5366bf931 100644 --- a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.ReflectionWrapper.cs +++ b/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.ReflectionWrapper.cs @@ -19,6 +19,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.Analyzer.CSharp using GetCachedDiagnosticsAsyncDelegateType = Func>>; using IsAvailableAsyncDelegateType = Func>; using StartRefinementSessionAsyncDelegateType = Func; +using GetOnTheFlyDocsAsyncDelegateType = Func, string, CancellationToken, Task>; internal sealed partial class CSharpCopilotCodeAnalysisService { @@ -33,6 +34,7 @@ private sealed class ReflectionWrapper : IExternalCSharpCopilotCodeAnalysisServi private const string AnalyzeDocumentAsyncMethodName = "AnalyzeDocumentAsync"; private const string GetCachedDiagnosticsAsyncMethodName = "GetCachedDiagnosticsAsync"; private const string StartRefinementSessionAsyncMethodName = "StartRefinementSessionAsync"; + private const string GetOnTheFlyDocsAsyncMethodName = "GetOnTheFlyDocsAsync"; // Create and cache closed delegate to ensure we use a singleton object and with better performance. private readonly Type? _analyzerType; @@ -42,6 +44,7 @@ private sealed class ReflectionWrapper : IExternalCSharpCopilotCodeAnalysisServi private readonly Lazy _lazyAnalyzeDocumentAsyncDelegate; private readonly Lazy _lazyGetCachedDiagnosticsAsyncDelegate; private readonly Lazy _lazyStartRefinementSessionAsyncDelegate; + private readonly Lazy _lazyGetOnTheFlyDocsAsyncDelegate; public ReflectionWrapper(IServiceProvider serviceProvider, IVsService brokeredServiceContainer) { @@ -69,6 +72,7 @@ public ReflectionWrapper(IServiceProvider serviceProvider, IVsService(string methodName, Type[] types) where T : Delegate @@ -104,6 +108,9 @@ public ReflectionWrapper(IServiceProvider serviceProvider, IVsService CreateDelegate(StartRefinementSessionAsyncMethodName, [typeof(Document), typeof(Document), typeof(Diagnostic), typeof(CancellationToken)]); + private GetOnTheFlyDocsAsyncDelegateType? CreateGetOnTheFlyDocsAsyncDelegate() + => CreateDelegate(GetOnTheFlyDocsAsyncMethodName, [typeof(string), typeof(ImmutableArray), typeof(string), typeof(CancellationToken)]); + public async Task IsAvailableAsync(CancellationToken cancellationToken) { if (_lazyIsAvailableAsyncDelegate.Value is null) @@ -143,5 +150,13 @@ public Task StartRefinementSessionAsync(Document oldDocument, Document newDocume return _lazyStartRefinementSessionAsyncDelegate.Value(oldDocument, newDocument, primaryDiagnostic, cancellationToken); } + + public async Task GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken) + { + if (_lazyGetOnTheFlyDocsAsyncDelegate.Value is null) + return string.Empty; + + return await _lazyGetOnTheFlyDocsAsyncDelegate.Value(symbolSignature, declarationCode, language, cancellationToken).ConfigureAwait(false); + } } } diff --git a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs b/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs index 2bac22ef7fb92..e9f1006c59e01 100644 --- a/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs +++ b/src/Tools/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs @@ -57,6 +57,9 @@ protected override Task IsAvailableCoreAsync(CancellationToken cancellatio protected override Task StartRefinementSessionCoreAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken) => _lazyExternalCopilotService.Value.StartRefinementSessionAsync(oldDocument, newDocument, primaryDiagnostic, cancellationToken); + protected override Task GetOnTheFlyDocsCoreAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken) + => _lazyExternalCopilotService.Value.GetOnTheFlyDocsAsync(symbolSignature, declarationCode, language, cancellationToken); + protected override async Task> GetDiagnosticsIntersectWithSpanAsync( Document document, IReadOnlyList diagnostics, TextSpan span, CancellationToken cancellationToken) { diff --git a/src/Tools/ExternalAccess/Copilot/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/Copilot/InternalAPI.Unshipped.txt index f2eec4444e3eb..0f4024be9597d 100644 --- a/src/Tools/ExternalAccess/Copilot/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/Copilot/InternalAPI.Unshipped.txt @@ -8,6 +8,7 @@ Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysis Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysisService.AnalyzeDocumentAsync(Microsoft.CodeAnalysis.Document! document, Microsoft.CodeAnalysis.Text.TextSpan? span, string! promptTitle, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysisService.GetAvailablePromptTitlesAsync(Microsoft.CodeAnalysis.Document! document, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysisService.GetCachedDiagnosticsAsync(Microsoft.CodeAnalysis.Document! document, string! promptTitle, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysisService.GetOnTheFlyDocsAsync(string! symbolSignature, System.Collections.Immutable.ImmutableArray declarationCode, string! language, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysisService.IsAvailableAsync(System.Threading.CancellationToken cancellation) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysisService.StartRefinementSessionAsync(Microsoft.CodeAnalysis.Document! oldDocument, Microsoft.CodeAnalysis.Document! newDocument, Microsoft.CodeAnalysis.Diagnostic? primaryDiagnostic, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! override Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper.Equals(object? obj) -> bool diff --git a/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs b/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs index 42525a23e8f98..fe18ef4273008 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Immutable; using System.Composition; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -37,12 +36,12 @@ public async Task SearchDocumentAsync( Document document, string searchPattern, IImmutableSet kinds, - Func onResultFound, + Func, Task> onResultsFound, CancellationToken cancellationToken) { var results = await _service.SearchDocumentAsync(document, searchPattern, kinds, cancellationToken).ConfigureAwait(false); - foreach (var result in results) - await onResultFound(new InternalFSharpNavigateToSearchResult(result)).ConfigureAwait(false); + if (results.Length > 0) + await onResultsFound(results.SelectAsArray(result => (INavigateToSearchResult)new InternalFSharpNavigateToSearchResult(result))).ConfigureAwait(false); } public async Task SearchProjectsAsync( @@ -52,7 +51,7 @@ public async Task SearchProjectsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -62,8 +61,8 @@ public async Task SearchProjectsAsync( foreach (var project in projects) { var results = await _service.SearchProjectAsync(project, priorityDocuments, searchPattern, kinds, cancellationToken).ConfigureAwait(false); - foreach (var result in results) - await onResultFound(project, new InternalFSharpNavigateToSearchResult(result)).ConfigureAwait(false); + if (results.Length > 0) + await onResultsFound(results.SelectAsArray(result => (INavigateToSearchResult)new InternalFSharpNavigateToSearchResult(result))).ConfigureAwait(false); await onProjectCompleted().ConfigureAwait(false); } diff --git a/src/Tools/ExternalAccess/OmniSharp/Formatting/OmniSharpFormatter.cs b/src/Tools/ExternalAccess/OmniSharp/Formatting/OmniSharpFormatter.cs index 23a9bfe717622..f8b81391c6405 100644 --- a/src/Tools/ExternalAccess/OmniSharp/Formatting/OmniSharpFormatter.cs +++ b/src/Tools/ExternalAccess/OmniSharp/Formatting/OmniSharpFormatter.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Formatting internal static class OmniSharpFormatter { public static Task FormatAsync(Document document, IEnumerable? spans, OmniSharpSyntaxFormattingOptionsWrapper options, CancellationToken cancellationToken) - => Formatter.FormatAsync(document, spans, options.CleanupOptions.FormattingOptions, rules: null, cancellationToken); + => Formatter.FormatAsync(document, spans, options.CleanupOptions.FormattingOptions, rules: default, cancellationToken); public static async Task OrganizeImportsAsync(Document document, OmniSharpOrganizeImportsOptionsWrapper options, CancellationToken cancellationToken) { diff --git a/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs b/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs index 0f5bcdcd96a74..49275c191a188 100644 --- a/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs +++ b/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Navigation; using Microsoft.CodeAnalysis.NavigateTo; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.NavigateTo; @@ -25,7 +26,7 @@ public static Task SearchAsync( var searcher = NavigateToSearcher.Create( solution, AsynchronousOperationListenerProvider.NullListener, - new OmniSharpNavigateToCallbackImpl(callback), + new OmniSharpNavigateToCallbackImpl(solution, callback), searchPattern, kinds, disposalToken: CancellationToken.None); @@ -33,23 +34,27 @@ public static Task SearchAsync( return searcher.SearchAsync(NavigateToSearchScope.Solution, cancellationToken); } - private sealed class OmniSharpNavigateToCallbackImpl(OmniSharpNavigateToCallback callback) : INavigateToSearchCallback + private sealed class OmniSharpNavigateToCallbackImpl(Solution solution, OmniSharpNavigateToCallback callback) : INavigateToSearchCallback { - public async Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) + public async Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { - var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(project.Solution, cancellationToken).ConfigureAwait(false); - var omniSharpResult = new OmniSharpNavigateToSearchResult( - result.AdditionalInformation, - result.Kind, - (OmniSharpNavigateToMatchKind)result.MatchKind, - result.IsCaseSensitive, - result.Name, - result.NameMatchSpans, - result.SecondarySort, - result.Summary!, - new OmniSharpNavigableItem(result.NavigableItem.DisplayTaggedParts, document, result.NavigableItem.SourceSpan)); + foreach (var result in results) + { + var project = solution.GetRequiredProject(result.NavigableItem.Document.Project.Id); + var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(project.Solution, cancellationToken).ConfigureAwait(false); + var omniSharpResult = new OmniSharpNavigateToSearchResult( + result.AdditionalInformation, + result.Kind, + (OmniSharpNavigateToMatchKind)result.MatchKind, + result.IsCaseSensitive, + result.Name, + result.NameMatchSpans, + result.SecondarySort, + result.Summary!, + new OmniSharpNavigableItem(result.NavigableItem.DisplayTaggedParts, document, result.NavigableItem.SourceSpan)); - await callback(project, omniSharpResult, cancellationToken).ConfigureAwait(false); + await callback(project, omniSharpResult, cancellationToken).ConfigureAwait(false); + } } public void Done(bool isFullyLoaded) diff --git a/src/Tools/ExternalAccess/Razor/ChecksumWrapper.cs b/src/Tools/ExternalAccess/Razor/ChecksumWrapper.cs new file mode 100644 index 0000000000000..e8986f09ca04d --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/ChecksumWrapper.cs @@ -0,0 +1,34 @@ +// 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; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor; + +internal readonly struct ChecksumWrapper(Checksum checksum) : IEquatable +{ + private readonly Checksum _value = checksum; + + public override int GetHashCode() + { + return _value.GetHashCode(); + } + + public override bool Equals(object? obj) + { + if (obj is ChecksumWrapper wrapper) + { + return Equals(wrapper); + } + return false; + } + + public bool Equals(ChecksumWrapper other) + { + return _value.Equals(other._value); + } + + public override string ToString() + => _value.ToString(); +} diff --git a/src/Tools/ExternalAccess/Razor/RazorAnalyzerAssemblyResolver.cs b/src/Tools/ExternalAccess/Razor/RazorAnalyzerAssemblyResolver.cs new file mode 100644 index 0000000000000..85c36e8c54a15 --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/RazorAnalyzerAssemblyResolver.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using System.Diagnostics; +using System.Reflection; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Composition; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor +{ + [Export(typeof(IAnalyzerAssemblyResolver)), Shared] + internal class RazorAnalyzerAssemblyResolver : IAnalyzerAssemblyResolver + { + private static Func? s_assemblyResolver; + + /// + /// We use this as a heuristic to catch a case where we set the resolver too + /// late and the resolver has already been asked to resolve a razor assembly. + /// + /// Note this isn't perfectly accurate but is only used to trigger an assert + /// in debug builds. + /// + private static bool s_razorRequested = false; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public RazorAnalyzerAssemblyResolver() + { + } + + internal static Func? AssemblyResolver + { + get => s_assemblyResolver; + set + { + Debug.Assert(s_assemblyResolver == null, "Assembly resolver should not be set multiple times."); + Debug.Assert(!s_razorRequested, "A razor assembly has already been requested before setting the resolver."); + s_assemblyResolver = value; + } + } + + public Assembly? ResolveAssembly(AssemblyName assemblyName) + { + s_razorRequested |= assemblyName.FullName.Contains("Razor"); + return s_assemblyResolver?.Invoke(assemblyName); + } + } +} diff --git a/src/Tools/ExternalAccess/Razor/RazorUri.cs b/src/Tools/ExternalAccess/Razor/RazorUri.cs index e83ddc1dffb67..dd0e03df5dab1 100644 --- a/src/Tools/ExternalAccess/Razor/RazorUri.cs +++ b/src/Tools/ExternalAccess/Razor/RazorUri.cs @@ -4,7 +4,6 @@ using System; using Microsoft.CodeAnalysis.LanguageServer; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.Razor; @@ -13,8 +12,6 @@ internal static class RazorUri public static Uri CreateAbsoluteUri(string absolutePath) => ProtocolConversions.CreateAbsoluteUri(absolutePath); - public static Uri CreateUri(this TextDocument document) - { - return document.GetURI(); - } + public static string GetDocumentFilePathFromUri(Uri uri) + => ProtocolConversions.GetDocumentFilePathFromUri(uri); } diff --git a/src/Tools/ExternalAccess/Razor/SolutionExtensions.cs b/src/Tools/ExternalAccess/Razor/SolutionExtensions.cs new file mode 100644 index 0000000000000..358ea29bf613b --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/SolutionExtensions.cs @@ -0,0 +1,20 @@ +// 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; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor; + +internal static class SolutionExtensions +{ + public static ImmutableArray GetTextDocuments(this Solution solution, Uri documentUri) + => LanguageServer.Extensions.GetTextDocuments(solution, documentUri); + + public static ImmutableArray GetDocumentIds(this Solution solution, Uri documentUri) + => LanguageServer.Extensions.GetDocumentIds(solution, documentUri); + + public static int GetWorkspaceVersion(this Solution solution) + => solution.WorkspaceVersion; +} diff --git a/src/Tools/ExternalAccess/Razor/TextDocumentExtensions.cs b/src/Tools/ExternalAccess/Razor/TextDocumentExtensions.cs new file mode 100644 index 0000000000000..d9ef4e5898b5a --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/TextDocumentExtensions.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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServer; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor; + +internal static class TextDocumentExtensions +{ + public static Uri CreateUri(this TextDocument document) + => document.GetURI(); + + public static async Task GetChecksumAsync(this TextDocument document, CancellationToken cancellationToken) + => new ChecksumWrapper(await document.State.GetChecksumAsync(cancellationToken).ConfigureAwait(false)); +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs new file mode 100644 index 0000000000000..c7b511ee906c2 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.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 Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using LSP = Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +internal sealed class HotReloadRequestContext(RequestContext context) +{ + internal LSP.ClientCapabilities ClientCapabilities => context.GetRequiredClientCapabilities(); + public TextDocument? TextDocument => context.TextDocument; + public Solution? Solution => context.Solution; + public bool IsTracking(TextDocument textDocument) => context.IsTracking(textDocument.GetURI()); +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs new file mode 100644 index 0000000000000..f1fe7a600dae4 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs @@ -0,0 +1,31 @@ +// 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; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +internal interface IHotReloadDiagnosticManager +{ + /// + /// Refreshes hot reload diagnostics. + /// + void RequestRefresh(); + + /// + /// Registers providers of hot reload diagnostics. Callers are responsible for refreshing diagnostics after registration. + /// + void Register(IEnumerable providers); + + /// + /// Unregisters providers of hot reload diagnostics. Callers are responsible for refreshing diagnostics after un-registration. + /// + void Unregister(IEnumerable providers); + + /// + /// Providers. + /// + ImmutableArray Providers { get; } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs new file mode 100644 index 0000000000000..85a8004f1db8d --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs @@ -0,0 +1,25 @@ +// 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.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +/// +/// Source of hot reload diagnostics. +/// +internal interface IHotReloadDiagnosticSource +{ + /// + /// Text document for which diagnostics are provided. + /// + DocumentId DocumentId { get; } + + /// + /// Provides list of diagnostics for the given document. + /// + ValueTask> GetDiagnosticsAsync(HotReloadRequestContext request, CancellationToken cancellationToken); +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..586a6a2d8ec90 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSourceProvider.cs @@ -0,0 +1,27 @@ +// 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.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +/// +/// Provides diagnostic sources. +/// +internal interface IHotReloadDiagnosticSourceProvider +{ + /// + /// True if this provider is for documents. False if it is for a workspace, i.e. for unopened documents. + /// + bool IsDocument { get; } + + /// + /// Creates the diagnostic sources. + /// + /// The context. + /// The cancellation token. + ValueTask> CreateDiagnosticSourcesAsync(HotReloadRequestContext context, CancellationToken cancellationToken); +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs new file mode 100644 index 0000000000000..8a6071ea4228a --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs @@ -0,0 +1,49 @@ +// 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 Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +[Export(typeof(IHotReloadDiagnosticManager)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class HotReloadDiagnosticManager([Import] IDiagnosticsRefresher diagnosticsRefresher) : IHotReloadDiagnosticManager +{ + private readonly object _syncLock = new(); + public ImmutableArray Providers { get; private set; } = []; + + public void RequestRefresh() + => diagnosticsRefresher.RequestWorkspaceRefresh(); + + public void Register(IEnumerable providers) + { + // We use array instead of e.g. HashSet because we expect the number of sources to be small. + // Usually 2, one workspace and one document provider. + lock (_syncLock) + { + foreach (var provider in providers) + { + if (!Providers.Contains(provider)) + Providers = Providers.Add(provider); + } + } + } + + public void Unregister(IEnumerable providers) + { + lock (_syncLock) + { + foreach (var provider in Providers) + Providers = Providers.Remove(provider); + } + } + +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs new file mode 100644 index 0000000000000..64d157b4a1343 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs @@ -0,0 +1,32 @@ +// 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.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +internal sealed class HotReloadDiagnosticSource(IHotReloadDiagnosticSource source, TextDocument textDocument) : IDiagnosticSource +{ + public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + var diagnostics = await source.GetDiagnosticsAsync(new HotReloadRequestContext(context), cancellationToken).ConfigureAwait(false); + var result = diagnostics.Select(diagnostic => DiagnosticData.Create(diagnostic, textDocument)).ToImmutableArray(); + return result; + } + + public TextDocumentIdentifier? GetDocumentIdentifier() => new() { Uri = textDocument.GetURI() }; + public ProjectOrDocumentId GetId() => new(textDocument.Id); + public Project GetProject() => textDocument.Project; + public bool IsLiveSource() => true; + public string ToDisplayString() => $"{this.GetType().Name}: {textDocument.FilePath ?? textDocument.Name} in {textDocument.Project.Name}"; +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..88bd5cceb9f90 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs @@ -0,0 +1,73 @@ +// 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 System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +internal abstract class HotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager diagnosticManager, bool isDocument) : IDiagnosticSourceProvider +{ + public string Name => "HotReloadDiagnostics"; + public bool IsDocument => isDocument; + + public bool IsEnabled(ClientCapabilities clientCapabilities) => true; + + public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (context.Solution is not Solution solution) + { + return ImmutableArray.Empty; + } + + var hotReloadContext = new HotReloadRequestContext(context); + using var _ = ArrayBuilder.GetInstance(out var sources); + foreach (var provider in diagnosticManager.Providers) + { + if (provider.IsDocument == isDocument) + { + var hotReloadSources = await provider.CreateDiagnosticSourcesAsync(hotReloadContext, cancellationToken).ConfigureAwait(false); + foreach (var hotReloadSource in hotReloadSources) + { + // Look for additional document first. Currently most common hot reload diagnostics come from *.xaml files. + TextDocument? textDocument = + solution.GetAdditionalDocument(hotReloadSource.DocumentId) ?? + solution.GetDocument(hotReloadSource.DocumentId); + if (textDocument != null) + { + sources.Add(new HotReloadDiagnosticSource(hotReloadSource, textDocument)); + } + } + } + } + + var result = sources.ToImmutableAndClear(); + return DiagnosticSourceManager.AggregateSourcesIfNeeded(result, isDocument); + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + private sealed class DocumentHotReloadDiagnosticSourceProvider([Import] IHotReloadDiagnosticManager diagnosticManager) + : HotReloadDiagnosticSourceProvider(diagnosticManager, isDocument: true) + { + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + private sealed class WorkspaceHotReloadDiagnosticSourceProvider([Import] IHotReloadDiagnosticManager diagnosticManager) + : HotReloadDiagnosticSourceProvider(diagnosticManager, isDocument: false) + { + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt index 504106533fe6b..a831db9ed8865 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt @@ -1,15 +1,47 @@ +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.ClientCapabilities.get -> Roslyn.LanguageServer.Protocol.ClientCapabilities! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.HotReloadRequestContext(Microsoft.CodeAnalysis.LanguageServer.Handler.RequestContext context) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.IsTracking(Microsoft.CodeAnalysis.TextDocument! textDocument) -> bool +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.Solution.get -> Microsoft.CodeAnalysis.Solution? +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.TextDocument.get -> Microsoft.CodeAnalysis.TextDocument? +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Providers.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Register(System.Collections.Generic.IEnumerable! providers) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.RequestRefresh() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Unregister(System.Collections.Generic.IEnumerable! providers) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.DocumentId.get -> Microsoft.CodeAnalysis.DocumentId! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.GetDiagnosticsAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider.IsDocument.get -> bool Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStartAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStopAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.InitializeAsync(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.RequestDataBridgeConnectionAsync(string! connectionId, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsServiceBroker -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsServiceBroker.GetServiceBrokerAsync() -> System.Threading.Tasks.Task! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.VisualDiagnosticsServiceBroker -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.VisualDiagnosticsServiceBroker.NotifyServiceBrokerInitialized.get -> Microsoft.CodeAnalysis.BrokeredServices.IOnServiceBrokerInitialized? -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.VisualDiagnosticsServiceBroker.NotifyServiceBrokerInitialized.set -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.VisualDiagnosticsServiceBroker.OnServiceBrokerInitialized(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.VisualDiagnosticsServiceBroker.VisualDiagnosticsServiceBroker() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.HotReloadDiagnosticManager(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.Providers.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.Register(System.Collections.Generic.IEnumerable! providers) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.RequestRefresh() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.Unregister(System.Collections.Generic.IEnumerable! providers) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetDiagnosticsAsync(Microsoft.CodeAnalysis.LanguageServer.Handler.RequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetDocumentIdentifier() -> Roslyn.LanguageServer.Protocol.TextDocumentIdentifier? +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetId() -> Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.ProjectOrDocumentId +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetProject() -> Microsoft.CodeAnalysis.Project! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source, Microsoft.CodeAnalysis.TextDocument! textDocument) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.IsLiveSource() -> bool +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.ToDisplayString() -> string! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(Microsoft.CodeAnalysis.LanguageServer.Handler.RequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.HotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager, bool isDocument) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.IsDocument.get -> bool +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.IsEnabled(Roslyn.LanguageServer.Protocol.ClientCapabilities! clientCapabilities) -> bool +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.Name.get -> string! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry.RunningProcessEntry() -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.csproj b/src/Tools/ExternalAccess/VisualDiagnostics/Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.csproj index ada2581f782b5..f63b387f65552 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.csproj +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/Tools/ExternalAccess/Xaml/External/IXamlDiagnosticSource.cs b/src/Tools/ExternalAccess/Xaml/External/IXamlDiagnosticSource.cs new file mode 100644 index 0000000000000..0b55c2089007d --- /dev/null +++ b/src/Tools/ExternalAccess/Xaml/External/IXamlDiagnosticSource.cs @@ -0,0 +1,16 @@ +// 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.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; + +internal interface IXamlDiagnosticSource +{ + Task> GetDiagnosticsAsync( + XamlRequestContext context, + CancellationToken cancellationToken); +} diff --git a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs new file mode 100644 index 0000000000000..1cee9dbe1b173 --- /dev/null +++ b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs @@ -0,0 +1,32 @@ +// 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.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; + +internal sealed class XamlDiagnosticSource(IXamlDiagnosticSource xamlDiagnosticSource, TextDocument document) : IDiagnosticSource +{ + bool IDiagnosticSource.IsLiveSource() => true; + Project IDiagnosticSource.GetProject() => document.Project; + ProjectOrDocumentId IDiagnosticSource.GetId() => new(document.Id); + TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = document.GetURI() }; + string IDiagnosticSource.ToDisplayString() => $"{this.GetType().Name}: {document.FilePath ?? document.Name} in {document.Project.Name}"; + + async Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + var xamlRequestContext = XamlRequestContext.FromRequestContext(context); + var diagnostics = await xamlDiagnosticSource.GetDiagnosticsAsync(xamlRequestContext, cancellationToken).ConfigureAwait(false); + var result = diagnostics.Select(e => DiagnosticData.Create(e, document)).ToImmutableArray(); + return result; + } +} diff --git a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..526a26f5e57e8 --- /dev/null +++ b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs @@ -0,0 +1,38 @@ +// 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 System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; + +[Export(typeof(IDiagnosticSourceProvider)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class XamlDiagnosticSourceProvider([Import(AllowDefault = true)] IXamlDiagnosticSource? xamlDiagnosticSource) : IDiagnosticSourceProvider +{ + bool IDiagnosticSourceProvider.IsDocument => true; + + string IDiagnosticSourceProvider.Name => "XamlDiagnostics"; + + bool IDiagnosticSourceProvider.IsEnabled(ClientCapabilities clientCapabilities) => true; + + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (xamlDiagnosticSource != null && context.TextDocument is { } document && + document.Project.GetAdditionalDocument(document.Id) != null) + { + return new([new XamlDiagnosticSource(xamlDiagnosticSource, document)]); + } + + return new([]); + } +} diff --git a/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt index d62fe5bb29af1..c8d63468defae 100644 --- a/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt @@ -37,6 +37,8 @@ Microsoft.CodeAnalysis.ExternalAccess.Xaml.ILocationService.GetSymbolLocationsAs Microsoft.CodeAnalysis.ExternalAccess.Xaml.IResolveCachedDataService Microsoft.CodeAnalysis.ExternalAccess.Xaml.IResolveCachedDataService.FromResolveData(object? resolveData) -> (object? data, System.Uri? uri) Microsoft.CodeAnalysis.ExternalAccess.Xaml.IResolveCachedDataService.ToResolveData(object! data, System.Uri! uri) -> object! +Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource.GetDiagnosticsAsync(Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlRequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlRequestHandler Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlRequestHandler.HandleRequestAsync(TRequest request, Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlRequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlRequestHandler.MutatesSolutionState.get -> bool @@ -51,6 +53,10 @@ Microsoft.CodeAnalysis.ExternalAccess.Xaml.ResolveDataConversions Microsoft.CodeAnalysis.ExternalAccess.Xaml.StringConstants Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlCommandAttribute Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlCommandAttribute.XamlCommandAttribute(string! command) -> void +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSource.XamlDiagnosticSource(Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource! xamlDiagnosticSource, Microsoft.CodeAnalysis.TextDocument! document) -> void +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSourceProvider.XamlDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource? xamlDiagnosticSource) -> void Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlMethodAttribute Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlMethodAttribute.XamlMethodAttribute(string! method) -> void Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlRequestContext diff --git a/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs b/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs index 778a6dbe08607..290022941f109 100644 --- a/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs +++ b/src/Tools/IdeBenchmarks/SQLitePersistentStorageBenchmark.cs @@ -61,13 +61,12 @@ public void GlobalSetup() "); - var connectionPoolService = _workspace.ExportProvider.GetExportedValue(); var asyncListener = _workspace.ExportProvider.GetExportedValue().GetListener(FeatureAttribute.PersistentStorage); - _storageService = new SQLitePersistentStorageService(connectionPoolService, new StorageConfiguration(), asyncListener); + _storageService = new SQLitePersistentStorageService(new StorageConfiguration(), asyncListener); var solution = _workspace.CurrentSolution; - _storage = _storageService.GetStorageWorkerAsync(SolutionKey.ToSolutionKey(solution), CancellationToken.None).AsTask().GetAwaiter().GetResult(); + _storage = _storageService.GetStorageAsync(SolutionKey.ToSolutionKey(solution), CancellationToken.None).AsTask().GetAwaiter().GetResult(); Console.WriteLine("Storage type: " + _storage.GetType()); _document = _workspace.CurrentSolution.Projects.Single().Documents.Single(); @@ -83,7 +82,6 @@ public void GlobalCleanup() } _document = null!; - _storage.Dispose(); _storage = null!; _storageService = null!; _workspace.Dispose(); diff --git a/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs b/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs index 7318e6a776139..bae7a906f62a3 100644 --- a/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs +++ b/src/Tools/IdeCoreBenchmarks/FindReferencesBenchmarks.cs @@ -84,10 +84,8 @@ private async Task LoadSolutionAsync() if (storageService == null) throw new ArgumentException("Couldn't get storage service"); - using (var storage = await storageService.GetStorageAsync(SolutionKey.ToSolutionKey(_workspace.CurrentSolution), CancellationToken.None)) - { - Console.WriteLine("Sucessfully got persistent storage instance"); - } + var storage = await storageService.GetStorageAsync(SolutionKey.ToSolutionKey(_workspace.CurrentSolution), CancellationToken.None); + Console.WriteLine("Successfully got persistent storage instance"); // There might be multiple projects with this name. That's ok. FAR goes and finds all the linked-projects // anyways to perform the search on all the equivalent symbols from them. So the end perf cost is the diff --git a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs index be535cf145ab5..8b79dd838e9f9 100644 --- a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs +++ b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs @@ -183,24 +183,24 @@ public async Task RunFullParallelIndexing() Console.WriteLine("Starting indexing"); var storageService = _workspace.Services.SolutionServices.GetPersistentStorageService(); - using (var storage = await storageService.GetStorageAsync(SolutionKey.ToSolutionKey(_workspace.CurrentSolution), CancellationToken.None)) - { - Console.WriteLine("Successfully got persistent storage instance"); - var start = DateTime.Now; - var indexTime = TimeSpan.Zero; - var tasks = _workspace.CurrentSolution.Projects.SelectMany(p => p.Documents).Select(d => Task.Run( - async () => - { - var tree = await d.GetSyntaxRootAsync(); - var stopwatch = SharedStopwatch.StartNew(); - await TopLevelSyntaxTreeIndex.GetIndexAsync(d, default); - await SyntaxTreeIndex.GetIndexAsync(d, default); - indexTime += stopwatch.Elapsed; - })).ToList(); - await Task.WhenAll(tasks); - Console.WriteLine("Indexing time : " + indexTime); - Console.WriteLine("Solution parallel: " + (DateTime.Now - start)); - } + var storage = await storageService.GetStorageAsync(SolutionKey.ToSolutionKey(_workspace.CurrentSolution), CancellationToken.None); + + Console.WriteLine("Successfully got persistent storage instance"); + var start = DateTime.Now; + var indexTime = TimeSpan.Zero; + var tasks = _workspace.CurrentSolution.Projects.SelectMany(p => p.Documents).Select(d => Task.Run( + async () => + { + var tree = await d.GetSyntaxRootAsync(); + var stopwatch = SharedStopwatch.StartNew(); + await TopLevelSyntaxTreeIndex.GetIndexAsync(d, default); + await SyntaxTreeIndex.GetIndexAsync(d, default); + indexTime += stopwatch.Elapsed; + })).ToList(); + await Task.WhenAll(tasks); + Console.WriteLine("Indexing time : " + indexTime); + Console.WriteLine("Solution parallel: " + (DateTime.Now - start)); + Console.WriteLine("DB flushed"); Console.ReadLine(); } @@ -229,10 +229,10 @@ private async Task SearchAsync(Solution solution, IGrouping(); await service.SearchProjectsAsync( solution, grouping.ToImmutableArray(), priorityDocuments, "Syntax", service.KindsProvided, activeDocument: null, - (_, r) => + r => { lock (results) - results.Add(r); + results.AddRange(r); return Task.CompletedTask; }, diff --git a/src/Tools/Replay/README.md b/src/Tools/Replay/README.md index 9e6e6986efcdc..66d752c869343 100644 --- a/src/Tools/Replay/README.md +++ b/src/Tools/Replay/README.md @@ -23,4 +23,41 @@ e:\code\roslyn\src\Tools\Replay> dotnet run --framework net472 e:\code\example\m This runs all of the compilation events in the binary log against the compiler and outputs the results to `e:\temp`. -[binary-log]: https://github.com/dotnet/msbuild/blob/main/documentation/wiki/Binary-Log.md \ No newline at end of file +[binary-log]: https://github.com/dotnet/msbuild/blob/main/documentation/wiki/Binary-Log.md + +## Example Usage + +### dotnet trace + +To profile with `dotnet trace` first run with the `-w` option to get the PID of the compiler server. Then start up `dotnet trace` against that PID and have replay continue + +Console 1 + +```cmd +e:\code\roslyn\src\Tools\Replay> dotnet run --framework net472 e:\code\example\msbuild.binlog -w +Binary Log: E:\code\example\msbuild.binlog +Client Directory: E:\code\roslyn\artifacts\bin\Replay\Release\net8.0\ +Output Directory: E:\code\roslyn\src\Tools\Replay\output +Pipe Name: 0254ccf8-294e-4b8f-a606-70f105b9e4a1 +Parallel: 6 + +Starting server +Process Id: 48752 +Press any key to continue +``` + +Console 2 + +```cmd +e:\users\jaredpar> dotnet trace collect --profile gc-verbose -p 48752 + +Provider Name Keywords Level Enabled By +Microsoft-Windows-DotNETRuntime 0x0000000000008003 Verbose(5) --profile + +Process : C:\Program Files\dotnet\dotnet.exe +Output File : C:\Users\jaredpar\dotnet.exe_20240502_083035.nettrace + +[00:00:01:13] Recording trace 379.5907 (MB) +``` + +Then go back to Console 1 and press any key to continue. The trace will automatically stop once the replay operation is complete. \ No newline at end of file diff --git a/src/Tools/Replay/Replay.cs b/src/Tools/Replay/Replay.cs index 444b79364ae03..47c4550450a52 100644 --- a/src/Tools/Replay/Replay.cs +++ b/src/Tools/Replay/Replay.cs @@ -56,7 +56,7 @@ static ReplayOptions ParseOptions(string[] args) if (string.IsNullOrEmpty(outputDirectory)) { - outputDirectory = Path.Combine(Environment.CurrentDirectory, "output"); + outputDirectory = Path.Combine(Path.GetTempPath(), "replay"); } return new ReplayOptions( diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/Grammar/GrammarGenerator.cs b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/Grammar/GrammarGenerator.cs index a56836798245d..5748e2260fd13 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/Grammar/GrammarGenerator.cs +++ b/src/Tools/Source/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/Grammar/GrammarGenerator.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Text; using System.Text.RegularExpressions; using Microsoft.CodeAnalysis.CSharp; @@ -20,13 +21,6 @@ internal static class GrammarGenerator { public static string Run(List types) { - // Syntax.xml refers to a special pseudo-element 'Modifier'. Synthesize that for the grammar. - var modifiers = GetMembers() - .Select(m => m + "Keyword").Where(n => GetSyntaxKind(n) != SyntaxKind.None) - .Select(n => new Kind { Name = n }).ToList(); - - types.Add(new Node { Name = "Modifier", Children = { new Field { Type = "SyntaxToken", Kinds = modifiers } } }); - var rules = types.ToDictionary(n => n.Name, _ => new List()); foreach (var type in types) { @@ -53,38 +47,50 @@ public static string Run(List types) var originalLastFieldKinds = lastField.Kinds.ToList(); for (int i = 0; i < originalFirstFieldKinds.Count; i++) { - firstField.Kinds = new List { originalFirstFieldKinds[i] }; - lastField.Kinds = new List { originalLastFieldKinds[i] }; - rules[type.Name].Add(HandleChildren(type.Children)); + firstField.Kinds = [originalFirstFieldKinds[i]]; + lastField.Kinds = [originalLastFieldKinds[i]]; + rules[type.Name].Add(Sequence(type.Children.Select(ToProduction))); } } else { for (int i = 0; i < originalFirstFieldKinds.Count; i++) { - firstField.Kinds = new List { originalFirstFieldKinds[i] }; - rules[type.Name].Add(HandleChildren(type.Children)); + firstField.Kinds = [originalFirstFieldKinds[i]]; + rules[type.Name].Add(Sequence(type.Children.Select(ToProduction))); } } } else { - rules[type.Name].Add(HandleChildren(type.Children)); + rules[type.Name].Add(Sequence(type.Children.Select(ToProduction))); } } } + // Add some rules not present in Syntax.xml. + AddLexicalRules(rules); + // The grammar will bottom out with certain lexical productions. Create rules for these. var lexicalRules = rules.Values.SelectMany(ps => ps).SelectMany(p => p.ReferencedRules) .Where(r => !rules.TryGetValue(r, out var productions) || productions.Count == 0).ToArray(); foreach (var name in lexicalRules) - rules[name] = new List { new Production("/* see lexical specification */") }; + rules[name] = [new("/* see lexical specification */")]; var seen = new HashSet(); // Define a few major sections to help keep the grammar file naturally grouped. - var majorRules = ImmutableArray.Create( - "CompilationUnitSyntax", "MemberDeclarationSyntax", "TypeSyntax", "StatementSyntax", "ExpressionSyntax", "XmlNodeSyntax", "StructuredTriviaSyntax"); + List majorRules = [ + "CompilationUnitSyntax", + "MemberDeclarationSyntax", + "TypeSyntax", + "StatementSyntax", + "ExpressionSyntax", + "XmlNodeSyntax", + "StructuredTriviaSyntax", + // Place all syntax tokens at the end to keep them out of the way. + "SyntaxToken", + .. rules["SyntaxToken"].SelectMany(r => r.ReferencedRules)]; var result = "// " + Environment.NewLine + "grammar csharp;" + Environment.NewLine; @@ -101,7 +107,7 @@ void processRule(string name, ref string result) // Order the productions to keep us independent from whatever changes happen in Syntax.xml. var sorted = rules[name].OrderBy(v => v); result += Environment.NewLine + RuleReference(name).Text + Environment.NewLine + " : " + - string.Join(Environment.NewLine + " | ", sorted) + Environment.NewLine + " ;" + Environment.NewLine; + string.Join(Environment.NewLine + " | ", sorted) + Environment.NewLine + " ;" + Environment.NewLine; // Now proceed in depth-first fashion through the referenced rules to keep related rules // close by. Don't recurse into major-sections to help keep them separated in grammar file. @@ -113,14 +119,209 @@ void processRule(string name, ref string result) } } - private static Production Join(string delim, IEnumerable productions) - => new Production(string.Join(delim, productions.Where(p => p.Text.Length > 0)), productions.SelectMany(p => p.ReferencedRules)); + private static void AddLexicalRules(Dictionary> rules) + { + addUtf8Rules(); + addTokenRules(); + addIdentifierRules(); + addRealLiteralRules(); + addNumericLiteralRules(); + addIntegerLiteralRules(); + addEscapeSequenceRules(); + addStringLiteralRules(); + addCharacterLiteralRules(); + + void addUtf8Rules() + { + var utf8Suffix = Choice(anyCasing("U8")); + rules.Add("Utf8StringLiteralToken", [Sequence([RuleReference("StringLiteralToken"), utf8Suffix])]); + rules.Add("Utf8MultiLineRawStringLiteralToken", [Sequence([RuleReference("MultiLineRawStringLiteralToken"), utf8Suffix])]); + rules.Add("Utf8SingleLineRawStringLiteralToken", [Sequence([RuleReference("SingleLineRawStringLiteralToken"), utf8Suffix])]); + } + + void addTokenRules() + { + rules["SyntaxToken"].AddRange([RuleReference("IdentifierToken"), RuleReference("Keyword"), RuleReference("NumericLiteralToken"), RuleReference("CharacterLiteralToken"), RuleReference("StringLiteralToken"), RuleReference("OperatorToken"), RuleReference("PunctuationToken")]); + + var modifierWords = GetMembers() + .Where(n => GetSyntaxKind(n + "Keyword") != SyntaxKind.None) + .Select(n => n.ToString().ToLower()); + rules.Add("Modifier", JoinWords(modifierWords)); + + var keywords = JoinWords(GetMembers().Where(k => SyntaxFacts.IsReservedKeyword(k)).Select(SyntaxFacts.GetText).Where(t => !modifierWords.Contains(t))); + keywords.Add(RuleReference("Modifier")); + rules.Add("Keyword", keywords); + + var operatorTokens = GetMembers().Where(m => SyntaxFacts.IsBinaryExpressionOperatorToken(m) || SyntaxFacts.IsPostfixUnaryExpression(m) || SyntaxFacts.IsPrefixUnaryExpression(m) || SyntaxFacts.IsAssignmentExpressionOperatorToken(m)); + rules.Add("OperatorToken", JoinWords(operatorTokens.Select(SyntaxFacts.GetText))); + + rules.Add("PunctuationToken", JoinWords(GetMembers() + .Where(m => SyntaxFacts.IsLanguagePunctuation(m) && !operatorTokens.Contains(m) && !m.ToString().StartsWith("Xml")) + .Select(SyntaxFacts.GetText))); + } + + void addIdentifierRules() + { + rules.Add("IdentifierToken", [Sequence([Text("@").Optional, RuleReference("IdentifierStartCharacter"), RuleReference("IdentifierPartCharacter")])]); + rules.Add("IdentifierStartCharacter", [RuleReference("LetterCharacter"), RuleReference("UnderscoreCharacter")]); + rules.Add("IdentifierPartCharacter", [RuleReference("LetterCharacter"), RuleReference("DecimalDigitCharacter"), RuleReference("ConnectingCharacter"), RuleReference("CombiningCharacter"), RuleReference("FormattingCharacter")]); + rules.Add("UnderscoreCharacter", [Text("_"), new("""'\\u005' /* unicode_escape_sequence for underscore */""")]); + rules.Add("LetterCharacter", [ + new("""/* [\p{L}\p{Nl}] category letter, all subcategories; category number, subcategory letter */"""), + new("unicode_escape_sequence /* only escapes for categories L & Nl allowed */")]); + + rules.Add("CombiningCharacter", [ + new("""/* [\p{Mn}\p{Mc}] category Mark, subcategories non-spacing and spacing combining */"""), + new("unicode_escape_sequence /* only escapes for categories Mn & Mc allowed */")]); + + rules.Add("DecimalDigitCharacter", [ + new("""/* [\p{Nd}] category number, subcategory decimal digit */"""), + new("unicode_escape_sequence /* only escapes for category Nd allowed */")]); + + rules.Add("ConnectingCharacter", [ + new("""/* [\p{Pc}] category Punctuation, subcategory connector */"""), + new("unicode_escape_sequence /* only escapes for category Pc allowed */")]); + + rules.Add("FormattingCharacter", [ + new("""/* [\p{Cf}] category Other, subcategory format. */"""), + new("unicode_escape_sequence /* only escapes for category Cf allowed */")]); + } + + void addRealLiteralRules() + { + var decimalDigitPlus = RuleReference("DecimalDigit").OneOrMany; + var exponentPart = RuleReference("ExponentPart"); + var exponentPartOpt = exponentPart.Optional; + var realTypeSuffix = RuleReference("RealTypeSuffix"); + var realTypeSuffixOpt = realTypeSuffix.Optional; + + rules.Add("RealLiteralToken", [ + Sequence([decimalDigitPlus, Text("."), decimalDigitPlus, exponentPartOpt, realTypeSuffixOpt]), + Sequence([Text("."), decimalDigitPlus, exponentPartOpt, realTypeSuffixOpt]), + Sequence([decimalDigitPlus, exponentPart, realTypeSuffixOpt]), + Sequence([decimalDigitPlus, realTypeSuffix]), + ]); + + rules.Add("ExponentPart", [Sequence([Choice(anyCasing("E")), Choice([Text("+"), Text("-")]).Optional, decimalDigitPlus])]); + rules.Add("RealTypeSuffix", [.. anyCasing("F"), .. anyCasing("D"), .. anyCasing("M")]); + } + + void addNumericLiteralRules() + { + rules.Add("NumericLiteralToken", [RuleReference("IntegerLiteralToken"), RuleReference("RealLiteralToken")]); + } + + void addIntegerLiteralRules() + { + var decimalDigit = RuleReference("DecimalDigit"); + var decimalDigitPlus = decimalDigit.OneOrMany; + var integerTypeSuffixOpt = RuleReference("IntegerTypeSuffix").Optional; + + rules.Add("IntegerLiteralToken", [RuleReference("DecimalIntegerLiteralToken"), RuleReference("HexadecimalIntegerLiteralToken")]); + rules.Add("DecimalIntegerLiteralToken", [Sequence([decimalDigitPlus, integerTypeSuffixOpt])]); + rules.Add("IntegerTypeSuffix", [.. anyCasing("U"), .. anyCasing("L"), .. anyCasing("UL"), .. anyCasing("LU")]); + rules.Add("DecimalDigit", [.. productionRange('0', '9')]); + rules.Add("HexadecimalDigit", [decimalDigit, .. productionRange('A', 'F'), .. productionRange('a', 'f')]); + rules.Add("HexadecimalIntegerLiteralToken", [Sequence([Choice([Text("0x"), Text("0X")]), RuleReference("HexadecimalDigit").OneOrMany, integerTypeSuffixOpt])]); + } + + void addEscapeSequenceRules() + { + var hexDigit = RuleReference("HexadecimalDigit"); + var hexDigitOpt = hexDigit.Optional; + + rules.Add("SimpleEscapeSequence", [Text(@"\'"), Text(@"\"""), Text(@"\\"), Text(@"\0"), Text(@"\a"), Text(@"\b"), Text(@"\f"), Text(@"\n"), Text(@"\r"), Text(@"\t"), Text(@"\v")]); + rules.Add("HexadecimalEscapeSequence", [Sequence([Text(@"\x"), hexDigit, .. repeat(hexDigitOpt, 3)])]); + rules.Add("UnicodeEscapeSequence", [Sequence([Text(@"\u"), .. repeat(hexDigit, 4)]), Sequence([Text(@"\U"), .. repeat(hexDigit, 8)])]); + } + + void addStringLiteralRules() + { + rules.Add("StringLiteralToken", [RuleReference("RegularStringLiteralToken"), RuleReference("VerbatimStringLiteralToken")]); + + rules.Add("RegularStringLiteralToken", [Sequence([Text("\""), RuleReference("RegularStringLiteralCharacter").ZeroOrMany, Text("\"")])]); + rules.Add("RegularStringLiteralCharacter", [RuleReference("SingleRegularStringLiteralCharacter"), RuleReference("SimpleEscapeSequence"), RuleReference("HexadecimalEscapeSequence"), RuleReference("UnicodeEscapeSequence")]); + rules.Add("SingleRegularStringLiteralCharacter", [new("""/* ~["\\\u000D\u000A\u0085\u2028\u2029] anything but ", \, and new_line_character */""")]); + + rules.Add("VerbatimStringLiteralToken", [Sequence([Text("@\""), RuleReference("VerbatimStringLiteralCharacter").ZeroOrMany, Text("\"")])]); + rules.Add("VerbatimStringLiteralCharacter", [RuleReference("SingleVerbatimStringLiteralCharacter"), RuleReference("QuoteEscapeSequence")]); + rules.Add("SingleVerbatimStringLiteralCharacter", [new("/* anything but quotation mark (U+0022) */")]); - private static Production HandleChildren(IEnumerable children, string delim = " ") - => Join(delim, children.Select(child => - child is Choice c ? HandleChildren(c.Children, delim: " | ").Parenthesize().Suffix("?", when: c.Optional) : - child is Sequence s ? HandleChildren(s.Children).Parenthesize() : - child is Field f ? HandleField(f).Suffix("?", when: f.IsOptional) : throw new InvalidOperationException())); + rules.Add("QuoteEscapeSequence", [Text("\"\"")]); + + rules.Add("InterpolatedMultiLineRawStringStartToken", [new(""""'$'+ '"""' '"'*"""")]); + rules.Add("InterpolatedRawStringEndToken", [new(""""'"""' '"'* /* must match number of quotes in raw_string_start_token */"""")]); + rules.Add("InterpolatedSingleLineRawStringStartToken", [new(""""'$'+ '"""' '"'*"""")]); + } + + void addCharacterLiteralRules() + { + rules.Add("CharacterLiteralToken", [Sequence([Text("'"), RuleReference("Character"), Text("'")])]); + rules.Add("Character", [RuleReference("SingleCharacter"), RuleReference("SimpleEscapeSequence"), RuleReference("HexadecimalEscapeSequence"), RuleReference("UnicodeEscapeSequence")]); + rules.Add("SingleCharacter", [new("""/* ~['\\\u000D\u000A\u0085\u2028\u2029] anything but ', \\, and new_line_character */""")]); + } + + IEnumerable productionRange(char start, char end) + { + for (char c = start; c <= end; c++) + yield return Text($"{c}"); + } + + IEnumerable repeat(Production production, int count) + => Enumerable.Repeat(production, count); + + IEnumerable anyCasing(string value) + { + var array = value.Select(c => char.IsLetter(c) ? [char.ToUpperInvariant(c), char.ToLowerInvariant(c)] : new[] { c }).ToArray(); + + var indices = new int[array.Length]; + var builder = new StringBuilder(); + do + { + for (var i = 0; i < value.Length; i++) + builder.Append(array[i][indices[i]]); + + yield return Text($"{builder}"); + builder.Clear(); + + for (var i = 0; i < indices.Length; i++) + { + if (++indices[i] < array[i].Length) + break; + + indices[i] = 0; + } + } + while (!indices.All(i => i == 0)); + } + } + + private static List JoinWords(IEnumerable strings) + => strings.Select(s => new Production($"""'{Escape(s)}'""")).ToList(); + + private static string Escape(string s) + => s.Replace(@"\", @"\\").Replace("'", @"\'"); + + private static Production Text(string value) + => new($"'{Escape(value)}'"); + + private static Production Join(IEnumerable productions, string delim) + => new(string.Join(delim, productions.Where(p => p.Text.Length > 0)), productions.SelectMany(p => p.ReferencedRules)); + + private static Production ToProduction(TreeTypeChild child) + => child switch + { + Choice c => Choice(c.Children.Select(ToProduction)).Suffix("?", when: c.Optional), + Sequence s => Sequence(s.Children.Select(ToProduction)).Parenthesize(), + Field f => HandleField(f).Suffix("?", when: f.IsOptional), + _ => throw new InvalidOperationException(), + }; + + private static Production Choice(IEnumerable productions, bool parenthesize = true) + => Join(productions, " | ").Parenthesize(parenthesize); + + private static Production Sequence(IEnumerable productions) + => Join(productions, " "); private static Production HandleField(Field field) // 'bool' fields are for a few properties we generate on DirectiveTrivia. They're not @@ -139,7 +340,7 @@ private static Production HandleSeparatedList(Field field, string elementType) private static Production HandleList(Field field, string elementType) => (elementType != "SyntaxToken" ? RuleReference(elementType) : - field.Name == "Commas" ? new Production("','") : + field.Name == "Commas" ? Text(",") : field.Name == "Modifiers" ? RuleReference("Modifier") : field.Name == "TextTokens" ? RuleReference(nameof(SyntaxKind.XmlTextLiteralToken)) : RuleReference(elementType)) .Suffix(field.MinCount == 0 ? "*" : "+"); @@ -147,11 +348,11 @@ private static Production HandleList(Field field, string elementType) private static Production HandleTokenField(Field field) => field.Kinds.Count == 0 ? HandleTokenName(field.Name) - : Join(" | ", field.Kinds.Select(k => HandleTokenName(k.Name))).Parenthesize(when: field.Kinds.Count >= 2); + : Choice(field.Kinds.Select(k => HandleTokenName(k.Name)), parenthesize: field.Kinds.Count >= 2); private static Production HandleTokenName(string tokenName) => GetSyntaxKind(tokenName) is var kind && kind == SyntaxKind.None ? RuleReference("SyntaxToken") : - SyntaxFacts.GetText(kind) is var text && text != "" ? new Production(text == "'" ? "'\\''" : $"'{text}'") : + SyntaxFacts.GetText(kind) is var text && text != "" ? Text(text) : tokenName.StartsWith("EndOf") ? new Production("") : tokenName.StartsWith("Omitted") ? new Production("/* epsilon */") : RuleReference(tokenName); @@ -162,32 +363,30 @@ private static IEnumerable GetMembers() where TEnum : struct, Enum => (IEnumerable)Enum.GetValues(typeof(TEnum)); private static Production RuleReference(string name) - => new Production( + => new( s_normalizationRegex.Replace(name.EndsWith("Syntax") ? name[..^"Syntax".Length] : name, "_").ToLower(), ImmutableArray.Create(name)); // Converts a PascalCased name into snake_cased name. - private static readonly Regex s_normalizationRegex = new Regex( - "(?<=[A-Z])(?=[A-Z][a-z]) | (?<=[^A-Z])(?=[A-Z]) | (?<=[A-Za-z])(?=[^A-Za-z])", + private static readonly Regex s_normalizationRegex = new( + "(?<=[A-Z])(?=[A-Z][a-z0-9]) | (?<=[^A-Z])(?=[A-Z]) | (?<=[A-Za-z0-9])(?=[^A-Za-z0-9])", RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); } - internal readonly struct Production : IComparable + internal readonly struct Production( + string text, IEnumerable referencedRules = null) : IComparable { - public readonly string Text; - public readonly ImmutableArray ReferencedRules; - - public Production(string text, IEnumerable referencedRules = null) - { - Text = text; - ReferencedRules = referencedRules?.ToImmutableArray() ?? ImmutableArray.Empty; - } + public readonly string Text = text; + public readonly ImmutableArray ReferencedRules = referencedRules?.ToImmutableArray() ?? ImmutableArray.Empty; public override string ToString() => Text; - public int CompareTo(Production other) => StringComparer.Ordinal.Compare(this.Text, other.Text); - public Production Prefix(string prefix) => new Production(prefix + this, ReferencedRules); - public Production Suffix(string suffix, bool when = true) => when ? new Production(this + suffix, ReferencedRules) : this; + public int CompareTo(Production other) => StringComparer.OrdinalIgnoreCase.Compare(this.Text, other.Text); + public Production Prefix(string prefix) => new(prefix + this, ReferencedRules); + public Production Suffix(string suffix, bool when = true) => when ? new(this + suffix, ReferencedRules) : this; public Production Parenthesize(bool when = true) => when ? Prefix("(").Suffix(")") : this; + public Production Optional => Suffix("?"); + public Production ZeroOrMany => Suffix("*"); + public Production OneOrMany => Suffix("+"); } } diff --git a/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs b/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs index 5012e6c7c4acb..f5fcb69f0bb5f 100644 --- a/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs +++ b/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs @@ -42,6 +42,7 @@ internal sealed class CSharpVisualStudioCopilotOptionsService : ICopilotOptionsS private const string CopilotOptionNamePrefix = "Microsoft.VisualStudio.Conversations"; private const string CopilotCodeAnalysisOptionName = "EnableCSharpCodeAnalysis"; private const string CopilotRefineOptionName = "EnableCSharpRefineQuickActionSuggestion"; + private const string CopilotOnTheFlyDocsOptionName = "EnableCSharpOnTheFlyDocs"; private static readonly UIContext s_copilotHasLoadedUIContext = UIContext.FromUIContextGuid(new Guid(CopilotHasLoadedGuid)); private static readonly UIContext s_gitHubAccountStatusDeterminedContext = UIContext.FromUIContextGuid(new Guid(GitHubAccountStatusDetermined)); @@ -84,4 +85,7 @@ public Task IsCodeAnalysisOptionEnabledAsync() public Task IsRefineOptionEnabledAsync() => IsCopilotOptionEnabledAsync(CopilotRefineOptionName); + + public Task IsOnTheFlyDocsOptionEnabledAsync() + => IsCopilotOptionEnabledAsync(CopilotOnTheFlyDocsOptionName); } diff --git a/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef b/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef index ee45c4813cee4..852593e3f17a3 100644 --- a/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef +++ b/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef @@ -198,4 +198,4 @@ [$RootKey$\SettingsManifests\{13c3bbb4-f18f-4111-9f54-a0fb010d9194}] @="Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService.CSharpPackage" "ManifestPath"="$PackageFolder$\UnifiedSettings\csharpSettings.registration.json" -"CacheTag"=qword:08DC1824DFE0117B +"CacheTag"=qword:CE76341698AB8CD3 diff --git a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj index a1822e09b908e..b42bdacc847c3 100644 --- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj +++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj @@ -82,5 +82,6 @@ + \ No newline at end of file diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs index fb895470cd613..4364077c46c92 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs @@ -85,12 +85,6 @@ protected AbstractPersistentStorageTests() ThreadPool.SetMinThreads(Math.Max(workerThreads, NumThreads), completionPortThreads); } - internal abstract AbstractPersistentStorageService GetStorageService( - IMefHostExportProvider exportProvider, - IPersistentStorageConfiguration configuration, - IPersistentStorageFaultInjector? faultInjector, - string rootFolder); - public void Dispose() { // This should cause the service to release the cached connection it maintains for the primary workspace @@ -121,7 +115,7 @@ public async Task TestNullFilePaths() var streamName = "stream"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); var project = solution.Projects.First(); var document = project.Documents.First(); Assert.False(await storage.WriteStreamAsync(project, streamName, EncodeString(""))); @@ -142,14 +136,14 @@ public async Task CacheDirectoryInPathWithSingleQuote(Size size, bool withChecks var streamName1 = "PersistentService_Solution_WriteReadDifferentInstances1"; var streamName2 = "PersistentService_Solution_WriteReadDifferentInstances2"; - await using (var storage = await GetStorageAsync(solution, folder)) { + var storage = await GetStorageAsync(solution, folder); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum))); } - await using (var storage = await GetStorageAsync(solution, folder)) { + var storage = await GetStorageAsync(solution, folder); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)))); Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName2, GetChecksum2(withChecksum)))); } @@ -163,14 +157,14 @@ public async Task PersistentService_Solution_WriteReadDifferentInstances(Size si var streamName1 = "PersistentService_Solution_WriteReadDifferentInstances1"; var streamName2 = "PersistentService_Solution_WriteReadDifferentInstances2"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum))); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)))); Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName2, GetChecksum2(withChecksum)))); } @@ -184,16 +178,16 @@ public async Task PersistentService_Solution_WriteReadReopenSolution(Size size, var streamName1 = "PersistentService_Solution_WriteReadReopenSolution1"; var streamName2 = "PersistentService_Solution_WriteReadReopenSolution2"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum))); } solution = CreateOrOpenSolution(); - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)))); Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName2, GetChecksum2(withChecksum)))); } @@ -207,7 +201,7 @@ public async Task PersistentService_Solution_WriteReadSameInstance(Size size, bo var streamName1 = "PersistentService_Solution_WriteReadSameInstance1"; var streamName2 = "PersistentService_Solution_WriteReadSameInstance2"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum))); @@ -223,7 +217,7 @@ public async Task PersistentService_Project_WriteReadSameInstance(Size size, boo var streamName1 = "PersistentService_Project_WriteReadSameInstance1"; var streamName2 = "PersistentService_Project_WriteReadSameInstance2"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); var project = solution.Projects.Single(); Assert.True(await storage.WriteStreamAsync(project, streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); @@ -241,7 +235,7 @@ public async Task PersistentService_Document_WriteReadSameInstance(Size size, bo var streamName1 = "PersistentService_Document_WriteReadSameInstance1"; var streamName2 = "PersistentService_Document_WriteReadSameInstance2"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); var document = solution.Projects.Single().Documents.Single(); Assert.True(await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); @@ -259,7 +253,7 @@ public async Task PersistentService_Solution_SimultaneousWrites([CombinatorialRa var streamName1 = "PersistentService_Solution_SimultaneousWrites1"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); DoSimultaneousWrites(s => storage.WriteStreamAsync(streamName1, EncodeString(s))); var value = int.Parse(ReadStringToEnd(await storage.ReadStreamAsync(streamName1))); Assert.True(value >= 0); @@ -274,7 +268,7 @@ public async Task PersistentService_Project_SimultaneousWrites([CombinatorialRan var streamName1 = "PersistentService_Project_SimultaneousWrites1"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); DoSimultaneousWrites(s => storage.WriteStreamAsync(solution.Projects.Single(), streamName1, EncodeString(s))); var value = int.Parse(ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single(), streamName1))); Assert.True(value >= 0); @@ -289,7 +283,7 @@ public async Task PersistentService_Document_SimultaneousWrites([CombinatorialRa var streamName1 = "PersistentService_Document_SimultaneousWrites1"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); DoSimultaneousWrites(s => storage.WriteStreamAsync(solution.Projects.Single().Documents.Single(), streamName1, EncodeString(s))); var value = int.Parse(ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single().Documents.Single(), streamName1))); Assert.True(value >= 0); @@ -303,7 +297,7 @@ public async Task PersistentService_Solution_SimultaneousReads(Size size, bool w var solution = CreateOrOpenSolution(); var streamName1 = "PersistentService_Solution_SimultaneousReads1"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); DoSimultaneousReads(async () => ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum))), GetData1(size)); } @@ -315,7 +309,7 @@ public async Task PersistentService_Project_SimultaneousReads(Size size, bool wi var solution = CreateOrOpenSolution(); var streamName1 = "PersistentService_Project_SimultaneousReads1"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(solution.Projects.Single(), streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); DoSimultaneousReads(async () => ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single(), streamName1, GetChecksum1(withChecksum))), GetData1(size)); } @@ -328,7 +322,7 @@ public async Task PersistentService_Document_SimultaneousReads(Size size, bool w var solution = CreateOrOpenSolution(); var streamName1 = "PersistentService_Document_SimultaneousReads1"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(solution.Projects.Single().Documents.Single(), streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); DoSimultaneousReads(async () => ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single().Documents.Single(), streamName1, GetChecksum1(withChecksum))), GetData1(size)); } @@ -341,7 +335,7 @@ public async Task TestReadChecksumReturnsNullWhenNeverWritten([CombinatorialRang var streamName1 = "TestReadChecksumReturnsNullWhenNeverWritten"; - await using var storage = await GetStorageAsync(solution); + var storage = await GetStorageAsync(solution); Assert.False(await storage.ChecksumMatchesAsync(streamName1, s_checksum1)); } @@ -353,13 +347,13 @@ public async Task TestCanReadWithNullChecksumSomethingWrittenWithNonNullChecksum var streamName1 = "TestCanReadWithNullChecksumSomethingWrittenWithNonNullChecksum"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), s_checksum1)); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, checksum: null))); } } @@ -372,13 +366,13 @@ public async Task TestCannotReadWithMismatchedChecksums(Size size, [Combinatoria var streamName1 = "TestCannotReadWithMismatchedChecksums"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), s_checksum1)); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.Null(await storage.ReadStreamAsync(streamName1, s_checksum2)); } } @@ -391,13 +385,13 @@ public async Task TestCannotReadChecksumIfWriteDidNotIncludeChecksum(Size size, var streamName1 = "TestCannotReadChecksumIfWriteDidNotIncludeChecksum"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: null)); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.False(await storage.ChecksumMatchesAsync(streamName1, s_checksum1)); } } @@ -410,13 +404,13 @@ public async Task TestReadChecksumProducesWrittenChecksum(Size size, [Combinator var streamName1 = "TestReadChecksumProducesWrittenChecksum"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: s_checksum1)); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(streamName1, s_checksum1)); } } @@ -429,14 +423,14 @@ public async Task TestReadChecksumProducesLastWrittenChecksum1(Size size, [Combi var streamName1 = "TestReadChecksumProducesLastWrittenChecksum1"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: s_checksum1)); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: null)); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.False(await storage.ChecksumMatchesAsync(streamName1, s_checksum1)); } } @@ -449,14 +443,14 @@ public async Task TestReadChecksumProducesLastWrittenChecksum2(Size size, [Combi var streamName1 = "TestReadChecksumProducesLastWrittenChecksum2"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: null)); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: s_checksum1)); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(streamName1, s_checksum1)); } } @@ -469,14 +463,14 @@ public async Task TestReadChecksumProducesLastWrittenChecksum3(Size size, [Combi var streamName1 = "TestReadChecksumProducesLastWrittenChecksum3"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: s_checksum1)); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), checksum: s_checksum2)); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(streamName1, s_checksum2)); } } @@ -490,13 +484,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKey(Size size, [Combina var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); } @@ -511,13 +505,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocument(Size size, [Combinator var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); } @@ -532,13 +526,13 @@ public async Task TestOpenWithSolutionReadWithDocumentKey(Size size, [Combinator var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); } @@ -553,13 +547,13 @@ public async Task TestOpenWithSolutionReadWithDocument(Size size, [Combinatorial var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); } @@ -574,13 +568,13 @@ public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument1(Size size, var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -598,13 +592,13 @@ public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument2(Size size, var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); @@ -622,13 +616,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument1(Size si var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -646,13 +640,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument2(Size si var streamName1 = "stream"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); @@ -670,13 +664,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKey_WriteWithSolutionKe var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); } @@ -691,13 +685,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocument_WriteWithSolutionKey(S var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); } @@ -712,13 +706,13 @@ public async Task TestOpenWithSolutionReadWithDocumentKey_WriteWithSolutionKey(S var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); } @@ -733,13 +727,13 @@ public async Task TestOpenWithSolutionReadWithDocument_WriteWithSolutionKey(Size var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); } @@ -754,13 +748,13 @@ public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument1_WriteWithS var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -778,13 +772,13 @@ public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument2_WriteWithS var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); @@ -802,13 +796,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument1_WriteWi var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -826,13 +820,13 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument2_WriteWi var streamName1 = "stream"; - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), checksum: s_checksum1); } - await using (var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution))) { + var storage = await GetStorageFromKeyAsync(solution.Workspace.Services, SolutionKey.ToSolutionKey(solution)); Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); @@ -859,13 +853,13 @@ public async Task PersistentService_ReadByteTwice(Size size, bool withChecksum, var solution = CreateOrOpenSolution(); var streamName1 = "PersistentService_ReadByteTwice"; - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); } - await using (var storage = await GetStorageAsync(solution)) { + var storage = await GetStorageAsync(solution); using var stream = await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)); Contract.ThrowIfNull(stream); stream.ReadByte(); @@ -883,8 +877,8 @@ public async Task TestPersistSyntaxTreeIndex([CombinatorialRange(0, Iterations)] var document = solution.GetRequiredDocument(id); - await using (var storage = await GetStorageAsync(solution)) { + _ = await GetStorageAsync(solution); var index = await SyntaxTreeIndex.GetRequiredIndexAsync(document, default); await index.SaveAsync(document, _storageService!); @@ -903,8 +897,8 @@ public async Task TestPersistTopLevelSyntaxTreeIndex([CombinatorialRange(0, Iter var document = solution.GetRequiredDocument(id); - await using (var storage = await GetStorageAsync(solution)) { + _ = await GetStorageAsync(solution); var index = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(document, default); await index.SaveAsync(document, _storageService!); @@ -981,6 +975,7 @@ private static void DoSimultaneousWrites(Func write) protected Solution CreateOrOpenSolution(TempDirectory? persistentFolder = null, bool nullPaths = false) { persistentFolder ??= _persistentFolder; + _storageService?.GetTestAccessor().Shutdown(); var solutionFile = persistentFolder.CreateOrOpenFile("Solution1.sln").WriteAllText(""); var info = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create(), solutionFile.Path); @@ -1016,13 +1011,14 @@ internal async Task GetStorageAsync( _storageService?.GetTestAccessor().Shutdown(); var configuration = new MockPersistentStorageConfiguration(solution.Id, persistentFolder.Path, throwOnFailure); - _storageService = GetStorageService(solution.Workspace.Services.SolutionServices.ExportProvider, configuration, faultInjector, _persistentFolder.Path); - var storage = await _storageService.GetStorageAsync(SolutionKey.ToSolutionKey(solution), CancellationToken.None); + _storageService = (AbstractPersistentStorageService)solution.Workspace.Services.SolutionServices.GetPersistentStorageService(); + var storage = await _storageService.GetStorageAsync( + SolutionKey.ToSolutionKey(solution), configuration, faultInjector, CancellationToken.None); // If we're injecting faults, we expect things to be strange if (faultInjector == null) { - Assert.NotEqual(NoOpPersistentStorage.TestAccessor.StorageInstance, storage); + Assert.NotEqual(NoOpPersistentStorage.TestAccessor.GetStorageInstance(SolutionKey.ToSolutionKey(solution)), storage); } return storage; @@ -1031,17 +1027,16 @@ internal async Task GetStorageAsync( internal async Task GetStorageFromKeyAsync( HostWorkspaceServices services, SolutionKey solutionKey, IPersistentStorageFaultInjector? faultInjector = null) { - // If we handed out one for a previous test, we need to shut that down first - _storageService?.GetTestAccessor().Shutdown(); var configuration = new MockPersistentStorageConfiguration(solutionKey.Id, _persistentFolder.Path, throwOnFailure: true); - _storageService = GetStorageService(services.SolutionServices.ExportProvider, configuration, faultInjector, _persistentFolder.Path); - var storage = await _storageService.GetStorageAsync(solutionKey, CancellationToken.None); + _storageService = (AbstractPersistentStorageService)services.SolutionServices.GetPersistentStorageService(); + var storage = await _storageService.GetStorageAsync( + solutionKey, configuration, faultInjector, CancellationToken.None); // If we're injecting faults, we expect things to be strange if (faultInjector == null) { - Assert.NotEqual(NoOpPersistentStorage.TestAccessor.StorageInstance, storage); + Assert.NotEqual(NoOpPersistentStorage.TestAccessor.GetStorageInstance(solutionKey), storage); } return storage; diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs index ef6a87a205418..6f1ef7522c274 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SQLite.v2; using Microsoft.CodeAnalysis.Storage; @@ -23,13 +22,6 @@ namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices /// public class SQLiteV2PersistentStorageTests : AbstractPersistentStorageTests { - internal override AbstractPersistentStorageService GetStorageService(IMefHostExportProvider exportProvider, IPersistentStorageConfiguration configuration, IPersistentStorageFaultInjector? faultInjector, string relativePathBase) - => new SQLitePersistentStorageService( - exportProvider.GetExports().Single().Value, - configuration, - exportProvider.GetExports().Single().Value.GetListener(FeatureAttribute.PersistentStorage), - faultInjector); - [Fact] public async Task TestCrashInNewConnection() { @@ -46,7 +38,7 @@ public async Task TestCrashInNewConnection() // Because instantiating the connection will fail, we will not get back // a working persistent storage. We are testing a fault recovery code path. - await using (var storage = await GetStorageAsync(solution, faultInjector: faultInjector, throwOnFailure: false)) + var storage = await GetStorageAsync(solution, faultInjector: faultInjector, throwOnFailure: false); using (var memStream = new MemoryStream()) using (var streamWriter = new StreamWriter(memStream)) { diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index 4258c8a03cd5b..cd9d0faf9bdc3 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -102,7 +102,7 @@ internal override object GetOptionsDefaultValue(IOption2 option) [Fact] public async Task IntelliSensePageTests() { - var registrationFileStream = typeof(CSharpUnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("Roslyn.VisualStudio.CSharp.UnitTests.csharpSettings.registration.json"); + using var registrationFileStream = typeof(CSharpUnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("Roslyn.VisualStudio.CSharp.UnitTests.csharpSettings.registration.json"); using var reader = new StreamReader(registrationFileStream); var registrationFile = await reader.ReadToEndAsync().ConfigureAwait(false); var registrationJsonObject = JObject.Parse(registrationFile, new JsonLoadSettings() { CommentHandling = CommentHandling.Ignore }); @@ -110,7 +110,10 @@ public async Task IntelliSensePageTests() Assert.Equal("C#", actual: categoriesTitle.ToString()); var optionPageId = registrationJsonObject.SelectToken("$.categories['textEditor.csharp.intellisense'].legacyOptionPageId"); Assert.Equal(Guids.CSharpOptionPageIntelliSenseIdString, optionPageId!.ToString()); - TestUnifiedSettingsCategory(registrationJsonObject, categoryBasePath: "textEditor.csharp.intellisense", languageName: LanguageNames.CSharp); + using var pkgdefFileStream = typeof(CSharpUnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("Roslyn.VisualStudio.CSharp.UnitTests.PackageRegistration.pkgdef"); + using var pkgdefReader = new StreamReader(pkgdefFileStream); + var pkgdefFile = await pkgdefReader.ReadToEndAsync().ConfigureAwait(false); + TestUnifiedSettingsCategory(registrationJsonObject, categoryBasePath: "textEditor.csharp.intellisense", languageName: LanguageNames.CSharp, pkgdefFile); } } } diff --git a/src/VisualStudio/Core/Def/AnalyzerDependency/AnalyzerFileWatcherService.cs b/src/VisualStudio/Core/Def/AnalyzerDependency/AnalyzerFileWatcherService.cs deleted file mode 100644 index 570fe959cf266..0000000000000 --- a/src/VisualStudio/Core/Def/AnalyzerDependency/AnalyzerFileWatcherService.cs +++ /dev/null @@ -1,110 +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.ComponentModel.Composition; -using System.IO; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation; - -[Export(typeof(AnalyzerFileWatcherService))] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class AnalyzerFileWatcherService(SVsServiceProvider serviceProvider) -{ - private readonly IVsFileChangeEx _fileChangeService = (IVsFileChangeEx)serviceProvider.GetService(typeof(SVsFileChangeEx)); - - private readonly Dictionary _fileChangeTrackers = new(StringComparer.OrdinalIgnoreCase); - - /// - /// Holds a list of assembly modified times that we can use to detect a file change prior to the being in place. - /// Once it's in place and subscribed, we'll remove the entry because any further changes will be detected that way. - /// - private readonly Dictionary _assemblyUpdatedTimesUtc = new(StringComparer.OrdinalIgnoreCase); - - private readonly object _guard = new(); - - private static DateTime? GetLastUpdateTimeUtc(string fullPath) - { - try - { - var creationTimeUtc = File.GetCreationTimeUtc(fullPath); - var writeTimeUtc = File.GetLastWriteTimeUtc(fullPath); - - return writeTimeUtc > creationTimeUtc ? writeTimeUtc : creationTimeUtc; - } - catch (IOException) - { - return null; - } - catch (UnauthorizedAccessException) - { - return null; - } - } - - internal void TrackFilePathAndReportErrorIfChanged(string filePath) - { - lock (_guard) - { - if (!_fileChangeTrackers.TryGetValue(filePath, out var tracker)) - { - tracker = new FileChangeTracker(_fileChangeService, filePath); - tracker.UpdatedOnDisk += Tracker_UpdatedOnDisk; - _ = tracker.StartFileChangeListeningAsync(); - - _fileChangeTrackers.Add(filePath, tracker); - } - - if (_assemblyUpdatedTimesUtc.TryGetValue(filePath, out var assemblyUpdatedTime)) - { - var currentFileUpdateTime = GetLastUpdateTimeUtc(filePath); - - if (currentFileUpdateTime != null) - { - // If the the tracker is in place, at this point we can stop checking any further for this assembly - if (tracker.PreviousCallToStartFileChangeHasAsynchronouslyCompleted) - { - _assemblyUpdatedTimesUtc.Remove(filePath); - } - } - } - else - { - // We don't have an assembly updated time. This means we either haven't ever checked it, or we have a file watcher in place. - // If the file watcher is in place, then nothing further to do. Otherwise we'll add the update time to the map for future checking - if (!tracker.PreviousCallToStartFileChangeHasAsynchronouslyCompleted) - { - var currentFileUpdateTime = GetLastUpdateTimeUtc(filePath); - - if (currentFileUpdateTime != null) - { - _assemblyUpdatedTimesUtc[filePath] = currentFileUpdateTime.Value; - } - } - } - } - } - - private void Tracker_UpdatedOnDisk(object sender, EventArgs e) - { - var tracker = (FileChangeTracker)sender; - var filePath = tracker.FilePath; - - lock (_guard) - { - // Once we've created a diagnostic for a given analyzer file, there's - // no need to keep watching it. - _fileChangeTrackers.Remove(filePath); - } - - tracker.Dispose(); - tracker.UpdatedOnDisk -= Tracker_UpdatedOnDisk; - } -} diff --git a/src/VisualStudio/Core/Def/CodeCleanup/AbstractCodeCleanUpFixer.cs b/src/VisualStudio/Core/Def/CodeCleanup/AbstractCodeCleanUpFixer.cs index ab3025275b5a2..3f422f9f713b9 100644 --- a/src/VisualStudio/Core/Def/CodeCleanup/AbstractCodeCleanUpFixer.cs +++ b/src/VisualStudio/Core/Def/CodeCleanup/AbstractCodeCleanUpFixer.cs @@ -11,10 +11,8 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeCleanup; -using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; @@ -69,7 +67,13 @@ private async Task FixHierarchyContentAsync(IVsHierarchyCodeCleanupScope h var hierarchy = hierarchyContent.Hierarchy; if (hierarchy == null) { - return await FixSolutionAsync(_workspace.CurrentSolution, context).ConfigureAwait(true); + var solution = _workspace.CurrentSolution; + return await FixAsync( + _workspace, + // Just defer to FixProjectsAsync, passing in all fixable projects in the solution. + (progress, cancellationToken) => FixProjectsAsync( + _globalOptions, solution, solution.Projects.Where(p => p.SupportsCompilation).ToImmutableArray(), context.EnabledFixIds, progress, cancellationToken), + context).ConfigureAwait(false); } // Map the hierarchy to a ProjectId. For hierarchies mapping to multitargeted projects, we first try to @@ -90,9 +94,7 @@ private async Task FixHierarchyContentAsync(IVsHierarchyCodeCleanupScope h { var projectHierarchyItem = _vsHierarchyItemManager.GetHierarchyItem(hierarchyContent.Hierarchy, (uint)VSConstants.VSITEMID.Root); if (!hierarchyToProjectMap.TryGetProjectId(projectHierarchyItem, targetFrameworkMoniker: null, out projectId)) - { return false; - } } var itemId = hierarchyContent.ItemId; @@ -101,12 +103,15 @@ private async Task FixHierarchyContentAsync(IVsHierarchyCodeCleanupScope h await TaskScheduler.Default; var project = _workspace.CurrentSolution.GetProject(projectId); - if (project == null) - { + if (project == null || !project.SupportsCompilation) return false; - } - return await FixProjectAsync(project, context).ConfigureAwait(true); + return await FixAsync( + _workspace, + // Just defer to FixProjectsAsync, passing in this single project to fix. + (progress, cancellationToken) => FixProjectsAsync( + _globalOptions, project.Solution, [project], context.EnabledFixIds, progress, cancellationToken), + context).ConfigureAwait(false); } else if (hierarchy.GetCanonicalName(itemId, out var path) == 0) { @@ -126,54 +131,25 @@ private async Task FixHierarchyContentAsync(IVsHierarchyCodeCleanupScope h var documentIds = solution.GetDocumentIdsWithFilePath(path); var documentId = documentIds.FirstOrDefault(id => id.ProjectId == projectId); if (documentId is null) - { return false; - } var document = solution.GetRequiredDocument(documentId); var options = _globalOptions.GetCodeActionOptions(document.Project.Services); - return await FixDocumentAsync(document, options, context).ConfigureAwait(true); + + return await FixAsync( + _workspace, + async (progress, cancellationToken) => + { + var newDocument = await FixDocumentAsync(document, context.EnabledFixIds, progress, options, cancellationToken).ConfigureAwait(true); + return newDocument.Project.Solution; + }, + context).ConfigureAwait(false); } } return false; } - private Task FixSolutionAsync(Solution solution, ICodeCleanUpExecutionContext context) - { - return FixAsync(_workspace, ApplyFixAsync, context); - - // Local function - Task ApplyFixAsync(IProgress progress, CancellationToken cancellationToken) - { - return FixSolutionAsync(solution, context.EnabledFixIds, progress, cancellationToken); - } - } - - private Task FixProjectAsync(Project project, ICodeCleanUpExecutionContext context) - { - return FixAsync(_workspace, ApplyFixAsync, context); - - // Local function - async Task ApplyFixAsync(IProgress progress, CancellationToken cancellationToken) - { - var newProject = await FixProjectAsync(project, context.EnabledFixIds, progress, addProgressItemsForDocuments: true, cancellationToken).ConfigureAwait(true); - return newProject.Solution; - } - } - - private Task FixDocumentAsync(Document document, CodeActionOptions options, ICodeCleanUpExecutionContext context) - { - return FixAsync(document.Project.Solution.Workspace, ApplyFixAsync, context); - - // Local function - async Task ApplyFixAsync(IProgress progress, CancellationToken cancellationToken) - { - var newDocument = await FixDocumentAsync(document, context.EnabledFixIds, progress, options, cancellationToken).ConfigureAwait(true); - return newDocument.Project.Solution; - } - } - private Task FixTextBufferAsync(TextBufferCodeCleanUpScope textBufferScope, ICodeCleanUpExecutionContext context) { var buffer = textBufferScope.SubjectBuffer; @@ -215,9 +191,7 @@ private async Task FixAsync( { var workspaceStatusService = workspace.Services.GetService(); if (workspaceStatusService != null) - { await workspaceStatusService.WaitUntilFullyLoadedAsync(context.OperationContext.UserCancellationToken).ConfigureAwait(true); - } } using (var scope = context.OperationContext.AddScope(allowCancellation: true, description: EditorFeaturesResources.Applying_changes)) @@ -233,76 +207,51 @@ private async Task FixAsync( } } - private async Task FixSolutionAsync( + private static async Task FixProjectsAsync( + IGlobalOptionService globalOptions, Solution solution, + ImmutableArray projects, FixIdContainer enabledFixIds, IProgress progressTracker, CancellationToken cancellationToken) { - // Prepopulate the solution progress tracker with the total number of documents to process - foreach (var projectId in solution.ProjectIds) - { - var project = solution.GetRequiredProject(projectId); - if (!CanCleanupProject(project)) - { - continue; - } - - progressTracker.AddItems(project.DocumentIds.Count); - } - - foreach (var projectId in solution.ProjectIds) - { - cancellationToken.ThrowIfCancellationRequested(); - - var project = solution.GetRequiredProject(projectId); - var newProject = await FixProjectAsync(project, enabledFixIds, progressTracker, addProgressItemsForDocuments: false, cancellationToken).ConfigureAwait(false); - solution = newProject.Solution; - } - - return solution; - } - - private async Task FixProjectAsync( - Project project, - FixIdContainer enabledFixIds, - IProgress progressTracker, - bool addProgressItemsForDocuments, - CancellationToken cancellationToken) - { - if (!CanCleanupProject(project)) - { - return project; - } - - if (addProgressItemsForDocuments) - { - progressTracker.AddItems(project.DocumentIds.Count); - } - - var ideOptions = _globalOptions.GetCodeActionOptions(project.Services); - - foreach (var documentId in project.DocumentIds) - { - cancellationToken.ThrowIfCancellationRequested(); + // Add an item for each document in all the projects we're processing. + progressTracker.AddItems(projects.Sum(static p => p.DocumentIds.Count)); - var document = project.GetRequiredDocument(documentId); - progressTracker.Report(CodeAnalysisProgress.Description(document.Name)); - - // FixDocumentAsync reports progress within a document, but we only want to report progress at the - // project granularity. So we pass CodeAnalysisProgress.None here so that inner progress updates don't - // affect us. - var fixedDocument = await FixDocumentAsync(document, enabledFixIds, CodeAnalysisProgress.None, ideOptions, cancellationToken).ConfigureAwait(false); - project = fixedDocument.Project; - progressTracker.ItemCompleted(); - } - - return project; + // Run in parallel across all projects. + var changedRoots = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync( + source: projects, + produceItems: static async (project, callback, args, cancellationToken) => + { + Contract.ThrowIfFalse(project.SupportsCompilation); + cancellationToken.ThrowIfCancellationRequested(); + + var ideOptions = args.globalOptions.GetCodeActionOptions(project.Services); + + // And for each project, process all the documents in parallel. + await RoslynParallel.ForEachAsync( + source: project.Documents, + cancellationToken, + async (document, cancellationToken) => + { + using var _ = args.progressTracker.ItemCompletedScope(); + + // FixDocumentAsync reports progress within a document, but we only want to report progress at + // the document granularity. So we pass CodeAnalysisProgress.None here so that inner progress + // updates don't affect us. + var fixedDocument = await FixDocumentAsync(document, args.enabledFixIds, CodeAnalysisProgress.None, ideOptions, cancellationToken).ConfigureAwait(false); + if (fixedDocument == document) + return; + + callback((document.Id, await fixedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false))); + }).ConfigureAwait(false); + }, + args: (globalOptions, solution, enabledFixIds, progressTracker), + cancellationToken).ConfigureAwait(false); + + return solution.WithDocumentSyntaxRoots(changedRoots); } - private static bool CanCleanupProject(Project project) - => project.Services.GetService() != null; - private static async Task FixDocumentAsync( Document document, FixIdContainer enabledFixIds, @@ -310,10 +259,9 @@ private static async Task FixDocumentAsync( CodeActionOptions ideOptions, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); if (document.IsGeneratedCode(cancellationToken)) - { return document; - } var codeCleanupService = document.GetRequiredLanguageService(); diff --git a/src/VisualStudio/Core/Def/Implementation/VisualStudioMetadataAsSourceFileSupportService.cs b/src/VisualStudio/Core/Def/Implementation/VisualStudioMetadataAsSourceFileSupportService.cs deleted file mode 100644 index 3f623ce2e8ef7..0000000000000 --- a/src/VisualStudio/Core/Def/Implementation/VisualStudioMetadataAsSourceFileSupportService.cs +++ /dev/null @@ -1,76 +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.ComponentModel.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.MetadataAsSource; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation; - -[Export(typeof(VisualStudioMetadataAsSourceFileSupportService))] -internal sealed class VisualStudioMetadataAsSourceFileSupportService : IVsSolutionEvents -{ - private readonly IThreadingContext _threadingContext; - private readonly IMetadataAsSourceFileService _metadataAsSourceFileService; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioMetadataAsSourceFileSupportService( - IThreadingContext threadingContext, - IMetadataAsSourceFileService metadataAsSourceFileService) - { - _threadingContext = threadingContext; - _metadataAsSourceFileService = metadataAsSourceFileService; - } - - public async Task InitializeAsync(IAsyncServiceProvider serviceProvider, CancellationToken cancellationToken) - { - var solution = await serviceProvider.GetServiceAsync(_threadingContext.JoinableTaskFactory).ConfigureAwait(false); - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - // Intentionally ignore the event-cookie we get back out. We never stop listening to solution events. - ErrorHandler.ThrowOnFailure(solution.AdviseSolutionEvents(this, out _)); - } - - public int OnAfterCloseSolution(object pUnkReserved) - { - _metadataAsSourceFileService.CleanupGeneratedFiles(); - - return VSConstants.S_OK; - } - - public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) - => VSConstants.E_NOTIMPL; - - public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) - => VSConstants.E_NOTIMPL; - - public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution) - => VSConstants.E_NOTIMPL; - - public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) - => VSConstants.E_NOTIMPL; - - public int OnBeforeCloseSolution(object pUnkReserved) - => VSConstants.E_NOTIMPL; - - public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) - => VSConstants.E_NOTIMPL; - - public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) - => VSConstants.E_NOTIMPL; - - public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel) - => VSConstants.E_NOTIMPL; - - public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) - => VSConstants.E_NOTIMPL; -} diff --git a/src/VisualStudio/Core/Def/InheritanceMargin/MarginGlyph/InheritanceMarginGlyph.cs b/src/VisualStudio/Core/Def/InheritanceMargin/MarginGlyph/InheritanceMarginGlyph.cs index ba1b1f65608f3..9a6ea06bacea1 100644 --- a/src/VisualStudio/Core/Def/InheritanceMargin/MarginGlyph/InheritanceMarginGlyph.cs +++ b/src/VisualStudio/Core/Def/InheritanceMargin/MarginGlyph/InheritanceMarginGlyph.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Windows; using System.Windows.Automation; +using System.Windows.Automation.Peers; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; @@ -110,6 +111,9 @@ protected override void OnContextMenuOpening(ContextMenuEventArgs e) base.OnContextMenuOpening(e); } + protected override AutomationPeer OnCreateAutomationPeer() + => new InheritanceMarginAutomationPeer(this); + private void LazyInitializeContextMenu() { if (ContextMenu is not InheritanceMarginContextMenu) @@ -211,4 +215,12 @@ private void ResetFocus() } } } + + private sealed class InheritanceMarginAutomationPeer(InheritanceMarginGlyph owner) : ButtonAutomationPeer(owner) + { + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Group; + } + } } diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageDebugInfo.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageDebugInfo.cs index 9b5be14eb0845..9820e02d3087e 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageDebugInfo.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageDebugInfo.cs @@ -20,7 +20,7 @@ int IVsLanguageDebugInfo.GetLanguageID(IVsTextBuffer pBuffer, int iLine, int iCo { try { - return LanguageDebugInfo.GetLanguageID(pBuffer, iLine, iCol, out pguidLanguageID); + return _languageDebugInfo.GetLanguageID(pBuffer, iLine, iCol, out pguidLanguageID); } catch (Exception e) when (FatalError.ReportAndPropagate(e)) { @@ -32,7 +32,7 @@ int IVsLanguageDebugInfo.GetLocationOfName(string pszName, out string pbstrMkDoc { try { - return LanguageDebugInfo.GetLocationOfName(pszName, out pbstrMkDoc, out pspanLocation); + return _languageDebugInfo.GetLocationOfName(pszName, out pbstrMkDoc, out pspanLocation); } catch (Exception e) when (FatalError.ReportAndPropagate(e)) { @@ -44,7 +44,7 @@ int IVsLanguageDebugInfo.GetNameOfLocation(IVsTextBuffer pBuffer, int iLine, int { try { - return LanguageDebugInfo.GetNameOfLocation(pBuffer, iLine, iCol, out pbstrName, out piLineOffset); + return _languageDebugInfo.GetNameOfLocation(pBuffer, iLine, iCol, out pbstrName, out piLineOffset); } catch (Exception e) when (FatalError.ReportAndPropagate(e)) { @@ -56,7 +56,7 @@ int IVsLanguageDebugInfo.GetProximityExpressions(IVsTextBuffer pBuffer, int iLin { try { - return LanguageDebugInfo.GetProximityExpressions(pBuffer, iLine, iCol, cLines, out ppEnum); + return _languageDebugInfo.GetProximityExpressions(pBuffer, iLine, iCol, cLines, out ppEnum); } catch (Exception e) when (FatalError.ReportAndPropagate(e)) { @@ -68,7 +68,7 @@ int IVsLanguageDebugInfo.IsMappedLocation(IVsTextBuffer pBuffer, int iLine, int { try { - return LanguageDebugInfo.IsMappedLocation(pBuffer, iLine, iCol); + return _languageDebugInfo.IsMappedLocation(pBuffer, iLine, iCol); } catch (Exception e) when (FatalError.ReportAndPropagate(e)) { @@ -80,7 +80,7 @@ int IVsLanguageDebugInfo.ResolveName(string pszName, uint dwFlags, out IVsEnumDe { try { - return LanguageDebugInfo.ResolveName(pszName, dwFlags, out ppNames); + return _languageDebugInfo.ResolveName(pszName, dwFlags, out ppNames); } catch (Exception e) when (FatalError.ReportAndPropagate(e)) { @@ -92,7 +92,7 @@ int IVsLanguageDebugInfo.ValidateBreakpointLocation(IVsTextBuffer pBuffer, int i { try { - return LanguageDebugInfo.ValidateBreakpointLocation(pBuffer, iLine, iCol, pCodeSpan); + return _languageDebugInfo.ValidateBreakpointLocation(pBuffer, iLine, iCol, pCodeSpan); } catch (Exception e) when (FatalError.ReportAndPropagate(e)) { diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs index e097e22eeee74..180174c688ae6 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs @@ -4,6 +4,7 @@ #nullable disable +using System.Collections.Immutable; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; @@ -66,12 +67,13 @@ private int FormatWorker(IVsTextLayer textLayer, TextSpan[] selections, Cancella // Since we know we are on the UI thread, lets get the base indentation now, so that there is less // cleanup work to do later in Venus. var ruleFactory = Workspace.Services.GetService(); - var rules = ruleFactory.CreateRule(documentSyntax, start).Concat(Formatter.GetDefaultFormattingRules(document.Project.Services)); // use formatting that return text changes rather than tree rewrite which is more expensive var formatter = document.GetRequiredLanguageService(); - var originalChanges = formatter.GetFormattingResult(root, [adjustedSpan], formattingOptions, rules, cancellationToken) - .GetTextChanges(cancellationToken); + var originalChanges = formatter.GetFormattingResult( + root, [adjustedSpan], formattingOptions, + [ruleFactory.CreateRule(documentSyntax, start), .. Formatter.GetDefaultFormattingRules(document.Project.Services)], + cancellationToken).GetTextChanges(cancellationToken); var originalSpan = RoslynTextSpan.FromBounds(start, end); var formattedChanges = ruleFactory.FilterFormattedChanges(document.Id, originalSpan, originalChanges); diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs index 867aa1f21ef16..bac0507b76bf7 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs @@ -2,11 +2,11 @@ // 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; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; @@ -19,13 +19,12 @@ using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; -using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; using Microsoft.VisualStudio.LanguageServices.Implementation.Venus; -using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Outlining; using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Threading; using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; @@ -36,7 +35,8 @@ internal abstract partial class AbstractLanguageService { internal TPackage Package { get; } - internal VsLanguageDebugInfo LanguageDebugInfo { get; private set; } + + private readonly VsLanguageDebugInfo _languageDebugInfo; // DevDiv 753309: // We've redefined some VS interfaces that had incorrect PIAs. When @@ -51,15 +51,14 @@ internal abstract partial class AbstractLanguageService /// Whether or not we have been set up. This is set once everything is wired up and cleared once tear down has begun. @@ -71,9 +70,21 @@ internal abstract partial class AbstractLanguageService private bool _isSetUp; + protected abstract string ContentTypeName { get; } + protected abstract string LanguageName { get; } + protected abstract string RoslynLanguageName { get; } + protected abstract Guid DebuggerLanguageId { get; } + protected AbstractLanguageService(TPackage package) { Package = package; + + Debug.Assert(!this.Package.JoinableTaskFactory.Context.IsOnMainThread, "Language service should be instantiated on background thread"); + + this.EditorOptionsService = this.Package.ComponentModel.GetService(); + this.Workspace = this.Package.ComponentModel.GetService(); + this.EditorAdaptersFactoryService = this.Package.ComponentModel.GetService(); + this._languageDebugInfo = CreateLanguageDebugInfo(); } public override IServiceProvider SystemServiceProvider @@ -82,32 +93,28 @@ public override IServiceProvider SystemServiceProvider /// /// Setup and TearDown go in reverse order. /// - internal void Setup() + public async Task SetupAsync(CancellationToken cancellationToken) { - this.ComAggregate = CreateComAggregate(); - // First, acquire any services we need throughout our lifetime. - this.GetServices(); + // This method should only contain calls to acquire services off of the component model + // or service providers. Anything else which is more complicated should go in Initialize + // instead. - // TODO: Is the below access to component model required or can be removed? - _ = this.Package.ComponentModel; + // Start off a background task to prime some components we'll need for editing. + Task.Run(() => + { + var formatter = this.Workspace.Services.GetLanguageServices(RoslynLanguageName).GetService(); + formatter?.GetDefaultFormattingRules(); + }, cancellationToken).Forget(); - // Start off a background task to prime some components we'll need for editing - VsTaskLibraryHelper.CreateAndStartTask(VsTaskLibraryHelper.ServiceInstance, VsTaskRunContext.BackgroundThread, - () => PrimeLanguageServiceComponentsOnBackground()); + await this.Package.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - // Finally, once our connections are established, set up any initial state that we need. - // Note: we may be instantiated at any time (including when the IDE is already - // debugging). We must not assume anything about our initial state and must instead - // query for all the information we need at this point. - this.Initialize(); + // Creating the com aggregate has to happen on the UI thread. + this.ComAggregate = Interop.ComAggregate.CreateAggregatedObject(this); _isSetUp = true; } - private object CreateComAggregate() - => Interop.ComAggregate.CreateAggregatedObject(this); - internal void TearDown() { if (!_isSetUp) @@ -117,9 +124,6 @@ internal void TearDown() _isSetUp = false; GC.SuppressFinalize(this); - - this.Uninitialize(); - this.RemoveServices(); } ~AbstractLanguageService() @@ -130,50 +134,6 @@ internal void TearDown() } } - protected virtual void GetServices() - { - // This method should only contain calls to acquire services off of the component model - // or service providers. Anything else which is more complicated should go in Initialize - // instead. - this.EditorOptionsService = this.Package.ComponentModel.GetService(); - this.Workspace = this.Package.ComponentModel.GetService(); - this.EditorAdaptersFactoryService = this.Package.ComponentModel.GetService(); - this.AnalyzerFileWatcherService = this.Package.ComponentModel.GetService(); - } - - protected virtual void RemoveServices() - { - this.EditorAdaptersFactoryService = null; - this.Workspace = null; - } - - /// - /// Called right after we instantiate the language service. Used to set up any internal - /// state we need. - /// - /// Try to keep this method fairly clean. Any complicated logic should go in methods called - /// from this one. Initialize and Uninitialize go in reverse order - /// - protected virtual void Initialize() - { - InitializeLanguageDebugInfo(); - } - - protected virtual void Uninitialize() - { - UninitializeLanguageDebugInfo(); - } - - private void PrimeLanguageServiceComponentsOnBackground() - { - var formatter = this.Workspace.Services.GetLanguageServices(RoslynLanguageName).GetService(); - formatter?.GetDefaultFormattingRules(); - } - - protected abstract string ContentTypeName { get; } - protected abstract string LanguageName { get; } - protected abstract string RoslynLanguageName { get; } - protected virtual void SetupNewTextView(IVsTextView textView) { Contract.ThrowIfNull(textView); @@ -201,7 +161,7 @@ protected virtual void SetupNewTextView(IVsTextView textView) // If the file is metadata as source, and the user has the preference set to collapse them, then // always collapse all metadata as source var globalOptions = this.Package.ComponentModel.GetService(); - var options = BlockStructureOptionsStorage.GetBlockStructureOptions(globalOptions, openDocument.Project.Language, isMetadataAsSource: masWorkspace is not null); + var options = BlockStructureOptionsStorage.GetBlockStructureOptions(globalOptions, openDocument.Project.Language, isMetadataAsSource: true); collapseAllImplementations = masWorkspace.FileService.ShouldCollapseOnOpen(openDocument.FilePath, options); } @@ -255,15 +215,9 @@ private void ConditionallyCollapseOutliningRegions(IVsTextView textView, IWpfTex } } - private void InitializeLanguageDebugInfo() - => this.LanguageDebugInfo = this.CreateLanguageDebugInfo(); - - protected abstract Guid DebuggerLanguageId { get; } - private VsLanguageDebugInfo CreateLanguageDebugInfo() { - var workspace = this.Workspace; - var languageServices = workspace.Services.GetLanguageServices(RoslynLanguageName); + var languageServices = this.Workspace.Services.GetLanguageServices(RoslynLanguageName); return new VsLanguageDebugInfo( this.DebuggerLanguageId, @@ -273,9 +227,6 @@ private VsLanguageDebugInfo CreateLanguageDebugInfo() this.Package.ComponentModel.GetService()); } - private void UninitializeLanguageDebugInfo() - => this.LanguageDebugInfo = null; - protected virtual IVsContainedLanguage CreateContainedLanguage( IVsTextBufferCoordinator bufferCoordinator, ProjectSystemProject project, IVsHierarchy hierarchy, uint itemid) diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractPackage`2.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractPackage`2.cs index b4c28e70037d4..d53c66478e03d 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractPackage`2.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractPackage`2.cs @@ -51,14 +51,16 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke RegisterEditorFactory(editorFactory); } - RegisterLanguageService(typeof(TLanguageService), async ct => + RegisterLanguageService(typeof(TLanguageService), async cancellationToken => { - await JoinableTaskFactory.SwitchToMainThreadAsync(ct); + // Ensure we're on the BG when creating the language service. + await TaskScheduler.Default; // Create the language service, tell it to set itself up, then store it in a field // so we can notify it that it's time to clean up. _languageService = CreateLanguageService(); - _languageService.Setup(); + await _languageService.SetupAsync(cancellationToken).ConfigureAwait(false); + return _languageService.ComAggregate; }); diff --git a/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs b/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs index 545a528028c67..17a4c3c03891b 100644 --- a/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs +++ b/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Text; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Editor; @@ -21,6 +22,7 @@ using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Threading; using Microsoft.VisualStudio.Utilities; using IServiceProvider = System.IServiceProvider; using Task = System.Threading.Tasks.Task; @@ -519,11 +521,9 @@ private static async Task FindReferencesAsync( try { - // Kick off the work to do the actual finding on a BG thread. That way we don' - // t block the calling (UI) thread too long if we happen to do our work on this - // thread. - await Task.Run( - () => FindReferencesAsync(symbolListItem, project, context, classificationOptions, cancellationToken), cancellationToken).ConfigureAwait(false); + // Switch to teh background so we don't block the calling thread (the UI thread) while we're doing this work. + await TaskScheduler.Default; + await FindReferencesAsync(symbolListItem, project, context, classificationOptions, cancellationToken).ConfigureAwait(false); } finally { diff --git a/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs b/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs index 1c51b4865aa4b..cf0b5b0f3cc8b 100644 --- a/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs +++ b/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs @@ -3,9 +3,11 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Search.Data; using Microsoft.VisualStudio.Text.PatternMatching; @@ -21,13 +23,16 @@ internal sealed partial class RoslynSearchItemsSourceProvider /// private sealed class RoslynNavigateToSearchCallback : INavigateToSearchCallback { + private readonly Solution _solution; private readonly RoslynSearchItemsSourceProvider _provider; private readonly ISearchCallback _searchCallback; public RoslynNavigateToSearchCallback( + Solution solution, RoslynSearchItemsSourceProvider provider, ISearchCallback searchCallback) { + _solution = solution; _provider = provider; _searchCallback = searchCallback; } @@ -51,30 +56,34 @@ public void ReportIncomplete() _searchCallback.ReportIncomplete(IncompleteReason.Parsing); } - public Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) + public Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { // Convert roslyn pattern matches to the platform type. - var matches = result.Matches.SelectAsArray(static m => new PatternMatch( + foreach (var result in results) + { + var matches = result.Matches.SelectAsArray(static m => new PatternMatch( ConvertKind(m.Kind), punctuationStripped: false, m.IsCaseSensitive, m.MatchedSpans.SelectAsArray(static s => s.ToSpan()))); - // Weight the items based on the overall pattern matching weights. We want the items that have the best - // pattern matches (low .Kind values) to have the highest float values (as higher is better for the VS - // api). - var perProviderItemPriority = float.MaxValue - Enumerable.Sum(result.Matches.Select(m => (int)m.Kind)); + // Weight the items based on the overall pattern matching weights. We want the items that have the best + // pattern matches (low .Kind values) to have the highest float values (as higher is better for the VS + // api). + var perProviderItemPriority = float.MaxValue - Enumerable.Sum(result.Matches.Select(m => (int)m.Kind)); - _searchCallback.AddItem(new RoslynCodeSearchResult( - _provider, - result, - GetResultType(result.Kind), - result.Name, - result.SecondarySort, - matches, - result.NavigableItem.Document.FilePath, - perProviderItemPriority, - project.Language)); + var project = _solution.GetRequiredProject(result.NavigableItem.Document.Project.Id); + _searchCallback.AddItem(new RoslynCodeSearchResult( + _provider, + result, + GetResultType(result.Kind), + result.Name, + result.SecondarySort, + matches, + result.NavigableItem.Document.FilePath, + perProviderItemPriority, + project.Language)); + } return Task.CompletedTask; } diff --git a/src/VisualStudio/Core/Def/NavigateTo/RoslynSearchItemsSource.cs b/src/VisualStudio/Core/Def/NavigateTo/RoslynSearchItemsSource.cs index d535811c7cea4..3d32fc0b23cf3 100644 --- a/src/VisualStudio/Core/Def/NavigateTo/RoslynSearchItemsSource.cs +++ b/src/VisualStudio/Core/Def/NavigateTo/RoslynSearchItemsSource.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.VisualStudio.Search.Data; +using Microsoft.VisualStudio.Threading; namespace Microsoft.CodeAnalysis.NavigateTo; @@ -46,7 +47,7 @@ public override async Task PerformSearchAsync(ISearchQuery searchQuery, ISearchC var cancellationTriggeredTask = Task.Delay(-1, cancellationToken); // Now, kick off the actual search work concurrently with the waiting task. - var searchTask = Task.Run(() => PerformSearchWorkerAsync(searchQuery, searchCallback, cancellationToken), cancellationToken); + var searchTask = PerformSearchWorkerAsync(searchQuery, searchCallback, cancellationToken); // Now wait for either task to complete. This allows us to bail out of the call into us once the // cancellation token is signaled, even if search work is still happening. This is desirable as the @@ -65,6 +66,9 @@ private async Task PerformSearchWorkerAsync( ISearchCallback searchCallback, CancellationToken cancellationToken) { + // Ensure we yield immedaitely so our caller can proceed with other work. + await Task.Yield().ConfigureAwait(false); + var searchValue = searchQuery.QueryString.Trim(); if (string.IsNullOrWhiteSpace(searchValue)) return; @@ -89,10 +93,11 @@ private async Task PerformSearchWorkerAsync( // Create a nav-to callback that will take results and translate them to aiosp results for the // callback passed to us. + var solution = provider._workspace.CurrentSolution; var searcher = NavigateToSearcher.Create( - provider._workspace.CurrentSolution, + solution, provider._asyncListener, - new RoslynNavigateToSearchCallback(provider, searchCallback), + new RoslynNavigateToSearchCallback(solution, provider, searchCallback), searchValue, kinds, provider._threadingContext.DisposalToken); diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs index 5ad659283582a..1c2da80987c39 100644 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs +++ b/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.GraphModel; using Microsoft.CodeAnalysis.NavigateTo; +using System.Collections.Immutable; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Progression; @@ -14,11 +15,13 @@ internal sealed partial class SearchGraphQuery { private class ProgressionNavigateToSearchCallback : INavigateToSearchCallback { + private readonly Solution _solution; private readonly IGraphContext _context; private readonly GraphBuilder _graphBuilder; - public ProgressionNavigateToSearchCallback(IGraphContext context, GraphBuilder graphBuilder) + public ProgressionNavigateToSearchCallback(Solution solution, IGraphContext context, GraphBuilder graphBuilder) { + _solution = solution; _context = context; _graphBuilder = graphBuilder; } @@ -36,14 +39,17 @@ public void ReportIncomplete() { } - public async Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) + public async Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { - var node = await _graphBuilder.CreateNodeAsync(project.Solution, result, cancellationToken).ConfigureAwait(false); - if (node != null) + foreach (var result in results) { - // _context.OutputNodes is not threadsafe. So ensure only one navto callback can mutate it at a time. - lock (this) - _context.OutputNodes.Add(node); + var node = await _graphBuilder.CreateNodeAsync(_solution, result, cancellationToken).ConfigureAwait(false); + if (node != null) + { + // _context.OutputNodes is not threadsafe. So ensure only one navto callback can mutate it at a time. + lock (this) + _context.OutputNodes.Add(node); + } } } } diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/SearchGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/SearchGraphQuery.cs index 0d60c8feb2a90..2c95f2f1c2315 100644 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/SearchGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Progression/GraphQueries/SearchGraphQuery.cs @@ -19,7 +19,7 @@ internal sealed partial class SearchGraphQuery( public async Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) { var graphBuilder = await GraphBuilder.CreateForInputNodesAsync(solution, context.InputNodes, cancellationToken).ConfigureAwait(false); - var callback = new ProgressionNavigateToSearchCallback(context, graphBuilder); + var callback = new ProgressionNavigateToSearchCallback(solution, context, graphBuilder); // We have a specialized host for progression vs normal nav-to. Progression itself will tell the client if // the project is fully loaded or not. But after that point, the client will be considered fully loaded and diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.MetadataCache.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.MetadataCache.cs deleted file mode 100644 index 8cd6e19b78c26..0000000000000 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.MetadataCache.cs +++ /dev/null @@ -1,67 +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.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; - -internal sealed partial class VisualStudioMetadataReferenceManager -{ - private sealed class MetadataCache - { - private readonly object _gate = new(); - - // value is ValueSource so that how metadata is re-acquired back are different per entry. - private readonly Dictionary _metadataCache = []; - - public bool TryGetMetadata(FileKey key, [NotNullWhen(true)] out AssemblyMetadata? metadata) - { - lock (_gate) - { - return TryGetMetadata_NoLock(key, out metadata); - } - } - - private bool TryGetMetadata_NoLock(FileKey key, [NotNullWhen(true)] out AssemblyMetadata? metadata) - => _metadataCache.TryGetValue(key, out metadata) && metadata != null; - - /// - /// Gets specified metadata from the cache, or retrieves metadata from given - /// and adds it to the cache if it's not there yet. - /// - /// - /// True if the metadata is retrieved from source, false if it already exists in the cache. - /// - public bool GetOrAddMetadata(FileKey key, AssemblyMetadata newMetadata, out AssemblyMetadata metadata) - { - lock (_gate) - { - if (TryGetMetadata_NoLock(key, out var cachedMetadata)) - { - metadata = cachedMetadata; - return false; - } - - // the source is expected to keep the metadata alive at this point - Contract.ThrowIfNull(newMetadata); - - // don't use "Add" since key might already exist with already released metadata - _metadataCache[key] = newMetadata; - metadata = newMetadata; - return true; - } - } - - public void ClearCache() - { - lock (_gate) - { - _metadataCache.Clear(); - } - } - } -} diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.NativeMethods.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.NativeMethods.cs index 696f5d410793c..5cbdacdcbb16a 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.NativeMethods.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.NativeMethods.cs @@ -2,20 +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. -#nullable disable - using System; using System.Runtime.InteropServices; -using Microsoft.CodeAnalysis; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; internal sealed partial class VisualStudioMetadataReferenceManager { + private static readonly Guid s_IID_IMetaDataImport = new("7DAC8207-D3AE-4c75-9B67-92801A497D44"); + [ComImport] [Guid("7998EA64-7F95-48B8-86FC-17CAF48BF5CB")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IMetaDataInfo + private interface IMetaDataInfo { // MetaData scope is opened (there's a reference to a MetaData interface for this scope). // Returns S_OK, COR_E_NOTSUPPORTED, or E_INVALIDARG (if NULL is passed). @@ -28,16 +27,19 @@ internal interface IMetaDataInfo } // Flags returned from IMetaDataInfo.GetFileMapping - internal enum CorFileMapping : uint + private enum CorFileMapping : uint { - Flat = 0, // Flat file mapping - file is mapped as data file (code:SEC_IMAGE flag was not - // passed to code:CreateFileMapping). - ExecutableImage = 1 // Executable image file mapping - file is mapped for execution - // (either via code:LoadLibrary or code:CreateFileMapping with code:SEC_IMAGE flag). + Flat = 0, // Flat file mapping - file is mapped as data file (code:SEC_IMAGE flag was not + // passed to code:CreateFileMapping). +#if false + ExecutableImage = 1 // Executable image file mapping - file is mapped for execution + // (either via code:LoadLibrary or code:CreateFileMapping with code:SEC_IMAGE flag). +#endif } - internal enum CorOpenFlags : uint + private enum CorOpenFlags : uint { +#if false Read = 0, Write = 1, ReadWriteMask = 1, @@ -45,10 +47,13 @@ internal enum CorOpenFlags : uint CopyMemory = 2, ManifestMetadata = 8, +#endif ReadOnly = 16, +#if false TakeOwnership = 32, CacheImage = 4, NoTypeLib = 128 +#endif } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 01a6a41f6695d..5c20f8ef178b4 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -14,9 +14,7 @@ using System.Runtime.InteropServices; using System.Threading; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.VisualStudio.Shell.Interop; using Roslyn.Utilities; @@ -34,8 +32,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; /// internal sealed partial class VisualStudioMetadataReferenceManager : IWorkspaceService, IDisposable { - private static readonly Guid s_IID_IMetaDataImport = new("7DAC8207-D3AE-4c75-9B67-92801A497D44"); - private static readonly ConditionalWeakTable s_lifetimeMap = new(); /// @@ -47,29 +43,33 @@ internal sealed partial class VisualStudioMetadataReferenceManager : IWorkspaceS /// private static readonly ConditionalWeakTable> s_metadataToStorageHandles = new(); - private readonly MetadataCache _metadataCache = new(); + private readonly object _metadataCacheLock = new(); + + /// + /// Access locked with . + /// + private readonly Dictionary _metadataCache = []; + private readonly ImmutableArray _runtimeDirectories; private readonly TemporaryStorageService _temporaryStorageService; - - internal IVsXMLMemberIndexService XmlMemberIndexService { get; } + private readonly IVsXMLMemberIndexService _xmlMemberIndexService; + private readonly ReaderWriterLockSlim _smartOpenScopeLock = new(); /// /// The smart open scope service. This can be null during shutdown when using the service might crash. Any - /// use of this field or derived types should be synchronized with to ensure + /// use of this field or derived types should be synchronized with to ensure /// you don't grab the field and then use it while shutdown continues. /// private IVsSmartOpenScope? SmartOpenScopeServiceOpt { get; set; } - private readonly ReaderWriterLockSlim _readerWriterLock = new(); - - internal VisualStudioMetadataReferenceManager( + public VisualStudioMetadataReferenceManager( IServiceProvider serviceProvider, TemporaryStorageService temporaryStorageService) { _runtimeDirectories = GetRuntimeDirectories(); - XmlMemberIndexService = (IVsXMLMemberIndexService)serviceProvider.GetService(typeof(SVsXMLMemberIndexService)); - Assumes.Present(XmlMemberIndexService); + _xmlMemberIndexService = (IVsXMLMemberIndexService)serviceProvider.GetService(typeof(SVsXMLMemberIndexService)); + Assumes.Present(_xmlMemberIndexService); SmartOpenScopeServiceOpt = (IVsSmartOpenScope)serviceProvider.GetService(typeof(SVsSmartOpenScope)); Assumes.Present(SmartOpenScopeServiceOpt); @@ -80,7 +80,7 @@ internal VisualStudioMetadataReferenceManager( public void Dispose() { - using (_readerWriterLock.DisposableWrite()) + using (_smartOpenScopeLock.DisposableWrite()) { // IVsSmartOpenScope can't be used as we shutdown, and this is pretty commonly hit according to // Windows Error Reporting as we try creating metadata for compilations. @@ -88,11 +88,17 @@ public void Dispose() } } + private bool TryGetMetadata(FileKey key, [NotNullWhen(true)] out AssemblyMetadata? metadata) + { + lock (_metadataCacheLock) + return _metadataCache.TryGetValue(key, out metadata); + } + public IReadOnlyList? GetStorageHandles(string fullPath, DateTime snapshotTimestamp) { var key = new FileKey(fullPath, snapshotTimestamp); // check existing metadata - if (_metadataCache.TryGetMetadata(key, out var source) && + if (TryGetMetadata(key, out var source) && s_metadataToStorageHandles.TryGetValue(source, out var handles)) { return handles; @@ -102,10 +108,7 @@ public void Dispose() } public PortableExecutableReference CreateMetadataReferenceSnapshot(string filePath, MetadataReferenceProperties properties) - => new VisualStudioMetadataReference.Snapshot(this, properties, filePath, fileChangeTrackerOpt: null); - - public void ClearCache() - => _metadataCache.ClearCache(); + => new VisualStudioPortableExecutableReference(this, properties, filePath, fileChangeTracker: null); private bool VsSmartScopeCandidate(string fullPath) => _runtimeDirectories.Any(static (d, fullPath) => fullPath.StartsWith(d, StringComparison.OrdinalIgnoreCase), fullPath); @@ -135,36 +138,50 @@ private static ImmutableArray GetRuntimeDirectories() internal Metadata GetMetadata(string fullPath, DateTime snapshotTimestamp) { var key = new FileKey(fullPath, snapshotTimestamp); + // check existing metadata - if (_metadataCache.TryGetMetadata(key, out var metadata)) - return metadata; + if (!TryGetMetadata(key, out var metadata)) + { + // wasn't in the cache. create a new instance. + metadata = GetMetadataWorker(fullPath); + Contract.ThrowIfNull(metadata); - var newMetadata = GetMetadataWorker(); + lock (_metadataCacheLock) + { + // Now try to create and add the metadata to the cache. If we fail to add it (because some other thread + // beat us to this), then Dispose the metadata we just created and will return the existing metadata + // instead. + if (_metadataCache.TryGetValue(key, out var cachedMetadata)) + { + metadata.Dispose(); + return cachedMetadata; + } - if (!_metadataCache.GetOrAddMetadata(key, newMetadata, out metadata)) - newMetadata.Dispose(); + // don't use "Add" since key might already exist with already released metadata + _metadataCache[key] = metadata; + return metadata; + } + } return metadata; - AssemblyMetadata GetMetadataWorker() + AssemblyMetadata GetMetadataWorker(string fullPath) { - if (VsSmartScopeCandidate(key.FullPath)) - { - var newMetadata = CreateAssemblyMetadataFromMetadataImporter(key); - return newMetadata; - } - else - { - // use temporary storage - return CreateAssemblyMetadata(key, GetMetadataFromTemporaryStorage); - } + var (metadata, handles) = VsSmartScopeCandidate(fullPath) + ? CreateAssemblyMetadataFromMetadataImporter(fullPath) + : CreateAssemblyMetadata(fullPath, fullPath => GetMetadataFromTemporaryStorage(fullPath, _temporaryStorageService)); + + if (handles != null) + s_metadataToStorageHandles.Add(metadata, handles); + + return metadata; } } - private (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) GetMetadataFromTemporaryStorage( - FileKey moduleFileKey) + private static (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) GetMetadataFromTemporaryStorage( + string fullPath, TemporaryStorageService temporaryStorageService) { - GetStorageInfoFromTemporaryStorage(moduleFileKey, out var storageHandle, out var stream); + GetStorageInfoFromTemporaryStorage(fullPath, temporaryStorageService, out var storageHandle, out var stream); unsafe { @@ -175,8 +192,8 @@ AssemblyMetadata GetMetadataWorker() return (metadata, storageHandle); } - void GetStorageInfoFromTemporaryStorage( - FileKey moduleFileKey, out TemporaryStorageStreamHandle storageHandle, out UnmanagedMemoryStream stream) + static void GetStorageInfoFromTemporaryStorage( + string fullPath, TemporaryStorageService temporaryStorageService, out TemporaryStorageStreamHandle storageHandle, out UnmanagedMemoryStream stream) { int size; @@ -185,7 +202,7 @@ void GetStorageInfoFromTemporaryStorage( { // Open a file on disk, find the metadata section, copy those bytes into the temp stream, and release // the file immediately after. - using (var fileStream = FileUtilities.OpenRead(moduleFileKey.FullPath)) + using (var fileStream = FileUtilities.OpenRead(fullPath)) { var headers = new PEHeaders(fileStream); @@ -206,7 +223,7 @@ void GetStorageInfoFromTemporaryStorage( // location, so we can create a metadata value wrapping that. This will also let us share the memory // for that metadata value with our OOP process. copyStream.Position = 0; - storageHandle = _temporaryStorageService.WriteToTemporaryStorage(copyStream, CancellationToken.None); + storageHandle = temporaryStorageService.WriteToTemporaryStorage(copyStream, CancellationToken.None); } // Now, read the data from the memory-mapped-file back into a stream that we load into the metadata value. @@ -235,21 +252,21 @@ static void StreamCopy(Stream source, Stream destination, int start, int length) /// /// - private AssemblyMetadata CreateAssemblyMetadataFromMetadataImporter(FileKey fileKey) + private (AssemblyMetadata assemblyMetadata, IReadOnlyList? handles) CreateAssemblyMetadataFromMetadataImporter(string fullPath) { - return CreateAssemblyMetadata(fileKey, fileKey => + return CreateAssemblyMetadata(fullPath, fullPath => { - var metadata = TryCreateModuleMetadataFromMetadataImporter(fileKey); + var metadata = TryCreateModuleMetadataFromMetadataImporter(fullPath); if (metadata != null) return (metadata, storageHandle: null); // getting metadata didn't work out through importer. fallback to shadow copy one - return GetMetadataFromTemporaryStorage(fileKey); + return GetMetadataFromTemporaryStorage(fullPath, _temporaryStorageService); }); - ModuleMetadata? TryCreateModuleMetadataFromMetadataImporter(FileKey moduleFileKey) + ModuleMetadata? TryCreateModuleMetadataFromMetadataImporter(string fullPath) { - if (!TryGetFileMappingFromMetadataImporter(moduleFileKey, out var info, out var pImage, out var length)) + if (!TryGetFileMappingFromMetadataImporter(fullPath, out var info, out var pImage, out var length)) { return null; } @@ -262,16 +279,12 @@ private AssemblyMetadata CreateAssemblyMetadataFromMetadataImporter(FileKey file return metadata; } - bool TryGetFileMappingFromMetadataImporter(FileKey fileKey, [NotNullWhen(true)] out IMetaDataInfo? info, out IntPtr pImage, out long length) + bool TryGetFileMappingFromMetadataImporter(string fullPath, [NotNullWhen(true)] out IMetaDataInfo? info, out IntPtr pImage, out long length) { // We might not be able to use COM services to get this if VS is shutting down. We'll synchronize to make sure this // doesn't race against - using (_readerWriterLock.DisposableRead()) + using (_smartOpenScopeLock.DisposableRead()) { - // here, we don't care about timestamp since all those bits should be part of Fx. and we assume that - // it won't be changed in the middle of VS running. - var fullPath = fileKey.FullPath; - info = null; pImage = default; length = default; @@ -299,47 +312,45 @@ bool TryGetFileMappingFromMetadataImporter(FileKey fileKey, [NotNullWhen(true)] /// /// - private static AssemblyMetadata CreateAssemblyMetadata( - FileKey fileKey, - Func moduleMetadataFactory) + private static (AssemblyMetadata assemblyMetadata, IReadOnlyList? handles) CreateAssemblyMetadata( + string fullPath, + Func moduleMetadataFactory) { - var (manifestModule, manifestHandle) = moduleMetadataFactory(fileKey); + var (manifestModule, manifestHandle) = moduleMetadataFactory(fullPath); + var moduleNames = manifestModule.GetModuleNames(); - using var _1 = ArrayBuilder.GetInstance(out var moduleBuilder); - using var _2 = ArrayBuilder.GetInstance(out var storageHandles); + var modules = new FixedSizeArrayBuilder(1 + moduleNames.Length); + var handles = new FixedSizeArrayBuilder(1 + moduleNames.Length); + + modules.Add(manifestModule); + handles.Add(manifestHandle); string? assemblyDir = null; - foreach (var moduleName in manifestModule.GetModuleNames()) + foreach (var moduleName in moduleNames) { - if (assemblyDir is null) - { - moduleBuilder.Add(manifestModule); - assemblyDir = Path.GetDirectoryName(fileKey.FullPath); - } + assemblyDir ??= Path.GetDirectoryName(fullPath); // Suppression should be removed or addressed https://github.com/dotnet/roslyn/issues/41636 - var moduleFileKey = FileKey.Create(PathUtilities.CombineAbsoluteAndRelativePaths(assemblyDir, moduleName)!); - var (metadata, metadataStorageHandle) = moduleMetadataFactory(moduleFileKey); - - moduleBuilder.Add(metadata); - storageHandles.Add(metadataStorageHandle); - } + var moduleFileKey = PathUtilities.CombineAbsoluteAndRelativePaths(assemblyDir, moduleName)!; - if (moduleBuilder.Count == 0) - { - moduleBuilder.Add(manifestModule); - storageHandles.Add(manifestHandle); + var (moduleMetadata, moduleHandle) = moduleMetadataFactory(moduleFileKey); + modules.Add(moduleMetadata); + handles.Add(moduleHandle); } - var result = AssemblyMetadata.Create(moduleBuilder.ToImmutable()); + var assembly = AssemblyMetadata.Create(modules.MoveToImmutable()); // If we got any null handles, then we weren't able to map this whole assembly into memory mapped files. So we // can't use those to transfer over the data efficiently to the OOP process. In that case, we don't store the // handles at all. - Contract.ThrowIfTrue(storageHandles.Count == 0); - if (storageHandles.All(h => h != null)) - s_metadataToStorageHandles.Add(result, storageHandles.ToImmutable()); + var storageHandles = handles.MoveToImmutable(); + return (assembly, storageHandles.Any(h => h is null) ? null : storageHandles); + } - return result; + public static class TestAccessor + { + public static (AssemblyMetadata assemblyMetadata, IReadOnlyList? handles) CreateAssemblyMetadata( + string fullPath, TemporaryStorageService temporaryStorageService) + => VisualStudioMetadataReferenceManager.CreateAssemblyMetadata(fullPath, fullPath => GetMetadataFromTemporaryStorage(fullPath, temporaryStorageService)); } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.Factory.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManagerFactory.cs similarity index 74% rename from src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.Factory.cs rename to src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManagerFactory.cs index 28b98b418f0ca..373bc843d89f3 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.Factory.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManagerFactory.cs @@ -14,15 +14,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; // TODO: Remove this type. This factory is needed just to instantiate a singleton of VisualStudioMetadataReferenceProvider. // We should be able to MEF-instantiate a singleton of VisualStudioMetadataReferenceProvider without creating this factory. [ExportWorkspaceServiceFactory(typeof(VisualStudioMetadataReferenceManager), ServiceLayer.Host), Shared] -internal class VisualStudioMetadataReferenceManagerFactory : IWorkspaceServiceFactory +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class VisualStudioMetadataReferenceManagerFactory(SVsServiceProvider serviceProvider) : IWorkspaceServiceFactory { private VisualStudioMetadataReferenceManager? _singleton; - private readonly IServiceProvider _serviceProvider; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioMetadataReferenceManagerFactory(SVsServiceProvider serviceProvider) - => _serviceProvider = serviceProvider; public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { @@ -30,7 +26,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { // If we're in VS we know we must be able to get a TemporaryStorageService var temporaryStorage = (TemporaryStorageService)workspaceServices.GetRequiredService(); - Interlocked.CompareExchange(ref _singleton, new VisualStudioMetadataReferenceManager(_serviceProvider, temporaryStorage), null); + Interlocked.CompareExchange(ref _singleton, new VisualStudioMetadataReferenceManager(serviceProvider, temporaryStorage), null); } return _singleton; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceProviderServiceFactory.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceProviderServiceFactory.cs index 1d513382ef67a..cb23cbbed58e6 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceProviderServiceFactory.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceProviderServiceFactory.cs @@ -2,11 +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. -#nullable disable - using System; using System.Composition; -using System.Diagnostics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; @@ -14,29 +11,20 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; [ExportWorkspaceServiceFactory(typeof(IMetadataService), ServiceLayer.Host), Shared] -internal sealed class VsMetadataServiceFactory : IWorkspaceServiceFactory +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class VisualStudioMetadataServiceFactory() : IWorkspaceServiceFactory { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VsMetadataServiceFactory() - { - } - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) => new Service(workspaceServices); - private sealed class Service : IMetadataService + private sealed class Service(HostWorkspaceServices workspaceServices) : IMetadataService { - private readonly Lazy _manager; - - public Service(HostWorkspaceServices workspaceServices) - { - // We will defer creation of this reference manager until we have to to avoid it being constructed too - // early and potentially causing deadlocks. We do initialize it on the UI thread in the - // VisualStudioWorkspaceImpl.DeferredState constructor to ensure it gets created there. - _manager = new Lazy( - () => workspaceServices.GetRequiredService()); - } + // We will defer creation of this reference manager until we have to to avoid it being constructed too early + // and potentially causing deadlocks. We do initialize it on the UI thread in the + // VisualStudioWorkspaceImpl.DeferredState constructor to ensure it gets created there. + private readonly Lazy _manager = new( + () => workspaceServices.GetRequiredService()); public PortableExecutableReference GetReference(string resolvedPath, MetadataReferenceProperties properties) => _manager.Value.CreateMetadataReferenceSnapshot(resolvedPath, properties); diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioPortableExecutableReference.cs similarity index 59% rename from src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs rename to src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioPortableExecutableReference.cs index 761a41ea3d178..e84fed7d27bfe 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioPortableExecutableReference.cs @@ -2,8 +2,6 @@ // 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.Diagnostics; @@ -17,42 +15,43 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; -// TODO: This class is now an empty container just to hold onto the nested type. Renaming that is an invasive change that will be it's own commit. -internal static class VisualStudioMetadataReference +internal partial class VisualStudioMetadataReferenceManager { /// - /// Represents a metadata reference corresponding to a specific version of a file. - /// If a file changes in future this reference will still refer to the original version. + /// Represents a metadata reference corresponding to a specific version of a file. If a file changes in future this + /// reference will still refer to the original version. /// /// - /// The compiler observes the metadata content a reference refers to by calling - /// and the observed metadata is memoized by the compilation. However we drop compilations to decrease memory consumption. - /// When the compilation is recreated for a solution the compiler asks for metadata again and we need to provide the original content, - /// not read the file again. Therefore we need to save the timestamp on the . - /// - /// When the VS observes a change in a metadata reference file the project version is advanced and a new instance of - /// is created for the corresponding reference. + /// The compiler observes the metadata content a reference refers to by calling and the observed metadata is memoized by the compilation. + /// When the VS observes a change in a metadata reference file the project version is advanced and a new + /// instance of is created for the corresponding reference. /// [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] - internal sealed class Snapshot : PortableExecutableReference, ISupportTemporaryStorage + private sealed class VisualStudioPortableExecutableReference : PortableExecutableReference, ISupportTemporaryStorage { private readonly VisualStudioMetadataReferenceManager _provider; private readonly Lazy _timestamp; - private Exception _error; - private readonly FileChangeTracker _fileChangeTrackerOpt; + private readonly FileChangeTracker? _fileChangeTracker; + + private Exception? _error; - internal Snapshot(VisualStudioMetadataReferenceManager provider, MetadataReferenceProperties properties, string fullPath, FileChangeTracker fileChangeTrackerOpt) + internal VisualStudioPortableExecutableReference( + VisualStudioMetadataReferenceManager provider, + MetadataReferenceProperties properties, + string fullPath, + FileChangeTracker? fileChangeTracker) : base(properties, fullPath) { Debug.Assert(Properties.Kind == MetadataImageKind.Assembly); _provider = provider; - _fileChangeTrackerOpt = fileChangeTrackerOpt; + _fileChangeTracker = fileChangeTracker; _timestamp = new Lazy(() => { try { - _fileChangeTrackerOpt?.EnsureSubscription(); + _fileChangeTracker?.EnsureSubscription(); return FileUtilities.GetFileTimeStamp(this.FilePath); } @@ -69,15 +68,15 @@ internal Snapshot(VisualStudioMetadataReferenceManager provider, MetadataReferen }, LazyThreadSafetyMode.PublicationOnly); } + private new string FilePath => base.FilePath!; + protected override Metadata GetMetadataImpl() { // Fetch the timestamp first, so as to populate _error if needed var timestamp = _timestamp.Value; if (_error != null) - { throw _error; - } try { @@ -87,30 +86,28 @@ protected override Metadata GetMetadataImpl() { throw ExceptionUtilities.Unreachable(); } - } - private bool SaveMetadataReadingException(Exception e) - { - // Save metadata reading failure so that future compilations created - // with this reference snapshot fail consistently in the same way. - if (e is IOException or BadImageFormatException) + bool SaveMetadataReadingException(Exception e) { - _error = e; - } + // Save metadata reading failure so that future compilations created + // with this reference snapshot fail consistently in the same way. + if (e is IOException or BadImageFormatException) + _error = e; - return false; + return false; + } } protected override DocumentationProvider CreateDocumentationProvider() - => new VisualStudioDocumentationProvider(this.FilePath, _provider.XmlMemberIndexService); + => new VisualStudioDocumentationProvider(this.FilePath, _provider._xmlMemberIndexService); protected override PortableExecutableReference WithPropertiesImpl(MetadataReferenceProperties properties) - => new Snapshot(_provider, properties, this.FilePath, _fileChangeTrackerOpt); + => new VisualStudioPortableExecutableReference(_provider, properties, this.FilePath, _fileChangeTracker); private string GetDebuggerDisplay() => "Metadata File: " + FilePath; - public IReadOnlyList StorageHandles + public IReadOnlyList? StorageHandles => _provider.GetStorageHandles(this.FilePath, _timestamp.Value); } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs index 5d5366d40dd6b..f6c0795f1e344 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -124,7 +124,7 @@ public VisualStudioWorkspaceImpl(ExportProvider exportProvider, IAsyncServicePro ProjectSystemProjectFactory = new ProjectSystemProjectFactory(this, FileChangeWatcher, CheckForAddedFileBeingOpenMaybeAsync, RemoveProjectFromMaps); - _ = Task.Run(() => InitializeUIAffinitizedServicesAsync(asyncServiceProvider)); + InitializeUIAffinitizedServicesAsync(asyncServiceProvider).Forget(); _lazyExternalErrorDiagnosticUpdateSource = new Lazy(() => new ExternalErrorDiagnosticUpdateSource( @@ -183,8 +183,9 @@ internal void SubscribeExternalErrorDiagnosticUpdateSourceToSolutionBuildEvents( public async Task InitializeUIAffinitizedServicesAsync(IAsyncServiceProvider asyncServiceProvider) { + // Yield the thread, so the caller can proceed and return immediately. // Create services that are bound to the UI thread - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(_threadingContext.DisposalToken); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, _threadingContext.DisposalToken); // Fetch the session synchronously on the UI thread; if this doesn't happen before we try using this on // the background thread then we will experience hangs like we see in this bug: diff --git a/src/VisualStudio/Core/Def/RoslynPackage.cs b/src/VisualStudio/Core/Def/RoslynPackage.cs index 93730649e413e..76a77b536afee 100644 --- a/src/VisualStudio/Core/Def/RoslynPackage.cs +++ b/src/VisualStudio/Core/Def/RoslynPackage.cs @@ -232,8 +232,6 @@ protected override async Task LoadComponentsAsync(CancellationToken cancellation await this.ComponentModel.GetService().InitializeAsync(this, cancellationToken).ConfigureAwait(false); await this.ComponentModel.GetService().InitializeAsync(this, cancellationToken).ConfigureAwait(false); - await this.ComponentModel.GetService().InitializeAsync(this, cancellationToken).ConfigureAwait(false); - await this.ComponentModel.GetService().InitializeAsync(this, cancellationToken).ConfigureAwait(false); await this.ComponentModel.GetService().InitializeAsync(this, cancellationToken).ConfigureAwait(false); await this.ComponentModel.GetService().InitializeAsync(this, cancellationToken).ConfigureAwait(false); diff --git a/src/VisualStudio/Core/Def/SymbolSearch/VisualStudioSymbolSearchService.cs b/src/VisualStudio/Core/Def/SymbolSearch/VisualStudioSymbolSearchService.cs index a658f1d2e748b..b29accb7d3325 100644 --- a/src/VisualStudio/Core/Def/SymbolSearch/VisualStudioSymbolSearchService.cs +++ b/src/VisualStudio/Core/Def/SymbolSearch/VisualStudioSymbolSearchService.cs @@ -2,13 +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. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -25,8 +22,8 @@ using Microsoft.VisualStudio.LanguageServices.Storage; using Microsoft.VisualStudio.Settings; using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Shell.Settings; +using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; using VSShell = Microsoft.VisualStudio.Shell; @@ -40,18 +37,18 @@ namespace Microsoft.VisualStudio.LanguageServices.SymbolSearch; /// date by downloading patches on a daily basis. /// [ExportWorkspaceService(typeof(ISymbolSearchService), ServiceLayer.Host), Shared] -internal partial class VisualStudioSymbolSearchService : AbstractDelayStartedService, ISymbolSearchService +internal partial class VisualStudioSymbolSearchService : AbstractDelayStartedService, ISymbolSearchService, IDisposable { private readonly SemaphoreSlim _gate = new(initialCount: 1); // Note: A remote engine is disposable as it maintains a connection with ServiceHub, // but we want to keep it alive until the VS is closed, so we don't dispose it. - private ISymbolSearchUpdateEngine _lazyUpdateEngine; + private ISymbolSearchUpdateEngine? _lazyUpdateEngine; private readonly SVsServiceProvider _serviceProvider; private readonly IPackageInstallerService _installerService; - private string _localSettingsDirectory; + private string? _localSettingsDirectory; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -69,7 +66,32 @@ public VisualStudioSymbolSearchService( [SymbolSearchOptionsStorage.SearchReferenceAssemblies, SymbolSearchOptionsStorage.SearchNuGetPackages]) { _serviceProvider = serviceProvider; - _installerService = workspace.Services.GetService(); + _installerService = workspace.Services.GetRequiredService(); + } + + public void Dispose() + { + // Once we're disposed, swap out our engine with a no-op one so we don't try to do any more work, and dispose of + // our connection to the OOP server so it can be cleaned up. + // + // Kick off a Task for this so we don't block MEF from proceeding (as it will be calling us on the UI thread). + _ = DisposeAsync(); + return; + + async Task DisposeAsync() + { + // Make sure we get off the UI thread so that Dispose can return immediately. + await TaskScheduler.Default; + + ISymbolSearchUpdateEngine? updateEngine; + using (await _gate.DisposableWaitAsync().ConfigureAwait(false)) + { + updateEngine = _lazyUpdateEngine; + _lazyUpdateEngine = SymbolSearchUpdateNoOpEngine.Instance; + } + + updateEngine?.Dispose(); + } } protected override async Task EnableServiceAsync(CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs b/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs index 114576ac819e8..fe4d67f41a20f 100644 --- a/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs +++ b/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; @@ -804,13 +805,13 @@ private void AdjustIndentationForSpan( venusFormattingRules.Add(baseIndentationRule); venusFormattingRules.Add(ContainedDocumentPreserveFormattingRule.Instance); - var formattingRules = venusFormattingRules.Concat(Formatter.GetDefaultFormattingRules(document)); - var services = document.Project.Solution.Services; var formatter = document.GetRequiredLanguageService(); var changes = formatter.GetFormattingResult( root, new TextSpan[] { CommonFormattingHelpers.GetFormattingSpan(root, visibleSpan) }, - options, formattingRules, CancellationToken.None).GetTextChanges(CancellationToken.None); + options, + [.. venusFormattingRules, .. Formatter.GetDefaultFormattingRules(document)], + CancellationToken.None).GetTextChanges(CancellationToken.None); visibleSpans.Add(visibleSpan); var newChanges = FilterTextChanges(document.GetTextSynchronously(CancellationToken.None), visibleSpans, changes.ToReadOnlyCollection()).Where(t => visibleSpan.Contains(t.Span)); diff --git a/src/VisualStudio/Core/Def/Venus/ContainedLanguageCodeSupport.cs b/src/VisualStudio/Core/Def/Venus/ContainedLanguageCodeSupport.cs index d3aa181cca369..091bb01bbda55 100644 --- a/src/VisualStudio/Core/Def/Venus/ContainedLanguageCodeSupport.cs +++ b/src/VisualStudio/Core/Def/Venus/ContainedLanguageCodeSupport.cs @@ -228,14 +228,12 @@ public static Tuple EnsureEventHandler( newRoot = Simplifier.ReduceAsync( targetDocument.WithSyntaxRoot(newRoot), Simplifier.Annotation, options.CleanupOptions.SimplifierOptions, cancellationToken).WaitAndGetResult_Venus(cancellationToken).GetSyntaxRootSynchronously(cancellationToken); - var formattingRules = additionalFormattingRule.Concat(Formatter.GetDefaultFormattingRules(targetDocument)); - newRoot = Formatter.Format( newRoot, Formatter.Annotation, targetDocument.Project.Solution.Services, options.CleanupOptions.FormattingOptions, - formattingRules, + [additionalFormattingRule, .. Formatter.GetDefaultFormattingRules(targetDocument)], cancellationToken); var newMember = newRoot.GetAnnotatedNodesAndTokens(annotation).Single(); diff --git a/src/VisualStudio/Core/Impl/CodeModel/RootCodeModel.cs b/src/VisualStudio/Core/Impl/CodeModel/RootCodeModel.cs index c412f1813e751..415f396d8f642 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/RootCodeModel.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/RootCodeModel.cs @@ -7,11 +7,13 @@ using System; using System.IO; using System.Runtime.InteropServices; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.ExternalElements; using Microsoft.VisualStudio.LanguageServices.Implementation.Interop; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; +using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel @@ -39,8 +41,8 @@ private RootCodeModel(CodeModelState state, EnvDTE.Project parent, ProjectId pro private Project GetProject() => Workspace.CurrentSolution.GetProject(_projectId); - private Compilation GetCompilation() - => GetProject().GetCompilationAsync().Result; + private Task GetCompilationAsync() + => GetProject().GetCompilationAsync(); private ComHandle GetFileCodeModel(object location) { @@ -127,27 +129,34 @@ EnvDTE.CodeElements ICodeElementContainer.GetCollec => CodeElements; public EnvDTE.CodeElements CodeElements - { - get + => this.State.ThreadingContext.JoinableTaskFactory.Run(async () => { - var compilation = GetCompilation(); + var compilation = await GetCompilationAsync().ConfigureAwait(true); + + // Need to ensure we're on the UI thread to make the ExternalCodeNamespace. It creates UI thread bound + // com aggregates. + await this.State.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); var rootNamespace = ExternalCodeNamespace.Create(this.State, _projectId, compilation.GlobalNamespace); return rootNamespace.Members; - } - } + }); public EnvDTE.CodeType CodeTypeFromFullName(string name) - { - var compilation = GetCompilation(); - var typeSymbol = CodeModelService.GetTypeSymbolFromFullName(name, compilation); - if (typeSymbol == null || - typeSymbol.TypeKind is TypeKind.Error or TypeKind.Unknown) + => this.State.ThreadingContext.JoinableTaskFactory.Run(async () => { - return null; - } + var compilation = await GetCompilationAsync().ConfigureAwait(true); + var typeSymbol = CodeModelService.GetTypeSymbolFromFullName(name, compilation); + if (typeSymbol == null || + typeSymbol.TypeKind is TypeKind.Error or TypeKind.Unknown) + { + return null; + } - return (EnvDTE.CodeType)CodeModelService.CreateCodeType(this.State, _projectId, typeSymbol); - } + // Need to ensure we're on the UI thread to make the call to CreateCodeType. It creates UI thread + // bound com aggregates. + await this.State.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); + + return (EnvDTE.CodeType)CodeModelService.CreateCodeType(this.State, _projectId, typeSymbol); + }); public EnvDTE.CodeTypeRef CreateCodeTypeRef(object type) => CodeModelService.CreateCodeTypeRef(this.State, _projectId, type); @@ -159,16 +168,15 @@ public void Remove(object element) => throw Exceptions.ThrowENotImpl(); public string DotNetNameFromLanguageSpecific(string languageName) - { - var compilation = GetCompilation(); - var typeSymbol = CodeModelService.GetTypeSymbolFromFullName(languageName, compilation); - if (typeSymbol == null) + => this.State.ThreadingContext.JoinableTaskFactory.Run(async () => { - throw Exceptions.ThrowEInvalidArg(); - } + var compilation = await GetCompilationAsync().ConfigureAwait(true); + var typeSymbol = CodeModelService.GetTypeSymbolFromFullName(languageName, compilation); + if (typeSymbol == null) + throw Exceptions.ThrowEInvalidArg(); - return MetadataNameHelpers.GetMetadataName(typeSymbol); - } + return MetadataNameHelpers.GetMetadataName(typeSymbol); + }); public EnvDTE.CodeElement ElementFromID(string id) => throw Exceptions.ThrowENotImpl(); diff --git a/src/VisualStudio/Core/Test.Next/Options/VisualStudioSettingsOptionPersisterTests.cs b/src/VisualStudio/Core/Test.Next/Options/VisualStudioSettingsOptionPersisterTests.cs index 7381a03351a4f..be85443f41957 100644 --- a/src/VisualStudio/Core/Test.Next/Options/VisualStudioSettingsOptionPersisterTests.cs +++ b/src/VisualStudio/Core/Test.Next/Options/VisualStudioSettingsOptionPersisterTests.cs @@ -155,8 +155,7 @@ public void SettingsChangeEvent() refreshedOptions.Clear(); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void SettingsManagerReadOptionValue_Success( [CombinatorialValues( typeof(bool), @@ -189,8 +188,7 @@ public void SettingsManagerReadOptionValue_Success( Assert.Equal(optionValue, result.Value); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void SettingsManagerReadOptionValue_Error( [CombinatorialValues( GetValueResult.Missing, diff --git a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs index d638391df2977..c42102f1d18db 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs @@ -704,7 +704,7 @@ private static AnalyzerFileReference CreateShadowCopiedAnalyzerReference(TempRoo return new AnalyzerFileReference(original, new MockShadowCopyAnalyzerAssemblyLoader(ImmutableDictionary.Empty.Add(original, shadow.Path))); } - private class MissingAnalyzerLoader : AnalyzerAssemblyLoader + private class MissingAnalyzerLoader() : AnalyzerAssemblyLoader([]) { protected override string PreparePathToLoad(string fullPath) => throw new FileNotFoundException(fullPath); diff --git a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs index f7424abe817b4..f9826d7e8aafe 100644 --- a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -146,5 +147,79 @@ public async Task TestProjectSynchronization() TestUtils.VerifyAssetStorage(map, storage); } + + [Fact] + public async Task TestAssetArrayOrdering() + { + var code1 = @"class Test1 { void Method() { } }"; + var code2 = @"class Test2 { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp([code1, code2]); + var project = workspace.CurrentSolution.Projects.First(); + + await project.State.GetChecksumAsync(CancellationToken.None); + + var map = await project.GetAssetMapAsync(CancellationToken.None); + + using var remoteWorkspace = CreateRemoteWorkspace(); + + var sessionId = Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())); + var storage = new SolutionAssetCache(); + var assetSource = new OrderedAssetSource(workspace.Services.GetService(), map); + + var service = new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); + + using var _ = ArrayBuilder.GetInstance(out var allProjectChecksums); + var stateChecksums = await project.State.GetStateChecksumsAsync(CancellationToken.None); + + var textChecksums = stateChecksums.Documents.TextChecksums; + var textChecksumsReversed = new ChecksumCollection(textChecksums.Children.Reverse().ToImmutableArray()); + + var documents = await service.GetAssetsArrayAsync( + AssetPath.FullLookupForTesting, textChecksums, CancellationToken.None); + Assert.True(documents.Length == 2); + + storage.GetTestAccessor().Clear(); + var documentsReversed = await service.GetAssetsArrayAsync( + AssetPath.FullLookupForTesting, textChecksumsReversed, CancellationToken.None); + Assert.True(documentsReversed.Length == 2); + + Assert.True(documents.Select(d => d.ContentChecksum).SequenceEqual(documentsReversed.Reverse().Select(d => d.ContentChecksum))); + } + + private sealed class OrderedAssetSource( + ISerializerService serializerService, + IReadOnlyDictionary map) : IAssetSource + { + public ValueTask GetAssetsAsync( + Checksum solutionChecksum, + AssetPath assetPath, + ReadOnlyMemory checksums, + ISerializerService deserializerService, + Action callback, + TArg arg, + CancellationToken cancellationToken) + { + foreach (var (checksum, asset) in map) + { + if (checksums.Span.IndexOf(checksum) >= 0) + { + using var stream = new MemoryStream(); + using (var writer = new ObjectWriter(stream, leaveOpen: true)) + { + serializerService.Serialize(asset, writer, cancellationToken); + } + + stream.Position = 0; + using var reader = ObjectReader.GetReader(stream, leaveOpen: true); + var deserialized = deserializerService.Deserialize(asset.GetWellKnownSynchronizationKind(), reader, cancellationToken); + Contract.ThrowIfNull(deserialized); + callback(checksum, (T)deserialized, arg); + } + } + + return ValueTaskFactory.CompletedTask; + } + } } } diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index d73b4bb4f6ee9..9ea28f64a6d06 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -241,8 +241,7 @@ await solution.CompilationState.GetChecksumAsync(CancellationToken.None), await remoteWorkspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] [WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1365014")] public async Task TestRemoteHostSynchronizeIncrementalUpdate(bool applyInBatch) { diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index 93583058aead3..7d162f9bed1a9 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -56,8 +56,7 @@ public async Task TestCreation() Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestGetSolutionWithPrimaryFlag(bool updatePrimaryBranch) { var code1 = @"class Test1 { void Method() { } }"; diff --git a/src/VisualStudio/Core/Test/CallHierarchy/CallHierarchyTests.vb b/src/VisualStudio/Core/Test/CallHierarchy/CallHierarchyTests.vb index 024656e352818..e7f6cfe009ee8 100644 --- a/src/VisualStudio/Core/Test/CallHierarchy/CallHierarchyTests.vb +++ b/src/VisualStudio/Core/Test/CallHierarchy/CallHierarchyTests.vb @@ -185,8 +185,7 @@ public class D : I End Using End Function - - + Public Async Function TestCallHierarchyCrossProjectForImplements() As Task Dim input = @@ -225,8 +224,7 @@ class CSharpIt : IChangeSignatureOptionsService End Using End Function - - + Public Async Function TestCallHierarchyCrossProjectForCallsTo() As Task Dim input = @@ -264,8 +262,7 @@ class D End Using End Function - - + Public Async Function TestMustInheritMethodInclusionToOverrides() As Task Dim input = @@ -292,8 +289,7 @@ End Class End Using End Function - - + Public Async Function TestNavigateCrossProject() As Task Dim input = @@ -327,8 +323,7 @@ class D : C End Using End Function - - + Public Async Function TestUseDocumentIdWhenNavigating() As Task Dim input = @@ -367,8 +362,7 @@ namespace N End Using End Function - - + Public Async Function TestDisplayErrorWhenNotOnMemberCS() As Task Dim input = @@ -390,8 +384,7 @@ cla$$ss C End Using End Function - - + Public Async Function TestDisplayErrorWhenNotOnMemberCS2() As Task Dim input = @@ -414,8 +407,7 @@ class CC End Using End Function - - + Public Async Function TestDisplayErrorWhenNotOnMemberCS3() As Task Dim input = @@ -438,8 +430,7 @@ class CC End Using End Function - - + Public Async Function TestDisplayErrorWhenNotOnMemberVB() As Task Dim input = diff --git a/src/VisualStudio/Core/Test/ChangeSignature/AddParameterViewModelTests.vb b/src/VisualStudio/Core/Test/ChangeSignature/AddParameterViewModelTests.vb index 6910f9af386e0..b167207e190d1 100644 --- a/src/VisualStudio/Core/Test/ChangeSignature/AddParameterViewModelTests.vb +++ b/src/VisualStudio/Core/Test/ChangeSignature/AddParameterViewModelTests.vb @@ -308,8 +308,7 @@ class MyClass End Using End Function - - + Public Sub AddParameter_SubmittingTypeWithModifiersIsInvalid() Dim markup = - + Public Async Function ClassDesigner1() As Task Dim text = @@ -445,8 +444,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.DebuggerIntelliSense End Using End Function - - + Public Async Function ClassDesigner2() As Task Dim text = @@ -470,8 +468,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.DebuggerIntelliSense End Using End Function - - + Public Async Function CompletionUsesContextBufferPositions() As Task Dim text = @@ -735,8 +732,7 @@ $$ End Using End Function - - + Public Async Function TestItemDescription() As Task Dim text = diff --git a/src/VisualStudio/Core/Test/DebuggerIntelliSense/VisualBasicDebuggerIntellisenseTests.vb b/src/VisualStudio/Core/Test/DebuggerIntelliSense/VisualBasicDebuggerIntellisenseTests.vb index 9aa0ce192fccf..8adb3a0b82751 100644 --- a/src/VisualStudio/Core/Test/DebuggerIntelliSense/VisualBasicDebuggerIntellisenseTests.vb +++ b/src/VisualStudio/Core/Test/DebuggerIntelliSense/VisualBasicDebuggerIntellisenseTests.vb @@ -336,8 +336,7 @@ End Module End Using End Function - - + Public Async Function StoppedOnEndSub() As Task Dim text = @@ -352,8 +351,7 @@ End Module End Using End Function - - + Public Async Function StoppedOnEndProperty() As Task Dim text = diff --git a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj index 7e8b130d74ac7..15fc95f510bd7 100644 --- a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj +++ b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj @@ -62,4 +62,8 @@ + + + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Test/ObjectBrowser/CSharp/ObjectBrowerTests.vb b/src/VisualStudio/Core/Test/ObjectBrowser/CSharp/ObjectBrowerTests.vb index 3e1e88ab21818..3decfbbb50f12 100644 --- a/src/VisualStudio/Core/Test/ObjectBrowser/CSharp/ObjectBrowerTests.vb +++ b/src/VisualStudio/Core/Test/ObjectBrowser/CSharp/ObjectBrowerTests.vb @@ -149,8 +149,7 @@ namespace N End Using End Sub - - + Public Sub TestContent_InheritedMembers1() Dim code = @@ -202,8 +201,7 @@ class C : B End Using End Sub - - + Public Sub TestContent_InheritedMembers2() Dim code = @@ -256,8 +254,7 @@ class C : B End Using End Sub - - + Public Sub TestContent_InheritedMembers3() Dim code = @@ -310,8 +307,7 @@ class C : B End Using End Sub - - + Public Sub TestContent_HelpKeyword_Ctor() Dim code = @@ -503,8 +499,7 @@ $" {String.Format(ServicesVSResources.Member_of_0, "C")}") End Using End Sub - - + Public Sub TestDescription_MethodInInterface() Dim code = @@ -1472,8 +1467,7 @@ $" {String.Format(ServicesVSResources.Member_of_0, "C")}") End Using End Sub - - + Public Sub TestNavInfo_Class() Dim code = @@ -1498,8 +1492,7 @@ namespace EditorFunctionalityHelper End Using End Sub - - + Public Sub TestNavInfo_NestedEnum() Dim code = @@ -1531,8 +1524,7 @@ namespace EditorFunctionalityHelper End Using End Sub - - + Public Sub TestCheckedBinaryOperator() Dim code = @@ -1558,8 +1550,7 @@ class C End Using End Sub - - + Public Sub TestCheckedUnaryOperator() Dim code = @@ -1585,8 +1576,7 @@ class C End Using End Sub - - + Public Sub TestCheckedCastOperator() Dim code = diff --git a/src/VisualStudio/Core/Test/ObjectBrowser/VisualBasic/ObjectBrowerTests.vb b/src/VisualStudio/Core/Test/ObjectBrowser/VisualBasic/ObjectBrowerTests.vb index c32984afce2c0..033fe1aec30a8 100644 --- a/src/VisualStudio/Core/Test/ObjectBrowser/VisualBasic/ObjectBrowerTests.vb +++ b/src/VisualStudio/Core/Test/ObjectBrowser/VisualBasic/ObjectBrowerTests.vb @@ -142,8 +142,7 @@ End Namespace End Using End Sub - - + Public Sub TestContent_InheritedMembers1() Dim code = @@ -197,8 +196,7 @@ End Class End Using End Sub - - + Public Sub TestContent_InheritedMembers2() Dim code = @@ -253,8 +251,7 @@ End Class End Using End Sub - - + Public Sub TestContent_InheritedMembers3() Dim code = @@ -309,8 +306,7 @@ End Class End Using End Sub - - + Public Sub TestContent_HelpKeyword_Ctor() Dim code = @@ -887,8 +883,7 @@ $" {String.Format(ServicesVSResources.Member_of_0, "N.C")}") End Using End Sub - - + Public Sub TestDescription_SubInInterface() Dim code = @@ -2286,8 +2281,7 @@ ServicesVSResources.Value_colon & vbCrLf & End Using End Sub - - + Public Sub TestNavInfo_Class() Dim code = @@ -2310,8 +2304,7 @@ End Namespace End Using End Sub - - + Public Sub TestNavInfo_NestedEnum() Dim code = diff --git a/src/VisualStudio/Core/Test/Progression/ContainsChildrenGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/ContainsChildrenGraphQueryTests.vb index 6a0b1c7681dd2..bc9061ce5293d 100644 --- a/src/VisualStudio/Core/Test/Progression/ContainsChildrenGraphQueryTests.vb +++ b/src/VisualStudio/Core/Test/Progression/ContainsChildrenGraphQueryTests.vb @@ -72,8 +72,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression End Function - - + Public Async Function ContainsChildrenForFileWithIllegalPath() As Task Using testState = ProgressionTestState.Create() Dim graph = New Graph @@ -88,8 +87,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression End Function - - + Public Async Function ContainsChildrenForNotYetLoadedSolution() As Task Using testState = ProgressionTestState.Create( @@ -127,8 +125,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression End Using End Function - - + Public Async Function ContainsChildrenForNodeWithRelativeUriPath() As Task Using testState = ProgressionTestState.Create( diff --git a/src/VisualStudio/Core/Test/Progression/InheritsFromGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/InheritsFromGraphQueryTests.vb index 33b1f45e68347..37c17c1fecc2d 100644 --- a/src/VisualStudio/Core/Test/Progression/InheritsFromGraphQueryTests.vb +++ b/src/VisualStudio/Core/Test/Progression/InheritsFromGraphQueryTests.vb @@ -43,8 +43,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression End Using End Function - - + Public Async Function TestErrorBaseType() As Task Using testState = ProgressionTestState.Create( diff --git a/src/VisualStudio/Core/Test/ReferenceManager/VisualStudioMetadataReferenceManagerTests.vb b/src/VisualStudio/Core/Test/ReferenceManager/VisualStudioMetadataReferenceManagerTests.vb new file mode 100644 index 0000000000000..58b838656219b --- /dev/null +++ b/src/VisualStudio/Core/Test/ReferenceManager/VisualStudioMetadataReferenceManagerTests.vb @@ -0,0 +1,86 @@ +' 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. + +Imports System.IO +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Host +Imports Microsoft.CodeAnalysis.Serialization +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +Imports Roslyn.Utilities + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ReferenceManager + + Public Class VisualStudioMetadataReferenceManagerTests + + Public Sub TestReferenceAssemblyWithMultipleModules() + Using workspace = EditorTestWorkspace.CreateCSharp("") + Dim assemblyDir = Path.GetDirectoryName(GetType(Object).Assembly.Location) + Dim enterprisePath = Path.Combine(assemblyDir, "System.EnterpriseServices.dll") + + Dim tempStorageService = DirectCast(workspace.Services.GetRequiredService(Of ITemporaryStorageServiceInternal), TemporaryStorageService) + Dim serializerService = DirectCast(workspace.Services.GetRequiredService(Of ISerializerService), SerializerService) + + Dim tuple = VisualStudioMetadataReferenceManager.TestAccessor.CreateAssemblyMetadata( + enterprisePath, tempStorageService) + Assert.NotNull(tuple.assemblyMetadata) + Assert.NotNull(tuple.handles) + + ' We should have two handles as this assembly has two modules (itself, and one submodule for + ' System.EnterpriseServices.Wrapper.dll) + Assert.Equal(2, tuple.handles.Count) + + Dim testReference = New TestPEReference( + enterprisePath, tuple.assemblyMetadata, tuple.handles) + + Dim stream = New MemoryStream() + Dim writer = New ObjectWriter(stream, leaveOpen:=True) + serializerService.Serialize(testReference, writer, cancellationToken:=Nothing) + + stream.Position = 0 + Dim reader = ObjectReader.GetReader(stream, leaveOpen:=True) + Dim deserialized = DirectCast(serializerService.Deserialize( + WellKnownSynchronizationKind.MetadataReference, reader, cancellationToken:=Nothing), MetadataReference) + + Dim checksum1 = SerializerService.CreateChecksum(testReference, cancellationToken:=Nothing) + Dim checksum2 = SerializerService.CreateChecksum(deserialized, cancellationToken:=Nothing) + + ' Serializing the original reference and the deserialized reference should produce the same checksum + Assert.Equal(checksum1, checksum2) + End Using + End Sub + + Private Class TestPEReference + Inherits PortableExecutableReference + Implements ISupportTemporaryStorage + + Private ReadOnly _metadata As Microsoft.CodeAnalysis.Metadata + Private ReadOnly _storageHandles As IReadOnlyList(Of ITemporaryStorageStreamHandle) + + Public Sub New(fullPath As String, metadata As Microsoft.CodeAnalysis.Metadata, storageHandles As IReadOnlyList(Of ITemporaryStorageStreamHandle)) + MyBase.New(New MetadataReferenceProperties(), fullPath) + _metadata = metadata + _storageHandles = storageHandles + End Sub + + Public ReadOnly Property StorageHandles As IReadOnlyList(Of ITemporaryStorageStreamHandle) Implements ISupportTemporaryStorage.StorageHandles + Get + Return _storageHandles + End Get + End Property + + Protected Overrides Function CreateDocumentationProvider() As DocumentationProvider + Throw New NotImplementedException() + End Function + + Protected Overrides Function WithPropertiesImpl(properties As MetadataReferenceProperties) As PortableExecutableReference + Throw New NotImplementedException() + End Function + + Protected Overrides Function GetMetadataImpl() As Microsoft.CodeAnalysis.Metadata + Return _metadata + End Function + End Class + End Class +End Namespace diff --git a/src/VisualStudio/Core/Test/Snippets/CSharpSnippetExpansionClientTests.vb b/src/VisualStudio/Core/Test/Snippets/CSharpSnippetExpansionClientTests.vb index 17ff5c571085e..40116e888fd13 100644 --- a/src/VisualStudio/Core/Test/Snippets/CSharpSnippetExpansionClientTests.vb +++ b/src/VisualStudio/Core/Test/Snippets/CSharpSnippetExpansionClientTests.vb @@ -67,8 +67,7 @@ using G.H.I; Await TestSnippetAddImportsAsync(originalCode, namespacesToAdd, placeSystemNamespaceFirst:=True, expectedUpdatedCode:=expectedUpdatedCode) End Function - - + Public Async Function TestAddImport_InsideNamespace() As Task Dim originalCode = " using A; diff --git a/src/VisualStudio/Core/Test/Snippets/SnippetCompletionProviderTests.vb b/src/VisualStudio/Core/Test/Snippets/SnippetCompletionProviderTests.vb index 9f54e4b6ded16..403f1480ad5a8 100644 --- a/src/VisualStudio/Core/Test/Snippets/SnippetCompletionProviderTests.vb +++ b/src/VisualStudio/Core/Test/Snippets/SnippetCompletionProviderTests.vb @@ -72,8 +72,7 @@ End Class.Value End Using End Function - - + Public Async Function SnippetNotOfferedInComments() As Task Dim markup = Class C @@ -89,8 +88,7 @@ End Class.Value End Using End Function - - + Public Async Function SnippetsNotOfferedInDocComments() As Task Dim markup = Class C diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb new file mode 100644 index 0000000000000..7f42c3704baf2 --- /dev/null +++ b/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb @@ -0,0 +1,97 @@ +' 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. + +Imports System.Collections.Immutable +Imports System.IO +Imports System.Reflection +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Completion +Imports Microsoft.CodeAnalysis.Options +Imports Microsoft.VisualStudio.LanguageServices +Imports Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings +Imports Newtonsoft.Json.Linq + +Namespace Roslyn.VisualStudio.VisualBasic.UnitTests.UnifiedSettings + Public Class VisualBasicUnifiedSettingsTests + Inherits UnifiedSettingsTests + + Friend Overrides ReadOnly Property OnboardedOptions As ImmutableArray(Of IOption2) + Get + Return ImmutableArray.Create(Of IOption2)( + CompletionOptionsStorage.TriggerOnTypingLetters, + CompletionOptionsStorage.TriggerOnDeletion, + CompletionViewOptionsStorage.HighlightMatchingPortionsOfCompletionListItems, + CompletionViewOptionsStorage.ShowCompletionItemFilters, + CompletionOptionsStorage.SnippetsBehavior, + CompletionOptionsStorage.EnterKeyBehavior, + CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, + CompletionViewOptionsStorage.EnableArgumentCompletionSnippets + ) + End Get + End Property + + Friend Overrides Function GetEnumOptionValues([option] As IOption2) As Object() + Dim allValues = [Enum].GetValues([option].Type).Cast(Of Object) + If [option].Equals(CompletionOptionsStorage.SnippetsBehavior) Then + 'SnippetsRule.Default is used as a stub value, overridden per language at runtime. + ' It is not shown in the option page + Return allValues.Where(Function(value) Not value.Equals(SnippetsRule.Default)).ToArray() + ElseIf [option].Equals(CompletionOptionsStorage.EnterKeyBehavior) Then + ' EnterKeyRule.Default is used as a stub value, overridden per language at runtime. + ' It Is Not shown in the option page + Return allValues.Where(Function(value) Not value.Equals(EnterKeyRule.Default)).ToArray() + End If + + Return MyBase.GetEnumOptionValues([option]) + End Function + + Friend Overrides Function GetOptionsDefaultValue([option] As IOption2) As Object + ' The default values of some options are set at runtime. option.defaultValue is just a dummy value in this case. + ' However, in unified settings we always set the correct value in registration.json. + If [option].Equals(CompletionOptionsStorage.SnippetsBehavior) Then + ' CompletionOptionsStorage.SnippetsBehavior's default value is SnippetsRule.Default. + ' It's overridden differently per-language at runtime. + Return SnippetsRule.IncludeAfterTypingIdentifierQuestionTab + ElseIf [option].Equals(CompletionOptionsStorage.EnterKeyBehavior) Then + ' CompletionOptionsStorage.EnterKeyBehavior's default value is EnterKeyBehavior.Default. + ' It's overridden differently per-language at runtime. + Return EnterKeyRule.Always + ElseIf [option].Equals(CompletionOptionsStorage.TriggerOnDeletion) Then + ' CompletionOptionsStorage.TriggerOnDeletion's default value is null. + ' It's enabled by default for Visual Basic + Return True + ElseIf [option].Equals(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces) Then + ' CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces's default value is null + ' It's enabled by default for Visual Basic + Return True + ElseIf [option].Equals(CompletionViewOptionsStorage.EnableArgumentCompletionSnippets) Then + ' CompletionViewOptionsStorage.EnableArgumentCompletionSnippets' default value is null + ' It's disabled by default for Visual Basic + Return False + End If + + Return MyBase.GetOptionsDefaultValue([option]) + End Function + + + Public Async Function IntelliSensePageTests() As Task + Using registrationFileStream = GetType(VisualBasicUnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("visualBasicSettings.registration.json") + Using pkgDefFileStream = GetType(VisualBasicUnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("PackageRegistration.pkgdef") + Using pkgDefFileReader = New StreamReader(pkgDefFileStream) + Using reader = New StreamReader(registrationFileStream) + Dim registrationFile = Await reader.ReadToEndAsync().ConfigureAwait(False) + Dim pkgDefFile = Await pkgDefFileReader.ReadToEndAsync().ConfigureAwait(False) + Dim registrationJsonObject = JObject.Parse(registrationFile, New JsonLoadSettings()) + Dim categoriesTitle = registrationJsonObject.SelectToken("$.categories['textEditor.basic'].title") + Assert.Equal("Visual Basic", categoriesTitle) + Dim optionPageId = registrationJsonObject.SelectToken("$.categories['textEditor.basic.intellisense'].legacyOptionPageId") + Assert.Equal(Guids.VisualBasicOptionPageIntelliSenseIdString, optionPageId.ToString()) + TestUnifiedSettingsCategory(registrationJsonObject, categoryBasePath:="textEditor.basic.intellisense", languageName:=LanguageNames.VisualBasic, pkgDefFile) + End Using + End Using + End Using + End Using + End Function + End Class +End Namespace diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/InteractiveWindowInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/InteractiveWindowInProcess.cs index bf450cd2d9fb9..dd48c32854087 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/InteractiveWindowInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/InteractiveWindowInProcess.cs @@ -259,7 +259,7 @@ public async Task VerifyTagsAsync(int expectedCount, CancellationToken can var view = await GetActiveTextViewAsync(cancellationToken); - bool filterTag(IMappingTagSpan tag) + static bool filterTag(IMappingTagSpan tag) { return tag.Tag.GetType().Equals(typeof(TTag)); } diff --git a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj index d739438f4952f..6b93eb53390b8 100644 --- a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj +++ b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj @@ -167,17 +167,6 @@ TargetFramework=net472 BindingRedirect - - Workspaces.MSBuild - BuiltProjectOutputGroup;SatelliteDllsProjectOutputGroup - true - TargetFramework=net472 - BindingRedirect - - false - CSharpWorkspace BuiltProjectOutputGroup;SatelliteDllsProjectOutputGroup @@ -318,11 +307,8 @@ SemanticSearchRefs - - TargetFramework=$(NetRoslyn) true false - false SemanticSearchRefs PublishProjectOutputGroup diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb index 8a25462f33b34..c0261ecc1802c 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb @@ -62,7 +62,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr GetType(ProjectCodeModelFactory), GetType(CPSProjectFactory), GetType(VisualStudioRuleSetManagerFactory), - GetType(VsMetadataServiceFactory), + GetType(VisualStudioMetadataServiceFactory), GetType(VisualStudioMetadataReferenceManagerFactory), GetType(MockWorkspaceEventListenerProvider), GetType(HierarchyItemToProjectIdMap), diff --git a/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb index 48abfad77a558..ecf8ba700f9bf 100644 --- a/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb +++ b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb @@ -3,6 +3,9 @@ ' See the LICENSE file in the project root for more information. Imports System.Collections.Immutable +Imports System.IO.Hashing +Imports System.Text +Imports System.Text.RegularExpressions Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Options Imports Microsoft.VisualStudio.LanguageServices.Options @@ -28,7 +31,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings Return [Enum].GetValues(type).Cast(Of Object).AsArray() End Function - Protected Sub TestUnifiedSettingsCategory(registrationJsonObject As JObject, categoryBasePath As String, languageName As String) + Protected Sub TestUnifiedSettingsCategory(registrationJsonObject As JObject, categoryBasePath As String, languageName As String, pkdDefFile As String) Dim actualAllSettings = registrationJsonObject.SelectToken($"$.properties").Children.OfType(Of JProperty). Where(Function(setting) setting.Name.StartsWith(categoryBasePath)). Select(Function(setting) setting.Name). @@ -62,6 +65,17 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings Throw ExceptionUtilities.UnexpectedValue(optionName) End If Next + + Dim registrationFileBytes = ASCIIEncoding.ASCII.GetBytes(registrationJsonObject.ToString()) + Dim hash = XxHash128.Hash(registrationFileBytes) + Dim tagBytes = hash.Take(8).ToArray() + Dim expectedCacheTagValue = BitConverter.ToInt64(tagBytes, 0).ToString("X16") + + Dim regexExp = New Regex("""CacheTag""=qword:\w{16}") + Dim match = regexExp.Match(pkdDefFile, 0).Value + Dim actual = match.Substring(match.Length - 16) + ' Please change the CacheTag value in pkddef if you modify the unified settings regirstration file + Assert.Equal(expectedCacheTagValue, actual) End Sub Private Shared Sub VerifySettings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2, languageName As String) diff --git a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx index feb1994aec76a..b0aabb98df28e 100644 --- a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx +++ b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx @@ -244,15 +244,6 @@ _Never add new line on enter - - Always include snippets - - - Include snippets when ?-Tab is typed after an identifier - - - Never include snippets - Snippets behavior diff --git a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb index 046f984628ff0..f03d6a481434d 100644 --- a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb +++ b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicPackage.vb @@ -38,6 +38,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic + Friend NotInheritable Class VisualBasicPackage Inherits AbstractPackage(Of VisualBasicPackage, VisualBasicLanguageService) diff --git a/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj b/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj index b9cc5973ed78e..b94764b606b87 100644 --- a/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj +++ b/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj @@ -14,6 +14,12 @@ true false + + + PreserveNewest + true + + @@ -44,12 +50,12 @@ - + - + true VSPackage Designer diff --git a/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml.vb b/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml.vb index 26f9bfa584311..7a28520e808f1 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml.vb @@ -42,8 +42,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options End Sub Private Sub SetEnterKeyDefaultBehavior() - Dim snippetValue = Me.OptionStore.GetOption(CompletionOptionsStorage.EnterKeyBehavior, LanguageNames.VisualBasic) - If snippetValue = SnippetsRule.Default Then + Dim enterKeyRule = Me.OptionStore.GetOption(CompletionOptionsStorage.EnterKeyBehavior, LanguageNames.VisualBasic) + If enterKeyRule = EnterKeyRule.Default Then Always_add_new_line_on_enter.IsChecked = True End If End Sub diff --git a/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageStrings.vb b/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageStrings.vb index e3b00fcebfd41..39b3bf040f610 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageStrings.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageStrings.vb @@ -35,13 +35,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options BasicVSResources.Snippets_behavior Public ReadOnly Property Option_Never_include_snippets As String = - BasicVSResources.Never_include_snippets + VSPackage.Never_include_snippets Public ReadOnly Property Option_Always_include_snippets As String = - BasicVSResources.Always_include_snippets + VSPackage.Always_include_snippets Public ReadOnly Property Option_Include_snippets_when_question_Tab_is_typed_after_an_identifier As String = - BasicVSResources.Include_snippets_when_Tab_is_typed_after_an_identifier + VSPackage.Include_snippets_when_Tab_is_typed_after_an_identifier Public ReadOnly Property Option_Show_items_from_unimported_namespaces As String = BasicVSResources.Show_items_from_unimported_namespaces diff --git a/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef b/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef index bfb3a04c6856c..088ecdbfeb727 100644 --- a/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef +++ b/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef @@ -188,4 +188,11 @@ "Indent Style"=dword:00000002 [$RootKey$\VB Editor\Roslyn] -"DisplayLineSeparators"=dword:00000001 \ No newline at end of file +"DisplayLineSeparators"=dword:00000001 + +// CacheTag value should be changed when registration file changes +// See https://devdiv.visualstudio.com/DevDiv/_wiki/wikis/DevDiv.wiki/39345/Manifest-Build-Deployment-and-Setup-Authoring-In-Depth?anchor=example-pkgdef-key for more infomation +[$RootKey$\SettingsManifests\{574fc912-f74f-4b4e-92c3-f695c208a2bb}] +@="Microsoft.VisualStudio.LanguageServices.VisualBasic.VisualBasicPackage" +"ManifestPath"="$PackageFolder$\UnifiedSettings\visualBasicSettings.registration.json" +"CacheTag"=qword:5DE8496A8900B809 diff --git a/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json b/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json new file mode 100644 index 0000000000000..c744cc9dc0357 --- /dev/null +++ b/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json @@ -0,0 +1,185 @@ +// NOTE: +// When this file is changed. Please also update the cache tag under settings entry in src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef +// Otherwise your change might be ignored. +// See https://devdiv.visualstudio.com/DevDiv/_wiki/wikis/DevDiv.wiki/39345/Manifest-Build-Deployment-and-Setup-Authoring-In-Depth?anchor=example-pkgdef-key for more details +{ + "properties": { + // CompletionOptionsStorage.TriggerOnTypingLetters + "textEditor.basic.intellisense.triggerCompletionOnTypingLetters": { + "title": "@Show_completion_list_after_a_character_is_typed;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", + "type": "boolean", + "default": true, + "order": 0, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.TriggerOnTypingLetters" + } + } + } + }, + // CompletionOptionsStorage.TriggerOnDeletion + "textEditor.basic.intellisense.triggerCompletionOnDeletion": { + "title": "@Show_completion_list_after_a_character_is_deleted;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", + "type": "boolean", + "default": true, + "order": 1, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.TriggerOnDeletion" + } + } + } + }, + // CompletionViewOptionsStorage.HighlightMatchingPortionsOfCompletionListItems + "textEditor.basic.intellisense.highlightMatchingPortionsOfCompletionListItems": { + "title": "@Highlight_matching_portions_of_completion_list_items;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", + "type": "boolean", + "default": true, + "order": 10, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.HighlightMatchingPortionsOfCompletionListItems" + } + } + } + }, + // CompletionViewOptionsStorage.ShowCompletionItemFilters + "textEditor.basic.intellisense.showCompletionItemFilters": { + "title": "@Show_completion_item_filters;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", + "type": "boolean", + "default": true, + "order": 20, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.ShowCompletionItemFilters" + } + } + } + }, + // CompletionOptionsStorage.SnippetsBehavior + "textEditor.basic.intellisense.snippetsBehavior": { + "title": "@Snippets_behavior;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", + "type": "string", + "enum": [ "neverInclude", "alwaysInclude", "includeAfterTypingIdentifierQuestionTab" ], + "enumItemLabels": [ "@Never_include_snippets;{574fc912-f74f-4b4e-92c3-f695c208a2bb}", "@Always_include_snippets;{574fc912-f74f-4b4e-92c3-f695c208a2bb}", "@Include_snippets_when_Tab_is_typed_after_an_identifier;{574fc912-f74f-4b4e-92c3-f695c208a2bb}" ], + "default": "includeAfterTypingIdentifierQuestionTab", + "order": 30, + "migration": { + "enumIntegerToString": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.SnippetsBehavior" + }, + "map": [ + { + "result": "neverInclude", + "match": 1 + }, + // '0' matches to SnippetsRule.Default. Means the behavior is decided by language. + // '3' matches to SnippetsRule.IncludeAfterTypingIdentifierQuestionTab. It's the default behavior for Visual Basic + // Put both mapping here, so it's possible for unified setting to load '0' from the storage. + // Put '3' in front, so unifed settings would persist '3' to storage when 'includeAfterTypingIdentifierQuestionTab' is selected. + { + "result": "alwaysInclude", + "match": 2 + }, + { + "result": "includeAfterTypingIdentifierQuestionTab", + "match": 3 + }, + { + "result": "includeAfterTypingIdentifierQuestionTab", + "match": 0 + } + ] + } + } + }, + // CompletionOptionsStorage.EnterKeyBehavior + "textEditor.basic.intellisense.returnKeyCompletionBehavior": { + "title": "@Enter_key_behavior_colon;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", + "type": "string", + "enum": [ "never", "afterFullyTypedWord", "always" ], + "enumItemLabels": [ "@Never_add_new_line_on_enter;{574fc912-f74f-4b4e-92c3-f695c208a2bb}", "@Only_add_new_line_on_enter_after_end_of_fully_typed_word;{574fc912-f74f-4b4e-92c3-f695c208a2bb}", "@Always_add_new_line_on_enter;{574fc912-f74f-4b4e-92c3-f695c208a2bb}" ], + "default": "always", + "order": 40, + "migration": { + "enumIntegerToString": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.EnterKeyBehavior" + }, + "map": [ + // '0' matches to EnterKeyRule.Default. Means the behavior is decided by langauge. + // '2' matches to EnterKeyRule.Alwasys. It's the default behavior for Visual Basic + // Put both mapping here, so it's possible for unified setting to load '0' from the storage. + // Put '2' in front, so unifed settings would persist '2' to storage when 'always' is selected. + { + "result": "never", + "match": 1 + }, + { + "result": "always", + "match": 2 + }, + { + "result": "always", + "match": 0 + }, + { + "result": "afterFullyTypedWord", + "match": 3 + } + ] + } + } + }, + // CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces + "textEditor.basic.intellisense.showCompletionItemsFromUnimportedNamespaces": { + "title": "@Show_items_from_unimported_namespaces;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", + "type": "boolean", + "default": true, + "order": 50, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.ShowItemsFromUnimportedNamespaces" + } + } + } + }, + // CompletionViewOptionsStorage.EnableArgumentCompletionSnippets + "textEditor.basic.intellisense.enableArgumentCompletionSnippets": { + "title": "@Tab_twice_to_insert_arguments;..\\Microsoft.VisualStudio.LanguageServices.dll", + "type": "boolean", + "default": false, + "order": 60, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.VisualBasic.Specific.EnableArgumentCompletionSnippets" + } + } + } + } + }, + "categories": { + "textEditor.basic":{ + "title": "Visual Basic" + }, + "textEditor.basic.intellisense": { + "title": "@112;{574fc912-f74f-4b4e-92c3-f695c208a2bb}", + "legacyOptionPageId": "04460A3B-1B5F-4402-BC6D-89A4F6F0A8D7" + } + } +} diff --git a/src/VisualStudio/VisualBasic/Impl/VSPackage.resx b/src/VisualStudio/VisualBasic/Impl/VSPackage.resx index a77f0471bc1f1..00e7dc9bbac3c 100644 --- a/src/VisualStudio/VisualBasic/Impl/VSPackage.resx +++ b/src/VisualStudio/VisualBasic/Impl/VSPackage.resx @@ -208,4 +208,22 @@ Use enhanced colors;Editor Color Scheme;Inheritance Margin;Import Directives;Visual Basic Tools Help > About + + Always add new line on enter + + + Always include snippets + + + Include snippets when ?-Tab is typed after an identifier + + + Never add new line on enter + + + Never include snippets + + + Only add new line on enter after end of fully typed word + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf index 84a1def04bec8..a095b351cce37 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf @@ -272,21 +272,6 @@ _Při stisku Enter nikdy nepřidávat nový řádek - - Always include snippets - Vždy zahrnovat fragmenty - - - - Include snippets when ?-Tab is typed after an identifier - Zahrnovat fragmenty po zadání ?-Tab za identifikátor - - - - Never include snippets - Nikdy nezahrnovat fragmenty - - Snippets behavior Chování fragmentů diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf index 25dd17f7e540f..63b9d71ab0553 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf @@ -272,21 +272,6 @@ _Nie neue Zeile beim Drücken der EINGABETASTE einfügen - - Always include snippets - Schnipsel immer einschließen - - - - Include snippets when ?-Tab is typed after an identifier - Schnipsel einschließen, wenn ?-TAB nach einem Bezeichner eingegeben wird - - - - Never include snippets - Schnipsel nie einschließen - - Snippets behavior Schnipselverhalten diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf index b9fac78124ec8..ecad7b68a9f27 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf @@ -272,21 +272,6 @@ _No agregar nunca una nueva línea al presionar Entrar - - Always include snippets - Incluir siempre fragmentos de código - - - - Include snippets when ?-Tab is typed after an identifier - Incluir fragmentos de código cuando ?-Tab se escriba después de un identificador - - - - Never include snippets - No incluir nunca fragmentos de código - - Snippets behavior Comportamiento de los fragmentos de código diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf index bc7b5d5411ccb..244dafeaadc9e 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf @@ -272,21 +272,6 @@ _Ne jamais ajouter de nouvelle ligne après Entrée - - Always include snippets - Toujours inclure les extraits de code - - - - Include snippets when ?-Tab is typed after an identifier - Inclure les extraits de code quand ?-Tab est typé après un identificateur - - - - Never include snippets - Ne jamais inclure d'extrait de code - - Snippets behavior Comportement des extraits de code diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf index 2875c1089924e..b8cbfe8759d4c 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf @@ -272,21 +272,6 @@ _Non aggiungere mai una nuova riga dopo INVIO - - Always include snippets - Includi sempre i frammenti - - - - Include snippets when ?-Tab is typed after an identifier - Includi i frammenti quando si digita ?+TAB dopo un identificatore - - - - Never include snippets - Non includere mai i frammenti - - Snippets behavior Comportamento dei frammenti diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf index 0d5f609496cd6..b12555f31c426 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf @@ -272,21 +272,6 @@ Enter キーで新しい行を追加しない(_N) - - Always include snippets - 常にスニペットを含める - - - - Include snippets when ?-Tab is typed after an identifier - 識別子の後に ? Tab を入力したときにスニペットを含める - - - - Never include snippets - スニペットを含めない - - Snippets behavior スニペットの動作 diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf index 44a0191e1b8d8..19878d608dcdf 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf @@ -272,21 +272,6 @@ <Enter> 키를 누르면 새 줄 추가 안 함(_N) - - Always include snippets - 코드 조각 항상 포함 - - - - Include snippets when ?-Tab is typed after an identifier - 식별자 뒤에 ?-Tab을 입력하면 코드 조각 포함 - - - - Never include snippets - 코드 조각 포함 안 함 - - Snippets behavior 코드 조각 동작 diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf index be4079612f862..821817918bd48 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf @@ -272,21 +272,6 @@ _Nigdy nie dodawaj nowego wiersza po naciśnięciu klawisza Enter - - Always include snippets - Zawsze dołączaj fragmenty kodu - - - - Include snippets when ?-Tab is typed after an identifier - Dołącz fragmenty kodu po wpisaniu znaku ? po identyfikatorze i naciśnięciu klawisza Tab - - - - Never include snippets - Nigdy nie dołączaj fragmentów kodu - - Snippets behavior Zachowanie fragmentów kodu diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf index 12bbbe50c8e6f..71f7adbfcc571 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf @@ -272,21 +272,6 @@ _Nunca adicionar nova linha ao inserir - - Always include snippets - Sempre incluir snippets - - - - Include snippets when ?-Tab is typed after an identifier - Incluir snippets quando ?-Tab for digitado após um identificador - - - - Never include snippets - Nunca incluir snippets - - Snippets behavior Comportamento de snippets diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf index 18e80b678e73e..2f19095174378 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf @@ -272,21 +272,6 @@ _Никогда не добавлять новую строку при нажатии клавиши ВВОД - - Always include snippets - Всегда включать фрагменты кода - - - - Include snippets when ?-Tab is typed after an identifier - Включать фрагменты кода, когда после идентификатора указывается "?-Tab" - - - - Never include snippets - Никогда не включать фрагменты кода - - Snippets behavior Поведение фрагментов кода diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf index d5186e12e001b..fe333e73dae87 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf @@ -272,21 +272,6 @@ Enter tuşuna basıldığında _hiçbir zaman yeni satır ekleme - - Always include snippets - Kod parçacıklarını her zaman dahil et - - - - Include snippets when ?-Tab is typed after an identifier - Bir tanımlayıcıdan sonra ?-Tab yazılırsa kod parçacıklarını dahil et - - - - Never include snippets - Kod parçacıklarını hiçbir zaman dahil etme - - Snippets behavior Kod parçacığı davranışı diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf index 5d3359de43850..4532d2fa79a4a 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf @@ -272,21 +272,6 @@ 按下 Enter 时不添加新行(_N) - - Always include snippets - 始终包含片段 - - - - Include snippets when ?-Tab is typed after an identifier - 在标识符后键入 ?-Tab 时包含片段 - - - - Never include snippets - 从不包含片段 - - Snippets behavior 片段行为 diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf index b912f0c3e7c4b..b1a8e9c40547a 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf @@ -272,21 +272,6 @@ 永不在按下 Enter 鍵時加入新行(_N) - - Always include snippets - 一律包含程式碼片段 - - - - Include snippets when ?-Tab is typed after an identifier - 在識別碼後輸入 ?-Tab 時包含程式碼片段 - - - - Never include snippets - 一律不包含程式碼片段 - - Snippets behavior 程式碼片段行為 diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf index 89b8f916f76d4..276c0e8c18faf 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf @@ -130,6 +130,36 @@ Používat rozšířené barvy;Barevné schéma editoru;Okraj dědičnosti;Direk Nástroje Visual Basicu Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf index 5f0cd72bc7208..a3e9414b14f48 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf @@ -130,6 +130,36 @@ Erweiterte Farben verwenden;Editor-Farbschema;Vererbungsspielraum;Richtlinien im Visual Basic-Tools Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf index 8a092b2a86686..a318fc614937a 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf @@ -130,6 +130,36 @@ Usar colores mejorados;Combinación de colores del editor;Margen de herencia;Dir Herramientas de Visual Basic Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf index 6f9d5251b0861..d202187102890 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf @@ -130,6 +130,36 @@ Utiliser des couleurs améliorées ; Modèle de couleurs de l’éditeur;Marge d Outils Visual Basic Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf index 05ead449eda01..0ffbe854456b1 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf @@ -130,6 +130,36 @@ Usare colori avanzati; Combinazione colori editor;Margine di ereditarietà;Diret Strumenti di Visual Basic Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf index c4cb8c5e5ff15..8b06a20801ee9 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf @@ -130,6 +130,36 @@ JSON 文字列のエディター機能の検出と提供; Visual Basic ツール Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf index b43840a0f901b..efb4258efa09a 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf @@ -130,6 +130,36 @@ JSON 문자열 색상 지정, Visual Basic 도구 Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf index dfc04b5da1e7f..2a996c13f65dd 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf @@ -130,6 +130,36 @@ Używanie rozszerzonych kolorów; Schemat kolorów edytora;Margines dziedziczeni Narzędzia języka Visual Basic Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf index 7a64fb3e6120b..88970da2f6d56 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf @@ -130,6 +130,36 @@ Usar cores aprimoradas;Esquema de Cores do Editor;Margem de Herança;Importar Di Ferramentas do Visual Basic Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf index b0315632359ad..63dbfc68c0bb5 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf @@ -130,6 +130,36 @@ JSON; Инструменты Visual Basic Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf index 7c8826d805575..10bf015c805aa 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf @@ -130,6 +130,36 @@ Gelişmiş renkleri kullan;Düzenleyici Renk Düzeni;Devralma Kenar Boşluğu;İ Visual Basic Araçları Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf index 8e6b9e052b60d..9dc70f453cd84 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf @@ -130,6 +130,36 @@ JSON; Visual Basic 工具 Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf index 5784a3d19b718..06fd1d217bfde 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf @@ -130,6 +130,36 @@ JSON; Visual Basic 工具 Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + + + Include snippets when ?-Tab is typed after an identifier + Include snippets when ?-Tab is typed after an identifier + + + + Never add new line on enter + Never add new line on enter + + + + Never include snippets + Never include snippets + + + + Only add new line on enter after end of fully typed word + Only add new line on enter after end of fully typed word + + \ No newline at end of file diff --git a/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs b/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs index 5df82749fae48..61015f6f2af0a 100644 --- a/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs +++ b/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs @@ -331,10 +331,8 @@ public ImmutableArray GetFormattingChangesOnPaste(ParsedDocument doc var formattingSpan = CommonFormattingHelpers.GetFormattingSpan(document.Root, textSpan); var service = _services.GetRequiredService(); - var rules = new List() { new PasteFormattingRule() }; - rules.AddRange(service.GetDefaultFormattingRules()); - - var result = service.GetFormattingResult(document.Root, [formattingSpan], options, rules, cancellationToken); + var result = service.GetFormattingResult( + document.Root, [formattingSpan], options, [new PasteFormattingRule(), .. service.GetDefaultFormattingRules()], cancellationToken); return [.. result.GetTextChanges(cancellationToken)]; } diff --git a/src/Workspaces/CSharp/Portable/Formatting/TypingFormattingRule.cs b/src/Workspaces/CSharp/Portable/Formatting/TypingFormattingRule.cs index 96365c7cbe9e6..5cca530503ddf 100644 --- a/src/Workspaces/CSharp/Portable/Formatting/TypingFormattingRule.cs +++ b/src/Workspaces/CSharp/Portable/Formatting/TypingFormattingRule.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Utilities; using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.CSharp.Formatting; @@ -13,7 +14,7 @@ internal class TypingFormattingRule : BaseFormattingRule { public static readonly TypingFormattingRule Instance = new(); - public override void AddSuppressOperations(List list, SyntaxNode node, in NextSuppressOperationAction nextOperation) + public override void AddSuppressOperations(ArrayBuilder list, SyntaxNode node, in NextSuppressOperationAction nextOperation) { if (TryAddSuppressionOnMissingCloseBraceCase(list, node)) { @@ -23,7 +24,7 @@ public override void AddSuppressOperations(List list, SyntaxN base.AddSuppressOperations(list, node, in nextOperation); } - private static bool TryAddSuppressionOnMissingCloseBraceCase(List list, SyntaxNode node) + private static bool TryAddSuppressionOnMissingCloseBraceCase(ArrayBuilder list, SyntaxNode node) { var bracePair = node.GetBracePair(); if (!bracePair.IsValidBracketOrBracePair()) diff --git a/src/Workspaces/CSharpTest/Formatting/FormattingTests_Patterns.cs b/src/Workspaces/CSharpTest/Formatting/FormattingTests_Patterns.cs index b1159ead211fd..4caeafdd8856c 100644 --- a/src/Workspaces/CSharpTest/Formatting/FormattingTests_Patterns.cs +++ b/src/Workspaces/CSharpTest/Formatting/FormattingTests_Patterns.cs @@ -17,8 +17,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Formatting [Trait(Traits.Feature, Traits.Features.Formatting)] public class FormattingTests_Patterns : CSharpFormattingTestBase { - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FormatRelationalPatterns1( [CombinatorialValues("<", "<=", ">", ">=")] string operatorText, BinaryOperatorSpacingOptions spacing) @@ -76,8 +75,7 @@ bool Method(int value) await AssertFormatAsync(expected, content, changedOptionSet: changingOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FormatRelationalPatterns2( [CombinatorialValues("<", "<=", ">", ">=")] string operatorText, BinaryOperatorSpacingOptions spacing, @@ -167,8 +165,7 @@ bool Method(int value) await AssertFormatAsync(expected, content, changedOptionSet: changingOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FormatNotPatterns1(BinaryOperatorSpacingOptions spacing) { var content = $@" @@ -224,8 +221,7 @@ bool Method(int value) await AssertFormatAsync(expected, content, changedOptionSet: changingOptions); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FormatNotPatterns2( BinaryOperatorSpacingOptions spacing, bool spaceWithinExpressionParentheses) diff --git a/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs b/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs index 0cba06c47234c..05ad5789c365a 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs @@ -11,11 +11,10 @@ using System.Threading.Tasks; using System.Xml; using Microsoft.Build.Framework; -using Microsoft.CodeAnalysis.MSBuild.Logging; using Roslyn.Utilities; using MSB = Microsoft.Build; -namespace Microsoft.CodeAnalysis.MSBuild.Build +namespace Microsoft.CodeAnalysis.MSBuild { internal class ProjectBuildManager { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/BuildHost.cs b/src/Workspaces/Core/MSBuild.BuildHost/BuildHost.cs index 4bfd90f760c37..bc1e0320e7e40 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/BuildHost.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/BuildHost.cs @@ -4,7 +4,6 @@ extern alias workspaces; using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -13,26 +12,22 @@ using Microsoft.Build.Construction; using Microsoft.Build.Locator; using Microsoft.Build.Logging; -using Microsoft.CodeAnalysis.MSBuild; -using Microsoft.CodeAnalysis.MSBuild.Build; -using Microsoft.CodeAnalysis.MSBuild.Rpc; -using Microsoft.Extensions.Logging; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost; +namespace Microsoft.CodeAnalysis.MSBuild; -internal sealed class BuildHost +internal sealed class BuildHost : IBuildHost { - private readonly ILogger _logger; + private readonly BuildHostLogger _logger; private readonly ImmutableDictionary _globalMSBuildProperties; private readonly string? _binaryLogPath; private readonly RpcServer _server; private readonly object _gate = new object(); private ProjectBuildManager? _buildManager; - public BuildHost(ILoggerFactory loggerFactory, ImmutableDictionary globalMSBuildProperties, string? binaryLogPath, RpcServer server) + public BuildHost(BuildHostLogger logger, ImmutableDictionary globalMSBuildProperties, string? binaryLogPath, RpcServer server) { - _logger = loggerFactory.CreateLogger(); + _logger = logger; _globalMSBuildProperties = globalMSBuildProperties; _binaryLogPath = binaryLogPath; _server = server; @@ -109,7 +104,7 @@ private bool TryEnsureMSBuildLoaded(string projectOrSolutionFilePath) #if NET472 || NET6_0 // If we're compiling against net472 or net6.0, we get our MemberNotNull from the workspaces assembly. It has it in the net6.0 case since we're consuming the netstandard2.0 version of Workspaces. [workspaces::System.Diagnostics.CodeAnalysis.MemberNotNull(nameof(_buildManager))] #else // If we're compiling against net7.0 or higher, then we're getting it staright from the framework. - [MemberNotNull(nameof(_buildManager))] + [System.Diagnostics.CodeAnalysis.MemberNotNull(nameof(_buildManager))] #endif [MethodImpl(MethodImplOptions.NoInlining)] // Do not inline this, since this creates MSBuild types which are being loaded by the caller private void CreateBuildManager() @@ -177,8 +172,8 @@ public async Task LoadProjectFileAsync(string projectFilePath, string langu ProjectFileLoader projectLoader = languageName switch { - LanguageNames.CSharp => new CSharp.CSharpProjectFileLoader(), - LanguageNames.VisualBasic => new VisualBasic.VisualBasicProjectFileLoader(), + LanguageNames.CSharp => new CSharpProjectFileLoader(), + LanguageNames.VisualBasic => new VisualBasicProjectFileLoader(), _ => throw ExceptionUtilities.UnexpectedValue(languageName) }; diff --git a/src/Workspaces/Core/MSBuild.BuildHost/BuildHostLogger.cs b/src/Workspaces/Core/MSBuild.BuildHost/BuildHostLogger.cs new file mode 100644 index 0000000000000..c58341779c7f8 --- /dev/null +++ b/src/Workspaces/Core/MSBuild.BuildHost/BuildHostLogger.cs @@ -0,0 +1,16 @@ +// 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; + +namespace Microsoft.CodeAnalysis.MSBuild; + +internal sealed class BuildHostLogger(TextWriter output) +{ + public void LogInformation(string message) + => output.WriteLine(message); + + public void LogCritical(string message) + => output.WriteLine(message); +} diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpCommandLineArgumentReader.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpCommandLineArgumentReader.cs index 5908aa94455a3..35bd8d2b0a204 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpCommandLineArgumentReader.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpCommandLineArgumentReader.cs @@ -3,10 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using Microsoft.CodeAnalysis.MSBuild; using MSB = Microsoft.Build; -namespace Microsoft.CodeAnalysis.CSharp +namespace Microsoft.CodeAnalysis.MSBuild { internal class CSharpCommandLineArgumentReader : CommandLineArgumentReader { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpProjectFile.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpProjectFile.cs index 91846935a3ae5..cc81848d8f3e4 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpProjectFile.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpProjectFile.cs @@ -5,11 +5,9 @@ using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.MSBuild; -using Microsoft.CodeAnalysis.MSBuild.Build; -using Microsoft.CodeAnalysis.MSBuild.Logging; using MSB = Microsoft.Build; -namespace Microsoft.CodeAnalysis.CSharp +namespace Microsoft.CodeAnalysis.MSBuild { internal class CSharpProjectFile : ProjectFile { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpProjectFileLoader.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpProjectFileLoader.cs index b578b9d1b0ae9..9f7400d847110 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpProjectFileLoader.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/CSharp/CSharpProjectFileLoader.cs @@ -2,12 +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 Microsoft.CodeAnalysis.MSBuild; -using Microsoft.CodeAnalysis.MSBuild.Build; -using Microsoft.CodeAnalysis.MSBuild.Logging; using MSB = Microsoft.Build; -namespace Microsoft.CodeAnalysis.CSharp +namespace Microsoft.CodeAnalysis.MSBuild { internal partial class CSharpProjectFileLoader : ProjectFileLoader { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/DiagnosticLog.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/DiagnosticLog.cs index 9d30cf894f6a3..aa616bc5e3f5e 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/DiagnosticLog.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/DiagnosticLog.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.Linq; -namespace Microsoft.CodeAnalysis.MSBuild.Logging +namespace Microsoft.CodeAnalysis.MSBuild { internal class DiagnosticLog : IEnumerable { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/MSBuildDiagnosticLogItem.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/MSBuildDiagnosticLogItem.cs index b0a3710cf91b5..b1edc6c883208 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/MSBuildDiagnosticLogItem.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/MSBuildDiagnosticLogItem.cs @@ -4,7 +4,7 @@ using System; -namespace Microsoft.CodeAnalysis.MSBuild.Logging +namespace Microsoft.CodeAnalysis.MSBuild { internal class MSBuildDiagnosticLogItem : DiagnosticLogItem { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/MSBuildDiagnosticLogger.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/MSBuildDiagnosticLogger.cs index 0454566bdb2f8..43f720925310d 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/MSBuildDiagnosticLogger.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/MSBuildDiagnosticLogger.cs @@ -6,7 +6,7 @@ using Roslyn.Utilities; using MSB = Microsoft.Build; -namespace Microsoft.CodeAnalysis.MSBuild.Logging +namespace Microsoft.CodeAnalysis.MSBuild { internal class MSBuildDiagnosticLogger : MSB.Framework.ILogger { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs index 0dcc41acbffff..13e65ac97f128 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs @@ -32,7 +32,6 @@ internal static class Extensions public static IEnumerable GetProjectReferences(this MSB.Execution.ProjectInstance executedProject) => executedProject .GetItems(ItemNames.ProjectReference) - .Where(i => i.ReferenceOutputAssemblyIsTrue()) .Select(CreateProjectFileReference); public static ImmutableArray GetPackageReferences(this MSB.Execution.ProjectInstance executedProject) @@ -55,7 +54,7 @@ public static ImmutableArray GetPackageReferences(this MSB.Exe /// Create a from a ProjectReference node in the MSBuild file. /// private static ProjectFileReference CreateProjectFileReference(MSB.Execution.ProjectItemInstance reference) - => new(reference.EvaluatedInclude, reference.GetAliases()); + => new(reference.EvaluatedInclude, reference.GetAliases(), reference.ReferenceOutputAssemblyIsTrue()); public static ImmutableArray GetAliases(this MSB.Framework.ITaskItem item) { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs index 92fb55d25e1b1..788b90134bd08 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs @@ -11,14 +11,12 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.MSBuild.Build; -using Microsoft.CodeAnalysis.MSBuild.Logging; using Roslyn.Utilities; using MSB = Microsoft.Build; namespace Microsoft.CodeAnalysis.MSBuild { - internal abstract class ProjectFile + internal abstract class ProjectFile : IProjectFile { private readonly ProjectFileLoader _loader; private readonly MSB.Evaluation.Project? _loadedProject; diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileLoader.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileLoader.cs index d63ccabff51d7..382d61e014f62 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileLoader.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileLoader.cs @@ -5,8 +5,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.MSBuild.Build; -using Microsoft.CodeAnalysis.MSBuild.Logging; using MSB = Microsoft.Build; namespace Microsoft.CodeAnalysis.MSBuild diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicCommandLineArgumentReader.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicCommandLineArgumentReader.cs index 4264b0dc46efc..829829239183d 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicCommandLineArgumentReader.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicCommandLineArgumentReader.cs @@ -4,11 +4,10 @@ using System; using System.Collections.Immutable; -using Microsoft.CodeAnalysis.MSBuild; using Roslyn.Utilities; using MSB = Microsoft.Build; -namespace Microsoft.CodeAnalysis.VisualBasic +namespace Microsoft.CodeAnalysis.MSBuild { internal class VisualBasicCommandLineArgumentReader : CommandLineArgumentReader { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicProjectFile.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicProjectFile.cs index d8f195f136e76..2acfd35e517e1 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicProjectFile.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicProjectFile.cs @@ -4,12 +4,9 @@ using System.Collections.Generic; using System.Collections.Immutable; -using Microsoft.CodeAnalysis.MSBuild; -using Microsoft.CodeAnalysis.MSBuild.Build; -using Microsoft.CodeAnalysis.MSBuild.Logging; using MSB = Microsoft.Build; -namespace Microsoft.CodeAnalysis.VisualBasic +namespace Microsoft.CodeAnalysis.MSBuild { internal class VisualBasicProjectFile : ProjectFile { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicProjectFileLoader.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicProjectFileLoader.cs index 99f82fa28e1e6..0ccc774e167f4 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicProjectFileLoader.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/VisualBasic/VisualBasicProjectFileLoader.cs @@ -2,12 +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 Microsoft.CodeAnalysis.MSBuild; -using Microsoft.CodeAnalysis.MSBuild.Build; -using Microsoft.CodeAnalysis.MSBuild.Logging; using MSB = Microsoft.Build; -namespace Microsoft.CodeAnalysis.VisualBasic +namespace Microsoft.CodeAnalysis.MSBuild { internal partial class VisualBasicProjectFileLoader : ProjectFileLoader { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj b/src/Workspaces/Core/MSBuild.BuildHost/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj index 063d82e3b829a..c895b665da4dc 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj +++ b/src/Workspaces/Core/MSBuild.BuildHost/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.csproj @@ -28,9 +28,7 @@ - - @@ -41,8 +39,8 @@ - + diff --git a/src/Workspaces/Core/MSBuild.BuildHost/Program.cs b/src/Workspaces/Core/MSBuild.BuildHost/Program.cs index 973f7d5151535..18b67f5d7e705 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/Program.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/Program.cs @@ -6,11 +6,9 @@ using System.Collections.Immutable; using System.CommandLine; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.MSBuild.Rpc; -using Microsoft.Extensions.Logging; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost; +namespace Microsoft.CodeAnalysis.MSBuild; internal static class Program { @@ -31,26 +29,13 @@ internal static async Task Main(string[] args) propertiesBuilder.Add(propertyParts[0], propertyParts[1]); } - // Create a console logger that logs everything to standard error instead of standard out; by setting the threshold to Trace - // everything will go to standard error. - var loggerFactory = LoggerFactory.Create(builder => - builder.AddConsole(configure => - { - // DisableColors is deprecated in favor of us moving to simple console, but that loses the LogToStandardErrorThreshold - // which we also need -#pragma warning disable CS0618 - configure.DisableColors = true; -#pragma warning restore CS0618 - configure.LogToStandardErrorThreshold = LogLevel.Trace; - })); - - var logger = loggerFactory.CreateLogger(typeof(Program)); + var logger = new BuildHostLogger(Console.Error); logger.LogInformation($"BuildHost Runtime Version: {System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}"); var server = new RpcServer(sendingStream: Console.OpenStandardOutput(), receivingStream: Console.OpenStandardInput()); - var targetObject = server.AddTarget(new BuildHost(loggerFactory, propertiesBuilder.ToImmutable(), binaryLogPath, server)); + var targetObject = server.AddTarget(new BuildHost(logger, propertiesBuilder.ToImmutable(), binaryLogPath, server)); Contract.ThrowIfFalse(targetObject == 0, "The first object registered should have target 0, which is assumed by the client."); await server.RunAsync().ConfigureAwait(false); diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/DiagnosticLogItem.cs b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/DiagnosticLogItem.cs similarity index 95% rename from src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/DiagnosticLogItem.cs rename to src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/DiagnosticLogItem.cs index 5be36343b592d..7b9d8897001f5 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/Logging/DiagnosticLogItem.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/DiagnosticLogItem.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.Serialization; -namespace Microsoft.CodeAnalysis.MSBuild.Logging +namespace Microsoft.CodeAnalysis.MSBuild { [DataContract] internal class DiagnosticLogItem diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/DocumentFileInfo.cs b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/DocumentFileInfo.cs similarity index 100% rename from src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/DocumentFileInfo.cs rename to src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/DocumentFileInfo.cs diff --git a/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/IBuildHost.cs b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/IBuildHost.cs new file mode 100644 index 0000000000000..dcc0e48ef2f5d --- /dev/null +++ b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/IBuildHost.cs @@ -0,0 +1,21 @@ +// 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.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.MSBuild; + +/// +/// RPC methods. +/// +internal interface IBuildHost +{ + bool HasUsableMSBuild(string projectOrSolutionFilePath); + ImmutableArray<(string ProjectPath, string ProjectGuid)> GetProjectsInSolution(string solutionFilePath); + Task LoadProjectFileAsync(string projectFilePath, string languageName, CancellationToken cancellationToken); + Task TryGetProjectOutputPathAsync(string projectFilePath, CancellationToken cancellationToken); + Task ShutdownAsync(); +} diff --git a/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/IProjectFile.cs b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/IProjectFile.cs new file mode 100644 index 0000000000000..7d93476b118ef --- /dev/null +++ b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/IProjectFile.cs @@ -0,0 +1,28 @@ +// 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.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.MSBuild; + +/// +/// RPC methods. +/// +internal interface IProjectFile +{ + ImmutableArray GetDiagnosticLogItems(); + string GetDocumentExtension(SourceCodeKind kind); + Task> GetProjectFileInfosAsync(CancellationToken cancellationToken); + void AddDocument(string filePath, string? logicalPath); + void RemoveDocument(string filePath); + void AddMetadataReference(string metadataReferenceIdentity, MetadataReferenceProperties properties, string? hintPath); + void RemoveMetadataReference(string shortAssemblyName, string fullAssemblyName, string filePath); + void AddProjectReference(string projectName, ProjectFileReference reference); + void RemoveProjectReference(string projectName, string projectFilePath); + void AddAnalyzerReference(string fullPath); + void RemoveAnalyzerReference(string fullPath); + void Save(); +} diff --git a/src/Workspaces/Core/MSBuild.BuildHost/Rpc/JsonSettings.cs b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/JsonSettings.cs similarity index 96% rename from src/Workspaces/Core/MSBuild.BuildHost/Rpc/JsonSettings.cs rename to src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/JsonSettings.cs index a4dee42f83fcf..ee75ca9bf1ca5 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/Rpc/JsonSettings.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/JsonSettings.cs @@ -6,7 +6,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -namespace Microsoft.CodeAnalysis.MSBuild.Rpc; +namespace Microsoft.CodeAnalysis.MSBuild; internal static class JsonSettings { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MonoMSBuildDiscovery.cs b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/MonoMSBuildDiscovery.cs similarity index 98% rename from src/Workspaces/Core/MSBuild.BuildHost/MonoMSBuildDiscovery.cs rename to src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/MonoMSBuildDiscovery.cs index e8f11292cb262..99a376391a654 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MonoMSBuildDiscovery.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/MonoMSBuildDiscovery.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost; +namespace Microsoft.CodeAnalysis.MSBuild; internal static class MonoMSBuildDiscovery { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/PackageReference.cs b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/PackageReference.cs similarity index 74% rename from src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/PackageReference.cs rename to src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/PackageReference.cs index a337cfded8bfc..f637b25ff8b32 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/PackageReference.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/PackageReference.cs @@ -1,9 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.Immutable; -using System.Diagnostics; using System.Runtime.Serialization; namespace Microsoft.CodeAnalysis.MSBuild; diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileInfo.cs b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/ProjectFileInfo.cs similarity index 99% rename from src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileInfo.cs rename to src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/ProjectFileInfo.cs index bd5fbd6371bf6..e5ac67ffd9721 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileInfo.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/ProjectFileInfo.cs @@ -3,9 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Diagnostics; using System.Runtime.Serialization; -using Microsoft.CodeAnalysis.MSBuild.Logging; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.MSBuild diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileReference.cs b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/ProjectFileReference.cs similarity index 75% rename from src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileReference.cs rename to src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/ProjectFileReference.cs index 6ed6024473d6a..7a27a20ea17c1 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFileReference.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/ProjectFileReference.cs @@ -27,12 +27,19 @@ internal sealed class ProjectFileReference [DataMember(Order = 1)] public ImmutableArray Aliases { get; } - public ProjectFileReference(string path, ImmutableArray aliases) + /// + /// The value of "ReferenceOutputAssembly" metadata. + /// + [DataMember(Order = 2)] + public bool ReferenceOutputAssembly { get; } + + public ProjectFileReference(string path, ImmutableArray aliases, bool referenceOutputAssembly) { Debug.Assert(!aliases.IsDefault); - this.Path = path; - this.Aliases = aliases; + Path = path; + Aliases = aliases; + ReferenceOutputAssembly = referenceOutputAssembly; } } } diff --git a/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Request.cs b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/Request.cs similarity index 94% rename from src/Workspaces/Core/MSBuild.BuildHost/Rpc/Request.cs rename to src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/Request.cs index 0a7d7beae83e9..4b27b8c022e8a 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Request.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/Request.cs @@ -5,7 +5,7 @@ using System.Collections.Immutable; using Newtonsoft.Json.Linq; -namespace Microsoft.CodeAnalysis.MSBuild.Rpc; +namespace Microsoft.CodeAnalysis.MSBuild; internal sealed class Request { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Response.cs b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/Response.cs similarity index 89% rename from src/Workspaces/Core/MSBuild.BuildHost/Rpc/Response.cs rename to src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/Response.cs index 2fa7d6e3efe61..ff8a604b80a02 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Response.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/Response.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json.Linq; -namespace Microsoft.CodeAnalysis.MSBuild.Rpc; +namespace Microsoft.CodeAnalysis.MSBuild; internal sealed class Response { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/Rpc/TextReaderExtensions.cs b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/TextReaderExtensions.cs similarity index 97% rename from src/Workspaces/Core/MSBuild.BuildHost/Rpc/TextReaderExtensions.cs rename to src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/TextReaderExtensions.cs index 8778e50b619ea..b476ad5a1a105 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/Rpc/TextReaderExtensions.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/Contracts/TextReaderExtensions.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.CodeAnalysis.MSBuild.Rpc; +namespace Microsoft.CodeAnalysis.MSBuild; internal static class TextReaderExtensions { diff --git a/src/Workspaces/Core/MSBuild.BuildHost/Rpc/RpcServer.cs b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/RpcServer.cs index 3390c68f5f450..a6a08b8ed3050 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/Rpc/RpcServer.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/Rpc/RpcServer.cs @@ -13,7 +13,7 @@ using Newtonsoft.Json.Linq; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.MSBuild.Rpc; +namespace Microsoft.CodeAnalysis.MSBuild; /// /// Implements the server side of the RPC channel used to communicate with the build host. diff --git a/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs b/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs index 0bdbcd428bba0..928ff407b8b67 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs @@ -14,8 +14,6 @@ using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; -using Microsoft.CodeAnalysis.MSBuild.Rpc; -using Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost; using Microsoft.Extensions.Logging; using Roslyn.Utilities; diff --git a/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs b/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs index b32d95b27ad91..450024049fb33 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using Microsoft.CodeAnalysis.MSBuild.Logging; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.MSBuild diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs index 19c234bb6c285..8a89561728484 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs @@ -14,7 +14,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.MSBuild.Build; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs index d2fc3f9fe896d..26e000e02c6c5 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs @@ -209,26 +209,35 @@ private async Task ResolveReferencesAsync(ProjectId id, Proj continue; } - // If we don't know how to load a project (that is, it's not a language we support), we can still - // attempt to verify that its output exists on disk and is included in our set of metadata references. - // If it is, we'll just leave it in place. - if (!IsProjectLoadable(projectReferencePath) && - await VerifyUnloadableProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false)) + if (projectFileReference.ReferenceOutputAssembly) { - continue; - } - - // If metadata is preferred, see if the project reference's output exists on disk and is included - // in our metadata references. If it is, don't create a project reference; we'll just use the metadata. - if (_preferMetadataForReferencesOfDiscoveredProjects && - await VerifyProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false)) - { - continue; + // If we don't know how to load a project (that is, it's not a language we support), we can still + // attempt to verify that its output exists on disk and is included in our set of metadata references. + // If it is, we'll just leave it in place. + if (!IsProjectLoadable(projectReferencePath) && + await VerifyUnloadableProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false)) + { + continue; + } + + // If metadata is preferred, see if the project reference's output exists on disk and is included + // in our metadata references. If it is, don't create a project reference; we'll just use the metadata. + if (_preferMetadataForReferencesOfDiscoveredProjects && + await VerifyProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false)) + { + continue; + } + + // Finally, we'll try to load and reference the project. + if (await TryLoadAndAddReferenceAsync(id, projectReferencePath, aliases, builder, cancellationToken).ConfigureAwait(false)) + { + continue; + } } - - // Finally, we'll try to load and reference the project. - if (await TryLoadAndAddReferenceAsync(id, projectReferencePath, aliases, builder, cancellationToken).ConfigureAwait(false)) + else { + // Load the project but do not add a reference: + _ = await LoadProjectInfosFromPathAsync(projectReferencePath, _discoveredProjectOptions, cancellationToken).ConfigureAwait(false); continue; } } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs index c5c2b09fd0eb2..92b03542374ce 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.MSBuild.Rpc; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -647,7 +646,9 @@ protected override void ApplyProjectReferenceAdded(ProjectId projectId, ProjectR var project = this.CurrentSolution.GetProject(projectReference.ProjectId); if (project?.FilePath is not null) { - _applyChangesProjectFile.AddProjectReferenceAsync(project.Name, new ProjectFileReference(project.FilePath, projectReference.Aliases), CancellationToken.None).Wait(); + // Only "ReferenceOutputAssembly=true" project references are represented in the workspace: + var reference = new ProjectFileReference(project.FilePath, projectReference.Aliases, referenceOutputAssembly: true); + _applyChangesProjectFile.AddProjectReferenceAsync(project.Name, reference, CancellationToken.None).Wait(); } this.OnProjectReferenceAdded(projectId, projectReference); diff --git a/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj b/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj index 6fb4366c78c83..9b3e73b619771 100644 --- a/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj +++ b/src/Workspaces/Core/MSBuild/Microsoft.CodeAnalysis.Workspaces.MSBuild.csproj @@ -24,10 +24,7 @@ - - - + @@ -38,7 +35,7 @@ for it. PrivateAssets="all" is needed to prevent this reference from becoming a package reference in the package, as a workaround for https://github.com/NuGet/Home/issues/3891. --> - + true @@ -50,12 +47,7 @@ InternalUtilities\GlobalAssemblyCache.cs - - - - - - + @@ -63,6 +55,12 @@ + + + + + + diff --git a/src/Workspaces/Core/MSBuild/Rpc/RemoteBuildHost.cs b/src/Workspaces/Core/MSBuild/Rpc/RemoteBuildHost.cs index 808d2885e646a..5a457f05da7e8 100644 --- a/src/Workspaces/Core/MSBuild/Rpc/RemoteBuildHost.cs +++ b/src/Workspaces/Core/MSBuild/Rpc/RemoteBuildHost.cs @@ -6,9 +6,8 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost; -namespace Microsoft.CodeAnalysis.MSBuild.Rpc; +namespace Microsoft.CodeAnalysis.MSBuild; internal sealed class RemoteBuildHost { @@ -23,22 +22,22 @@ public RemoteBuildHost(RpcClient client) } public Task HasUsableMSBuildAsync(string projectOrSolutionFilePath, CancellationToken cancellationToken) - => _client.InvokeAsync(BuildHostTargetObject, nameof(BuildHost.HasUsableMSBuild), parameters: [projectOrSolutionFilePath], cancellationToken); + => _client.InvokeAsync(BuildHostTargetObject, nameof(IBuildHost.HasUsableMSBuild), parameters: [projectOrSolutionFilePath], cancellationToken); public Task> GetProjectsInSolutionAsync(string solutionFilePath, CancellationToken cancellationToken) - => _client.InvokeAsync>(BuildHostTargetObject, nameof(BuildHost.GetProjectsInSolution), parameters: [solutionFilePath], cancellationToken); + => _client.InvokeAsync>(BuildHostTargetObject, nameof(IBuildHost.GetProjectsInSolution), parameters: [solutionFilePath], cancellationToken); public async Task LoadProjectFileAsync(string projectFilePath, string languageName, CancellationToken cancellationToken) { - var remoteProjectFileTargetObject = await _client.InvokeAsync(BuildHostTargetObject, nameof(BuildHost.LoadProjectFileAsync), parameters: [projectFilePath, languageName], cancellationToken).ConfigureAwait(false); + var remoteProjectFileTargetObject = await _client.InvokeAsync(BuildHostTargetObject, nameof(IBuildHost.LoadProjectFileAsync), parameters: [projectFilePath, languageName], cancellationToken).ConfigureAwait(false); return new RemoteProjectFile(_client, remoteProjectFileTargetObject); } public Task TryGetProjectOutputPathAsync(string projectFilePath, CancellationToken cancellationToken) - => _client.InvokeNullableAsync(BuildHostTargetObject, nameof(BuildHost.TryGetProjectOutputPathAsync), parameters: [projectFilePath], cancellationToken); + => _client.InvokeNullableAsync(BuildHostTargetObject, nameof(IBuildHost.TryGetProjectOutputPathAsync), parameters: [projectFilePath], cancellationToken); public Task ShutdownAsync(CancellationToken cancellationToken) - => _client.InvokeAsync(BuildHostTargetObject, nameof(BuildHost.ShutdownAsync), parameters: [], cancellationToken); + => _client.InvokeAsync(BuildHostTargetObject, nameof(IBuildHost.ShutdownAsync), parameters: [], cancellationToken); } diff --git a/src/Workspaces/Core/MSBuild/Rpc/RemoteInvocationException.cs b/src/Workspaces/Core/MSBuild/Rpc/RemoteInvocationException.cs index 0e3d803737ceb..61fde83b8a63c 100644 --- a/src/Workspaces/Core/MSBuild/Rpc/RemoteInvocationException.cs +++ b/src/Workspaces/Core/MSBuild/Rpc/RemoteInvocationException.cs @@ -4,7 +4,7 @@ using System; -namespace Microsoft.CodeAnalysis.MSBuild.Rpc; +namespace Microsoft.CodeAnalysis.MSBuild; internal sealed class RemoteInvocationException : Exception { diff --git a/src/Workspaces/Core/MSBuild/Rpc/RemoteProjectFile.cs b/src/Workspaces/Core/MSBuild/Rpc/RemoteProjectFile.cs index a2ce680237812..59b91fbb346c2 100644 --- a/src/Workspaces/Core/MSBuild/Rpc/RemoteProjectFile.cs +++ b/src/Workspaces/Core/MSBuild/Rpc/RemoteProjectFile.cs @@ -5,10 +5,8 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.MSBuild; -using Microsoft.CodeAnalysis.MSBuild.Logging; -namespace Microsoft.CodeAnalysis.MSBuild.Rpc; +namespace Microsoft.CodeAnalysis.MSBuild; internal sealed class RemoteProjectFile { @@ -22,38 +20,38 @@ public RemoteProjectFile(RpcClient client, int remoteProjectFileTargetObject) } public Task> GetDiagnosticLogItemsAsync(CancellationToken cancellationToken) - => _client.InvokeAsync>(_remoteProjectFileTargetObject, nameof(ProjectFile.GetDiagnosticLogItems), parameters: [], cancellationToken); + => _client.InvokeAsync>(_remoteProjectFileTargetObject, nameof(IProjectFile.GetDiagnosticLogItems), parameters: [], cancellationToken); public Task GetDocumentExtensionAsync(SourceCodeKind sourceCodeKind, CancellationToken cancellationToken) - => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(ProjectFile.GetDocumentExtension), parameters: [sourceCodeKind], cancellationToken); + => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(IProjectFile.GetDocumentExtension), parameters: [sourceCodeKind], cancellationToken); public Task> GetProjectFileInfosAsync(CancellationToken cancellationToken) - => _client.InvokeAsync>(_remoteProjectFileTargetObject, nameof(ProjectFile.GetProjectFileInfosAsync), parameters: [], cancellationToken); + => _client.InvokeAsync>(_remoteProjectFileTargetObject, nameof(IProjectFile.GetProjectFileInfosAsync), parameters: [], cancellationToken); public Task AddDocumentAsync(string filePath, string? logicalPath, CancellationToken cancellationToken) - => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(ProjectFile.AddDocument), parameters: [filePath, logicalPath], cancellationToken); + => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(IProjectFile.AddDocument), parameters: [filePath, logicalPath], cancellationToken); public Task RemoveDocumentAsync(string filePath, CancellationToken cancellationToken) - => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(ProjectFile.RemoveDocument), parameters: [filePath], cancellationToken); + => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(IProjectFile.RemoveDocument), parameters: [filePath], cancellationToken); public Task AddMetadataReferenceAsync(string metadataReferenceIdentity, MetadataReferenceProperties properties, string? hintPath, CancellationToken cancellationToken) - => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(ProjectFile.AddMetadataReference), parameters: [metadataReferenceIdentity, properties, hintPath], cancellationToken); + => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(IProjectFile.AddMetadataReference), parameters: [metadataReferenceIdentity, properties, hintPath], cancellationToken); public Task RemoveMetadataReferenceAsync(string shortAssemblyName, string fullAssemblyName, string filePath, CancellationToken cancellationToken) - => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(ProjectFile.RemoveMetadataReference), parameters: [shortAssemblyName, fullAssemblyName, filePath], cancellationToken); + => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(IProjectFile.RemoveMetadataReference), parameters: [shortAssemblyName, fullAssemblyName, filePath], cancellationToken); public Task AddProjectReferenceAsync(string projectName, ProjectFileReference projectFileReference, CancellationToken cancellationToken) - => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(ProjectFile.AddProjectReference), parameters: [projectName, projectFileReference], cancellationToken); + => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(IProjectFile.AddProjectReference), parameters: [projectName, projectFileReference], cancellationToken); public Task RemoveProjectReferenceAsync(string projectName, string projectFilePath, CancellationToken cancellationToken) - => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(ProjectFile.RemoveProjectReference), parameters: [projectName, projectFilePath], cancellationToken); + => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(IProjectFile.RemoveProjectReference), parameters: [projectName, projectFilePath], cancellationToken); public Task AddAnalyzerReferenceAsync(string fullPath, CancellationToken cancellationToken) - => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(ProjectFile.AddAnalyzerReference), parameters: [fullPath], cancellationToken); + => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(IProjectFile.AddAnalyzerReference), parameters: [fullPath], cancellationToken); public Task RemoveAnalyzerReferenceAsync(string fullPath, CancellationToken cancellationToken) - => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(ProjectFile.RemoveAnalyzerReference), parameters: [fullPath], cancellationToken); + => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(IProjectFile.RemoveAnalyzerReference), parameters: [fullPath], cancellationToken); public Task SaveAsync(CancellationToken cancellationToken) - => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(ProjectFile.Save), parameters: [], cancellationToken); + => _client.InvokeAsync(_remoteProjectFileTargetObject, nameof(IProjectFile.Save), parameters: [], cancellationToken); } diff --git a/src/Workspaces/Core/MSBuild/Rpc/RpcClient.cs b/src/Workspaces/Core/MSBuild/Rpc/RpcClient.cs index 76873433c4c38..83d1a7b43c068 100644 --- a/src/Workspaces/Core/MSBuild/Rpc/RpcClient.cs +++ b/src/Workspaces/Core/MSBuild/Rpc/RpcClient.cs @@ -13,10 +13,10 @@ using Newtonsoft.Json.Linq; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.MSBuild.Rpc; +namespace Microsoft.CodeAnalysis.MSBuild; /// -/// Implements the client side of the RPC channel used to communicate with the build host, which is using . +/// Implements the client side of the RPC channel used to communicate with the build host, which is using RpcServer. /// /// /// The RPC system implemented here is pretty close to something like JSON-RPC; however since we need the Build Host to be usable in Source Build diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs index 28a3af953bf7f..dac6f076718ea 100644 --- a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs +++ b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs @@ -8,14 +8,11 @@ using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CaseCorrection; using Microsoft.CodeAnalysis.CodeCleanup; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; @@ -32,7 +29,7 @@ namespace Microsoft.CodeAnalysis.CodeActions; /// /// An action produced by a or a . /// -public abstract class CodeAction +public abstract partial class CodeAction { private static readonly Dictionary s_isNonProgressGetChangedSolutionAsyncOverridden = []; private static readonly Dictionary s_isNonProgressComputeOperationsAsyncOverridden = []; @@ -248,7 +245,7 @@ private protected virtual async Task> GetOpe if (operations != null) { - return await this.PostProcessAsync(originalSolution, operations, cancellationToken).ConfigureAwait(false); + return await PostProcessAsync(originalSolution, operations, cancellationToken).ConfigureAwait(false); } return []; @@ -269,7 +266,7 @@ internal async Task> GetPreviewOperationsAsy if (operations != null) { - return await this.PostProcessAsync(originalSolution, operations, cancellationToken).ConfigureAwait(false); + return await PostProcessAsync(originalSolution, operations, cancellationToken).ConfigureAwait(false); } return []; @@ -408,7 +405,7 @@ protected virtual Task GetChangedDocumentAsync(IProgress GetChangedDocumentInternalAsync(CancellationToken cancellation) @@ -420,11 +417,13 @@ internal Task GetChangedDocumentInternalAsync(CancellationToken cancel /// A list of operations. /// A cancellation token. /// A new list of operations with post processing steps applied to any 's. +#pragma warning disable CA1822 // Mark members as static. This is a public API. protected Task> PostProcessAsync(IEnumerable operations, CancellationToken cancellationToken) - => PostProcessAsync(originalSolution: null!, operations, cancellationToken); +#pragma warning restore CA1822 // Mark members as static + => PostProcessAsync(originalSolution: null, operations, cancellationToken); - internal async Task> PostProcessAsync( - Solution originalSolution, IEnumerable operations, CancellationToken cancellationToken) + internal static async Task> PostProcessAsync( + Solution? originalSolution, IEnumerable operations, CancellationToken cancellationToken) { using var result = TemporaryArray.Empty; @@ -432,7 +431,7 @@ internal async Task> PostProcessAsync( { if (op is ApplyChangesOperation ac) { - result.Add(new ApplyChangesOperation(await this.PostProcessChangesAsync(originalSolution, ac.ChangedSolution, cancellationToken).ConfigureAwait(false))); + result.Add(new ApplyChangesOperation(await PostProcessChangesAsync(originalSolution, ac.ChangedSolution, cancellationToken).ConfigureAwait(false))); } else { @@ -448,11 +447,13 @@ internal async Task> PostProcessAsync( /// /// The solution changed by the . /// A cancellation token +#pragma warning disable CA1822 // Mark members as static. This is a public API. protected Task PostProcessChangesAsync(Solution changedSolution, CancellationToken cancellationToken) - => PostProcessChangesAsync(originalSolution: null!, changedSolution, cancellationToken); +#pragma warning restore CA1822 // Mark members as static + => PostProcessChangesAsync(originalSolution: null, changedSolution, cancellationToken); - internal async Task PostProcessChangesAsync( - Solution originalSolution, + private static async Task PostProcessChangesAsync( + Solution? originalSolution, Solution changedSolution, CancellationToken cancellationToken) { @@ -461,38 +462,10 @@ internal async Task PostProcessChangesAsync( // underneath us). But it's the only option we have for the compat case with existing public extension // points. originalSolution ??= changedSolution.Workspace.CurrentSolution; - var solutionChanges = changedSolution.GetChanges(originalSolution); - var processedSolution = changedSolution; - - // process changed projects - foreach (var projectChanges in solutionChanges.GetProjectChanges()) - { - var documentsToProcess = projectChanges.GetChangedDocuments(onlyGetDocumentsWithTextChanges: true).Concat( - projectChanges.GetAddedDocuments()); - - foreach (var documentId in documentsToProcess) - { - var document = processedSolution.GetRequiredDocument(documentId); - var processedDocument = await PostProcessChangesAsync(document, cancellationToken).ConfigureAwait(false); - processedSolution = processedDocument.Project.Solution; - } - } - - // process completely new projects too - foreach (var addedProject in solutionChanges.GetAddedProjects()) - { - var documentsToProcess = addedProject.DocumentIds; - - foreach (var documentId in documentsToProcess) - { - var document = processedSolution.GetRequiredDocument(documentId); - var processedDocument = await PostProcessChangesAsync(document, cancellationToken).ConfigureAwait(false); - processedSolution = processedDocument.Project.Solution; - } - } - - return processedSolution; + var globalOptions = changedSolution.Services.GetService(); + var fallbackOptions = globalOptions?.Provider ?? CodeActionOptions.DefaultProvider; + return await CleanSyntaxAndSemanticsAsync(originalSolution, changedSolution, fallbackOptions, CodeAnalysisProgress.None, cancellationToken).ConfigureAwait(false); } /// @@ -518,25 +491,6 @@ protected virtual async Task PostProcessChangesAsync(Document document return document; } - internal static async Task CleanupDocumentAsync( - Document document, CodeCleanupOptions options, CancellationToken cancellationToken) - { - document = await ImportAdder.AddImportsFromSymbolAnnotationAsync( - document, Simplifier.AddImportsAnnotation, options.AddImportOptions, cancellationToken).ConfigureAwait(false); - - document = await Simplifier.ReduceAsync(document, Simplifier.Annotation, options.SimplifierOptions, cancellationToken).ConfigureAwait(false); - - // format any node with explicit formatter annotation - document = await Formatter.FormatAsync(document, Formatter.Annotation, options.FormattingOptions, cancellationToken).ConfigureAwait(false); - - // format any elastic whitespace - document = await Formatter.FormatAsync(document, SyntaxAnnotation.ElasticAnnotation, options.FormattingOptions, cancellationToken).ConfigureAwait(false); - - document = await CaseCorrector.CaseCorrectAsync(document, CaseCorrector.Annotation, cancellationToken).ConfigureAwait(false); - - return document; - } - #region Factories for standard code actions /// diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeActionWithOptions.cs b/src/Workspaces/Core/Portable/CodeActions/CodeActionWithOptions.cs index 5914b7fbd7a7b..0f8c3b5471b5c 100644 --- a/src/Workspaces/Core/Portable/CodeActions/CodeActionWithOptions.cs +++ b/src/Workspaces/Core/Portable/CodeActions/CodeActionWithOptions.cs @@ -44,7 +44,7 @@ public abstract class CodeActionWithOptions : CodeAction if (operations != null) { - operations = await this.PostProcessAsync(originalSolution, operations, cancellationToken).ConfigureAwait(false); + operations = await PostProcessAsync(originalSolution, operations, cancellationToken).ConfigureAwait(false); } return operations; diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeAction_Cleanup.cs b/src/Workspaces/Core/Portable/CodeActions/CodeAction_Cleanup.cs new file mode 100644 index 0000000000000..0e1a0cebd1f9f --- /dev/null +++ b/src/Workspaces/Core/Portable/CodeActions/CodeAction_Cleanup.cs @@ -0,0 +1,174 @@ +// 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 System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CaseCorrection; +using Microsoft.CodeAnalysis.CodeCleanup; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Simplification; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CodeActions; + +public abstract partial class CodeAction +{ + /// + /// We do cleanup in N serialized passes. This allows us to process all documents in parallel, while only forking + /// the solution N times *total* (instead of N times *per* document). + /// + private static readonly ImmutableArray>> s_cleanupPasses = + [ + // First, ensure that everything is formatted as the feature asked for. We want to do this prior to doing + // semantic cleanup as the semantic cleanup passes may end up making changes that end up dropping some of + // the formatting/elastic annotations that the feature wanted respected. + static (document, options, cancellationToken) => CleanupSyntaxAsync(document, options, cancellationToken), + // Then add all missing imports to all changed documents. + static (document, options, cancellationToken) => ImportAdder.AddImportsFromSymbolAnnotationAsync(document, Simplifier.AddImportsAnnotation, options.AddImportOptions, cancellationToken), + // Then simplify any expanded constructs. + static (document, options, cancellationToken) => Simplifier.ReduceAsync(document, Simplifier.Annotation, options.SimplifierOptions, cancellationToken), + // The do any necessary case correction for VB files. + static (document, options, cancellationToken) => CaseCorrector.CaseCorrectAsync(document, CaseCorrector.Annotation, cancellationToken), + // Finally, after doing the semantic cleanup, do another syntax cleanup pass to ensure that the tree is in a + // good state. The semantic cleanup passes may have introduced new nodes with elastic trivia that have to be + // cleaned. + static (document, options, cancellationToken) => CleanupSyntaxAsync(document, options, cancellationToken), + ]; + + private static async Task CleanupSyntaxAsync(Document document, CodeCleanupOptions options, CancellationToken cancellationToken) + { + Contract.ThrowIfFalse(document.SupportsSyntaxTree); + + // format any node with explicit formatter annotation + var document1 = await Formatter.FormatAsync(document, Formatter.Annotation, options.FormattingOptions, cancellationToken).ConfigureAwait(false); + + // format any elastic whitespace + var document2 = await Formatter.FormatAsync(document1, SyntaxAnnotation.ElasticAnnotation, options.FormattingOptions, cancellationToken).ConfigureAwait(false); + return document2; + } + + internal static ImmutableArray GetAllChangedOrAddedDocumentIds( + Solution originalSolution, + Solution changedSolution) + { + var solutionChanges = changedSolution.GetChanges(originalSolution); + var documentIds = solutionChanges + .GetProjectChanges() + .SelectMany(p => p.GetChangedDocuments(onlyGetDocumentsWithTextChanges: true).Concat(p.GetAddedDocuments())) + .Concat(solutionChanges.GetAddedProjects().SelectMany(p => p.DocumentIds)) + .ToImmutableArray(); + return documentIds; + } + + internal static async Task CleanSyntaxAndSemanticsAsync( + Solution originalSolution, + Solution changedSolution, + CodeCleanupOptionsProvider optionsProvider, + IProgress progress, + CancellationToken cancellationToken) + { + var documentIds = GetAllChangedOrAddedDocumentIds(originalSolution, changedSolution); + var documentIdsAndOptionsToClean = await GetDocumentIdsAndOptionsToCleanAsync().ConfigureAwait(false); + + // Then do a pass where we cleanup semantics. + var cleanedSolution = await RunAllCleanupPassesInOrderAsync( + changedSolution, documentIdsAndOptionsToClean, progress, cancellationToken).ConfigureAwait(false); + + return cleanedSolution; + + async Task> GetDocumentIdsAndOptionsToCleanAsync() + { + using var _ = ArrayBuilder<(DocumentId documentId, CodeCleanupOptions options)>.GetInstance(documentIds.Length, out var documentIdsAndOptions); + foreach (var documentId in documentIds) + { + var document = changedSolution.GetRequiredDocument(documentId); + + // Only care about documents that support syntax. Non-C#/VB files can't be cleaned. + if (document.SupportsSyntaxTree) + { + var codeActionOptions = await document.GetCodeCleanupOptionsAsync(optionsProvider, cancellationToken).ConfigureAwait(false); + documentIdsAndOptions.Add((documentId, codeActionOptions)); + } + } + + return documentIdsAndOptions.ToImmutableAndClear(); + } + } + + internal static async ValueTask CleanupDocumentAsync(Document document, CodeCleanupOptions options, CancellationToken cancellationToken) + { + if (!document.SupportsSyntaxTree) + return document; + + var cleanedSolution = await RunAllCleanupPassesInOrderAsync( + document.Project.Solution, + [(document.Id, options)], + CodeAnalysisProgress.None, + cancellationToken).ConfigureAwait(false); + + return cleanedSolution.GetRequiredDocument(document.Id); + } + + private static async Task RunAllCleanupPassesInOrderAsync( + Solution solution, + ImmutableArray<(DocumentId documentId, CodeCleanupOptions options)> documentIdsAndOptions, + IProgress progress, + CancellationToken cancellationToken) + { + // One item per document per cleanup pass. + progress.AddItems(documentIdsAndOptions.Length * s_cleanupPasses.Length); + + var currentSolution = solution; + foreach (var cleanupPass in s_cleanupPasses) + currentSolution = await RunParallelCleanupPassAsync(currentSolution, cleanupPass).ConfigureAwait(false); + + return currentSolution; + + async Task RunParallelCleanupPassAsync( + Solution solution, Func> cleanupDocumentAsync) + { + // We're about to making a ton of calls to this new solution, including expensive oop calls to get up to + // date compilations, skeletons and SG docs. Create and pin this solution so that all remote calls operate + // on the same fork and do not cause the forked solution to be created and dropped repeatedly. + using var _ = await RemoteKeepAliveSession.CreateAsync(solution, cancellationToken).ConfigureAwait(false); + + var changedRoots = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync( + source: documentIdsAndOptions, + produceItems: static async (documentIdAndOptions, callback, args, cancellationToken) => + { + // As we finish each document, update our progress. + using var _ = args.progress.ItemCompletedScope(); + + var (documentId, options) = documentIdAndOptions; + + // Fetch the current state of the document from this fork of the solution. + var document = args.solution.GetRequiredDocument(documentId); + Contract.ThrowIfFalse(document.SupportsSyntaxTree, "GetDocumentIdsAndOptionsAsync should only be returning documents that support syntax"); + + // Now, perform the requested cleanup pass on it. + var cleanedDocument = await args.cleanupDocumentAsync(document, options, cancellationToken).ConfigureAwait(false); + if (cleanedDocument is null || cleanedDocument == document) + return; + + // Now get the cleaned root and pass it back to the consumer. + var newRoot = await cleanedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + callback((documentId, newRoot)); + }, + args: (solution, progress, cleanupDocumentAsync), + cancellationToken).ConfigureAwait(false); + + // Grab all the cleaned roots and produce the new solution snapshot from that. + return solution.WithDocumentSyntaxRoots(changedRoots); + } + } +} diff --git a/src/Workspaces/Core/Portable/CodeCleanup/Providers/FormatCodeCleanupProvider.cs b/src/Workspaces/Core/Portable/CodeCleanup/Providers/FormatCodeCleanupProvider.cs index 84bdd989f7768..179de6c20bd69 100644 --- a/src/Workspaces/Core/Portable/CodeCleanup/Providers/FormatCodeCleanupProvider.cs +++ b/src/Workspaces/Core/Portable/CodeCleanup/Providers/FormatCodeCleanupProvider.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.CodeCleanup.Providers; -internal sealed class FormatCodeCleanupProvider(IEnumerable? rules = null) : ICodeCleanupProvider +internal sealed class FormatCodeCleanupProvider(ImmutableArray rules = default) : ICodeCleanupProvider { public string Name => PredefinedCodeCleanupProviderNames.Format; diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs index a7e5192a9bdf0..c5fccb6afbcfb 100644 --- a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs +++ b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs @@ -253,15 +253,10 @@ private static Action> GetRegisterCodeFix private static async Task ApplyChangesAsync( Solution currentSolution, - ImmutableArray<(DocumentId, TextChangeMerger)> docIdsAndMerger, + ImmutableArray<(DocumentId documentId, TextChangeMerger merger)> docIdsAndMerger, CancellationToken cancellationToken) { - foreach (var (documentId, textMerger) in docIdsAndMerger) - { - var newText = await textMerger.GetFinalMergedTextAsync(cancellationToken).ConfigureAwait(false); - currentSolution = currentSolution.WithDocumentText(documentId, newText); - } - - return currentSolution; + var docIdsAndTexts = await docIdsAndMerger.SelectAsArrayAsync(async t => (t.documentId, await t.merger.GetFinalMergedTextAsync(cancellationToken).ConfigureAwait(false))).ConfigureAwait(false); + return currentSolution.WithDocumentTexts(docIdsAndTexts); } } diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/DocumentBasedFixAllProvider.cs b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/DocumentBasedFixAllProvider.cs index 4f2f90530fff3..88e8fc3f4d4da 100644 --- a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/DocumentBasedFixAllProvider.cs +++ b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/DocumentBasedFixAllProvider.cs @@ -8,8 +8,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixesAndRefactorings; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -27,20 +25,15 @@ namespace Microsoft.CodeAnalysis.CodeFixes; /// project and then appropriately bucketed by document. These are then passed to for implementors to process. /// -public abstract class DocumentBasedFixAllProvider : FixAllProvider +public abstract class DocumentBasedFixAllProvider(ImmutableArray supportedFixAllScopes) : FixAllProvider { - private readonly ImmutableArray _supportedFixAllScopes; + private readonly ImmutableArray _supportedFixAllScopes = supportedFixAllScopes; protected DocumentBasedFixAllProvider() : this(DefaultSupportedFixAllScopes) { } - protected DocumentBasedFixAllProvider(ImmutableArray supportedFixAllScopes) - { - _supportedFixAllScopes = supportedFixAllScopes; - } - /// /// Produce a suitable title for the fix-all this type creates in . Override this if customizing that title is desired. @@ -72,79 +65,34 @@ public sealed override IEnumerable GetSupportedFixAllScopes() fixAllContext.GetDefaultFixAllTitle(), fixAllContext, FixAllContextsHelperAsync); private Task FixAllContextsHelperAsync(FixAllContext originalFixAllContext, ImmutableArray fixAllContexts) - => DocumentBasedFixAllProviderHelpers.FixAllContextsAsync(originalFixAllContext, fixAllContexts, - originalFixAllContext.Progress, - this.GetFixAllTitle(originalFixAllContext), - DetermineDiagnosticsAndGetFixedDocumentsAsync); - - private async Task> DetermineDiagnosticsAndGetFixedDocumentsAsync( - FixAllContext fixAllContext, - IProgress progressTracker) - { - // First, determine the diagnostics to fix. - var diagnostics = await DetermineDiagnosticsAsync(fixAllContext, progressTracker).ConfigureAwait(false); - - // Second, get the fixes for all the diagnostics, and apply them to determine the new root/text for each doc. - return await GetFixedDocumentsAsync(fixAllContext, progressTracker, diagnostics).ConfigureAwait(false); - } - - /// - /// Determines all the diagnostics we should be fixing for the given . - /// - private static async Task>> DetermineDiagnosticsAsync(FixAllContext fixAllContext, IProgress progressTracker) - { - using var _ = progressTracker.ItemCompletedScope(); - return await FixAllContextHelper.GetDocumentDiagnosticsToFixAsync(fixAllContext).ConfigureAwait(false); - } - - /// - /// Attempts to fix all the provided returning, for each updated document, either - /// the new syntax root for that document or its new text. Syntax roots are returned for documents that support - /// them, and are used to perform a final cleanup pass for formatting/simplication/etc. Text is returned for - /// documents that don't support syntax. - /// - private async Task> GetFixedDocumentsAsync( - FixAllContext fixAllContext, IProgress progressTracker, ImmutableDictionary> diagnostics) + => DocumentBasedFixAllProviderHelpers.FixAllContextsAsync( + originalFixAllContext, + fixAllContexts, + originalFixAllContext.Progress, + this.GetFixAllTitle(originalFixAllContext), + DetermineDiagnosticsAndGetFixedDocumentsAsync); + + private async Task DetermineDiagnosticsAndGetFixedDocumentsAsync( + FixAllContext fixAllContext, Func onDocumentFixed) { var cancellationToken = fixAllContext.CancellationToken; - using var _1 = progressTracker.ItemCompletedScope(); - using var _2 = ArrayBuilder>.GetInstance(out var tasks); - - var docIdToNewRootOrText = new Dictionary(); - if (!diagnostics.IsEmpty) - { - // Then, process all documents in parallel to get the change for each doc. - foreach (var (document, documentDiagnostics) in diagnostics) + // First, determine the diagnostics to fix. + var documentToDiagnostics = await FixAllContextHelper.GetDocumentDiagnosticsToFixAsync(fixAllContext).ConfigureAwait(false); + + // Second, get the fixes for each document+diagnostics pair in parallel, and apply them to determine the new + // root/text for each doc. + await RoslynParallel.ForEachAsync( + source: documentToDiagnostics, + cancellationToken, + async (kvp, cancellationToken) => { + var (document, documentDiagnostics) = kvp; if (documentDiagnostics.IsDefaultOrEmpty) - continue; - - tasks.Add(Task.Run(async () => - { - var newDocument = await this.FixAllAsync(fixAllContext, document, documentDiagnostics).ConfigureAwait(false); - if (newDocument == null || newDocument == document) - return default; - - // For documents that support syntax, grab the tree so that we can clean it up later. If it's a - // language that doesn't support that, then just grab the text. - var node = newDocument.SupportsSyntaxTree ? await newDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false) : null; - var text = newDocument.SupportsSyntaxTree ? null : await newDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - - return (document.Id, (node, text)); - }, cancellationToken)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); - - foreach (var task in tasks) - { - var (docId, nodeOrText) = await task.ConfigureAwait(false); - if (docId != null) - docIdToNewRootOrText[docId] = nodeOrText; - } - } + return; - return docIdToNewRootOrText; + var newDocument = await this.FixAllAsync(fixAllContext, document, documentDiagnostics).ConfigureAwait(false); + await onDocumentFixed(document, newDocument).ConfigureAwait(false); + }).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.DiagnosticProvider.cs b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.DiagnosticProvider.cs index 2c7b4ed56d743..391678a382115 100644 --- a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.DiagnosticProvider.cs +++ b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContext.DiagnosticProvider.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeFixesAndRefactorings; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeFixes; @@ -81,26 +82,27 @@ internal static async Task>(); - - var tasks = project.Solution.Projects.Select(async p => new - { - Project = p, - Diagnostics = await fixAllContext.GetProjectDiagnosticsAsync(p).ConfigureAwait(false) - }).ToArray(); - - await Task.WhenAll(tasks).ConfigureAwait(false); - - foreach (var task in tasks) - { - var projectAndDiagnostics = await task.ConfigureAwait(false); - if (projectAndDiagnostics.Diagnostics.Any()) + return await ProducerConsumer<(Project project, ImmutableArray diagnostics)>.RunParallelAsync( + source: project.Solution.Projects, + produceItems: static async (project, callback, fixAllContext, cancellationToken) => { - projectsAndDiagnostics[projectAndDiagnostics.Project] = projectAndDiagnostics.Diagnostics; - } - } - - return projectsAndDiagnostics.ToImmutable(); + var diagnostics = await fixAllContext.GetProjectDiagnosticsAsync(project).ConfigureAwait(false); + callback((project, diagnostics)); + }, + consumeItems: static async (results, args, cancellationToken) => + { + var projectsAndDiagnostics = ImmutableDictionary.CreateBuilder>(); + + await foreach (var (project, diagnostics) in results) + { + if (diagnostics.Any()) + projectsAndDiagnostics.Add(project, diagnostics); + } + + return projectsAndDiagnostics.ToImmutable(); + }, + args: fixAllContext, + fixAllContext.CancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContextHelper.cs b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContextHelper.cs index 97a51241386aa..834c0d8f05cdb 100644 --- a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContextHelper.cs +++ b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllContextHelper.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.CodeFixesAndRefactorings; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -64,23 +65,34 @@ public static async Task p.Language == project.Language) - .ToImmutableArray(); - - // Update the progress dialog with the count of projects to actually fix. We'll update the progress - // bar as we get all the documents in AddDocumentDiagnosticsAsync. - - progressTracker.AddItems(projectsToFix.Length); - - var diagnostics = new ConcurrentDictionary>(); - using (var _ = ArrayBuilder.GetInstance(projectsToFix.Length, out var tasks)) { - foreach (var projectToFix in projectsToFix) - tasks.Add(Task.Run(async () => await AddDocumentDiagnosticsAsync(diagnostics, projectToFix).ConfigureAwait(false), cancellationToken)); - - await Task.WhenAll(tasks).ConfigureAwait(false); - allDiagnostics = allDiagnostics.AddRange(diagnostics.SelectMany(i => i.Value)); + var projectsToFix = project.Solution.Projects + .Where(p => p.Language == project.Language) + .ToImmutableArray(); + + // Update the progress dialog with the count of projects to actually fix. We'll update the progress + // bar as we get all the documents in AddDocumentDiagnosticsAsync. + + progressTracker.AddItems(projectsToFix.Length); + + allDiagnostics = await ProducerConsumer>.RunParallelAsync( + source: projectsToFix, + produceItems: static async (projectToFix, callback, args, cancellationToken) => + { + using var _ = args.progressTracker.ItemCompletedScope(); + callback(await args.fixAllContext.GetAllDiagnosticsAsync(projectToFix).ConfigureAwait(false)); + }, + consumeItems: static async (results, args, cancellationToken) => + { + using var _ = ArrayBuilder.GetInstance(out var builder); + + await foreach (var diagnostics in results) + builder.AddRange(diagnostics); + + return builder.ToImmutableAndClear(); + }, + args: (fixAllContext, progressTracker), + cancellationToken).ConfigureAwait(false); } break; @@ -94,19 +106,6 @@ public static async Task> diagnostics, Project projectToFix) - { - try - { - var projectDiagnostics = await fixAllContext.GetAllDiagnosticsAsync(projectToFix).ConfigureAwait(false); - diagnostics.TryAdd(projectToFix.Id, projectDiagnostics); - } - finally - { - progressTracker.ItemCompleted(); - } - } - static async Task>> GetSpanDiagnosticsAsync( FixAllContext fixAllContext, IEnumerable>> documentsAndSpans) diff --git a/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs b/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs index 37248a1e7031f..a58b5618efa87 100644 --- a/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs +++ b/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs @@ -3,16 +3,13 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -29,134 +26,88 @@ internal static class DocumentBasedFixAllProviderHelpers ImmutableArray fixAllContexts, IProgress progressTracker, string progressTrackerDescription, - Func, Task>> getFixedDocumentsAsync) + Func, Task> getFixedDocumentsAsync) where TFixAllContext : IFixAllContext { - progressTracker.Report(CodeAnalysisProgress.Description(progressTrackerDescription)); + var cancellationToken = originalFixAllContext.CancellationToken; - var solution = originalFixAllContext.Solution; + progressTracker.Report(CodeAnalysisProgress.Description(progressTrackerDescription)); - // For code fixes, we have 3 pieces of work per project. Computing diagnostics, computing fixes, and applying fixes. - // For refactorings, we have 2 pieces of work per project. Computing refactorings, and applying refactorings. - var fixAllKind = originalFixAllContext.State.FixAllKind; - var workItemCount = fixAllKind == FixAllKind.CodeFix ? 3 : 2; - progressTracker.AddItems(fixAllContexts.Length * workItemCount); + var originalSolution = originalFixAllContext.Solution; - using var _1 = PooledDictionary.GetInstance(out var allContextsDocIdToNewRootOrText); - { - // First, iterate over all contexts, and collect all the changes for each of them. We'll be making a lot of - // calls to the remote server to compute diagnostics and changes. So keep a single connection alive to it - // so we never resync or recompute anything. - using var _2 = await RemoteKeepAliveSession.CreateAsync(solution, originalFixAllContext.CancellationToken).ConfigureAwait(false); - - foreach (var fixAllContext in fixAllContexts) - { - Contract.ThrowIfFalse( - fixAllContext.Scope is FixAllScope.Document or FixAllScope.Project or FixAllScope.ContainingMember or FixAllScope.ContainingType); - - // TODO: consider computing this in parallel. - var singleContextDocIdToNewRootOrText = await getFixedDocumentsAsync(fixAllContext, progressTracker).ConfigureAwait(false); - - // Note: it is safe to blindly add the dictionary for a particular context to the full dictionary. Each - // dictionary will only update documents within that context, and each context represents a distinct - // project, so these should all be distinct without collisions. However, to be very safe, we use an - // overwriting policy here to ensure nothing causes any problems here. - foreach (var kvp in singleContextDocIdToNewRootOrText) - allContextsDocIdToNewRootOrText[kvp.Key] = kvp.Value; - } - } + // One work item for each context. + progressTracker.AddItems(fixAllContexts.Length); - // Next, go and insert those all into the solution so all the docs in this particular project point at - // the new trees (or text). At this point though, the trees have not been cleaned up. We don't cleanup - // the documents as they are created, or one at a time as we add them, as that would cause us to run - // cleanup on N different solution forks (which would be very expensive). Instead, by adding all the - // changed documents to one solution, and then cleaning *those* we only perform cleanup semantics on one - // forked solution. - var currentSolution = solution; - foreach (var (docId, (newRoot, newText)) in allContextsDocIdToNewRootOrText) - { - currentSolution = newRoot != null - ? currentSolution.WithDocumentSyntaxRoot(docId, newRoot) - : currentSolution.WithDocumentText(docId, newText!); - } - - { - // We're about to making a ton of calls to this new solution, including expensive oop calls to get up to - // date compilations, skeletons and SG docs. Create and pin this solution so that all remote calls operate - // on the same fork and do not cause the forked solution to be created and dropped repeatedly. - using var _2 = await RemoteKeepAliveSession.CreateAsync(currentSolution, originalFixAllContext.CancellationToken).ConfigureAwait(false); - - var finalSolution = await CleanupAndApplyChangesAsync( - progressTracker, - currentSolution, - allContextsDocIdToNewRootOrText, - originalFixAllContext.CancellationToken).ConfigureAwait(false); - - return finalSolution; - } - } + // Do the initial pass to fixup documents. + var dirtySolution = await GetInitialUncleanedSolutionAsync(originalSolution).ConfigureAwait(false); - /// - /// Take all the fixed documents and format/simplify/clean them up (if the language supports that), and take the - /// resultant text and apply it to the solution. If the language doesn't support cleanup, then just take the - /// given text and apply that instead. - /// - private static async Task CleanupAndApplyChangesAsync( - IProgress progressTracker, - Solution currentSolution, - Dictionary docIdToNewRootOrText, - CancellationToken cancellationToken) - { - using var _1 = progressTracker.ItemCompletedScope(); + // Now do a pass to clean the fixed documents. + progressTracker.Report(CodeAnalysisProgress.Clear()); + progressTracker.Report(CodeAnalysisProgress.Description(WorkspacesResources.Running_code_cleanup_on_fixed_documents)); - if (docIdToNewRootOrText.Count > 0) - { + var cleanedSolution = await CodeAction.CleanSyntaxAndSemanticsAsync( + originalSolution, + dirtySolution, + originalFixAllContext.State.CodeActionOptionsProvider, + progressTracker, + cancellationToken).ConfigureAwait(false); - // Next, go and cleanup any trees we inserted. Once we clean the document, we get the text of it and insert - // that back into the final solution. This way we can release both the original fixed tree, and the cleaned - // tree (both of which can be much more expensive than just text). - // - // Do this in parallel across all the documents that were fixed. - - using var _2 = ArrayBuilder>.GetInstance(out var tasks); - foreach (var (docId, (newRoot, _)) in docIdToNewRootOrText) - { - if (newRoot != null) - tasks.Add(GetCleanedDocumentAsync(currentSolution.GetRequiredDocument(docId), cancellationToken)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); - - // Finally, apply the cleaned documents to the solution. - foreach (var task in tasks) - { - var (docId, cleanedText) = await task.ConfigureAwait(false); - currentSolution = currentSolution.WithDocumentText(docId, cleanedText); - } - } + // Once we clean the document, we get the text of it and insert that back into the final solution. This way we + // can release both the original fixed tree, and the cleaned tree (both of which can be much more expensive than + // just text). + var cleanedTexts = await CodeAction.GetAllChangedOrAddedDocumentIds(originalSolution, cleanedSolution) + .SelectAsArrayAsync(async documentId => (documentId, await cleanedSolution.GetRequiredDocument(documentId).GetTextAsync(cancellationToken).ConfigureAwait(false))) + .ConfigureAwait(false); - return currentSolution; + var finalSolution = cleanedSolution.WithDocumentTexts(cleanedTexts); + return finalSolution; - static async Task<(DocumentId docId, SourceText sourceText)> GetCleanedDocumentAsync(Document dirtyDocument, CancellationToken cancellationToken) + async Task GetInitialUncleanedSolutionAsync(Solution originalSolution) { - await Task.Yield(); - - var cleanedDocument = await PostProcessCodeAction.Instance.PostProcessChangesAsync(dirtyDocument, cancellationToken).ConfigureAwait(false); - var cleanedText = await cleanedDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - return (dirtyDocument.Id, cleanedText); + // First, iterate over all contexts, and collect all the changes for each of them. We'll be making a lot of + // calls to the remote server to compute diagnostics and changes. So keep a single connection alive to it + // so we never resync or recompute anything. + using var _ = await RemoteKeepAliveSession.CreateAsync(originalSolution, cancellationToken).ConfigureAwait(false); + + var changedRootsAndTexts = await ProducerConsumer<(DocumentId documentId, (SyntaxNode? node, SourceText? text))>.RunParallelAsync( + source: fixAllContexts, + produceItems: static async (fixAllContext, callback, args, cancellationToken) => + { + // Update our progress for each fixAllContext we process. + using var _ = args.progressTracker.ItemCompletedScope(); + + Contract.ThrowIfFalse( + fixAllContext.Scope is FixAllScope.Document or FixAllScope.Project or FixAllScope.ContainingMember or FixAllScope.ContainingType); + + // Defer to the FixAllProvider to actually compute each fixed document. + await args.getFixedDocumentsAsync( + fixAllContext, + async (originalDocument, newDocument) => + { + if (newDocument == null || newDocument == originalDocument) + return; + + var newRoot = newDocument.SupportsSyntaxTree ? await newDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false) : null; + var newText = newDocument.SupportsSyntaxTree ? null : await newDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + callback((newDocument.Id, (newRoot, newText))); + }).ConfigureAwait(false); + }, + args: (getFixedDocumentsAsync, progressTracker, originalSolution), + cancellationToken).ConfigureAwait(false); + + // Next, go and insert those all into the solution so all the docs in this particular project point + // at the new trees (or text). At this point though, the trees have not been semantically cleaned + // up. We don't cleanup the documents as they are created, or one at a time as we add them, as that + // would cause us to run semantic cleanup on N different solution forks (which would be very + // expensive as we'd fork, produce semantics, fork, produce semantics, etc. etc.). Instead, by + // adding all the changed documents to one solution, and then cleaning *those* we only perform + // cleanup semantics on one forked solution. + var changedRoots = changedRootsAndTexts.SelectAsArray(t => t.Item2.node != null, t => (t.documentId, t.Item2.node!, PreservationMode.PreserveValue)); + var changedTexts = changedRootsAndTexts.SelectAsArray(t => t.Item2.text != null, t => (t.documentId, t.Item2.text!, PreservationMode.PreserveValue)); + + return originalSolution + .WithDocumentSyntaxRoots(changedRoots) + .WithDocumentTexts(changedTexts); } } - - /// - /// Dummy class just to get access to - /// - private class PostProcessCodeAction : CodeAction - { - public static readonly PostProcessCodeAction Instance = new(); - - public override string Title => ""; - - public new Task PostProcessChangesAsync(Document document, CancellationToken cancellationToken) - => base.PostProcessChangesAsync(document, cancellationToken); - } } diff --git a/src/Workspaces/Core/Portable/CodeRefactorings/FixAllOccurences/DocumentBasedFixAllProvider.cs b/src/Workspaces/Core/Portable/CodeRefactorings/FixAllOccurences/DocumentBasedFixAllProvider.cs index 6fd6c060e18ff..6dbcf20bc1002 100644 --- a/src/Workspaces/Core/Portable/CodeRefactorings/FixAllOccurences/DocumentBasedFixAllProvider.cs +++ b/src/Workspaces/Core/Portable/CodeRefactorings/FixAllOccurences/DocumentBasedFixAllProvider.cs @@ -8,8 +8,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixesAndRefactorings; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -28,20 +26,15 @@ namespace Microsoft.CodeAnalysis.CodeRefactorings; /// /// TODO: Make public, tracked with https://github.com/dotnet/roslyn/issues/60703 /// -internal abstract class DocumentBasedFixAllProvider : FixAllProvider +internal abstract class DocumentBasedFixAllProvider(ImmutableArray supportedFixAllScopes) : FixAllProvider { - private readonly ImmutableArray _supportedFixAllScopes; + private readonly ImmutableArray _supportedFixAllScopes = supportedFixAllScopes; protected DocumentBasedFixAllProvider() : this(DefaultSupportedFixAllScopes) { } - protected DocumentBasedFixAllProvider(ImmutableArray supportedFixAllScopes) - { - _supportedFixAllScopes = supportedFixAllScopes; - } - /// /// Produce a suitable title for the fix-all this type creates in . Override this if customizing that title is desired. @@ -73,59 +66,37 @@ public sealed override IEnumerable GetSupportedFixAllScopes() fixAllContext.GetDefaultFixAllTitle(), fixAllContext, FixAllContextsHelperAsync); private Task FixAllContextsHelperAsync(FixAllContext originalFixAllContext, ImmutableArray fixAllContexts) - => DocumentBasedFixAllProviderHelpers.FixAllContextsAsync(originalFixAllContext, fixAllContexts, - originalFixAllContext.Progress, - this.GetFixAllTitle(originalFixAllContext), - GetFixedDocumentsAsync); + => DocumentBasedFixAllProviderHelpers.FixAllContextsAsync( + originalFixAllContext, + fixAllContexts, + originalFixAllContext.Progress, + this.GetFixAllTitle(originalFixAllContext), + GetFixedDocumentsAsync); /// - /// Attempts to apply fix all operations returning, for each updated document, either - /// the new syntax root for that document or its new text. Syntax roots are returned for documents that support - /// them, and are used to perform a final cleanup pass for formatting/simplication/etc. Text is returned for - /// documents that don't support syntax. + /// Attempts to apply fix all operations returning, for each updated document, either the new syntax root for that + /// document or its new text. Syntax roots are returned for documents that support them, and are used to perform a + /// final cleanup pass for formatting/simplification/etc. Text is returned for documents that don't support syntax. /// - private async Task> GetFixedDocumentsAsync( - FixAllContext fixAllContext, IProgress progressTracker) + private async Task GetFixedDocumentsAsync( + FixAllContext fixAllContext, Func onDocumentFixed) { Contract.ThrowIfFalse(fixAllContext.Scope is FixAllScope.Document or FixAllScope.Project or FixAllScope.ContainingMember or FixAllScope.ContainingType); var cancellationToken = fixAllContext.CancellationToken; - using var _1 = progressTracker.ItemCompletedScope(); - using var _2 = ArrayBuilder>.GetInstance(out var tasks); - - var docIdToNewRootOrText = new Dictionary(); - // Process all documents in parallel to get the change for each doc. var documentsAndSpansToFix = await fixAllContext.GetFixAllSpansAsync(cancellationToken).ConfigureAwait(false); - foreach (var (document, spans) in documentsAndSpansToFix) - { - tasks.Add(Task.Run(async () => + await RoslynParallel.ForEachAsync( + source: documentsAndSpansToFix, + cancellationToken, + async (tuple, cancellationToken) => { + var (document, spans) = tuple; var newDocument = await this.FixAllAsync(fixAllContext, document, spans).ConfigureAwait(false); - if (newDocument == null || newDocument == document) - return default; - - // For documents that support syntax, grab the tree so that we can clean it up later. If it's a - // language that doesn't support that, then just grab the text. - var node = newDocument.SupportsSyntaxTree ? await newDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false) : null; - var text = newDocument.SupportsSyntaxTree ? null : await newDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - - return (document.Id, (node, text)); - }, cancellationToken)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); - - foreach (var task in tasks) - { - var (docId, nodeOrText) = await task.ConfigureAwait(false); - if (docId != null) - docIdToNewRootOrText[docId] = nodeOrText; - } - - return docIdToNewRootOrText; + await onDocumentFixed(document, newDocument).ConfigureAwait(false); + }).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/Diagnostics/DefaultAnalyzerAssemblyLoaderService.cs b/src/Workspaces/Core/Portable/Diagnostics/DefaultAnalyzerAssemblyLoaderService.cs index db82f43f5df66..9443bf285ee4d 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DefaultAnalyzerAssemblyLoaderService.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DefaultAnalyzerAssemblyLoaderService.cs @@ -5,31 +5,35 @@ using System; using System.Composition; using System.IO; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; +using System.Collections.Immutable; +using System.Collections.Generic; namespace Microsoft.CodeAnalysis.Host; [ExportWorkspaceServiceFactory(typeof(IAnalyzerAssemblyLoaderProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class DefaultAnalyzerAssemblyLoaderServiceFactory() : IWorkspaceServiceFactory +internal sealed class DefaultAnalyzerAssemblyLoaderServiceFactory([ImportMany] IEnumerable externalResolvers) : IWorkspaceServiceFactory { public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new DefaultAnalyzerAssemblyLoaderProvider(workspaceServices.Workspace.Kind ?? "default"); + => new DefaultAnalyzerAssemblyLoaderProvider(workspaceServices.Workspace.Kind ?? "default", [.. externalResolvers]); - private sealed class DefaultAnalyzerAssemblyLoaderProvider(string workspaceKind) : IAnalyzerAssemblyLoaderProvider + private sealed class DefaultAnalyzerAssemblyLoaderProvider(string workspaceKind, ImmutableArray externalResolvers) : IAnalyzerAssemblyLoaderProvider { - private readonly DefaultAnalyzerAssemblyLoader _loader = new(); + private readonly DefaultAnalyzerAssemblyLoader _loader = new(externalResolvers); /// /// We include the of the workspace in the path we produce. That way we don't /// collide in the common case of a normal host workspace and OOP workspace running together. This avoids an /// annoying exception as each will try to clean up this directory, throwing exceptions because the other is - /// locking it. The exception is fine, since the cleanup is just hygenic and isn't intended to be needed for + /// locking it. The exception is fine, since the cleanup is just hygienic and isn't intended to be needed for /// correctness. But it is annoying and does cause noise in our perf test harness. /// private readonly IAnalyzerAssemblyLoader _shadowCopyLoader = DefaultAnalyzerAssemblyLoader.CreateNonLockingLoader( - Path.Combine(Path.GetTempPath(), "CodeAnalysis", "WorkspacesAnalyzerShadowCopies", workspaceKind)); + Path.Combine(Path.GetTempPath(), "CodeAnalysis", "WorkspacesAnalyzerShadowCopies", workspaceKind), + externalResolvers: externalResolvers); public IAnalyzerAssemblyLoader GetLoader(in AnalyzerAssemblyLoaderOptions options) => options.ShadowCopy ? _shadowCopyLoader : _loader; diff --git a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs index 8e3b0c044b186..148b1280ab343 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Workspaces.Diagnostics; using Roslyn.Utilities; @@ -372,62 +373,60 @@ private static async Task> GetPragmaSuppressionAnalyz var analyzers = documentAnalysisScope?.Analyzers ?? compilationWithAnalyzers.Analyzers; var suppressionAnalyzer = analyzers.OfType().FirstOrDefault(); if (suppressionAnalyzer == null) - { return []; - } if (documentAnalysisScope != null) { if (documentAnalysisScope.TextDocument is not Document document) - { return []; - } using var _ = ArrayBuilder.GetInstance(out var diagnosticsBuilder); - await AnalyzeDocumentAsync(suppressionAnalyzer, document, documentAnalysisScope.Span, diagnosticsBuilder.Add).ConfigureAwait(false); + await AnalyzeDocumentAsync( + compilationWithAnalyzers, analyzerInfoCache, suppressionAnalyzer, + document, documentAnalysisScope.Span, diagnosticsBuilder.Add, cancellationToken).ConfigureAwait(false); return diagnosticsBuilder.ToImmutableAndClear(); } else { if (compilationWithAnalyzers.AnalysisOptions.ConcurrentAnalysis) { - var bag = new ConcurrentBag(); - using var _ = ArrayBuilder.GetInstance(project.DocumentIds.Count, out var tasks); - foreach (var document in project.Documents) - { - tasks.Add(AnalyzeDocumentAsync(suppressionAnalyzer, document, span: null, bag.Add)); - } - - foreach (var document in await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false)) - { - tasks.Add(AnalyzeDocumentAsync(suppressionAnalyzer, document, span: null, bag.Add)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); - return [.. bag]; + return await ProducerConsumer.RunParallelAsync( + source: project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken), + produceItems: static async (document, callback, args, cancellationToken) => + { + await AnalyzeDocumentAsync( + args.compilationWithAnalyzers, args.analyzerInfoCache, args.suppressionAnalyzer, + document, span: null, callback, cancellationToken).ConfigureAwait(false); + }, + args: (compilationWithAnalyzers, analyzerInfoCache, suppressionAnalyzer), + cancellationToken).ConfigureAwait(false); } else { using var _ = ArrayBuilder.GetInstance(out var diagnosticsBuilder); - foreach (var document in project.Documents) + await foreach (var document in project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken)) { - await AnalyzeDocumentAsync(suppressionAnalyzer, document, span: null, diagnosticsBuilder.Add).ConfigureAwait(false); - } - - foreach (var document in await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false)) - { - await AnalyzeDocumentAsync(suppressionAnalyzer, document, span: null, diagnosticsBuilder.Add).ConfigureAwait(false); + await AnalyzeDocumentAsync( + compilationWithAnalyzers, analyzerInfoCache, suppressionAnalyzer, + document, span: null, diagnosticsBuilder.Add, cancellationToken).ConfigureAwait(false); } return diagnosticsBuilder.ToImmutableAndClear(); } } - async Task AnalyzeDocumentAsync(IPragmaSuppressionsAnalyzer suppressionAnalyzer, Document document, TextSpan? span, Action reportDiagnostic) + static async Task AnalyzeDocumentAsync( + CompilationWithAnalyzers compilationWithAnalyzers, + DiagnosticAnalyzerInfoCache analyzerInfoCache, + IPragmaSuppressionsAnalyzer suppressionAnalyzer, + Document document, + TextSpan? span, + Action reportDiagnostic, + CancellationToken cancellationToken) { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - await suppressionAnalyzer.AnalyzeAsync(semanticModel, span, compilationWithAnalyzers, - analyzerInfoCache.GetDiagnosticDescriptors, reportDiagnostic, cancellationToken).ConfigureAwait(false); + await suppressionAnalyzer.AnalyzeAsync( + semanticModel, span, compilationWithAnalyzers, analyzerInfoCache.GetDiagnosticDescriptors, reportDiagnostic, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs index d489d306ed371..edc98a9a92721 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs @@ -87,21 +87,22 @@ private async Task FindReferencesWorkerAsync(CancellationToken cancellationToken var count = _solution.Projects.SelectMany(p => p.DocumentIds).Count(); await _progressTracker.AddItemsAsync(count, cancellationToken).ConfigureAwait(false); - foreach (var project in _solution.Projects) - { - cancellationToken.ThrowIfCancellationRequested(); + await RoslynParallel.ForEachAsync( + source: SelectManyAsync(_solution.Projects, p => p.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken)), + cancellationToken, + ProcessDocumentAsync).ConfigureAwait(false); - var documentTasks = new List(); - foreach (var document in await project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false)) + static async IAsyncEnumerable SelectManyAsync(IEnumerable source, Func> selector) + { + foreach (var item in source) { - documentTasks.Add(ProcessDocumentAsync(document, cancellationToken)); + await foreach (var result in selector(item)) + yield return result; } - - await Task.WhenAll(documentTasks).ConfigureAwait(false); } } - private async Task ProcessDocumentAsync(Document document, CancellationToken cancellationToken) + private async ValueTask ProcessDocumentAsync(Document document, CancellationToken cancellationToken) { try { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs index 5ac66337bfae1..6ece9e7edac9e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs @@ -15,10 +15,10 @@ internal static partial class BaseTypeFinder public static ImmutableArray FindBaseTypesAndInterfaces(INamedTypeSymbol type) => FindBaseTypes(type).AddRange(type.AllInterfaces); - public static async ValueTask> FindOverriddenAndImplementedMembersAsync( + public static ImmutableArray FindOverriddenAndImplementedMembers( ISymbol symbol, Solution solution, CancellationToken cancellationToken) { - var results = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var results); // This is called for all: class, struct or interface member. results.AddRange(symbol.ExplicitOrImplicitInterfaceImplementations()); @@ -31,7 +31,7 @@ public static async ValueTask> FindOverriddenAndImplemen cancellationToken.ThrowIfCancellationRequested(); // Add to results overridden members only. Do not add hidden members. - if (await SymbolFinder.IsOverrideAsync(solution, symbol, member, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.IsOverride(solution, symbol, member)) { results.Add(member); @@ -64,7 +64,8 @@ public static async ValueTask> FindOverriddenAndImplemen } // Remove duplicates from interface implementations before adding their projects. - return results.ToImmutableAndFree().Distinct(); + results.RemoveDuplicates(); + return results.ToImmutableAndClear(); } private static ImmutableArray FindBaseTypes(INamedTypeSymbol type) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index aaad258c3e4e3..ba3cdc5889a6e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -32,17 +32,27 @@ public static async ValueTask GetCacheAsync(Document documen static async Task ComputeCacheAsync(Document document, CancellationToken cancellationToken) { + var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + // Find-Refs is not impacted by nullable types at all. So get a nullable-disabled semantic model to avoid // unnecessary costs while binding. var model = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - return new(document, model, root); + + // It's very costly to walk an entire tree. So if the tree is simple and doesn't contain + // any unicode escapes in it, then we do simple string matching to find the tokens. + var index = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); + + return new(document, text, model, root, index); } } public readonly Document Document; + public readonly SourceText Text; public readonly SemanticModel SemanticModel; public readonly SyntaxNode Root; + public readonly ISyntaxFactsService SyntaxFacts; + public readonly SyntaxTreeIndex SyntaxTreeIndex; private readonly ConcurrentDictionary _symbolInfoCache = []; private readonly ConcurrentDictionary> _identifierCache; @@ -50,11 +60,16 @@ static async Task ComputeCacheAsync(Document document, Cance private ImmutableHashSet? _aliasNameSet; private ImmutableArray _constructorInitializerCache; - private FindReferenceCache(Document document, SemanticModel semanticModel, SyntaxNode root) + private FindReferenceCache( + Document document, SourceText text, SemanticModel semanticModel, SyntaxNode root, SyntaxTreeIndex syntaxTreeIndex) { Document = document; + Text = text; SemanticModel = semanticModel; Root = root; + SyntaxTreeIndex = syntaxTreeIndex; + SyntaxFacts = document.GetRequiredLanguageService(); + _identifierCache = new(comparer: semanticModel.Language switch { LanguageNames.VisualBasic => StringComparer.OrdinalIgnoreCase, @@ -81,10 +96,8 @@ public SymbolInfo GetSymbolInfo(SyntaxNode node, CancellationToken cancellationT return null; } - public async ValueTask> FindMatchingIdentifierTokensAsync( - Document document, - string identifier, - CancellationToken cancellationToken) + public ImmutableArray FindMatchingIdentifierTokens( + string identifier, CancellationToken cancellationToken) { if (identifier == "") { @@ -99,103 +112,82 @@ public async ValueTask> FindMatchingIdentifierTokens if (_identifierCache.TryGetValue(identifier, out var result)) return result; - // It's very costly to walk an entire tree. So if the tree is simple and doesn't contain - // any unicode escapes in it, then we do simple string matching to find the tokens. - var info = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); - // If this document doesn't even contain this identifier (escaped or non-escaped) we don't have to search it at all. - if (!info.ProbablyContainsIdentifier(identifier)) + if (!this.SyntaxTreeIndex.ProbablyContainsIdentifier(identifier)) return []; - return await ComputeAndCacheTokensAsync(this, document, identifier, info, cancellationToken).ConfigureAwait(false); + // If the identifier was escaped in the file then we'll have to do a more involved search that actually + // walks the root and checks all identifier tokens. + // + // otherwise, we can use the text of the document to quickly find candidates and test those directly. + return this.SyntaxTreeIndex.ProbablyContainsEscapedIdentifier(identifier) + ? _identifierCache.GetOrAdd(identifier, identifier => FindMatchingIdentifierTokensFromTree(identifier, cancellationToken)) + : _identifierCache.GetOrAdd(identifier, _ => FindMatchingIdentifierTokensFromText(identifier, cancellationToken)); + } + + private bool IsMatch(string identifier, SyntaxToken token) + => !token.IsMissing && this.SyntaxFacts.IsIdentifier(token) && this.SyntaxFacts.TextMatch(token.ValueText, identifier); + + private ImmutableArray FindMatchingIdentifierTokensFromTree( + string identifier, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var result); + using var obj = SharedPools.Default>().GetPooledObject(); + + var stack = obj.Object; + stack.Push(this.Root); - static async ValueTask> ComputeAndCacheTokensAsync( - FindReferenceCache cache, Document document, string identifier, SyntaxTreeIndex info, CancellationToken cancellationToken) + while (stack.TryPop(out var current)) { - var syntaxFacts = document.GetRequiredLanguageService(); - var root = await cache.SemanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - - // If the identifier was escaped in the file then we'll have to do a more involved search that actually - // walks the root and checks all identifier tokens. - // - // otherwise, we can use the text of the document to quickly find candidates and test those directly. - if (info.ProbablyContainsEscapedIdentifier(identifier)) + cancellationToken.ThrowIfCancellationRequested(); + if (current.IsNode) { - return cache._identifierCache.GetOrAdd( - identifier, _ => FindMatchingIdentifierTokensFromTree(syntaxFacts, identifier, root)); + foreach (var child in current.AsNode()!.ChildNodesAndTokens().Reverse()) + stack.Push(child); } - else + else if (current.IsToken) { - var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - return cache._identifierCache.GetOrAdd( - identifier, _ => FindMatchingIdentifierTokensFromText(syntaxFacts, identifier, root, text, cancellationToken)); - } - } - - static bool IsMatch(ISyntaxFactsService syntaxFacts, string identifier, SyntaxToken token) - => !token.IsMissing && syntaxFacts.IsIdentifier(token) && syntaxFacts.TextMatch(token.ValueText, identifier); - - static ImmutableArray FindMatchingIdentifierTokensFromTree( - ISyntaxFactsService syntaxFacts, string identifier, SyntaxNode root) - { - using var _ = ArrayBuilder.GetInstance(out var result); - using var obj = SharedPools.Default>().GetPooledObject(); - - var stack = obj.Object; - stack.Push(root); + var token = current.AsToken(); + if (IsMatch(identifier, token)) + result.Add(token); - while (stack.TryPop(out var current)) - { - if (current.IsNode) - { - foreach (var child in current.AsNode()!.ChildNodesAndTokens().Reverse()) - stack.Push(child); - } - else if (current.IsToken) + if (token.HasStructuredTrivia) { - var token = current.AsToken(); - if (IsMatch(syntaxFacts, identifier, token)) - result.Add(token); - - if (token.HasStructuredTrivia) + // structured trivia can only be leading trivia + foreach (var trivia in token.LeadingTrivia) { - // structured trivia can only be leading trivia - foreach (var trivia in token.LeadingTrivia) - { - if (trivia.HasStructure) - stack.Push(trivia.GetStructure()!); - } + if (trivia.HasStructure) + stack.Push(trivia.GetStructure()!); } } } - - return result.ToImmutableAndClear(); } - static ImmutableArray FindMatchingIdentifierTokensFromText( - ISyntaxFactsService syntaxFacts, string identifier, SyntaxNode root, SourceText sourceText, CancellationToken cancellationToken) - { - using var _ = ArrayBuilder.GetInstance(out var result); + return result.ToImmutableAndClear(); + } - var index = 0; - while ((index = sourceText.IndexOf(identifier, index, syntaxFacts.IsCaseSensitive)) >= 0) - { - cancellationToken.ThrowIfCancellationRequested(); + private ImmutableArray FindMatchingIdentifierTokensFromText( + string identifier, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var result); - var token = root.FindToken(index, findInsideTrivia: true); - var span = token.Span; - if (span.Start == index && span.Length == identifier.Length && IsMatch(syntaxFacts, identifier, token)) - result.Add(token); + var index = 0; + while ((index = this.Text.IndexOf(identifier, index, this.SyntaxFacts.IsCaseSensitive)) >= 0) + { + cancellationToken.ThrowIfCancellationRequested(); - var nextIndex = index + identifier.Length; - nextIndex = Math.Max(nextIndex, token.SpanStart); - index = nextIndex; - } + var token = this.Root.FindToken(index, findInsideTrivia: true); + var span = token.Span; + if (span.Start == index && span.Length == identifier.Length && IsMatch(identifier, token)) + result.Add(token); - return result.ToImmutableAndClear(); + var nextIndex = index + identifier.Length; + nextIndex = Math.Max(nextIndex, token.SpanStart); + index = nextIndex; } - } + return result.ToImmutableAndClear(); + } public IEnumerable GetConstructorInitializerTokens( ISyntaxFactsService syntaxFacts, SyntaxNode root, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index eb3a7e932959e..a80669feb1155 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -18,10 +19,10 @@ namespace Microsoft.CodeAnalysis.FindSymbols; +using Reference = (SymbolGroup group, ISymbol symbol, ReferenceLocation location); + internal partial class FindReferencesSearchEngine { - private static readonly ObjectPool s_metadataUnifyingSymbolHashSetPool = new(() => []); - private readonly Solution _solution; private readonly IImmutableSet? _documents; private readonly ImmutableArray _finders; @@ -29,11 +30,6 @@ internal partial class FindReferencesSearchEngine private readonly IStreamingFindReferencesProgress _progress; private readonly FindReferencesSearchOptions _options; - /// - /// Scheduler to run our tasks on. If we're in mode, we'll - /// run all our tasks concurrently. Otherwise, we will run them serially using - /// - private readonly TaskScheduler _scheduler; private static readonly TaskScheduler s_exclusiveScheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler; /// @@ -57,83 +53,108 @@ public FindReferencesSearchEngine( _options = options; _progressTracker = progress.ProgressTracker; - - // If we're an explicit invocation, just defer to the threadpool to execute all our work in parallel to get - // things done as quickly as possible. If we're running implicitly, then use a - // ConcurrentExclusiveSchedulerPair's exclusive scheduler as that's the most built-in way in the TPL to get - // will run things serially. - _scheduler = _options.Explicit ? TaskScheduler.Default : s_exclusiveScheduler; } + /// + /// Options to control the parallelism of the search. If we're in mode, we'll run all our tasks concurrently. Otherwise, we will + /// run them serially using + /// + private ParallelOptions GetParallelOptions(CancellationToken cancellationToken) + => new() + { + CancellationToken = cancellationToken, + // If we're an explicit invocation, just defer to the threadpool to execute all our work in parallel to get + // things done as quickly as possible. If we're running implicitly, then use a exclusive scheduler as + // that's the most built-in way in the TPL to get will run things serially. + TaskScheduler = _options.Explicit ? TaskScheduler.Default : s_exclusiveScheduler, + }; + public Task FindReferencesAsync(ISymbol symbol, CancellationToken cancellationToken) => FindReferencesAsync([symbol], cancellationToken); public async Task FindReferencesAsync( ImmutableArray symbols, CancellationToken cancellationToken) { - var unifiedSymbols = new MetadataUnifyingSymbolHashSet(); - unifiedSymbols.AddRange(symbols); - await _progress.OnStartedAsync(cancellationToken).ConfigureAwait(false); try { - var disposable = await _progressTracker.AddSingleItemAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposable.ConfigureAwait(false); + await ProducerConsumer.RunAsync( + ProducerConsumerOptions.SingleReaderOptions, + produceItems: static (onItemFound, args, cancellationToken) => args.@this.PerformSearchAsync(args.symbols, onItemFound, cancellationToken), + consumeItems: static async (references, args, cancellationToken) => await args.@this._progress.OnReferencesFoundAsync(references, cancellationToken).ConfigureAwait(false), + (@this: this, symbols), + cancellationToken).ConfigureAwait(false); + } + finally + { + await _progress.OnCompletedAsync(cancellationToken).ConfigureAwait(false); + } + } - // Create the initial set of symbols to search for. As we walk the appropriate projects in the solution - // we'll expand this set as we discover new symbols to search for in each project. - var symbolSet = await SymbolSet.CreateAsync( - this, unifiedSymbols, includeImplementationsThroughDerivedTypes: true, cancellationToken).ConfigureAwait(false); + private async Task PerformSearchAsync( + ImmutableArray symbols, Action onReferenceFound, CancellationToken cancellationToken) + { + var unifiedSymbols = new MetadataUnifyingSymbolHashSet(); + unifiedSymbols.AddRange(symbols); - // Report the initial set of symbols to the caller. - var allSymbols = symbolSet.GetAllSymbols(); - await ReportGroupsAsync(allSymbols, cancellationToken).ConfigureAwait(false); + var disposable = await _progressTracker.AddSingleItemAsync(cancellationToken).ConfigureAwait(false); + await using var _ = disposable.ConfigureAwait(false); - // Determine the set of projects we actually have to walk to find results in. If the caller provided a - // set of documents to search, we only bother with those. - var projectsToSearch = await GetProjectsToSearchAsync(allSymbols, cancellationToken).ConfigureAwait(false); + // Create the initial set of symbols to search for. As we walk the appropriate projects in the solution + // we'll expand this set as we discover new symbols to search for in each project. + var symbolSet = await SymbolSet.CreateAsync( + this, unifiedSymbols, includeImplementationsThroughDerivedTypes: true, cancellationToken).ConfigureAwait(false); - // We need to process projects in order when updating our symbol set. Say we have three projects (A, B - // and C), we cannot necessarily find inherited symbols in C until we have searched B. Importantly, - // while we're processing each project linearly to update the symbol set we're searching for, we still - // then process the projects in parallel once we know the set of symbols we're searching for in that - // project. - var dependencyGraph = _solution.GetProjectDependencyGraph(); - await _progressTracker.AddItemsAsync(projectsToSearch.Length, cancellationToken).ConfigureAwait(false); + // Report the initial set of symbols to the caller. + var allSymbols = symbolSet.GetAllSymbols(); + await ReportGroupsAsync(allSymbols, cancellationToken).ConfigureAwait(false); - using var _1 = ArrayBuilder.GetInstance(out var tasks); + // Determine the set of projects we actually have to walk to find results in. If the caller provided a + // set of documents to search, we only bother with those. + var projectsToSearch = await GetProjectsToSearchAsync(allSymbols, cancellationToken).ConfigureAwait(false); - foreach (var projectId in dependencyGraph.GetTopologicallySortedProjects(cancellationToken)) - { - var currentProject = _solution.GetRequiredProject(projectId); - if (!projectsToSearch.Contains(currentProject)) - continue; + await _progressTracker.AddItemsAsync(projectsToSearch.Length, cancellationToken).ConfigureAwait(false); - // As we walk each project, attempt to grow the search set appropriately up and down the inheritance - // hierarchy and grab a copy of the symbols to be processed. Note: this has to happen serially - // which is why we do it in this loop and not inside the concurrent project processing that happens - // below. - await symbolSet.InheritanceCascadeAsync(currentProject, cancellationToken).ConfigureAwait(false); - allSymbols = symbolSet.GetAllSymbols(); + // Pull off and start searching each project as soon as we can once we've done the inheritance cascade into it. + await RoslynParallel.ForEachAsync( + GetProjectsAndSymbolsToSearchAsync(symbolSet, projectsToSearch, cancellationToken), + GetParallelOptions(cancellationToken), + async (tuple, cancellationToken) => await ProcessProjectAsync( + tuple.project, tuple.allSymbols, onReferenceFound, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); + } - // Report any new symbols we've cascaded to to our caller. - await ReportGroupsAsync(allSymbols, cancellationToken).ConfigureAwait(false); + private async IAsyncEnumerable<(Project project, ImmutableArray allSymbols)> GetProjectsAndSymbolsToSearchAsync( + SymbolSet symbolSet, + ImmutableArray projectsToSearch, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + // We need to process projects in order when updating our symbol set. Say we have three projects (A, B + // and C), we cannot necessarily find inherited symbols in C until we have searched B. Importantly, + // while we're processing each project linearly to update the symbol set we're searching for, we still + // then process the projects in parallel once we know the set of symbols we're searching for in that + // project. + var dependencyGraph = _solution.GetProjectDependencyGraph(); + foreach (var projectId in dependencyGraph.GetTopologicallySortedProjects(cancellationToken)) + { + var currentProject = _solution.GetRequiredProject(projectId); + if (!projectsToSearch.Contains(currentProject)) + continue; + + // As we walk each project, attempt to grow the search set appropriately up and down the inheritance + // hierarchy and grab a copy of the symbols to be processed. Note: this has to happen serially + // which is why we do it in this loop and not inside the concurrent project processing that happens + // below. + await symbolSet.InheritanceCascadeAsync(currentProject, cancellationToken).ConfigureAwait(false); + var allSymbols = symbolSet.GetAllSymbols(); - tasks.Add(CreateWorkAsync(() => ProcessProjectAsync(currentProject, allSymbols, cancellationToken), cancellationToken)); - } + // Report any new symbols we've cascaded to to our caller. + await ReportGroupsAsync(allSymbols, cancellationToken).ConfigureAwait(false); - // Now, wait for all projects to complete. - await Task.WhenAll(tasks).ConfigureAwait(false); - } - finally - { - await _progress.OnCompletedAsync(cancellationToken).ConfigureAwait(false); + yield return (currentProject, allSymbols); } } - public Task CreateWorkAsync(Func createWorkAsync, CancellationToken cancellationToken) - => Task.Factory.StartNew(createWorkAsync, cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap(); - /// /// Notify the caller of the engine about the definitions we've found that we're looking for. We'll only notify /// them once per symbol group, but we may have to notify about new symbols each time we expand our symbol set @@ -184,7 +205,8 @@ private Task> GetProjectsToSearchAsync( return DependentProjectsFinder.GetDependentProjectsAsync(_solution, symbols, projects, cancellationToken); } - private async Task ProcessProjectAsync(Project project, ImmutableArray allSymbols, CancellationToken cancellationToken) + private async ValueTask ProcessProjectAsync( + Project project, ImmutableArray allSymbols, Action onReferenceFound, CancellationToken cancellationToken) { using var _1 = PooledDictionary>.GetInstance(out var symbolToGlobalAliases); using var _2 = PooledDictionary.GetInstance(out var documentToSymbols); @@ -208,31 +230,22 @@ await finder.DetermineDocumentsToSearchAsync( _options, cancellationToken).ConfigureAwait(false); foreach (var document in foundDocuments) - { - var docSymbols = GetSymbolSet(documentToSymbols, document); - docSymbols.Add(symbol); - } + GetSymbolSet(documentToSymbols, document).Add(symbol); foundDocuments.Clear(); } } - using var _4 = ArrayBuilder.GetInstance(out var tasks); - foreach (var (document, docSymbols) in documentToSymbols) - { - tasks.Add(CreateWorkAsync(() => ProcessDocumentAsync( - document, docSymbols, symbolToGlobalAliases, cancellationToken), cancellationToken)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); + await RoslynParallel.ForEachAsync( + documentToSymbols, + GetParallelOptions(cancellationToken), + (kvp, cancellationToken) => + ProcessDocumentAsync(kvp.Key, kvp.Value, symbolToGlobalAliases, onReferenceFound, cancellationToken)).ConfigureAwait(false); } finally { foreach (var (_, symbols) in documentToSymbols) - { - symbols.Clear(); - s_metadataUnifyingSymbolHashSetPool.Free(symbols); - } + MetadataUnifyingSymbolHashSet.ClearAndFree(symbols); FreeGlobalAliases(symbolToGlobalAliases); @@ -240,56 +253,57 @@ await finder.DetermineDocumentsToSearchAsync( } static MetadataUnifyingSymbolHashSet GetSymbolSet(PooledDictionary dictionary, T key) where T : notnull - { - if (!dictionary.TryGetValue(key, out var set)) - { - set = s_metadataUnifyingSymbolHashSetPool.Allocate(); - dictionary.Add(key, set); - } - - return set; - } + => dictionary.GetOrAdd(key, static _ => MetadataUnifyingSymbolHashSet.AllocateFromPool()); } private static PooledHashSet? TryGet(Dictionary> dictionary, T key) where T : notnull => dictionary.TryGetValue(key, out var set) ? set : null; - private async Task ProcessDocumentAsync( + private async ValueTask ProcessDocumentAsync( Document document, MetadataUnifyingSymbolHashSet symbols, Dictionary> symbolToGlobalAliases, + Action onReferenceFound, CancellationToken cancellationToken) { - await _progress.OnFindInDocumentStartedAsync(document, cancellationToken).ConfigureAwait(false); - - try + // We're doing to do all of our processing of this document at once. This will necessitate all the + // appropriate finders checking this document for hits. We know that in the initial pass to determine + // documents, this document was already considered a strong match (e.g. we know it contains the name of + // the symbol being searched for). As such, we're almost certainly going to have to do semantic checks + // to now see if the candidate actually matches the symbol. This will require syntax and semantics. So + // just grab those once here and hold onto them for the lifetime of this call. + var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); + + // This search almost always involves trying to find the tokens matching the name of the symbol we're looking + // for. Get the cache ready with those tokens so that kicking of N searches to search for each symbol in + // parallel doesn't cause us to compute and cache the same thing concurrently. + + // Note: cascaded symbols will normally have the same name. That's ok. The second call to + // FindMatchingIdentifierTokens with the same name will short circuit since it will already see the result of + // the prior call. + foreach (var symbol in symbols) { - // We're doing to do all of our processing of this document at once. This will necessitate all the - // appropriate finders checking this document for hits. We know that in the initial pass to determine - // documents, this document was already considered a strong match (e.g. we know it contains the name of - // the symbol being searched for). As such, we're almost certainly going to have to do semantic checks - // to now see if the candidate actually matches the symbol. This will require syntax and semantics. So - // just grab those once here and hold onto them for the lifetime of this call. - var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); - - // scratch array to place results in. Populated/inspected/cleared in inner loop. - using var _ = ArrayBuilder.GetInstance(out var foundReferenceLocations); - - foreach (var symbol in symbols) + if (symbol.CanBeReferencedByName) + cache.FindMatchingIdentifierTokens(symbol.Name, cancellationToken); + } + + await RoslynParallel.ForEachAsync( + symbols, + GetParallelOptions(cancellationToken), + async (symbol, cancellationToken) => { - var globalAliases = TryGet(symbolToGlobalAliases, symbol); - var state = new FindReferencesDocumentState(cache, globalAliases); + // symbolToGlobalAliases is safe to read in parallel. It is created fully before this point and is no + // longer mutated. + var state = new FindReferencesDocumentState( + cache, TryGet(symbolToGlobalAliases, symbol)); - await ProcessDocumentAsync(symbol, state, foundReferenceLocations).ConfigureAwait(false); - } - } - finally - { - await _progress.OnFindInDocumentCompletedAsync(document, cancellationToken).ConfigureAwait(false); - } + await ProcessDocumentAsync(symbol, state, onReferenceFound).ConfigureAwait(false); + }).ConfigureAwait(false); + + return; async Task ProcessDocumentAsync( - ISymbol symbol, FindReferencesDocumentState state, ArrayBuilder foundReferenceLocations) + ISymbol symbol, FindReferencesDocumentState state, Action onReferenceFound) { cancellationToken.ThrowIfCancellationRequested(); @@ -298,14 +312,18 @@ async Task ProcessDocumentAsync( // This is safe to just blindly read. We can only ever get here after the call to ReportGroupsAsync // happened. So there must be a group for this symbol in our map. var group = _symbolToGroup[symbol]; + + // Note: nearly every finder will no-op when passed a in a symbol it's not applicable to. So it's + // simple to just iterate over all of them, knowing that will quickly skip all the irrelevant ones, + // and only do interesting work on the single relevant one. foreach (var finder in _finders) { await finder.FindReferencesInDocumentAsync( - symbol, state, StandardCallbacks.AddToArrayBuilder, foundReferenceLocations, _options, cancellationToken).ConfigureAwait(false); - foreach (var (_, location) in foundReferenceLocations) - await _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); - - foundReferenceLocations.Clear(); + symbol, state, + static (loc, tuple) => tuple.onReferenceFound((tuple.group, tuple.symbol, loc.Location)), + (group, symbol, onReferenceFound), + _options, + cancellationToken).ConfigureAwait(false); } } } @@ -324,24 +342,13 @@ private async Task AddGlobalAliasesAsync( var aliases = await finder.DetermineGlobalAliasesAsync( symbol, project, cancellationToken).ConfigureAwait(false); if (aliases.Length > 0) - { - var globalAliases = GetGlobalAliasesSet(symbolToGlobalAliases, symbol); - globalAliases.AddRange(aliases); - } + GetGlobalAliasesSet(symbolToGlobalAliases, symbol).AddRange(aliases); } } } private static PooledHashSet GetGlobalAliasesSet(PooledDictionary> dictionary, T key) where T : notnull - { - if (!dictionary.TryGetValue(key, out var set)) - { - set = PooledHashSet.GetInstance(); - dictionary.Add(key, set); - } - - return set; - } + => dictionary.GetOrAdd(key, static _ => PooledHashSet.GetInstance()); private static void FreeGlobalAliases(PooledDictionary> symbolToGlobalAliases) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index cb31339e8022c..0666734493488 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -91,40 +91,46 @@ async ValueTask PerformSearchInDocumentAsync( foreach (var symbol in symbols) { - var globalAliases = TryGet(symbolToGlobalAliases, symbol); - var state = new FindReferencesDocumentState(cache, globalAliases); + var state = new FindReferencesDocumentState( + cache, TryGet(symbolToGlobalAliases, symbol)); await PerformSearchInDocumentWorkerAsync(symbol, state).ConfigureAwait(false); } } - async ValueTask PerformSearchInDocumentWorkerAsync( - ISymbol symbol, FindReferencesDocumentState state) + async ValueTask PerformSearchInDocumentWorkerAsync(ISymbol symbol, FindReferencesDocumentState state) { - // Scratch buffer to place references for each finder. Cleared at the end of every loop iteration. - using var _ = ArrayBuilder.GetInstance(out var referencesForFinder); - // Always perform a normal search, looking for direct references to exactly that symbol. + await DirectSymbolSearchAsync(symbol, state).ConfigureAwait(false); + + // Now, for symbols that could involve inheritance, look for references to the same named entity, and + // see if it's a reference to a symbol that shares an inheritance relationship with that symbol. + await InheritanceSymbolSearchAsync(symbol, state).ConfigureAwait(false); + } + + async ValueTask DirectSymbolSearchAsync(ISymbol symbol, FindReferencesDocumentState state) + { + using var _ = ArrayBuilder.GetInstance(out var referencesForFinder); foreach (var finder in _finders) { await finder.FindReferencesInDocumentAsync( symbol, state, StandardCallbacks.AddToArrayBuilder, referencesForFinder, _options, cancellationToken).ConfigureAwait(false); - foreach (var (_, location) in referencesForFinder) - { - var group = await ReportGroupAsync(symbol, cancellationToken).ConfigureAwait(false); - await _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); - } - - referencesForFinder.Clear(); } - // Now, for symbols that could involve inheritance, look for references to the same named entity, and - // see if it's a reference to a symbol that shares an inheritance relationship with that symbol. + if (referencesForFinder.Count > 0) + { + var group = await ReportGroupAsync(symbol, cancellationToken).ConfigureAwait(false); + var references = referencesForFinder.SelectAsArray(r => (group, symbol, r.Location)); + await _progress.OnReferencesFoundAsync(references, cancellationToken).ConfigureAwait(false); + } + } + + async ValueTask InheritanceSymbolSearchAsync(ISymbol symbol, FindReferencesDocumentState state) + { if (InvolvesInheritance(symbol)) { - var tokens = await AbstractReferenceFinder.FindMatchingIdentifierTokensAsync( - state, symbol.Name, cancellationToken).ConfigureAwait(false); + var tokens = AbstractReferenceFinder.FindMatchingIdentifierTokens(state, symbol.Name, cancellationToken); foreach (var token in tokens) { @@ -138,7 +144,7 @@ await finder.FindReferencesInDocumentAsync( var candidateGroup = await ReportGroupAsync(candidate, cancellationToken).ConfigureAwait(false); var location = AbstractReferenceFinder.CreateReferenceLocation(state, token, candidateReason, cancellationToken); - await _progress.OnReferenceFoundAsync(candidateGroup, candidate, location, cancellationToken).ConfigureAwait(false); + await _progress.OnReferencesFoundAsync([(candidateGroup, candidate, location)], cancellationToken).ConfigureAwait(false); } } } @@ -180,7 +186,7 @@ async Task ComputeInheritanceRelationshipAsync( // Counter-intuitive, but if these are matching symbols, they do *not* have an inheritance relationship. // We do *not* want to report these as they would have been found in the original call to the finders in // PerformSearchInTextSpanAsync. - if (await SymbolFinder.OriginalSymbolsMatchAsync(_solution, searchSymbol, candidate, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.OriginalSymbolsMatch(_solution, searchSymbol, candidate)) return false; // walk up the original symbol's inheritance hierarchy to see if we hit the candidate. Don't walk down @@ -189,7 +195,7 @@ async Task ComputeInheritanceRelationshipAsync( this, [searchSymbol], includeImplementationsThroughDerivedTypes: false, cancellationToken).ConfigureAwait(false); foreach (var symbolUp in searchSymbolUpSet.GetAllSymbols()) { - if (await SymbolFinder.OriginalSymbolsMatchAsync(_solution, symbolUp, candidate, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.OriginalSymbolsMatch(_solution, symbolUp, candidate)) return true; } @@ -199,7 +205,7 @@ async Task ComputeInheritanceRelationshipAsync( this, [candidate], includeImplementationsThroughDerivedTypes: false, cancellationToken).ConfigureAwait(false); foreach (var candidateUp in candidateSymbolUpSet.GetAllSymbols()) { - if (await SymbolFinder.OriginalSymbolsMatchAsync(_solution, searchSymbol, candidateUp, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.OriginalSymbolsMatch(_solution, searchSymbol, candidateUp)) return true; } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs index 574d7b6652cd9..bc902b6dde014 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs @@ -49,7 +49,7 @@ protected sealed override Task DetermineDocumentsToSearchAsync( return Task.CompletedTask; } - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( TSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -60,13 +60,15 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( var container = GetContainer(symbol); if (container != null) { - await FindReferencesInContainerAsync(symbol, container, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInContainer(symbol, container, state, processResult, processResultData, cancellationToken); } else if (symbol.ContainingType != null && symbol.ContainingType.IsScriptClass) { - var tokens = await FindMatchingIdentifierTokensAsync(state, symbol.Name, cancellationToken).ConfigureAwait(false); - await FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); + var tokens = FindMatchingIdentifierTokens(state, symbol.Name, cancellationToken); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); } + + return ValueTaskFactory.CompletedTask; } private static ISymbol? GetContainer(ISymbol symbol) @@ -97,7 +99,7 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( return null; } - private ValueTask FindReferencesInContainerAsync( + private void FindReferencesInContainer( TSymbol symbol, ISymbol container, FindReferencesDocumentState state, @@ -121,6 +123,7 @@ private ValueTask FindReferencesInContainerAsync( } } - return FindReferencesInTokensAsync(symbol, state, tokens.ToImmutable(), processResult, processResultData, cancellationToken); + FindReferencesInTokens( + symbol, state, tokens.ToImmutable(), processResult, processResultData, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index 39b19e605551a..99787ba6b637f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -36,7 +36,7 @@ public abstract Task DetermineDocumentsToSearchAsync( public abstract ValueTask FindReferencesInDocumentAsync( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); - private static ValueTask<(bool matched, CandidateReason reason)> SymbolsMatchAsync( + private static (bool matched, CandidateReason reason) SymbolsMatch( ISymbol symbol, FindReferencesDocumentState state, SyntaxToken token, CancellationToken cancellationToken) { // delegates don't have exposed symbols for their constructors. so when you do `new MyDel()`, that's only a @@ -47,26 +47,25 @@ public abstract ValueTask FindReferencesInDocumentAsync( : state.SyntaxFacts.TryGetBindableParent(token); parent ??= token.Parent!; - return SymbolsMatchAsync(symbol, state, parent, cancellationToken); + return SymbolsMatch(symbol, state, parent, cancellationToken); } - protected static ValueTask<(bool matched, CandidateReason reason)> SymbolsMatchAsync( + protected static (bool matched, CandidateReason reason) SymbolsMatch( ISymbol searchSymbol, FindReferencesDocumentState state, SyntaxNode node, CancellationToken cancellationToken) { var symbolInfo = state.Cache.GetSymbolInfo(node, cancellationToken); - - return MatchesAsync(searchSymbol, state, symbolInfo, cancellationToken); + return Matches(searchSymbol, state, symbolInfo); } - protected static async ValueTask<(bool matched, CandidateReason reason)> MatchesAsync( - ISymbol searchSymbol, FindReferencesDocumentState state, SymbolInfo symbolInfo, CancellationToken cancellationToken) + protected static (bool matched, CandidateReason reason) Matches( + ISymbol searchSymbol, FindReferencesDocumentState state, SymbolInfo symbolInfo) { - if (await SymbolFinder.OriginalSymbolsMatchAsync(state.Solution, searchSymbol, symbolInfo.Symbol, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.OriginalSymbolsMatch(state.Solution, searchSymbol, symbolInfo.Symbol)) return (matched: true, CandidateReason.None); foreach (var candidate in symbolInfo.CandidateSymbols) { - if (await SymbolFinder.OriginalSymbolsMatchAsync(state.Solution, searchSymbol, candidate, cancellationToken).ConfigureAwait(false)) + if (SymbolFinder.OriginalSymbolsMatch(state.Solution, searchSymbol, candidate)) return (matched: true, symbolInfo.CandidateReason); } @@ -100,7 +99,7 @@ protected static async Task FindDocumentsAsync( return; } - foreach (var document in await project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false)) + await foreach (var document in project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken)) { if (scope != null && !scope.Contains(document)) continue; @@ -162,8 +161,7 @@ protected static Task FindDocumentsAsync( protected static bool IdentifiersMatch(ISyntaxFactsService syntaxFacts, string name, SyntaxToken token) => syntaxFacts.IsIdentifier(token) && syntaxFacts.TextMatch(token.ValueText, name); - [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", OftenCompletesSynchronously = true)] - protected static async ValueTask FindReferencesInDocumentUsingIdentifierAsync( + protected static void FindReferencesInDocumentUsingIdentifier( ISymbol symbol, string identifier, FindReferencesDocumentState state, @@ -171,14 +169,14 @@ protected static async ValueTask FindReferencesInDocumentUsingIdentifierAsync> FindMatchingIdentifierTokensAsync(FindReferencesDocumentState state, string identifier, CancellationToken cancellationToken) - => state.Cache.FindMatchingIdentifierTokensAsync(state.Document, identifier, cancellationToken); + public static ImmutableArray FindMatchingIdentifierTokens(FindReferencesDocumentState state, string identifier, CancellationToken cancellationToken) + => state.Cache.FindMatchingIdentifierTokens(identifier, cancellationToken); - protected static async ValueTask FindReferencesInTokensAsync( + protected static void FindReferencesInTokens( ISymbol symbol, FindReferencesDocumentState state, ImmutableArray tokens, @@ -193,12 +191,10 @@ protected static async ValueTask FindReferencesInTokensAsync( { cancellationToken.ThrowIfCancellationRequested(); - var (matched, reason) = await SymbolsMatchAsync( - symbol, state, token, cancellationToken).ConfigureAwait(false); + var (matched, reason) = SymbolsMatch(symbol, state, token, cancellationToken); if (matched) { var finderLocation = CreateFinderLocation(state, token, reason, cancellationToken); - processResult(finderLocation, processResultData); } } @@ -243,7 +239,7 @@ public static ReferenceLocation CreateReferenceLocation(FindReferencesDocumentSt return null; } - protected static async Task FindLocalAliasReferencesAsync( + protected static void FindLocalAliasReferences( ArrayBuilder initialReferences, ISymbol symbol, FindReferencesDocumentState state, @@ -253,10 +249,10 @@ protected static async Task FindLocalAliasReferencesAsync( { var aliasSymbols = GetLocalAliasSymbols(state, initialReferences, cancellationToken); if (!aliasSymbols.IsDefaultOrEmpty) - await FindReferencesThroughLocalAliasSymbolsAsync(symbol, state, aliasSymbols, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesThroughLocalAliasSymbols(symbol, state, aliasSymbols, processResult, processResultData, cancellationToken); } - protected static async Task FindLocalAliasReferencesAsync( + protected static void FindLocalAliasReferences( ArrayBuilder initialReferences, FindReferencesDocumentState state, Action processResult, @@ -265,7 +261,7 @@ protected static async Task FindLocalAliasReferencesAsync( { var aliasSymbols = GetLocalAliasSymbols(state, initialReferences, cancellationToken); if (!aliasSymbols.IsDefaultOrEmpty) - await FindReferencesThroughLocalAliasSymbolsAsync(state, aliasSymbols, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesThroughLocalAliasSymbols(state, aliasSymbols, processResult, processResultData, cancellationToken); } private static ImmutableArray GetLocalAliasSymbols( @@ -284,7 +280,7 @@ private static ImmutableArray GetLocalAliasSymbols( return aliasSymbols.ToImmutableAndClear(); } - private static async Task FindReferencesThroughLocalAliasSymbolsAsync( + private static void FindReferencesThroughLocalAliasSymbols( ISymbol symbol, FindReferencesDocumentState state, ImmutableArray localAliasSymbols, @@ -294,20 +290,20 @@ private static async Task FindReferencesThroughLocalAliasSymbolsAsync( { foreach (var localAliasSymbol in localAliasSymbols) { - await FindReferencesInDocumentUsingIdentifierAsync( - symbol, localAliasSymbol.Name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingIdentifier( + symbol, localAliasSymbol.Name, state, processResult, processResultData, cancellationToken); // the alias may reference an attribute and the alias name may end with an "Attribute" suffix. In this case search for the // shortened name as well (e.g. using GooAttribute = MyNamespace.GooAttribute; [Goo] class C1 {}) if (TryGetNameWithoutAttributeSuffix(localAliasSymbol.Name, state.SyntaxFacts, out var simpleName)) { - await FindReferencesInDocumentUsingIdentifierAsync( - symbol, simpleName, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingIdentifier( + symbol, simpleName, state, processResult, processResultData, cancellationToken); } } } - private static async Task FindReferencesThroughLocalAliasSymbolsAsync( + private static void FindReferencesThroughLocalAliasSymbols( FindReferencesDocumentState state, ImmutableArray localAliasSymbols, Action processResult, @@ -316,15 +312,15 @@ private static async Task FindReferencesThroughLocalAliasSymbolsAsync( { foreach (var aliasSymbol in localAliasSymbols) { - await FindReferencesInDocumentUsingIdentifierAsync( - aliasSymbol, aliasSymbol.Name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingIdentifier( + aliasSymbol, aliasSymbol.Name, state, processResult, processResultData, cancellationToken); // the alias may reference an attribute and the alias name may end with an "Attribute" suffix. In this case search for the // shortened name as well (e.g. using GooAttribute = MyNamespace.GooAttribute; [Goo] class C1 {}) if (TryGetNameWithoutAttributeSuffix(aliasSymbol.Name, state.SyntaxFacts, out var simpleName)) { - await FindReferencesInDocumentUsingIdentifierAsync( - aliasSymbol, simpleName, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingIdentifier( + aliasSymbol, simpleName, state, processResult, processResultData, cancellationToken); } } } @@ -371,7 +367,7 @@ protected static Task FindDocumentsWithForEachStatementsAsync(Project pro protected delegate void CollectMatchingReferences( SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData); - protected static async Task FindReferencesInDocumentAsync( + protected static void FindReferencesInDocument( FindReferencesDocumentState state, Func isRelevantDocument, CollectMatchingReferences collectMatchingReferences, @@ -379,8 +375,7 @@ protected static async Task FindReferencesInDocumentAsync( TData processResultData, CancellationToken cancellationToken) { - var document = state.Document; - var syntaxTreeInfo = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); + var syntaxTreeInfo = state.Cache.SyntaxTreeIndex; if (isRelevantDocument(syntaxTreeInfo)) { foreach (var node in state.Root.DescendantNodesAndSelf()) @@ -391,14 +386,15 @@ protected static async Task FindReferencesInDocumentAsync( } } - protected Task FindReferencesInForEachStatementsAsync( + protected void FindReferencesInForEachStatements( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsForEachStatement; @@ -429,14 +425,15 @@ void CollectMatchingReferences( } } - protected Task FindReferencesInCollectionInitializerAsync( + protected void FindReferencesInCollectionInitializer( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsCollectionInitializer; @@ -471,14 +468,15 @@ void CollectMatchingReferences( } } - protected Task FindReferencesInDeconstructionAsync( + protected void FindReferencesInDeconstruction( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsDeconstruction; @@ -508,14 +506,15 @@ void CollectMatchingReferences( } } - protected Task FindReferencesInAwaitExpressionAsync( + protected void FindReferencesInAwaitExpression( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsAwait; @@ -538,14 +537,15 @@ void CollectMatchingReferences( } } - protected Task FindReferencesInImplicitObjectCreationExpressionAsync( + protected void FindReferencesInImplicitObjectCreationExpression( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsImplicitObjectCreation; @@ -905,10 +905,10 @@ protected virtual ValueTask> DetermineCascadedSymbolsAsy return new([]); } - protected static ValueTask FindReferencesInDocumentUsingSymbolNameAsync( + protected static void FindReferencesInDocumentUsingSymbolName( TSymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingIdentifierAsync( + FindReferencesInDocumentUsingIdentifier( symbol, symbol.Name, state, processResult, processResultData, cancellationToken); } @@ -917,7 +917,7 @@ protected static async Task> GetAllMatchingGlobalAliasNam { using var result = TemporaryArray.Empty; - foreach (var document in await project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false)) + await foreach (var document in project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken)) { var index = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); foreach (var alias in index.GetGlobalAliases(name, arity)) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs index 13e1de1396ca0..e03b3805db4a8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs @@ -50,7 +50,7 @@ static bool SupportsGlobalSuppression(ISymbol symbol) /// [assembly: SuppressMessage("RuleCategory", "RuleId', Scope = "member", Target = "~F:C.Field")] /// [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", OftenCompletesSynchronously = true)] - protected static async ValueTask FindReferencesInDocumentInsideGlobalSuppressionsAsync( + protected static void FindReferencesInDocumentInsideGlobalSuppressions( ISymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -61,7 +61,7 @@ protected static async ValueTask FindReferencesInDocumentInsideGlobalSuppression return; // Check if we have any relevant global attributes in this document. - var info = await SyntaxTreeIndex.GetRequiredIndexAsync(state.Document, cancellationToken).ConfigureAwait(false); + var info = state.Cache.SyntaxTreeIndex; if (!info.ContainsGlobalSuppressMessageAttribute) return; @@ -80,7 +80,7 @@ protected static async ValueTask FindReferencesInDocumentInsideGlobalSuppression // We map the positions of documentation ID literals in tree to string literal tokens, // perform semantic checks to ensure these are valid references to the symbol // and if so, add these locations to the computed references. - var root = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); + var root = state.Root; foreach (var token in root.DescendantTokens()) { if (IsCandidate(state, token, expectedDocCommentId.Span, suppressMessageAttribute, cancellationToken, out var offsetOfReferenceInToken)) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs index 38cc211cfb9ad..b84e1bd7abaf1 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols.Finders; internal abstract class AbstractTypeParameterSymbolReferenceFinder : AbstractReferenceFinder { - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( ITypeParameterSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -29,21 +29,21 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( // T()`). In the former case GetSymbolInfo can be used to bind the symbol and check if it matches this symbol. // in the latter though GetSymbolInfo will fail and we have to directly check if we have the right type info. - var tokens = await FindMatchingIdentifierTokensAsync(state, symbol.Name, cancellationToken).ConfigureAwait(false); + var tokens = FindMatchingIdentifierTokens(state, symbol.Name, cancellationToken); - await FindReferencesInTokensAsync( + FindReferencesInTokens( symbol, state, tokens.WhereAsArray(static (token, state) => !IsObjectCreationToken(token, state), state), processResult, processResultData, - cancellationToken).ConfigureAwait(false); + cancellationToken); GetObjectCreationReferences( tokens.WhereAsArray(static (token, state) => IsObjectCreationToken(token, state), state), processResult, processResultData); - return; + return ValueTaskFactory.CompletedTask; static bool IsObjectCreationToken(SyntaxToken token, FindReferencesDocumentState state) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs index 4258a0eca666b..3ee5b2f3a72e4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs @@ -51,7 +51,7 @@ protected override Task DetermineDocumentsToSearchAsync( }, symbol.ContainingType.Name, processResult, processResultData, cancellationToken); } - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, Action processResult, @@ -61,18 +61,14 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( { var tokens = state.Cache.GetConstructorInitializerTokens(state.SyntaxFacts, state.Root, cancellationToken); if (state.SemanticModel.Language == LanguageNames.VisualBasic) - { - tokens = tokens.Concat(await FindMatchingIdentifierTokensAsync( - state, "New", cancellationToken).ConfigureAwait(false)).Distinct(); - } + tokens = tokens.Concat(FindMatchingIdentifierTokens(state, "New", cancellationToken)).Distinct(); var totalTokens = tokens.WhereAsArray( static (token, tuple) => TokensMatch(tuple.state, token, tuple.methodSymbol.ContainingType.Name, tuple.cancellationToken), (state, methodSymbol, cancellationToken)); - await FindReferencesInTokensAsync(methodSymbol, state, totalTokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); - - return; + FindReferencesInTokens(methodSymbol, state, totalTokens, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; // local functions static bool TokensMatch( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs index 5037d55e9d1ad..9e0d20f947dfd 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -90,7 +90,7 @@ private static bool IsPotentialReference(PredefinedType predefinedType, ISyntaxF => syntaxFacts.TryGetPredefinedType(token, out var actualType) && predefinedType == actualType; - protected override async ValueTask FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, Action processResult, @@ -100,8 +100,8 @@ protected override async ValueTask FindReferencesInDocumentAsync( { // First just look for this normal constructor references using the name of it's containing type. var name = methodSymbol.ContainingType.Name; - await AddReferencesInDocumentWorkerAsync( - methodSymbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + AddReferencesInDocumentWorker( + methodSymbol, name, state, processResult, processResultData, cancellationToken); // Next, look for constructor references through a global alias to our containing type. foreach (var globalAlias in state.GlobalAliases) @@ -112,27 +112,29 @@ await AddReferencesInDocumentWorkerAsync( if (state.SyntaxFacts.StringComparer.Equals(name, globalAlias)) continue; - await AddReferencesInDocumentWorkerAsync( - methodSymbol, globalAlias, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + AddReferencesInDocumentWorker( + methodSymbol, globalAlias, state, processResult, processResultData, cancellationToken); } // Finally, look for constructor references to predefined types (like `new int()`), // implicit object references, and inside global suppression attributes. - await FindPredefinedTypeReferencesAsync( - methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindPredefinedTypeReferences( + methodSymbol, state, processResult, processResultData, cancellationToken); - await FindReferencesInImplicitObjectCreationExpressionAsync( - methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInImplicitObjectCreationExpression( + methodSymbol, state, processResult, processResultData, cancellationToken); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + methodSymbol, state, processResult, processResultData, cancellationToken); + + return ValueTaskFactory.CompletedTask; } /// /// Finds references to in this , but only if it referenced /// though (which might be the actual name of the type, or a global alias to it). /// - private static async Task AddReferencesInDocumentWorkerAsync( + private static void AddReferencesInDocumentWorker( IMethodSymbol symbol, string name, FindReferencesDocumentState state, @@ -140,13 +142,13 @@ private static async Task AddReferencesInDocumentWorkerAsync( TData processResultData, CancellationToken cancellationToken) { - await FindOrdinaryReferencesAsync( - symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - await FindAttributeReferencesAsync( - symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindOrdinaryReferences( + symbol, name, state, processResult, processResultData, cancellationToken); + FindAttributeReferences( + symbol, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask FindOrdinaryReferencesAsync( + private static void FindOrdinaryReferences( IMethodSymbol symbol, string name, FindReferencesDocumentState state, @@ -154,11 +156,11 @@ private static ValueTask FindOrdinaryReferencesAsync( TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingIdentifierAsync( + FindReferencesInDocumentUsingIdentifier( symbol, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask FindPredefinedTypeReferencesAsync( + private static void FindPredefinedTypeReferences( IMethodSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -167,7 +169,7 @@ private static ValueTask FindPredefinedTypeReferencesAsync( { var predefinedType = symbol.ContainingType.SpecialType.ToPredefinedType(); if (predefinedType == PredefinedType.None) - return ValueTaskFactory.CompletedTask; + return; var tokens = state.Root .DescendantTokens(descendIntoTrivia: true) @@ -175,10 +177,10 @@ private static ValueTask FindPredefinedTypeReferencesAsync( static (token, tuple) => IsPotentialReference(tuple.predefinedType, tuple.state.SyntaxFacts, token), (state, predefinedType)); - return FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static ValueTask FindAttributeReferencesAsync( + private static void FindAttributeReferences( IMethodSymbol symbol, string name, FindReferencesDocumentState state, @@ -186,12 +188,11 @@ private static ValueTask FindAttributeReferencesAsync( TData processResultData, CancellationToken cancellationToken) { - return TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var simpleName) - ? FindReferencesInDocumentUsingIdentifierAsync(symbol, simpleName, state, processResult, processResultData, cancellationToken) - : ValueTaskFactory.CompletedTask; + if (TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var simpleName)) + FindReferencesInDocumentUsingIdentifier(symbol, simpleName, state, processResult, processResultData, cancellationToken); } - private Task FindReferencesInImplicitObjectCreationExpressionAsync( + private void FindReferencesInImplicitObjectCreationExpression( IMethodSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -208,7 +209,8 @@ private Task FindReferencesInImplicitObjectCreationExpressionAsync( ? -1 : symbol.Parameters.Length; - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsImplicitObjectCreation; diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs index 8784ccd1b37ef..7b5870acdbde7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -56,6 +57,7 @@ protected sealed override ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingSymbolNameAsync(symbol, state, processResult, processResultData, cancellationToken); + FindReferencesInDocumentUsingSymbolName(symbol, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs index 4078ddcbba83d..dfed302b16041 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs @@ -73,7 +73,8 @@ protected sealed override ValueTask FindReferencesInDocumentAsync( static (token, state) => IsPotentialReference(state.SyntaxFacts, token), state); - return FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } private static bool IsPotentialReference( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs index 11c23a50ec558..4377ecd8bb5b5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -40,7 +41,7 @@ protected override async Task DetermineDocumentsToSearchAsync( await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected override async ValueTask FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( IFieldSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -48,9 +49,10 @@ protected override async ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingSymbolName( + symbol, state, processResult, processResultData, cancellationToken); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs index 35816840c7d6c..0b2e5e08bb88c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs @@ -105,7 +105,7 @@ private static bool IsPotentialReference( predefinedType == actualType; } - protected override async ValueTask FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( INamedTypeSymbol namedType, FindReferencesDocumentState state, Action processResult, @@ -117,8 +117,8 @@ protected override async ValueTask FindReferencesInDocumentAsync( // First find all references to this type, either with it's actual name, or through potential // global alises to it. - await AddReferencesToTypeOrGlobalAliasToItAsync( - namedType, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken).ConfigureAwait(false); + AddReferencesToTypeOrGlobalAliasToIt( + namedType, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken); // The items in initialReferences need to be both reported and used later to calculate additional results. foreach (var location in initialReferences) @@ -127,25 +127,27 @@ await AddReferencesToTypeOrGlobalAliasToItAsync( // This named type may end up being locally aliased as well. If so, now find all the references // to the local alias. - await FindLocalAliasReferencesAsync( - initialReferences, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindLocalAliasReferences( + initialReferences, state, processResult, processResultData, cancellationToken); - await FindPredefinedTypeReferencesAsync( - namedType, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindPredefinedTypeReferences( + namedType, state, processResult, processResultData, cancellationToken); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - namedType, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + namedType, state, processResult, processResultData, cancellationToken); + + return ValueTaskFactory.CompletedTask; } - internal static async ValueTask AddReferencesToTypeOrGlobalAliasToItAsync( + internal static void AddReferencesToTypeOrGlobalAliasToIt( INamedTypeSymbol namedType, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - await AddNonAliasReferencesAsync( - namedType, namedType.Name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + AddNonAliasReferences( + namedType, namedType.Name, state, processResult, processResultData, cancellationToken); foreach (var globalAlias in state.GlobalAliases) { @@ -155,8 +157,8 @@ await AddNonAliasReferencesAsync( if (state.SyntaxFacts.StringComparer.Equals(namedType.Name, globalAlias)) continue; - await AddNonAliasReferencesAsync( - namedType, globalAlias, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + AddNonAliasReferences( + namedType, globalAlias, state, processResult, processResultData, cancellationToken); } } @@ -165,7 +167,7 @@ await AddNonAliasReferencesAsync( /// only if it referenced though (which might be the actual name /// of the type, or a global alias to it). /// - private static async ValueTask AddNonAliasReferencesAsync( + private static void AddNonAliasReferences( INamedTypeSymbol symbol, string name, FindReferencesDocumentState state, @@ -173,14 +175,14 @@ private static async ValueTask AddNonAliasReferencesAsync( TData processResultData, CancellationToken cancellationToken) { - await FindOrdinaryReferencesAsync( - symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindOrdinaryReferences( + symbol, name, state, processResult, processResultData, cancellationToken); - await FindAttributeReferencesAsync( - symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindAttributeReferences( + symbol, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask FindOrdinaryReferencesAsync( + private static void FindOrdinaryReferences( INamedTypeSymbol namedType, string name, FindReferencesDocumentState state, @@ -193,11 +195,11 @@ private static ValueTask FindOrdinaryReferencesAsync( // to the constructor not the type. That's a good thing as we don't want these object-creations to // associate with the type, but rather with the constructor itself. - return FindReferencesInDocumentUsingIdentifierAsync( + FindReferencesInDocumentUsingIdentifier( namedType, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask FindPredefinedTypeReferencesAsync( + private static void FindPredefinedTypeReferences( INamedTypeSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -206,7 +208,7 @@ private static ValueTask FindPredefinedTypeReferencesAsync( { var predefinedType = symbol.SpecialType.ToPredefinedType(); if (predefinedType == PredefinedType.None) - return ValueTaskFactory.CompletedTask; + return; var tokens = state.Root .DescendantTokens(descendIntoTrivia: true) @@ -214,10 +216,10 @@ private static ValueTask FindPredefinedTypeReferencesAsync( static (token, tuple) => IsPotentialReference(tuple.predefinedType, tuple.state.SyntaxFacts, token), (state, predefinedType)); - return FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static ValueTask FindAttributeReferencesAsync( + private static void FindAttributeReferences( INamedTypeSymbol namedType, string name, FindReferencesDocumentState state, @@ -225,8 +227,7 @@ private static ValueTask FindAttributeReferencesAsync( TData processResultData, CancellationToken cancellationToken) { - return TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var nameWithoutSuffix) - ? FindReferencesInDocumentUsingIdentifierAsync(namedType, nameWithoutSuffix, state, processResult, processResultData, cancellationToken) - : ValueTaskFactory.CompletedTask; + if (TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var nameWithoutSuffix)) + FindReferencesInDocumentUsingIdentifier(namedType, nameWithoutSuffix, state, processResult, processResultData, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs index e08a522c68576..3ca848269ce6a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -50,7 +51,7 @@ await FindDocumentsAsync( await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected override async ValueTask FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( INamespaceSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -60,16 +61,16 @@ protected override async ValueTask FindReferencesInDocumentAsync( { if (symbol.IsGlobalNamespace) { - await AddGlobalNamespaceReferencesAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + AddGlobalNamespaceReferences( + symbol, state, processResult, processResultData, cancellationToken); } else { using var _ = ArrayBuilder.GetInstance(out var initialReferences); var namespaceName = symbol.Name; - await AddNamedReferencesAsync( - symbol, namespaceName, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken).ConfigureAwait(false); + AddNamedReferences( + symbol, namespaceName, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken); foreach (var globalAlias in state.GlobalAliases) { @@ -79,27 +80,29 @@ await AddNamedReferencesAsync( if (state.SyntaxFacts.StringComparer.Equals(namespaceName, globalAlias)) continue; - await AddNamedReferencesAsync( - symbol, globalAlias, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken).ConfigureAwait(false); + AddNamedReferences( + symbol, globalAlias, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken); } // The items in initialReferences need to be both reported and used later to calculate additional results. foreach (var location in initialReferences) processResult(location, processResultData); - await FindLocalAliasReferencesAsync( - initialReferences, symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindLocalAliasReferences( + initialReferences, symbol, state, processResult, processResultData, cancellationToken); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); } + + return ValueTaskFactory.CompletedTask; } /// /// Finds references to in this , but only if it referenced /// though (which might be the actual name of the type, or a global alias to it). /// - private static async ValueTask AddNamedReferencesAsync( + private static void AddNamedReferences( INamespaceSymbol symbol, string name, FindReferencesDocumentState state, @@ -107,14 +110,13 @@ private static async ValueTask AddNamedReferencesAsync( TData processResultData, CancellationToken cancellationToken) { - var tokens = await FindMatchingIdentifierTokensAsync( - state, name, cancellationToken).ConfigureAwait(false); + var tokens = FindMatchingIdentifierTokens(state, name, cancellationToken); - await FindReferencesInTokensAsync( - symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInTokens( + symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static async Task AddGlobalNamespaceReferencesAsync( + private static void AddGlobalNamespaceReferences( INamespaceSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -127,7 +129,7 @@ private static async Task AddGlobalNamespaceReferencesAsync( static (token, state) => state.SyntaxFacts.IsGlobalNamespaceKeyword(token), state); - await FindReferencesInTokensAsync( - symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInTokens( + symbol, state, tokens, processResult, processResultData, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs index 5b3b92945dc16..a6d5cb8b80c0c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -47,7 +48,7 @@ private static Task FindDocumentsAsync( project, documents, static (index, op) => index.ContainsPredefinedOperator(op), op, processResult, processResultData, cancellationToken); } - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -62,10 +63,11 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( static (token, tuple) => IsPotentialReference(tuple.state.SyntaxFacts, tuple.op, token), (state, op)); - await FindReferencesInTokensAsync( - symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInTokens( + symbol, state, tokens, processResult, processResultData, cancellationToken); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } private static bool IsPotentialReference( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs index 1131b5489010d..bb1dbb697400a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -108,7 +109,7 @@ private static bool IsGetAwaiterMethod(IMethodSymbol methodSymbol) private static bool IsAddMethod(IMethodSymbol methodSymbol) => methodSymbol.Name == WellKnownMemberNames.CollectionInitializerAddMethodName; - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -116,22 +117,24 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingSymbolName( + symbol, state, processResult, processResultData, cancellationToken); if (IsForEachMethod(symbol)) - await FindReferencesInForEachStatementsAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInForEachStatements(symbol, state, processResult, processResultData, cancellationToken); if (IsDeconstructMethod(symbol)) - await FindReferencesInDeconstructionAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDeconstruction(symbol, state, processResult, processResultData, cancellationToken); if (IsGetAwaiterMethod(symbol)) - await FindReferencesInAwaitExpressionAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInAwaitExpression(symbol, state, processResult, processResultData, cancellationToken); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); if (IsAddMethod(symbol)) - await FindReferencesInCollectionInitializerAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInCollectionInitializer(symbol, state, processResult, processResultData, cancellationToken); + + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs index b13c380c6f9c9..adef39983674b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -46,7 +47,8 @@ protected override ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingIdentifierAsync(symbol, symbol.Name, state, processResult, processResultData, cancellationToken); + FindReferencesInDocumentUsingIdentifier(symbol, symbol.Name, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } protected override async ValueTask> DetermineCascadedSymbolsAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs index 7c12a31b19e68..cf0a657769c89 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs @@ -68,8 +68,8 @@ protected override async ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingSymbolName( + symbol, state, processResult, processResultData, cancellationToken); if (symbol.AssociatedSymbol is not IPropertySymbol property || !options.AssociatePropertyReferencesWithSpecificAccessor) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index cb70c9670db68..d5fed2c94c5aa 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -118,7 +119,7 @@ protected sealed override async Task DetermineDocumentsToSearchAsync( private static bool IsForEachProperty(IPropertySymbol symbol) => symbol.Name == WellKnownMemberNames.CurrentPropertyName; - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IPropertySymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -126,7 +127,7 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - await FindReferencesInDocumentUsingSymbolNameAsync( + FindReferencesInDocumentUsingSymbolName( symbol, state, static (loc, data) => @@ -146,16 +147,17 @@ await FindReferencesInDocumentUsingSymbolNameAsync( data.processResult(loc, data.processResultData); }, processResultData: (self: this, symbol, state, processResult, processResultData, options, cancellationToken), - cancellationToken).ConfigureAwait(false); + cancellationToken); if (IsForEachProperty(symbol)) - await FindReferencesInForEachStatementsAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInForEachStatements(symbol, state, processResult, processResultData, cancellationToken); if (symbol.IsIndexer) - await FindIndexerReferencesAsync(symbol, state, processResult, processResultData, options, cancellationToken).ConfigureAwait(false); + FindIndexerReferences(symbol, state, processResult, processResultData, options, cancellationToken); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } private static Task FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync( @@ -172,7 +174,7 @@ private static Task FindDocumentWithIndexerMemberCrefAsync( project, documents, static index => index.ContainsIndexerMemberCref, processResult, processResultData, cancellationToken); } - private static async Task FindIndexerReferencesAsync( + private static void FindIndexerReferences( IPropertySymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -200,8 +202,7 @@ private static async Task FindIndexerReferencesAsync( { cancellationToken.ThrowIfCancellationRequested(); - var (matched, candidateReason, indexerReference) = await ComputeIndexerInformationAsync( - symbol, state, node, cancellationToken).ConfigureAwait(false); + var (matched, candidateReason, indexerReference) = ComputeIndexerInformation(symbol, state, node, cancellationToken); if (!matched) continue; @@ -217,7 +218,7 @@ private static async Task FindIndexerReferencesAsync( } } - private static ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeIndexerInformationAsync( + private static (bool matched, CandidateReason reason, SyntaxNode indexerReference) ComputeIndexerInformation( IPropertySymbol symbol, FindReferencesDocumentState state, SyntaxNode node, @@ -228,33 +229,33 @@ private static async Task FindIndexerReferencesAsync( if (syntaxFacts.IsElementAccessExpression(node)) { // The indexerReference for an element access expression will not be null - return ComputeElementAccessInformationAsync(symbol, node, state, cancellationToken)!; + return ComputeElementAccessInformation(symbol, node, state, cancellationToken)!; } else if (syntaxFacts.IsImplicitElementAccess(node)) { - return ComputeImplicitElementAccessInformationAsync(symbol, node, state, cancellationToken)!; + return ComputeImplicitElementAccessInformation(symbol, node, state, cancellationToken)!; } else if (syntaxFacts.IsConditionalAccessExpression(node)) { - return ComputeConditionalAccessInformationAsync(symbol, node, state, cancellationToken); + return ComputeConditionalAccessInformation(symbol, node, state, cancellationToken); } else { Debug.Assert(syntaxFacts.IsIndexerMemberCref(node)); - return ComputeIndexerMemberCRefInformationAsync(symbol, state, node, cancellationToken); + return ComputeIndexerMemberCRefInformation(symbol, state, node, cancellationToken); } } - private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeIndexerMemberCRefInformationAsync( + private static (bool matched, CandidateReason reason, SyntaxNode indexerReference) ComputeIndexerMemberCRefInformation( IPropertySymbol symbol, FindReferencesDocumentState state, SyntaxNode node, CancellationToken cancellationToken) { - var (matched, reason) = await SymbolsMatchAsync(symbol, state, node, cancellationToken).ConfigureAwait(false); + var (matched, reason) = SymbolsMatch(symbol, state, node, cancellationToken); // For an IndexerMemberCRef the node itself is the indexer we are looking for. return (matched, reason, node); } - private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeConditionalAccessInformationAsync( + private static (bool matched, CandidateReason reason, SyntaxNode indexerReference) ComputeConditionalAccessInformation( IPropertySymbol symbol, SyntaxNode node, FindReferencesDocumentState state, CancellationToken cancellationToken) { // For a ConditionalAccessExpression the whenNotNull component is the indexer reference we are looking for @@ -269,16 +270,16 @@ private static async Task FindIndexerReferencesAsync( return default; } - var (matched, reason) = await SymbolsMatchAsync(symbol, state, indexerReference, cancellationToken).ConfigureAwait(false); + var (matched, reason) = SymbolsMatch(symbol, state, indexerReference, cancellationToken); return (matched, reason, indexerReference); } - private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode? indexerReference)> ComputeElementAccessInformationAsync( + private static (bool matched, CandidateReason reason, SyntaxNode? indexerReference) ComputeElementAccessInformation( IPropertySymbol symbol, SyntaxNode node, FindReferencesDocumentState state, CancellationToken cancellationToken) { // For an ElementAccessExpression the indexer we are looking for is the argumentList component. state.SyntaxFacts.GetPartsOfElementAccessExpression(node, out var expression, out var indexerReference); - if (expression != null && (await SymbolsMatchAsync(symbol, state, expression, cancellationToken).ConfigureAwait(false)).matched) + if (expression != null && SymbolsMatch(symbol, state, expression, cancellationToken).matched) { // Element access with explicit member name (allowed in VB). We will have // already added a reference location for the member name identifier, so skip @@ -286,15 +287,15 @@ private static async Task FindIndexerReferencesAsync( return default; } - var (matched, reason) = await SymbolsMatchAsync(symbol, state, node, cancellationToken).ConfigureAwait(false); + var (matched, reason) = SymbolsMatch(symbol, state, node, cancellationToken); return (matched, reason, indexerReference); } - private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeImplicitElementAccessInformationAsync( + private static (bool matched, CandidateReason reason, SyntaxNode indexerReference) ComputeImplicitElementAccessInformation( IPropertySymbol symbol, SyntaxNode node, FindReferencesDocumentState state, CancellationToken cancellationToken) { var argumentList = state.SyntaxFacts.GetArgumentListOfImplicitElementAccess(node); - var (matched, reason) = await SymbolsMatchAsync(symbol, state, node, cancellationToken).ConfigureAwait(false); + var (matched, reason) = SymbolsMatch(symbol, state, node, cancellationToken); return (matched, reason, argumentList); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/MetadataUnifyingSymbolHashSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/MetadataUnifyingSymbolHashSet.cs index 906b70075c4fb..7f31eb6cc5306 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/MetadataUnifyingSymbolHashSet.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/MetadataUnifyingSymbolHashSet.cs @@ -3,12 +3,24 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.FindSymbols; internal sealed class MetadataUnifyingSymbolHashSet : HashSet { + private static readonly ObjectPool s_metadataUnifyingSymbolHashSetPool = new(() => []); + public MetadataUnifyingSymbolHashSet() : base(MetadataUnifyingEquivalenceComparer.Instance) { } + + public static MetadataUnifyingSymbolHashSet AllocateFromPool() + => s_metadataUnifyingSymbolHashSetPool.Allocate(); + + public static void ClearAndFree(MetadataUnifyingSymbolHashSet set) + { + set.Clear(); + s_metadataUnifyingSymbolHashSetPool.Free(set); + } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs index 511fd7fb42fd7..b10cd55dfc0e9 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs @@ -2,6 +2,7 @@ // 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.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -26,9 +27,7 @@ private NoOpStreamingFindReferencesProgress() public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => default; public ValueTask OnStartedAsync(CancellationToken cancellationToken) => default; public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) => default; - public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) => default; - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) => default; - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) => default; + public ValueTask OnReferencesFoundAsync(ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) => default; private class NoOpProgressTracker : IStreamingProgressTracker { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs index 0a13aa5929644..32a4ce2080823 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; @@ -37,18 +38,6 @@ public ValueTask OnCompletedAsync(CancellationToken cancellationToken) return default; } - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) - { - _progress.OnFindInDocumentCompleted(document); - return default; - } - - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) - { - _progress.OnFindInDocumentStarted(document); - return default; - } - public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) { try @@ -64,9 +53,11 @@ public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken can } } - public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) + public ValueTask OnReferencesFoundAsync(ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) { - _progress.OnReferenceFound(symbol, location); + foreach (var (_, symbol, location) in references) + _progress.OnReferenceFound(symbol, location); + return default; } diff --git a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs index 1f3b3736c5090..2c95215a67b28 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs @@ -19,10 +19,8 @@ internal interface ICallback ValueTask ReferenceItemsCompletedAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken); ValueTask OnStartedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); ValueTask OnCompletedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); - ValueTask OnFindInDocumentStartedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken); - ValueTask OnFindInDocumentCompletedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken); ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup group, CancellationToken cancellationToken); - ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup group, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference, CancellationToken cancellationToken); + ValueTask OnReferencesFoundAsync(RemoteServiceCallbackId callbackId, ImmutableArray<(SerializableSymbolGroup group, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference)> references, CancellationToken cancellationToken); ValueTask AddLiteralItemsAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken); ValueTask LiteralItemsCompletedAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs index fa7a34043bc62..5a84f3f20655e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs @@ -72,11 +72,8 @@ internal interface IStreamingFindReferencesProgress ValueTask OnStartedAsync(CancellationToken cancellationToken); ValueTask OnCompletedAsync(CancellationToken cancellationToken); - ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken); - ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken); - ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken); - ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken); + ValueTask OnReferencesFoundAsync(ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken); } internal interface IStreamingFindLiteralReferencesProgress diff --git a/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs b/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs index e0a83cd9bd456..dd58ce4082086 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs @@ -64,7 +64,6 @@ internal partial class AbstractSyntaxIndex try { var storage = await storageService.GetStorageAsync(documentKey.Project.Solution, cancellationToken).ConfigureAwait(false); - await using var _ = storage.ConfigureAwait(false); // attempt to load from persisted state using var stream = await storage.ReadStreamAsync(documentKey, s_persistenceName, checksum, cancellationToken).ConfigureAwait(false); @@ -156,7 +155,6 @@ private async Task SaveAsync( try { var storage = await persistentStorageService.GetStorageAsync(solutionKey, cancellationToken).ConfigureAwait(false); - await using var _ = storage.ConfigureAwait(false); using (var stream = SerializableBytes.CreateWritableStream()) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs index 9018ef3297747..912ae358bea4b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs @@ -51,9 +51,6 @@ public ImmutableArray GetReferencedSymbols() public ValueTask OnStartedAsync(CancellationToken cancellationToken) => underlyingProgress.OnStartedAsync(cancellationToken); public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => underlyingProgress.OnCompletedAsync(cancellationToken); - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) => underlyingProgress.OnFindInDocumentCompletedAsync(document, cancellationToken); - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) => underlyingProgress.OnFindInDocumentStartedAsync(document, cancellationToken); - public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) { try @@ -72,13 +69,15 @@ public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken can } } - public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, ReferenceLocation location, CancellationToken cancellationToken) + public ValueTask OnReferencesFoundAsync( + ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken) { lock (_gate) { - _symbolToLocations[definition].Add(location); + foreach (var (_, definition, location) in references) + _symbolToLocations[definition].Add(location); } - return underlyingProgress.OnReferenceFoundAsync(group, definition, location, cancellationToken); + return underlyingProgress.OnReferencesFoundAsync(references, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs index 1c4eaad54f7a0..84d6270bf7145 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; using System.Threading; using System.Threading.Tasks; @@ -43,14 +44,8 @@ public ValueTask OnCompletedAsync(RemoteServiceCallbackId callbackId, Cancellati public ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup symbolGroup, CancellationToken cancellationToken) => GetFindReferencesCallback(callbackId).OnDefinitionFoundAsync(symbolGroup, cancellationToken); - public ValueTask OnFindInDocumentCompletedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken) - => GetFindReferencesCallback(callbackId).OnFindInDocumentCompletedAsync(documentId, cancellationToken); - - public ValueTask OnFindInDocumentStartedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken) - => GetFindReferencesCallback(callbackId).OnFindInDocumentStartedAsync(documentId, cancellationToken); - - public ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup symbolGroup, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference, CancellationToken cancellationToken) - => GetFindReferencesCallback(callbackId).OnReferenceFoundAsync(symbolGroup, definition, reference, cancellationToken); + public ValueTask OnReferencesFoundAsync(RemoteServiceCallbackId callbackId, ImmutableArray<(SerializableSymbolGroup group, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference)> references, CancellationToken cancellationToken) + => GetFindReferencesCallback(callbackId).OnReferencesFoundAsync(references, cancellationToken); public ValueTask OnStartedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) => GetFindReferencesCallback(callbackId).OnStartedAsync(cancellationToken); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 50b7dd3af8c58..2830187083028 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -8,8 +8,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols; @@ -40,18 +38,6 @@ public ValueTask OnStartedAsync(CancellationToken cancellationToken) public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => progress.OnCompletedAsync(cancellationToken); - public async ValueTask OnFindInDocumentStartedAsync(DocumentId documentId, CancellationToken cancellationToken) - { - var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - await progress.OnFindInDocumentStartedAsync(document, cancellationToken).ConfigureAwait(false); - } - - public async ValueTask OnFindInDocumentCompletedAsync(DocumentId documentId, CancellationToken cancellationToken) - { - var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - await progress.OnFindInDocumentCompletedAsync(document, cancellationToken).ConfigureAwait(false); - } - public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated, CancellationToken cancellationToken) { Contract.ThrowIfTrue(dehydrated.Symbols.Count == 0); @@ -78,34 +64,38 @@ public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated await progress.OnDefinitionFoundAsync(symbolGroup, cancellationToken).ConfigureAwait(false); } - public async ValueTask OnReferenceFoundAsync( - SerializableSymbolGroup serializableSymbolGroup, - SerializableSymbolAndProjectId serializableSymbol, - SerializableReferenceLocation reference, + public async ValueTask OnReferencesFoundAsync( + ImmutableArray<(SerializableSymbolGroup serializableSymbolGroup, SerializableSymbolAndProjectId serializableSymbol, SerializableReferenceLocation reference)> references, CancellationToken cancellationToken) { - SymbolGroup? symbolGroup; - ISymbol? symbol; - lock (_gate) + using var _ = ArrayBuilder<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)>.GetInstance(references.Length, out var rehydrated); + foreach (var (serializableSymbolGroup, serializableSymbol, reference) in references) { - // The definition may not be in the map if we failed to map it over using TryRehydrateAsync in OnDefinitionFoundAsync. - // Just ignore this reference. Note: while this is a degraded experience: - // - // 1. TryRehydrateAsync logs an NFE so we can track down while we're failing to roundtrip the - // definition so we can track down that issue. - // 2. NFE'ing and failing to show a result, is much better than NFE'ing and then crashing - // immediately afterwards. - if (!_groupMap.TryGetValue(serializableSymbolGroup, out symbolGroup) || - !_definitionMap.TryGetValue(serializableSymbol, out symbol)) + SymbolGroup? symbolGroup; + ISymbol? symbol; + lock (_gate) { - return; + // The definition may not be in the map if we failed to map it over using TryRehydrateAsync in OnDefinitionFoundAsync. + // Just ignore this reference. Note: while this is a degraded experience: + // + // 1. TryRehydrateAsync logs an NFE so we can track down while we're failing to roundtrip the + // definition so we can track down that issue. + // 2. NFE'ing and failing to show a result, is much better than NFE'ing and then crashing + // immediately afterwards. + if (!_groupMap.TryGetValue(serializableSymbolGroup, out symbolGroup) || + !_definitionMap.TryGetValue(serializableSymbol, out symbol)) + { + continue; + } } - } - var referenceLocation = await reference.RehydrateAsync( - solution, cancellationToken).ConfigureAwait(false); + var referenceLocation = await reference.RehydrateAsync( + solution, cancellationToken).ConfigureAwait(false); + rehydrated.Add((symbolGroup, symbol, referenceLocation)); + } - await progress.OnReferenceFoundAsync(symbolGroup, symbol, referenceLocation, cancellationToken).ConfigureAwait(false); + if (rehydrated.Count > 0) + await progress.OnReferencesFoundAsync(rehydrated.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs index 76d59fc83456d..c0d596fa2f7ed 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs @@ -30,11 +30,10 @@ Accessibility.Protected or return true; } - internal static async Task OriginalSymbolsMatchAsync( + internal static bool OriginalSymbolsMatch( Solution solution, ISymbol? searchSymbol, - ISymbol? symbolToMatch, - CancellationToken cancellationToken) + ISymbol? symbolToMatch) { if (ReferenceEquals(searchSymbol, symbolToMatch)) return true; @@ -48,7 +47,7 @@ internal static async Task OriginalSymbolsMatchAsync( if (searchSymbol.Equals(symbolToMatch)) return true; - if (await OriginalSymbolsMatchCoreAsync(solution, searchSymbol, symbolToMatch, cancellationToken).ConfigureAwait(false)) + if (OriginalSymbolsMatchCore(solution, searchSymbol, symbolToMatch)) return true; if (searchSymbol.Kind == SymbolKind.Namespace && symbolToMatch.Kind == SymbolKind.Namespace) @@ -60,8 +59,8 @@ internal static async Task OriginalSymbolsMatchAsync( var namespace2Count = namespace2.ConstituentNamespaces.Length; if (namespace1Count != namespace2Count) { - if ((namespace1Count > 1 && await namespace1.ConstituentNamespaces.AnyAsync(static (n, arg) => NamespaceSymbolsMatchAsync(arg.solution, n, arg.namespace2, arg.cancellationToken), (solution, namespace2, cancellationToken)).ConfigureAwait(false)) || - (namespace2Count > 1 && await namespace2.ConstituentNamespaces.AnyAsync(static (n2, arg) => NamespaceSymbolsMatchAsync(arg.solution, arg.namespace1, n2, arg.cancellationToken), (solution, namespace1, cancellationToken)).ConfigureAwait(false))) + if ((namespace1Count > 1 && namespace1.ConstituentNamespaces.Any(static (n, arg) => OriginalSymbolsMatch(arg.solution, n, arg.namespace2), (solution, namespace2))) || + (namespace2Count > 1 && namespace2.ConstituentNamespaces.Any(static (n2, arg) => OriginalSymbolsMatch(arg.solution, arg.namespace1, n2), (solution, namespace1)))) { return true; } @@ -71,11 +70,8 @@ internal static async Task OriginalSymbolsMatchAsync( return false; } - private static async Task OriginalSymbolsMatchCoreAsync( - Solution solution, - ISymbol searchSymbol, - ISymbol symbolToMatch, - CancellationToken cancellationToken) + private static bool OriginalSymbolsMatchCore( + Solution solution, ISymbol searchSymbol, ISymbol symbolToMatch) { if (searchSymbol == null || symbolToMatch == null) return false; @@ -121,32 +117,22 @@ private static async Task OriginalSymbolsMatchCoreAsync( if (equivalentTypesWithDifferingAssemblies.Count > 0) { // Step 3a) Ensure that all pairs of named types in equivalentTypesWithDifferingAssemblies are indeed equivalent types. - return await VerifyForwardedTypesAsync(solution, equivalentTypesWithDifferingAssemblies, cancellationToken).ConfigureAwait(false); + return VerifyForwardedTypes(solution, equivalentTypesWithDifferingAssemblies); } // 3b) If no such named type pairs were encountered, symbols ARE equivalent. return true; } - private static Task NamespaceSymbolsMatchAsync( - Solution solution, - INamespaceSymbol namespace1, - INamespaceSymbol namespace2, - CancellationToken cancellationToken) - { - return OriginalSymbolsMatchAsync(solution, namespace1, namespace2, cancellationToken); - } - /// /// Verifies that all pairs of named types in equivalentTypesWithDifferingAssemblies are equivalent forwarded types. /// - private static async Task VerifyForwardedTypesAsync( + private static bool VerifyForwardedTypes( Solution solution, - Dictionary equivalentTypesWithDifferingAssemblies, - CancellationToken cancellationToken) + Dictionary equivalentTypesWithDifferingAssemblies) { Contract.ThrowIfNull(equivalentTypesWithDifferingAssemblies); - Contract.ThrowIfTrue(!equivalentTypesWithDifferingAssemblies.Any()); + Contract.ThrowIfTrue(equivalentTypesWithDifferingAssemblies.Count == 0); // Must contain equivalents named types residing in different assemblies. Contract.ThrowIfFalse(equivalentTypesWithDifferingAssemblies.All(kvp => !SymbolEquivalenceComparer.Instance.Equals(kvp.Key.ContainingAssembly, kvp.Value.ContainingAssembly))); @@ -155,16 +141,13 @@ private static async Task VerifyForwardedTypesAsync( Contract.ThrowIfFalse(equivalentTypesWithDifferingAssemblies.All(kvp => kvp.Key.ContainingType == null)); Contract.ThrowIfFalse(equivalentTypesWithDifferingAssemblies.All(kvp => kvp.Value.ContainingType == null)); - // Cache compilations so we avoid recreating any as we walk the pairs of types. - using var _ = PooledHashSet.GetInstance(out var compilationSet); - foreach (var (type1, type2) in equivalentTypesWithDifferingAssemblies) { // Check if type1 was forwarded to type2 in type2's compilation, or if type2 was forwarded to type1 in // type1's compilation. We check both direction as this API is called from higher level comparison APIs // that are unordered. - if (!await VerifyForwardedTypeAsync(solution, candidate: type1, forwardedTo: type2, compilationSet, cancellationToken).ConfigureAwait(false) && - !await VerifyForwardedTypeAsync(solution, candidate: type2, forwardedTo: type1, compilationSet, cancellationToken).ConfigureAwait(false)) + if (!VerifyForwardedType(solution, candidate: type1, forwardedTo: type2) && + !VerifyForwardedType(solution, candidate: type2, forwardedTo: type1)) { return false; } @@ -177,30 +160,20 @@ private static async Task VerifyForwardedTypesAsync( /// Returns if was forwarded to in /// 's . /// - private static async Task VerifyForwardedTypeAsync( + private static bool VerifyForwardedType( Solution solution, INamedTypeSymbol candidate, - INamedTypeSymbol forwardedTo, - HashSet compilationSet, - CancellationToken cancellationToken) + INamedTypeSymbol forwardedTo) { // Only need to operate on original definitions. i.e. List is the type that is forwarded, // not List. candidate = GetOridinalUnderlyingType(candidate); forwardedTo = GetOridinalUnderlyingType(forwardedTo); - var forwardedToOriginatingProject = solution.GetOriginatingProject(forwardedTo); - if (forwardedToOriginatingProject == null) - return false; - - var forwardedToCompilation = await forwardedToOriginatingProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var forwardedToCompilation = solution.GetOriginatingCompilation(forwardedTo); if (forwardedToCompilation == null) return false; - // Cache the compilation so that if we need it while checking another set of forwarded types, we don't - // expensively throw it away and recreate it. - compilationSet.Add(forwardedToCompilation); - var candidateFullMetadataName = candidate.ContainingNamespace?.IsGlobalNamespace != false ? candidate.MetadataName : $"{candidate.ContainingNamespace.ToDisplayString(SymbolDisplayFormats.SignatureFormat)}.{candidate.MetadataName}"; diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs index 7bfe7ed73548e..ad70664b74512 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs @@ -54,10 +54,8 @@ internal static async Task> FindOverridesArrayAsync( var sourceMember = await FindSourceDefinitionAsync(m, solution, cancellationToken).ConfigureAwait(false); var bestMember = sourceMember ?? m; - if (await IsOverrideAsync(solution, bestMember, symbol, cancellationToken).ConfigureAwait(false)) - { + if (IsOverride(solution, bestMember, symbol)) results.Add(bestMember); - } } } } @@ -65,11 +63,11 @@ internal static async Task> FindOverridesArrayAsync( return results.ToImmutableAndFree(); } - internal static async Task IsOverrideAsync(Solution solution, ISymbol member, ISymbol symbol, CancellationToken cancellationToken) + internal static bool IsOverride(Solution solution, ISymbol member, ISymbol symbol) { for (var current = member; current != null; current = current.GetOverriddenMember()) { - if (await OriginalSymbolsMatchAsync(solution, current.GetOverriddenMember(), symbol.OriginalDefinition, cancellationToken).ConfigureAwait(false)) + if (OriginalSymbolsMatch(solution, current.GetOverriddenMember(), symbol.OriginalDefinition)) return true; } @@ -159,8 +157,7 @@ internal static async Task> FindImplementedInterfaceMemb var sourceMethod = await FindSourceDefinitionAsync(interfaceMember, solution, cancellationToken).ConfigureAwait(false); var bestMethod = sourceMethod ?? interfaceMember; - var implementations = await type.FindImplementationsForInterfaceMemberAsync( - bestMethod, solution, cancellationToken).ConfigureAwait(false); + var implementations = type.FindImplementationsForInterfaceMember(bestMethod, solution, cancellationToken); foreach (var implementation in implementations) { if (implementation != null && @@ -361,7 +358,7 @@ internal static async Task> FindMemberImplementationsArr using var _ = ArrayBuilder.GetInstance(out var results); foreach (var t in allTypes) { - var implementations = await t.FindImplementationsForInterfaceMemberAsync(symbol, solution, cancellationToken).ConfigureAwait(false); + var implementations = t.FindImplementationsForInterfaceMember(symbol, solution, cancellationToken); foreach (var implementation in implementations) { var sourceDef = await FindSourceDefinitionAsync(implementation, solution, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs index 22ccb6dcc399f..07564d99b7c1d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs @@ -61,7 +61,6 @@ private static async Task LoadOrCreateAsync( var persistentStorageService = services.GetPersistentStorageService(); var storage = await persistentStorageService.GetStorageAsync(solutionKey, cancellationToken).ConfigureAwait(false); - await using var _ = storage.ConfigureAwait(false); using (var stream = SerializableBytes.CreateWritableStream()) { @@ -91,7 +90,6 @@ private static async Task LoadOrCreateAsync( var persistentStorageService = services.GetPersistentStorageService(); var storage = await persistentStorageService.GetStorageAsync(solutionKey, cancellationToken).ConfigureAwait(false); - await using var _ = storage.ConfigureAwait(false); // Get the unique key to identify our data. var key = PrefixSymbolTreeInfo + keySuffix; diff --git a/src/Workspaces/Core/Portable/Formatting/AbstractFormattingService.cs b/src/Workspaces/Core/Portable/Formatting/AbstractFormattingService.cs index 00ea1fc9737f8..e56e065978dd7 100644 --- a/src/Workspaces/Core/Portable/Formatting/AbstractFormattingService.cs +++ b/src/Workspaces/Core/Portable/Formatting/AbstractFormattingService.cs @@ -18,6 +18,6 @@ internal abstract class AbstractFormattingService : IFormattingService public Task FormatAsync(Document document, IEnumerable? spans, LineFormattingOptions lineFormattingOptions, SyntaxFormattingOptions? syntaxFormattingOptions, CancellationToken cancellationToken) { Contract.ThrowIfNull(syntaxFormattingOptions); - return Formatter.FormatAsync(document, spans, syntaxFormattingOptions, rules: null, cancellationToken); + return Formatter.FormatAsync(document, spans, syntaxFormattingOptions, rules: default, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Formatting/Formatter.cs b/src/Workspaces/Core/Portable/Formatting/Formatter.cs index 6803e4697a6df..b68782503aa8a 100644 --- a/src/Workspaces/Core/Portable/Formatting/Formatter.cs +++ b/src/Workspaces/Core/Portable/Formatting/Formatter.cs @@ -52,7 +52,7 @@ public static Task FormatAsync(Document document, OptionSet? options = #pragma warning restore internal static Task FormatAsync(Document document, SyntaxFormattingOptions options, CancellationToken cancellationToken) - => FormatAsync(document, spans: null, options, rules: null, cancellationToken); + => FormatAsync(document, spans: null, options, rules: default, cancellationToken); /// /// Formats the whitespace in an area of a document corresponding to a text span. @@ -68,7 +68,7 @@ public static Task FormatAsync(Document document, TextSpan span, Optio #pragma warning restore internal static Task FormatAsync(Document document, TextSpan span, SyntaxFormattingOptions options, CancellationToken cancellationToken) - => FormatAsync(document, [span], options, rules: null, cancellationToken); + => FormatAsync(document, [span], options, rules: default, cancellationToken); /// /// Formats the whitespace in areas of a document corresponding to multiple non-overlapping spans. @@ -90,7 +90,7 @@ public static async Task FormatAsync(Document document, IEnumerable FormatAsync(Document document, IEnumerable? spans, SyntaxFormattingOptions options, IEnumerable? rules, CancellationToken cancellationToken) + internal static async Task FormatAsync(Document document, IEnumerable? spans, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var services = document.Project.Solution.Services; @@ -106,19 +106,19 @@ internal static async Task FormatAsync(Document document, IEnumerable< /// An optional cancellation token. /// The formatted document. public static Task FormatAsync(Document document, SyntaxAnnotation annotation, OptionSet? options = null, CancellationToken cancellationToken = default) - => FormatAsync(document, annotation, options, rules: null, cancellationToken: cancellationToken); + => FormatAsync(document, annotation, options, rules: default, cancellationToken: cancellationToken); internal static Task FormatAsync(Document document, SyntaxAnnotation annotation, SyntaxFormattingOptions options, CancellationToken cancellationToken) - => FormatAsync(document, annotation, options, rules: null, cancellationToken); + => FormatAsync(document, annotation, options, rules: default, cancellationToken); - internal static async Task FormatAsync(Document document, SyntaxAnnotation annotation, SyntaxFormattingOptions options, IEnumerable? rules, CancellationToken cancellationToken) + internal static async Task FormatAsync(Document document, SyntaxAnnotation annotation, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var services = document.Project.Solution.Services; return document.WithSyntaxRoot(Format(root, annotation, services, options, rules, cancellationToken)); } - internal static async Task FormatAsync(Document document, SyntaxAnnotation annotation, OptionSet? optionSet, IEnumerable? rules, CancellationToken cancellationToken) + internal static async Task FormatAsync(Document document, SyntaxAnnotation annotation, OptionSet? optionSet, ImmutableArray rules, CancellationToken cancellationToken) { if (document == null) { @@ -150,12 +150,12 @@ internal static async Task FormatAsync(Document document, SyntaxAnnota /// An optional cancellation token. /// The formatted tree's root node. public static SyntaxNode Format(SyntaxNode node, SyntaxAnnotation annotation, Workspace workspace, OptionSet? options = null, CancellationToken cancellationToken = default) - => Format(node, annotation, workspace, options, rules: null, cancellationToken); + => Format(node, annotation, workspace, options, rules: default, cancellationToken); internal static SyntaxNode Format(SyntaxNode node, SyntaxAnnotation annotation, SolutionServices services, SyntaxFormattingOptions options, CancellationToken cancellationToken) - => Format(node, annotation, services, options, rules: null, cancellationToken); + => Format(node, annotation, services, options, rules: default, cancellationToken); - private static SyntaxNode Format(SyntaxNode node, SyntaxAnnotation annotation, Workspace workspace, OptionSet? options, IEnumerable? rules, CancellationToken cancellationToken) + private static SyntaxNode Format(SyntaxNode node, SyntaxAnnotation annotation, Workspace workspace, OptionSet? options, ImmutableArray rules, CancellationToken cancellationToken) { if (workspace == null) { @@ -175,7 +175,7 @@ private static SyntaxNode Format(SyntaxNode node, SyntaxAnnotation annotation, W return Format(node, GetAnnotatedSpans(node, annotation), workspace, options, rules, cancellationToken); } - internal static SyntaxNode Format(SyntaxNode node, SyntaxAnnotation annotation, SolutionServices services, SyntaxFormattingOptions options, IEnumerable? rules, CancellationToken cancellationToken) + internal static SyntaxNode Format(SyntaxNode node, SyntaxAnnotation annotation, SolutionServices services, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken) => Format(node, GetAnnotatedSpans(node, annotation), services, options, rules, cancellationToken); /// @@ -187,10 +187,10 @@ internal static SyntaxNode Format(SyntaxNode node, SyntaxAnnotation annotation, /// An optional cancellation token. /// The formatted tree's root node. public static SyntaxNode Format(SyntaxNode node, Workspace workspace, OptionSet? options = null, CancellationToken cancellationToken = default) - => Format(node, [node.FullSpan], workspace, options, rules: null, cancellationToken); + => Format(node, [node.FullSpan], workspace, options, rules: default, cancellationToken); internal static SyntaxNode Format(SyntaxNode node, SolutionServices services, SyntaxFormattingOptions options, CancellationToken cancellationToken) - => Format(node, [node.FullSpan], services, options, rules: null, cancellationToken); + => Format(node, [node.FullSpan], services, options, rules: default, cancellationToken); /// /// Formats the whitespace in areas of a syntax tree identified by a span. @@ -202,10 +202,10 @@ internal static SyntaxNode Format(SyntaxNode node, SolutionServices services, Sy /// An optional cancellation token. /// The formatted tree's root node. public static SyntaxNode Format(SyntaxNode node, TextSpan span, Workspace workspace, OptionSet? options = null, CancellationToken cancellationToken = default) - => Format(node, [span], workspace, options, rules: null, cancellationToken: cancellationToken); + => Format(node, [span], workspace, options, rules: default, cancellationToken: cancellationToken); internal static SyntaxNode Format(SyntaxNode node, TextSpan span, SolutionServices services, SyntaxFormattingOptions options, CancellationToken cancellationToken) - => Format(node, [span], services, options, rules: null, cancellationToken: cancellationToken); + => Format(node, [span], services, options, rules: default, cancellationToken: cancellationToken); /// /// Formats the whitespace in areas of a syntax tree identified by multiple non-overlapping spans. @@ -217,18 +217,18 @@ internal static SyntaxNode Format(SyntaxNode node, TextSpan span, SolutionServic /// An optional cancellation token. /// The formatted tree's root node. public static SyntaxNode Format(SyntaxNode node, IEnumerable? spans, Workspace workspace, OptionSet? options = null, CancellationToken cancellationToken = default) - => Format(node, spans, workspace, options, rules: null, cancellationToken: cancellationToken); + => Format(node, spans, workspace, options, rules: default, cancellationToken: cancellationToken); - private static SyntaxNode Format(SyntaxNode node, IEnumerable? spans, Workspace workspace, OptionSet? options, IEnumerable? rules, CancellationToken cancellationToken) + private static SyntaxNode Format(SyntaxNode node, IEnumerable? spans, Workspace workspace, OptionSet? options, ImmutableArray rules, CancellationToken cancellationToken) { var formattingResult = GetFormattingResult(node, spans, workspace, options, rules, cancellationToken); return formattingResult == null ? node : formattingResult.GetFormattedRoot(cancellationToken); } - internal static SyntaxNode Format(SyntaxNode node, IEnumerable? spans, SolutionServices services, SyntaxFormattingOptions options, IEnumerable? rules, CancellationToken cancellationToken) + internal static SyntaxNode Format(SyntaxNode node, IEnumerable? spans, SolutionServices services, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken) => GetFormattingResult(node, spans, services, options, rules, cancellationToken).GetFormattedRoot(cancellationToken); - private static IFormattingResult? GetFormattingResult(SyntaxNode node, IEnumerable? spans, Workspace workspace, OptionSet? options, IEnumerable? rules, CancellationToken cancellationToken) + private static IFormattingResult? GetFormattingResult(SyntaxNode node, IEnumerable? spans, Workspace workspace, OptionSet? options, ImmutableArray rules, CancellationToken cancellationToken) { if (workspace == null) { @@ -252,7 +252,7 @@ internal static SyntaxNode Format(SyntaxNode node, IEnumerable? spans, return languageFormatter.GetFormattingResult(node, spans, formattingOptions, rules, cancellationToken); } - internal static IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerable? spans, SolutionServices services, SyntaxFormattingOptions options, IEnumerable? rules, CancellationToken cancellationToken) + internal static IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerable? spans, SolutionServices services, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken) { var formatter = services.GetRequiredLanguageService(node.Language); return formatter.GetFormattingResult(node, spans, options, rules, cancellationToken); @@ -267,10 +267,10 @@ internal static IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerab /// An optional cancellation token. /// The changes necessary to format the tree. public static IList GetFormattedTextChanges(SyntaxNode node, Workspace workspace, OptionSet? options = null, CancellationToken cancellationToken = default) - => GetFormattedTextChanges(node, [node.FullSpan], workspace, options, rules: null, cancellationToken: cancellationToken); + => GetFormattedTextChanges(node, [node.FullSpan], workspace, options, rules: default, cancellationToken: cancellationToken); internal static IList GetFormattedTextChanges(SyntaxNode node, SolutionServices services, SyntaxFormattingOptions options, CancellationToken cancellationToken) - => GetFormattedTextChanges(node, [node.FullSpan], services, options, rules: null, cancellationToken: cancellationToken); + => GetFormattedTextChanges(node, [node.FullSpan], services, options, rules: default, cancellationToken: cancellationToken); /// /// Determines the changes necessary to format the whitespace of a syntax tree. @@ -282,10 +282,10 @@ internal static IList GetFormattedTextChanges(SyntaxNode node, Solut /// An optional cancellation token. /// The changes necessary to format the tree. public static IList GetFormattedTextChanges(SyntaxNode node, TextSpan span, Workspace workspace, OptionSet? options = null, CancellationToken cancellationToken = default) - => GetFormattedTextChanges(node, [span], workspace, options, rules: null, cancellationToken); + => GetFormattedTextChanges(node, [span], workspace, options, rules: default, cancellationToken); internal static IList GetFormattedTextChanges(SyntaxNode node, TextSpan span, SolutionServices services, SyntaxFormattingOptions options, CancellationToken cancellationToken = default) - => GetFormattedTextChanges(node, [span], services, options, rules: null, cancellationToken); + => GetFormattedTextChanges(node, [span], services, options, rules: default, cancellationToken); /// /// Determines the changes necessary to format the whitespace of a syntax tree. @@ -297,12 +297,12 @@ internal static IList GetFormattedTextChanges(SyntaxNode node, TextS /// An optional cancellation token. /// The changes necessary to format the tree. public static IList GetFormattedTextChanges(SyntaxNode node, IEnumerable? spans, Workspace workspace, OptionSet? options = null, CancellationToken cancellationToken = default) - => GetFormattedTextChanges(node, spans, workspace, options, rules: null, cancellationToken); + => GetFormattedTextChanges(node, spans, workspace, options, rules: default, cancellationToken); internal static IList GetFormattedTextChanges(SyntaxNode node, IEnumerable? spans, SolutionServices services, SyntaxFormattingOptions options, CancellationToken cancellationToken = default) - => GetFormattedTextChanges(node, spans, services, options, rules: null, cancellationToken); + => GetFormattedTextChanges(node, spans, services, options, rules: default, cancellationToken); - private static IList GetFormattedTextChanges(SyntaxNode node, IEnumerable? spans, Workspace workspace, OptionSet? options, IEnumerable? rules, CancellationToken cancellationToken) + private static IList GetFormattedTextChanges(SyntaxNode node, IEnumerable? spans, Workspace workspace, OptionSet? options, ImmutableArray rules, CancellationToken cancellationToken) { var formattingResult = GetFormattingResult(node, spans, workspace, options, rules, cancellationToken); return formattingResult == null @@ -310,7 +310,7 @@ private static IList GetFormattedTextChanges(SyntaxNode node, IEnume : formattingResult.GetTextChanges(cancellationToken); } - internal static IList GetFormattedTextChanges(SyntaxNode node, IEnumerable? spans, SolutionServices services, SyntaxFormattingOptions options, IEnumerable? rules, CancellationToken cancellationToken = default) + internal static IList GetFormattedTextChanges(SyntaxNode node, IEnumerable? spans, SolutionServices services, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken = default) { var formatter = services.GetRequiredLanguageService(node.Language); return formatter.GetFormattingResult(node, spans, options, rules, cancellationToken).GetTextChanges(cancellationToken); diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/AbstractLinkedFileMergeConflictCommentAdditionService.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/AbstractLinkedFileMergeConflictCommentAdditionService.cs index 2d7adffa24ae2..3f76b79b27406 100644 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/AbstractLinkedFileMergeConflictCommentAdditionService.cs +++ b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/AbstractLinkedFileMergeConflictCommentAdditionService.cs @@ -5,8 +5,10 @@ #nullable disable using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -16,9 +18,9 @@ internal abstract class AbstractLinkedFileMergeConflictCommentAdditionService : { internal abstract string GetConflictCommentText(string header, string beforeString, string afterString); - public IEnumerable CreateEdits(SourceText originalSourceText, IEnumerable unmergedChanges) + public ImmutableArray CreateEdits(SourceText originalSourceText, ArrayBuilder unmergedChanges) { - var commentChanges = new List(); + using var _ = ArrayBuilder.GetInstance(out var commentChanges); foreach (var documentWithChanges in unmergedChanges) { @@ -28,12 +30,12 @@ public IEnumerable CreateEdits(SourceText originalSourceText, IEnume commentChanges.AddRange(comments); } - return commentChanges; + return commentChanges.ToImmutableAndClear(); } - private static IEnumerable> PartitionChangesForDocument(IEnumerable changes, SourceText originalSourceText) + private static List> PartitionChangesForDocument(IEnumerable changes, SourceText originalSourceText) { - var partitionedChanges = new List>(); + var partitionedChanges = new List>(); var currentPartition = new List { changes.First() diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/IMergeConflictHandler.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/IMergeConflictHandler.cs index e5c4f81e1d986..2e84348dbda34 100644 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/IMergeConflictHandler.cs +++ b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/IMergeConflictHandler.cs @@ -2,14 +2,14 @@ // 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.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis; internal interface IMergeConflictHandler { - IEnumerable CreateEdits(SourceText originalSourceText, IEnumerable unmergedChanges); + ImmutableArray CreateEdits(SourceText originalSourceText, ArrayBuilder unmergedChanges); } diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs index 1f7fb23a853f3..f55837400141f 100644 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs +++ b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs @@ -2,10 +2,7 @@ // 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; @@ -17,141 +14,156 @@ namespace Microsoft.CodeAnalysis; +using DocumentAndHashBuilder = ArrayBuilder<(Document newDocument, ImmutableArray newContentHash)>; + internal sealed class LinkedFileDiffMergingSession(Solution oldSolution, Solution newSolution, SolutionChanges solutionChanges) { - internal async Task MergeDiffsAsync(IMergeConflictHandler mergeConflictHandler, CancellationToken cancellationToken) + internal async Task MergeDiffsAsync(IMergeConflictHandler? mergeConflictHandler, CancellationToken cancellationToken) { - var sessionInfo = new LinkedFileDiffMergingSessionInfo(); + using var _1 = PooledDictionary.GetInstance(out var filePathToNewDocumentsAndHashes); + try + { + foreach (var documentId in solutionChanges.GetProjectChanges().SelectMany(p => p.GetChangedDocuments())) + { + // Don't need to do any merging whatsoever for documents that are not linked files. + var newDocument = newSolution.GetRequiredDocument(documentId); + var relatedDocumentIds = newSolution.GetRelatedDocumentIds(newDocument.Id); + if (relatedDocumentIds.Length == 1) + continue; - var linkedDocumentGroupsWithChanges = solutionChanges - .GetProjectChanges() - .SelectMany(p => p.GetChangedDocuments()) - .GroupBy(d => oldSolution.GetDocument(d).FilePath, StringComparer.OrdinalIgnoreCase); + var filePath = newDocument.FilePath; + Contract.ThrowIfNull(filePath); - var linkedFileMergeResults = new List(); + var newDocumentsAndHashes = filePathToNewDocumentsAndHashes.GetOrAdd(filePath, static (_, capacity) => DocumentAndHashBuilder.GetInstance(capacity), relatedDocumentIds.Length); - var updatedSolution = newSolution; - foreach (var linkedDocumentsWithChanges in linkedDocumentGroupsWithChanges) - { - var documentInNewSolution = newSolution.GetDocument(linkedDocumentsWithChanges.First()); + var newText = await newDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + var newContentHash = newText.GetContentHash(); + // Ignore any linked documents that we have the same contents as. + if (newDocumentsAndHashes.Any(t => t.newContentHash.SequenceEqual(newContentHash))) + continue; - // Ensure the first document in the group is the first in the list of - var allLinkedDocuments = documentInNewSolution.GetLinkedDocumentIds().Add(documentInNewSolution.Id); - if (allLinkedDocuments.Length == 1) - { - continue; + newDocumentsAndHashes.Add((newDocument, newContentHash)); } - SourceText mergedText; - if (linkedDocumentsWithChanges.Count() > 1) - { - var mergeGroupResult = await MergeLinkedDocumentGroupAsync(allLinkedDocuments, linkedDocumentsWithChanges, sessionInfo, mergeConflictHandler, cancellationToken).ConfigureAwait(false); - linkedFileMergeResults.Add(mergeGroupResult); - mergedText = mergeGroupResult.MergedSourceText; - } - else - { - mergedText = await newSolution.GetDocument(linkedDocumentsWithChanges.Single()).GetValueTextAsync(cancellationToken).ConfigureAwait(false); - } + var updatedSolution = newSolution; + using var _ = ArrayBuilder.GetInstance( + filePathToNewDocumentsAndHashes.Count(static kvp => kvp.Value.Count > 1), + out var linkedFileMergeResults); - foreach (var documentId in allLinkedDocuments) + foreach (var (filePath, newDocumentsAndHashes) in filePathToNewDocumentsAndHashes) { - updatedSolution = updatedSolution.WithDocumentText(documentId, mergedText); + Contract.ThrowIfTrue(newDocumentsAndHashes.Count == 0); + + // Don't need to do anything if this document has no linked siblings. + var firstNewDocument = newDocumentsAndHashes[0].newDocument; + + var relatedDocuments = newSolution.GetRelatedDocumentIds(firstNewDocument.Id); + Contract.ThrowIfTrue(relatedDocuments.Length == 1, "We should have skipped non-linked files in the prior loop."); + + if (newDocumentsAndHashes.Count == 1) + { + // The file has linked siblings, but we collapsed down to only one actual document change. Ensure that + // any linked files have that same content as well. + var firstSourceText = await firstNewDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + updatedSolution = updatedSolution.WithDocumentTexts( + relatedDocuments.SelectAsArray(d => (d, firstSourceText))); + } + else + { + // Otherwise, merge the changes and set all the linked files to that merged content. + var mergeGroupResult = await MergeLinkedDocumentGroupAsync(newDocumentsAndHashes, mergeConflictHandler, cancellationToken).ConfigureAwait(false); + linkedFileMergeResults.Add(mergeGroupResult); + updatedSolution = updatedSolution.WithDocumentTexts( + relatedDocuments.SelectAsArray(d => (d, mergeGroupResult.MergedSourceText))); + } } - } - return new LinkedFileMergeSessionResult(updatedSolution, linkedFileMergeResults); + return new LinkedFileMergeSessionResult(updatedSolution, linkedFileMergeResults); + } + finally + { + foreach (var (_, newDocumentsAndHashes) in filePathToNewDocumentsAndHashes) + newDocumentsAndHashes.Free(); + } } private async Task MergeLinkedDocumentGroupAsync( - IEnumerable allLinkedDocuments, - IEnumerable linkedDocumentGroup, - LinkedFileDiffMergingSessionInfo sessionInfo, - IMergeConflictHandler mergeConflictHandler, + DocumentAndHashBuilder newDocumentsAndHashes, + IMergeConflictHandler? mergeConflictHandler, CancellationToken cancellationToken) { - var groupSessionInfo = new LinkedFileGroupSessionInfo(); + Contract.ThrowIfTrue(newDocumentsAndHashes.Count < 2); // Automatically merge non-conflicting diffs while collecting the conflicting diffs var textDifferencingService = oldSolution.Services.GetRequiredService(); - var appliedChanges = await textDifferencingService.GetTextChangesAsync(oldSolution.GetDocument(linkedDocumentGroup.First()), newSolution.GetDocument(linkedDocumentGroup.First()), cancellationToken).ConfigureAwait(false); - var unmergedChanges = new List(); - foreach (var documentId in linkedDocumentGroup.Skip(1)) + var firstNewDocument = newDocumentsAndHashes[0].newDocument; + var firstOldDocument = oldSolution.GetRequiredDocument(firstNewDocument.Id); + var firstOldSourceText = await firstOldDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + + var allTextChangesAcrossLinkedFiles = await textDifferencingService.GetTextChangesAsync( + firstOldDocument, firstNewDocument, TextDifferenceTypes.Line, cancellationToken).ConfigureAwait(false); + + using var _ = ArrayBuilder.GetInstance(out var unmergedChanges); + for (int i = 1, n = newDocumentsAndHashes.Count; i < n; i++) { - appliedChanges = await AddDocumentMergeChangesAsync( - oldSolution.GetDocument(documentId), - newSolution.GetDocument(documentId), - [.. appliedChanges], + var siblingNewDocument = newDocumentsAndHashes[i].newDocument; + var siblingOldDocument = oldSolution.GetRequiredDocument(siblingNewDocument.Id); + + allTextChangesAcrossLinkedFiles = await AddDocumentMergeChangesAsync( + siblingOldDocument, + siblingNewDocument, + allTextChangesAcrossLinkedFiles, unmergedChanges, - groupSessionInfo, textDifferencingService, cancellationToken).ConfigureAwait(false); } - var originalDocument = oldSolution.GetDocument(linkedDocumentGroup.First()); - var originalSourceText = await originalDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - - // Add comments in source explaining diffs that could not be merged + var linkedDocuments = oldSolution.GetRelatedDocumentIds(firstOldDocument.Id); - IEnumerable allChanges; - IList mergeConflictResolutionSpan = []; + if (unmergedChanges.Count == 0) + return new LinkedFileMergeResult(linkedDocuments, firstOldSourceText.WithChanges(allTextChangesAcrossLinkedFiles), []); - if (unmergedChanges.Any()) - { - mergeConflictHandler ??= oldSolution.GetDocument(linkedDocumentGroup.First()).GetLanguageService(); - var mergeConflictTextEdits = mergeConflictHandler.CreateEdits(originalSourceText, unmergedChanges); + mergeConflictHandler ??= firstOldDocument.GetRequiredLanguageService(); + var mergeConflictTextEdits = mergeConflictHandler.CreateEdits(firstOldSourceText, unmergedChanges); - allChanges = MergeChangesWithMergeFailComments(appliedChanges, mergeConflictTextEdits, mergeConflictResolutionSpan, groupSessionInfo); - } - else - { - allChanges = appliedChanges; - } - - groupSessionInfo.LinkedDocuments = newSolution.GetDocumentIdsWithFilePath(originalDocument.FilePath).Length; - groupSessionInfo.DocumentsWithChanges = linkedDocumentGroup.Count(); - sessionInfo.LogLinkedFileResult(groupSessionInfo); - - return new LinkedFileMergeResult(allLinkedDocuments, originalSourceText.WithChanges(allChanges), mergeConflictResolutionSpan); + // Add comments in source explaining diffs that could not be merged + var (allChanges, mergeConflictResolutionSpans) = MergeChangesWithMergeFailComments(allTextChangesAcrossLinkedFiles, mergeConflictTextEdits); + return new LinkedFileMergeResult(linkedDocuments, firstOldSourceText.WithChanges(allChanges), mergeConflictResolutionSpans); } private static async Task> AddDocumentMergeChangesAsync( Document oldDocument, Document newDocument, - List cumulativeChanges, - List unmergedChanges, - LinkedFileGroupSessionInfo groupSessionInfo, + ImmutableArray cumulativeChanges, + ArrayBuilder unmergedChanges, IDocumentTextDifferencingService textDiffService, CancellationToken cancellationToken) { - var unmergedDocumentChanges = new List(); - var successfullyMergedChanges = ArrayBuilder.GetInstance(); + using var _1 = ArrayBuilder.GetInstance(out var unmergedDocumentChanges); + using var _2 = ArrayBuilder.GetInstance(out var successfullyMergedChanges); var cumulativeChangeIndex = 0; - var textchanges = await textDiffService.GetTextChangesAsync(oldDocument, newDocument, cancellationToken).ConfigureAwait(false); - foreach (var change in textchanges) + var textChanges = await textDiffService.GetTextChangesAsync( + oldDocument, newDocument, TextDifferenceTypes.Line, cancellationToken).ConfigureAwait(false); + foreach (var change in textChanges) { - while (cumulativeChangeIndex < cumulativeChanges.Count && cumulativeChanges[cumulativeChangeIndex].Span.End < change.Span.Start) + while (cumulativeChangeIndex < cumulativeChanges.Length && cumulativeChanges[cumulativeChangeIndex].Span.End < change.Span.Start) { // Existing change that does not overlap with the current change in consideration successfullyMergedChanges.Add(cumulativeChanges[cumulativeChangeIndex]); cumulativeChangeIndex++; - - groupSessionInfo.IsolatedDiffs++; } - if (cumulativeChangeIndex < cumulativeChanges.Count) + if (cumulativeChangeIndex < cumulativeChanges.Length) { var cumulativeChange = cumulativeChanges[cumulativeChangeIndex]; if (!cumulativeChange.Span.IntersectsWith(change.Span)) { // The current change in consideration does not intersect with any existing change successfullyMergedChanges.Add(change); - - groupSessionInfo.IsolatedDiffs++; } else { @@ -160,24 +172,12 @@ private static async Task> AddDocumentMergeChangesAsy // The current change in consideration overlaps an existing change but // the changes are not identical. unmergedDocumentChanges.Add(change); - - groupSessionInfo.OverlappingDistinctDiffs++; - if (change.Span == cumulativeChange.Span) - { - groupSessionInfo.OverlappingDistinctDiffsWithSameSpan++; - if (change.NewText.Contains(cumulativeChange.NewText) || cumulativeChange.NewText.Contains(change.NewText)) - { - groupSessionInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation++; - } - } } else { // The current change in consideration is identical to an existing change successfullyMergedChanges.Add(change); cumulativeChangeIndex++; - - groupSessionInfo.IdenticalDiffs++; } } } @@ -185,108 +185,99 @@ private static async Task> AddDocumentMergeChangesAsy { // The current change in consideration does not intersect with any existing change successfullyMergedChanges.Add(change); - - groupSessionInfo.IsolatedDiffs++; } } - while (cumulativeChangeIndex < cumulativeChanges.Count) + while (cumulativeChangeIndex < cumulativeChanges.Length) { // Existing change that does not overlap with the current change in consideration successfullyMergedChanges.Add(cumulativeChanges[cumulativeChangeIndex]); cumulativeChangeIndex++; - groupSessionInfo.IsolatedDiffs++; } - if (unmergedDocumentChanges.Any()) + if (unmergedDocumentChanges.Count != 0) { unmergedChanges.Add(new UnmergedDocumentChanges( - unmergedDocumentChanges.AsEnumerable(), + unmergedDocumentChanges.ToImmutableAndClear(), oldDocument.Project.Name, oldDocument.Id)); } - return successfullyMergedChanges.ToImmutableAndFree(); + return successfullyMergedChanges.ToImmutableAndClear(); } - private static IEnumerable MergeChangesWithMergeFailComments( - IEnumerable mergedChanges, - IEnumerable commentChanges, - IList mergeConflictResolutionSpans, - LinkedFileGroupSessionInfo groupSessionInfo) + private static (ImmutableArray mergeChanges, ImmutableArray mergeConflictResolutionSpans) MergeChangesWithMergeFailComments( + ImmutableArray mergedChanges, + ImmutableArray commentChanges) { - var mergedChangesList = NormalizeChanges(mergedChanges).ToList(); - var commentChangesList = NormalizeChanges(commentChanges).ToList(); + var mergedChangesList = NormalizeChanges(mergedChanges); + var commentChangesList = NormalizeChanges(commentChanges); - var combinedChanges = new List(); - var insertedMergeConflictCommentsAtAdjustedLocation = 0; + using var _1 = ArrayBuilder.GetInstance(out var combinedChanges); + using var _2 = ArrayBuilder.GetInstance(out var mergeConflictResolutionSpans); + var insertedMergeConflictCommentsAtAdjustedLocation = 0; var commentChangeIndex = 0; var currentPositionDelta = 0; foreach (var mergedChange in mergedChangesList) { - while (commentChangeIndex < commentChangesList.Count && commentChangesList[commentChangeIndex].Span.End <= mergedChange.Span.Start) + while (commentChangeIndex < commentChangesList.Length && commentChangesList[commentChangeIndex].Span.End <= mergedChange.Span.Start) { // Add a comment change that does not conflict with any merge change combinedChanges.Add(commentChangesList[commentChangeIndex]); - mergeConflictResolutionSpans.Add(new TextSpan(commentChangesList[commentChangeIndex].Span.Start + currentPositionDelta, commentChangesList[commentChangeIndex].NewText.Length)); - currentPositionDelta += (commentChangesList[commentChangeIndex].NewText.Length - commentChangesList[commentChangeIndex].Span.Length); + mergeConflictResolutionSpans.Add(new TextSpan(commentChangesList[commentChangeIndex].Span.Start + currentPositionDelta, commentChangesList[commentChangeIndex].NewText!.Length)); + currentPositionDelta += commentChangesList[commentChangeIndex].NewText!.Length - commentChangesList[commentChangeIndex].Span.Length; commentChangeIndex++; } - if (commentChangeIndex >= commentChangesList.Count || mergedChange.Span.End <= commentChangesList[commentChangeIndex].Span.Start) + if (commentChangeIndex >= commentChangesList.Length || mergedChange.Span.End <= commentChangesList[commentChangeIndex].Span.Start) { // Add a merge change that does not conflict with any comment change combinedChanges.Add(mergedChange); - currentPositionDelta += (mergedChange.NewText.Length - mergedChange.Span.Length); + currentPositionDelta += mergedChange.NewText!.Length - mergedChange.Span.Length; continue; } // The current comment insertion location conflicts with a merge diff location. Add the comment before the diff. var conflictingCommentInsertionLocation = new TextSpan(mergedChange.Span.Start, 0); - while (commentChangeIndex < commentChangesList.Count && commentChangesList[commentChangeIndex].Span.Start < mergedChange.Span.End) + while (commentChangeIndex < commentChangesList.Length && commentChangesList[commentChangeIndex].Span.Start < mergedChange.Span.End) { - combinedChanges.Add(new TextChange(conflictingCommentInsertionLocation, commentChangesList[commentChangeIndex].NewText)); - mergeConflictResolutionSpans.Add(new TextSpan(commentChangesList[commentChangeIndex].Span.Start + currentPositionDelta, commentChangesList[commentChangeIndex].NewText.Length)); - currentPositionDelta += commentChangesList[commentChangeIndex].NewText.Length; + combinedChanges.Add(new TextChange(conflictingCommentInsertionLocation, commentChangesList[commentChangeIndex].NewText!)); + mergeConflictResolutionSpans.Add(new TextSpan(commentChangesList[commentChangeIndex].Span.Start + currentPositionDelta, commentChangesList[commentChangeIndex].NewText!.Length)); + currentPositionDelta += commentChangesList[commentChangeIndex].NewText!.Length; commentChangeIndex++; insertedMergeConflictCommentsAtAdjustedLocation++; } combinedChanges.Add(mergedChange); - currentPositionDelta += (mergedChange.NewText.Length - mergedChange.Span.Length); + currentPositionDelta += mergedChange.NewText!.Length - mergedChange.Span.Length; } - while (commentChangeIndex < commentChangesList.Count) + while (commentChangeIndex < commentChangesList.Length) { // Add a comment change that does not conflict with any merge change combinedChanges.Add(commentChangesList[commentChangeIndex]); - mergeConflictResolutionSpans.Add(new TextSpan(commentChangesList[commentChangeIndex].Span.Start + currentPositionDelta, commentChangesList[commentChangeIndex].NewText.Length)); + mergeConflictResolutionSpans.Add(new TextSpan(commentChangesList[commentChangeIndex].Span.Start + currentPositionDelta, commentChangesList[commentChangeIndex].NewText!.Length)); - currentPositionDelta += (commentChangesList[commentChangeIndex].NewText.Length - commentChangesList[commentChangeIndex].Span.Length); + currentPositionDelta += commentChangesList[commentChangeIndex].NewText!.Length - commentChangesList[commentChangeIndex].Span.Length; commentChangeIndex++; } - groupSessionInfo.InsertedMergeConflictComments = commentChanges.Count(); - groupSessionInfo.InsertedMergeConflictCommentsAtAdjustedLocation = insertedMergeConflictCommentsAtAdjustedLocation; - - return NormalizeChanges(combinedChanges); + return (NormalizeChanges(combinedChanges.ToImmutableAndClear()), mergeConflictResolutionSpans.ToImmutableAndClear()); } - private static IEnumerable NormalizeChanges(IEnumerable changes) + private static ImmutableArray NormalizeChanges(ImmutableArray changes) { - if (changes.Count() <= 1) - { + if (changes.Length <= 1) return changes; - } - changes = changes.OrderBy(c => c.Span.Start); - var normalizedChanges = new List(); + var orderedChanges = changes.Sort(static (c1, c2) => c1.Span.Start - c2.Span.Start); + using var _ = ArrayBuilder.GetInstance(changes.Length, out var normalizedChanges); - var currentChange = changes.First(); - foreach (var nextChange in changes.Skip(1)) + var currentChange = changes[0]; + foreach (var nextChange in changes.AsSpan()[1..]) { if (nextChange.Span.Start == currentChange.Span.End) { @@ -300,27 +291,11 @@ private static IEnumerable NormalizeChanges(IEnumerable } normalizedChanges.Add(currentChange); - return normalizedChanges; - } - - internal class LinkedFileDiffMergingSessionInfo - { - public readonly List LinkedFileGroups = []; - public void LogLinkedFileResult(LinkedFileGroupSessionInfo info) - => LinkedFileGroups.Add(info); - } + // If we didn't merge anything, can just return the original ordered changes. + if (normalizedChanges.Count == orderedChanges.Length) + return orderedChanges; - internal class LinkedFileGroupSessionInfo - { - public int LinkedDocuments; - public int DocumentsWithChanges; - public int IsolatedDiffs; - public int IdenticalDiffs; - public int OverlappingDistinctDiffs; - public int OverlappingDistinctDiffsWithSameSpan; - public int OverlappingDistinctDiffsWithSameSpanAndSubstringRelation; - public int InsertedMergeConflictComments; - public int InsertedMergeConflictCommentsAtAdjustedLocation; + return normalizedChanges.ToImmutableAndClear(); } } diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileMergeResult.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileMergeResult.cs index a6a2fdea585d9..daf2558de6b42 100644 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileMergeResult.cs +++ b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileMergeResult.cs @@ -2,18 +2,15 @@ // 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.Collections.Generic; -using System.Linq; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis; -internal sealed class LinkedFileMergeResult(IEnumerable documentIds, SourceText mergedSourceText, IEnumerable mergeConflictResolutionSpans) +internal readonly struct LinkedFileMergeResult(ImmutableArray documentIds, SourceText mergedSourceText, ImmutableArray mergeConflictResolutionSpans) { - public IEnumerable DocumentIds { get; internal set; } = documentIds; - public SourceText MergedSourceText { get; internal set; } = mergedSourceText; - public IEnumerable MergeConflictResolutionSpans { get; } = mergeConflictResolutionSpans; - public bool HasMergeConflicts { get { return MergeConflictResolutionSpans.Any(); } } + public readonly ImmutableArray DocumentIds = documentIds; + public readonly SourceText MergedSourceText = mergedSourceText; + public readonly ImmutableArray MergeConflictResolutionSpans = mergeConflictResolutionSpans; + public bool HasMergeConflicts => !MergeConflictResolutionSpans.IsEmpty; } diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileMergeSessionResult.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileMergeSessionResult.cs index fd532034cbb68..0d3f426e966a6 100644 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileMergeSessionResult.cs +++ b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileMergeSessionResult.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.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis; @@ -13,19 +13,16 @@ internal sealed class LinkedFileMergeSessionResult { public Solution MergedSolution { get; } - private readonly Dictionary> _mergeConflictCommentSpans = []; - public Dictionary> MergeConflictCommentSpans => _mergeConflictCommentSpans; + public readonly Dictionary> MergeConflictCommentSpans = []; - public LinkedFileMergeSessionResult(Solution mergedSolution, IEnumerable fileMergeResults) + public LinkedFileMergeSessionResult(Solution mergedSolution, ArrayBuilder fileMergeResults) { this.MergedSolution = mergedSolution; foreach (var fileMergeResult in fileMergeResults) { foreach (var documentId in fileMergeResult.DocumentIds) - { - _mergeConflictCommentSpans.Add(documentId, fileMergeResult.MergeConflictResolutionSpans); - } + MergeConflictCommentSpans.Add(documentId, fileMergeResult.MergeConflictResolutionSpans); } } } diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/UnmergedDocumentChanges.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/UnmergedDocumentChanges.cs index a5586170c71fa..d5f463427e08d 100644 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/UnmergedDocumentChanges.cs +++ b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/UnmergedDocumentChanges.cs @@ -2,16 +2,14 @@ // 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.Collections.Generic; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis; -internal sealed class UnmergedDocumentChanges(IEnumerable unmergedChanges, string projectName, DocumentId documentId) +internal readonly struct UnmergedDocumentChanges(ImmutableArray unmergedChanges, string projectName, DocumentId documentId) { - public IEnumerable UnmergedChanges { get; } = unmergedChanges; - public string ProjectName { get; } = projectName; - public DocumentId DocumentId { get; } = documentId; + public readonly ImmutableArray UnmergedChanges = unmergedChanges; + public readonly string ProjectName = projectName; + public readonly DocumentId DocumentId = documentId; } diff --git a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj index 2ef8b99744fab..39c411bd46163 100644 --- a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj @@ -39,6 +39,7 @@ + diff --git a/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs b/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs index 4f8bb3b9ddc17..fc5b311d973ef 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs @@ -20,18 +20,7 @@ namespace Microsoft.CodeAnalysis.Remote; /// internal abstract class RemoteHostClient : IDisposable { - public event EventHandler? StatusChanged; - - protected void Started() - { - OnStatusChanged(started: true); - } - - public virtual void Dispose() - => OnStatusChanged(started: false); - - private void OnStatusChanged(bool started) - => StatusChanged?.Invoke(this, started); + public abstract void Dispose(); public static Task TryGetClientAsync(Project project, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs b/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs index 31746fb54306c..ee221fb20114f 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs @@ -47,15 +47,21 @@ internal static class RemoteUtilities /// a solution textually equivalent to the newSolution passed to . /// public static async Task UpdateSolutionAsync( - Solution oldSolution, ImmutableArray<(DocumentId, ImmutableArray)> documentTextChanges, CancellationToken cancellationToken) + Solution oldSolution, + ImmutableArray<(DocumentId documentId, ImmutableArray textChanges)> documentTextChanges, + CancellationToken cancellationToken) { var currentSolution = oldSolution; - foreach (var (docId, textChanges) in documentTextChanges) - { - var text = await oldSolution.GetDocument(docId).GetValueTextAsync(cancellationToken).ConfigureAwait(false); - currentSolution = currentSolution.WithDocumentText(docId, text.WithChanges(textChanges)); - } - return currentSolution; + var documentIdsAndTexts = await documentTextChanges + .SelectAsArrayAsync(async (tuple, cancellationToken) => + { + var oldText = await oldSolution.GetDocument(tuple.documentId).GetValueTextAsync(cancellationToken).ConfigureAwait(false); + var newText = oldText.WithChanges(tuple.textChanges); + return (tuple.documentId, newText); + }, cancellationToken) + .ConfigureAwait(false); + + return oldSolution.WithDocumentTexts(documentIdsAndTexts); } } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 37bc567b3dd73..06e2b900e3035 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -33,20 +33,20 @@ internal sealed class SerializableSourceText /// The storage location for . /// /// - /// Exactly one of or will be non-. + /// Exactly one of or will be non-. /// - private readonly TemporaryTextStorage? _storage; + private readonly TemporaryStorageTextHandle? _storageHandle; /// /// The in the current process. /// /// - /// + /// /// private readonly SourceText? _text; /// - /// Weak reference to a SourceText computed from . Useful so that if multiple requests + /// Weak reference to a SourceText computed from . Useful so that if multiple requests /// come in for the source text, the same one can be returned as long as something is holding it alive. /// private readonly WeakReference _computedText = new(target: null); @@ -56,26 +56,26 @@ internal sealed class SerializableSourceText /// public readonly Checksum ContentChecksum; - public SerializableSourceText(TemporaryTextStorage storage, ImmutableArray contentHash) - : this(storage, text: null, contentHash) + public SerializableSourceText(TemporaryStorageTextHandle storageHandle) + : this(storageHandle, text: null, storageHandle.ContentHash) { } public SerializableSourceText(SourceText text, ImmutableArray contentHash) - : this(storage: null, text, contentHash) + : this(storageHandle: null, text, contentHash) { } - private SerializableSourceText(TemporaryTextStorage? storage, SourceText? text, ImmutableArray contentHash) + private SerializableSourceText(TemporaryStorageTextHandle? storageHandle, SourceText? text, ImmutableArray contentHash) { - Debug.Assert(storage is null != text is null); + Debug.Assert(storageHandle is null != text is null); - _storage = storage; + _storageHandle = storageHandle; _text = text; ContentChecksum = Checksum.Create(contentHash); #if DEBUG - var computedContentHash = TryGetText()?.GetContentHash() ?? _storage!.ContentHash; + var computedContentHash = TryGetText()?.GetContentHash() ?? _storageHandle!.ContentHash; Debug.Assert(contentHash.SequenceEqual(computedContentHash)); #endif } @@ -95,7 +95,7 @@ public async ValueTask GetTextAsync(CancellationToken cancellationTo return text; // Read and cache the text from the storage object so that other requests may see it if still kept alive by something. - text = await _storage!.ReadTextAsync(cancellationToken).ConfigureAwait(false); + text = await _storageHandle!.ReadFromTemporaryStorageAsync(cancellationToken).ConfigureAwait(false); _computedText.SetTarget(text); return text; } @@ -107,7 +107,7 @@ public SourceText GetText(CancellationToken cancellationToken) return text; // Read and cache the text from the storage object so that other requests may see it if still kept alive by something. - text = _storage!.ReadText(cancellationToken); + text = _storageHandle!.ReadFromTemporaryStorage(cancellationToken); _computedText.SetTarget(text); return text; } @@ -120,10 +120,10 @@ public static ValueTask FromTextDocumentStateAsync( // If we're already pointing at a serializable loader, we can just use that directly. return new(serializableLoader.SerializableSourceText); } - else if (state.Storage is TemporaryTextStorage storage) + else if (state.StorageHandle is TemporaryStorageTextHandle storageHandle) { // Otherwise, if we're pointing at a memory mapped storage location, we can create the source text that directly wraps that. - return new(new SerializableSourceText(storage, storage.ContentHash)); + return new(new SerializableSourceText(storageHandle)); } else { @@ -142,58 +142,55 @@ public static ValueTask FromTextDocumentStateAsync( public void Serialize(ObjectWriter writer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - if (_storage is not null) - { - writer.WriteInt32((int)_storage.ChecksumAlgorithm); - writer.WriteEncoding(_storage.Encoding); - writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(_storage.ContentHash)!); + if (_storageHandle is not null) + { writer.WriteInt32((int)SerializationKinds.MemoryMapFile); - writer.WriteString(_storage.Name); - writer.WriteInt64(_storage.Offset); - writer.WriteInt64(_storage.Size); + _storageHandle.Identifier.WriteTo(writer); + writer.WriteInt32((int)_storageHandle.ChecksumAlgorithm); + writer.WriteEncoding(_storageHandle.Encoding); + writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(_storageHandle.ContentHash)!); } else { RoslynDebug.AssertNotNull(_text); + writer.WriteInt32((int)SerializationKinds.Bits); writer.WriteInt32((int)_text.ChecksumAlgorithm); writer.WriteEncoding(_text.Encoding); writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(_text.GetContentHash())!); - writer.WriteInt32((int)SerializationKinds.Bits); _text.WriteTo(writer, cancellationToken); } } public static SerializableSourceText Deserialize( ObjectReader reader, - ITemporaryStorageServiceInternal storageService, + TemporaryStorageService storageService, ITextFactoryService textService, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var checksumAlgorithm = (SourceHashAlgorithm)reader.ReadInt32(); - var encoding = reader.ReadEncoding(); - var contentHash = ImmutableCollectionsMarshal.AsImmutableArray(reader.ReadByteArray()); - var kind = (SerializationKinds)reader.ReadInt32(); Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); if (kind == SerializationKinds.MemoryMapFile) { - var storage2 = (TemporaryStorageService)storageService; - - var name = reader.ReadRequiredString(); - var offset = reader.ReadInt64(); - var size = reader.ReadInt64(); + var identifier = TemporaryStorageIdentifier.ReadFrom(reader); + var checksumAlgorithm = (SourceHashAlgorithm)reader.ReadInt32(); + var encoding = reader.ReadEncoding(); + var contentHash = ImmutableCollectionsMarshal.AsImmutableArray(reader.ReadByteArray()); + var storageHandle = storageService.GetTextHandle(identifier, checksumAlgorithm, encoding, contentHash); - var storage = storage2.AttachTemporaryTextStorage(name, offset, size, checksumAlgorithm, encoding, contentHash); - return new SerializableSourceText(storage, contentHash); + return new SerializableSourceText(storageHandle); } else { + var checksumAlgorithm = (SourceHashAlgorithm)reader.ReadInt32(); + var encoding = reader.ReadEncoding(); + var contentHash = ImmutableCollectionsMarshal.AsImmutableArray(reader.ReadByteArray()); + return new SerializableSourceText( SourceTextExtensions.ReadFrom(textService, reader, encoding, checksumAlgorithm, cancellationToken), contentHash); diff --git a/src/Workspaces/Core/Portable/Serialization/SerializedMetadataReference.cs b/src/Workspaces/Core/Portable/Serialization/SerializedMetadataReference.cs new file mode 100644 index 0000000000000..230bb0ddec317 --- /dev/null +++ b/src/Workspaces/Core/Portable/Serialization/SerializedMetadataReference.cs @@ -0,0 +1,85 @@ +// 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 System.Linq; +using Microsoft.CodeAnalysis.Host; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Serialization; + +using static TemporaryStorageService; + +internal partial class SerializerService +{ + [DebuggerDisplay("{" + nameof(Display) + ",nq}")] + private sealed class SerializedMetadataReference : PortableExecutableReference, ISupportTemporaryStorage + { + private readonly Metadata _metadata; + private readonly ImmutableArray _storageHandles; + private readonly DocumentationProvider _provider; + + public IReadOnlyList StorageHandles => _storageHandles; + + public SerializedMetadataReference( + MetadataReferenceProperties properties, + string? fullPath, + Metadata metadata, + ImmutableArray storageHandles, + DocumentationProvider initialDocumentation) + : base(properties, fullPath, initialDocumentation) + { + Contract.ThrowIfTrue(storageHandles.IsDefault); + _metadata = metadata; + _storageHandles = storageHandles; + + _provider = initialDocumentation; + } + + protected override DocumentationProvider CreateDocumentationProvider() + { + // this uses documentation provider given at the constructor + throw ExceptionUtilities.Unreachable(); + } + + protected override Metadata GetMetadataImpl() + => _metadata; + + protected override PortableExecutableReference WithPropertiesImpl(MetadataReferenceProperties properties) + => new SerializedMetadataReference(properties, FilePath, _metadata, _storageHandles, _provider); + + public override string ToString() + { + var metadata = TryGetMetadata(this); + var modules = GetModules(metadata); + + return $""" + {nameof(SerializedMetadataReference)} + FilePath={this.FilePath} + Kind={this.Properties.Kind} + Aliases={this.Properties.Aliases.Join(",")} + EmbedInteropTypes={this.Properties.EmbedInteropTypes} + MetadataKind={metadata switch { null => "null", AssemblyMetadata => "assembly", ModuleMetadata => "module", _ => metadata.GetType().Name }} + Guids={modules.Select(m => GetMetadataGuid(m).ToString()).Join(",")} + """; + + static ImmutableArray GetModules(Metadata? metadata) + { + if (metadata is AssemblyMetadata assemblyMetadata) + { + if (TryGetModules(assemblyMetadata, out var modules)) + return modules; + } + else if (metadata is ModuleMetadata moduleMetadata) + { + return [moduleMetadata]; + } + + return []; + } + } + } +} diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index e958468cd5861..5e2f922c5ed2b 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection.Metadata; using System.Threading; @@ -22,6 +22,28 @@ internal partial class SerializerService { private const int MetadataFailed = int.MaxValue; + /// + /// Allow analyzer tests to exercise the oop codepaths, even though they're referring to in-memory instances of + /// DiagnosticAnalyzers. In that case, we'll just share the in-memory instance of the analyzer across the OOP + /// boundary (which still runs in proc in tests), but we will still exercise all codepaths that use the RemoteClient + /// as well as exercising all codepaths that send data across the OOP boundary. Effectively, this allows us to + /// pretend that a is a during tests. + /// + private static readonly object s_analyzerImageReferenceMapGate = new(); + private static IBidirectionalMap s_analyzerImageReferenceMap = BidirectionalMap.Empty; + + private static bool TryGetAnalyzerImageReferenceGuid(AnalyzerImageReference imageReference, out Guid guid) + { + lock (s_analyzerImageReferenceMapGate) + return s_analyzerImageReferenceMap.TryGetValue(imageReference, out guid); + } + + private static bool TryGetAnalyzerImageReferenceFromGuid(Guid guid, [NotNullWhen(true)] out AnalyzerImageReference? imageReference) + { + lock (s_analyzerImageReferenceMapGate) + return s_analyzerImageReferenceMap.TryGetKey(guid, out imageReference); + } + public static Checksum CreateChecksum(MetadataReference reference, CancellationToken cancellationToken) { if (reference is PortableExecutableReference portable) @@ -50,6 +72,11 @@ public static Checksum CreateChecksum(AnalyzerReference reference, CancellationT writer.WriteBoolean(IsAnalyzerReferenceWithShadowCopyLoader(file)); break; + case AnalyzerImageReference analyzerImageReference: + Contract.ThrowIfFalse(TryGetAnalyzerImageReferenceGuid(analyzerImageReference, out var guid), "AnalyzerImageReferences are only supported during testing"); + writer.WriteGuid(guid); + break; + default: throw ExceptionUtilities.UnexpectedValue(reference); } @@ -100,6 +127,12 @@ public virtual void WriteAnalyzerReferenceTo(AnalyzerReference reference, Object writer.WriteBoolean(IsAnalyzerReferenceWithShadowCopyLoader(file)); break; + case AnalyzerImageReference analyzerImageReference: + Contract.ThrowIfFalse(TryGetAnalyzerImageReferenceGuid(analyzerImageReference, out var guid), "AnalyzerImageReferences are only supported during testing"); + writer.WriteString(nameof(AnalyzerImageReference)); + writer.WriteGuid(guid); + break; + default: throw ExceptionUtilities.UnexpectedValue(reference); } @@ -110,11 +143,17 @@ public virtual AnalyzerReference ReadAnalyzerReferenceFrom(ObjectReader reader, cancellationToken.ThrowIfCancellationRequested(); var type = reader.ReadString(); - if (type == nameof(AnalyzerFileReference)) + switch (type) { - var fullPath = reader.ReadRequiredString(); - var shadowCopy = reader.ReadBoolean(); - return new AnalyzerFileReference(fullPath, _analyzerLoaderProvider.GetLoader(new AnalyzerAssemblyLoaderOptions(shadowCopy))); + case nameof(AnalyzerFileReference): + var fullPath = reader.ReadRequiredString(); + var shadowCopy = reader.ReadBoolean(); + return new AnalyzerFileReference(fullPath, _analyzerLoaderProvider.GetLoader(new AnalyzerAssemblyLoaderOptions(shadowCopy))); + + case nameof(AnalyzerImageReference): + var guid = reader.ReadGuid(); + Contract.ThrowIfFalse(TryGetAnalyzerImageReferenceFromGuid(guid, out var analyzerImageReference)); + return analyzerImageReference; } throw ExceptionUtilities.UnexpectedValue(type); @@ -203,13 +242,15 @@ private static void WriteMvidTo(ModuleMetadata metadata, ObjectWriter writer, Ca cancellationToken.ThrowIfCancellationRequested(); writer.WriteInt32((int)metadata.Kind); + writer.WriteGuid(GetMetadataGuid(metadata)); + } + private static Guid GetMetadataGuid(ModuleMetadata metadata) + { var metadataReader = metadata.GetMetadataReader(); - var mvidHandle = metadataReader.GetModuleDefinition().Mvid; var guid = metadataReader.GetGuid(mvidHandle); - - writer.WriteGuid(guid); + return guid; } private static void WritePortableExecutableReferenceTo( @@ -385,7 +426,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it // will not be released by the host. var storageIdentifier = TemporaryStorageIdentifier.ReadFrom(reader); - var storageHandle = _storageService.GetHandle(storageIdentifier); + var storageHandle = TemporaryStorageService.GetStreamHandle(storageIdentifier); return ReadModuleMetadataFromStorage(storageHandle); } @@ -500,40 +541,15 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere => new MissingMetadataReference(properties, FilePath, _provider); } - [DebuggerDisplay("{" + nameof(Display) + ",nq}")] - private sealed class SerializedMetadataReference : PortableExecutableReference, ISupportTemporaryStorage + public static class TestAccessor { - private readonly Metadata _metadata; - private readonly ImmutableArray _storageHandles; - private readonly DocumentationProvider _provider; - - public IReadOnlyList StorageHandles => _storageHandles; - - public SerializedMetadataReference( - MetadataReferenceProperties properties, - string? fullPath, - Metadata metadata, - ImmutableArray storageHandles, - DocumentationProvider initialDocumentation) - : base(properties, fullPath, initialDocumentation) + public static void AddAnalyzerImageReference(AnalyzerImageReference analyzerImageReference) { - Contract.ThrowIfTrue(storageHandles.IsDefault); - _metadata = metadata; - _storageHandles = storageHandles; - - _provider = initialDocumentation; - } - - protected override DocumentationProvider CreateDocumentationProvider() - { - // this uses documentation provider given at the constructor - throw ExceptionUtilities.Unreachable(); + lock (s_analyzerImageReferenceMapGate) + { + if (!s_analyzerImageReferenceMap.ContainsKey(analyzerImageReference)) + s_analyzerImageReferenceMap = s_analyzerImageReferenceMap.Add(analyzerImageReference, Guid.NewGuid()); + } } - - protected override Metadata GetMetadataImpl() - => _metadata; - - protected override PortableExecutableReference WithPropertiesImpl(MetadataReferenceProperties properties) - => new SerializedMetadataReference(properties, FilePath, _metadata, _storageHandles, _provider); } } diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/IAsyncEnumerableExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/IAsyncEnumerableExtensions.cs index 3a390a2769457..a41b629c3f889 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/IAsyncEnumerableExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/IAsyncEnumerableExtensions.cs @@ -93,4 +93,14 @@ public static void CompletesChannel(this Task task, Channel channel) static (task, channel) => ((Channel)channel!).Writer.Complete(task.Exception), channel, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default); } + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously +#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods + public static async IAsyncEnumerable AsAsyncEnumerable(this IEnumerable source) +#pragma warning restore VSTHRD200 // Use "Async" suffix for async methods +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + { + foreach (var item in source) + yield return item; + } } diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs index aba239ec1cb92..f9ae9b7f0b3fd 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs @@ -658,11 +658,10 @@ public static ImmutableArray FilterToVisibleAndBrowsableSymbols( // PERF: HasUnsupportedMetadata may require recreating the syntax tree to get the base class, so first // check to see if we're referencing a symbol defined in source. - static bool isSymbolDefinedInSource(Location l) => l.IsInSource; return symbols.WhereAsArray((s, arg) => // Check if symbol is namespace (which is always visible) first to avoid realizing all locations // of each namespace symbol, which might end up allocating in LOH - (s.IsNamespace() || s.Locations.Any(isSymbolDefinedInSource) || !s.HasUnsupportedMetadata) && + (s.IsNamespace() || s.Locations.Any(static loc => loc.IsInSource) || !s.HasUnsupportedMetadata) && !s.IsDestructor() && s.IsEditorBrowsable( arg.hideAdvancedMembers, diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs index 6bef4e0d8324e..4153fcdbc4fa3 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs @@ -27,7 +27,7 @@ internal static partial class ITypeSymbolExtensions /// interfaceMember, or this type doesn't supply a member that successfully implements /// interfaceMember). /// - public static async Task> FindImplementationsForInterfaceMemberAsync( + public static ImmutableArray FindImplementationsForInterfaceMember( this ITypeSymbol typeSymbol, ISymbol interfaceMember, Solution solution, @@ -97,13 +97,11 @@ not SymbolKind.Method and // OriginalSymbolMatch allows types to be matched across different assemblies if they are considered to // be the same type, which provides a more accurate implementations list for interfaces. var constructedInterfaceMember = - await constructedInterface.GetMembers(interfaceMember.Name).FirstOrDefaultAsync( - typeSymbol => SymbolFinder.OriginalSymbolsMatchAsync(solution, typeSymbol, interfaceMember, cancellationToken)).ConfigureAwait(false); + constructedInterface.GetMembers(interfaceMember.Name).FirstOrDefault( + typeSymbol => SymbolFinder.OriginalSymbolsMatch(solution, typeSymbol, interfaceMember)); if (constructedInterfaceMember == null) - { continue; - } // Now we need to walk the base type chain, but we start at the first type that actually // has the interface directly in its interface hierarchy. diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs index 8cc93a4bf90a0..4e7cd40576ebd 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs @@ -48,6 +48,7 @@ internal static class FeatureAttribute public const string NavigableSymbols = nameof(NavigableSymbols); public const string NavigateTo = nameof(NavigateTo); public const string NavigationBar = nameof(NavigationBar); + public const string OnTheFlyDocs = nameof(OnTheFlyDocs); public const string Outlining = nameof(Outlining); public const string OrganizeDocument = nameof(OrganizeDocument); public const string PackageInstaller = nameof(PackageInstaller); diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs new file mode 100644 index 0000000000000..a4b3e0854ad7b --- /dev/null +++ b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs @@ -0,0 +1,338 @@ +// 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.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Shared.Utilities; + +internal readonly record struct ProducerConsumerOptions +{ + /// + /// Used when the consumeItems routine will only pull items on a single thread (never concurrently). produceItems + /// can be called concurrently on many threads. + /// + public static readonly ProducerConsumerOptions SingleReaderOptions = new() { SingleReader = true }; + + /// + /// Used when the consumeItems routine will only pull items on a single thread (never concurrently). produceItems + /// can be called on a single thread as well (never concurrently). + /// + public static readonly ProducerConsumerOptions SingleReaderWriterOptions = new() { SingleReader = true, SingleWriter = true }; + + /// + public bool SingleWriter { get; init; } + + /// + public bool SingleReader { get; init; } +} + +internal static class ProducerConsumer +{ + private static async Task BatchReaderIntoArraysAsync( + ChannelReader reader, + Func, TArgs, CancellationToken, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var items); + while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + // Grab as many items as we can from the channel at once and report in a single array. Then wait for the + // next set of items to be available. + while (reader.TryRead(out var item)) + items.Add(item); + + await consumeItems(items.ToImmutableAndClear(), args, cancellationToken).ConfigureAwait(false); + } + + return default; + } + + /// + /// Version of when caller the prefers the results being pre-packaged into arrays to process. + /// + public static Task RunAsync( + ProducerConsumerOptions options, + Func, TArgs, CancellationToken, Task> produceItems, + Func, TArgs, CancellationToken, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) + { + return RunChannelAsync( + options, + static (onItemFound, args, cancellationToken) => args.produceItems(onItemFound, args.args, cancellationToken), + static (reader, args, cancellationToken) => BatchReaderIntoArraysAsync(reader, args.consumeItems, args.args, cancellationToken), + args: (produceItems, consumeItems, args), + cancellationToken); + } + + /// + /// Version of when the caller prefers working with a stream of results. + /// + public static Task RunAsync( + ProducerConsumerOptions options, + Func, TArgs, CancellationToken, Task> produceItems, + Func, TArgs, CancellationToken, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) + { + // Bridge to sibling helper that takes a consumeItems that returns a value. + return RunChannelAsync( + options, + produceItems: static (callback, args, cancellationToken) => args.produceItems(callback, args.args, cancellationToken), + consumeItems: static async (items, args, cancellationToken) => + { + await args.consumeItems(items.ReadAllAsync(cancellationToken), args.args, cancellationToken).ConfigureAwait(false); + return default(VoidResult); + }, + args: (produceItems, consumeItems, args), + cancellationToken); + } + + /// + /// IEnumerable<TSource> -> Task. Callback receives IAsyncEnumerable items. + /// + public static Task RunParallelAsync( + IEnumerable source, + Func, TArgs, CancellationToken, Task> produceItems, + Func, TArgs, CancellationToken, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) + { + // Bridge to sibling helper that operates on an IAsyncEnumerable. + return RunParallelAsync(source.AsAsyncEnumerable(), produceItems, consumeItems, args, cancellationToken); + } + + /// + /// IAsyncEnumerable<TSource> -> Task. Callback receives IAsyncEnumerable items. + /// + public static Task RunParallelAsync( + IAsyncEnumerable source, + Func, TArgs, CancellationToken, Task> produceItems, + Func, TArgs, CancellationToken, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) + { + // Bridge to sibling helper that takes a consumeItems that returns a value. + return RunParallelAsync( + source, + produceItems: static (item, callback, args, cancellationToken) => args.produceItems(item, callback, args.args, cancellationToken), + consumeItems: static async (items, args, cancellationToken) => + { + await args.consumeItems(items, args.args, cancellationToken).ConfigureAwait(false); + return default(VoidResult); + }, + args: (produceItems, consumeItems, args), + cancellationToken); + } + + /// + /// IEnumerable<TSource> -> Task. Callback receives ImmutableArray of items. + /// + public static Task RunParallelAsync( + IEnumerable source, + Func, TArgs, CancellationToken, Task> produceItems, + Func, TArgs, CancellationToken, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) + { + // Bridge to sibling helper that operates on an IAsyncEnumerable. + return RunParallelAsync(source.AsAsyncEnumerable(), produceItems, consumeItems, args, cancellationToken); + } + + /// + /// IAsyncEnumerable<TSource> -> Task. Callback receives ImmutableArray of items. + /// + public static Task RunParallelAsync( + IAsyncEnumerable source, + Func, TArgs, CancellationToken, Task> produceItems, + Func, TArgs, CancellationToken, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) + { + // Bridge to sibling helper that takes a consumeItems that returns a value. + return RunParallelChannelAsync( + source, + produceItems: static (item, callback, args, cancellationToken) => args.produceItems(item, callback, args.args, cancellationToken), + consumeItems: static (items, args, cancellationToken) => BatchReaderIntoArraysAsync(items, args.consumeItems, args.args, cancellationToken), + args: (produceItems, consumeItems, args), + cancellationToken); + } + + /// + /// IEnumerable<TSource> -> Task<TResult> Callback receives an IAsyncEnumerable of items. + /// + public static Task RunParallelAsync( + IEnumerable source, + Func, TArgs, CancellationToken, Task> produceItems, + Func, TArgs, CancellationToken, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) + { + // Bridge to sibling helper that operates on an IAsyncEnumerable. + return RunParallelAsync(source.AsAsyncEnumerable(), produceItems, consumeItems, args, cancellationToken); + } + + /// + /// IAsyncEnumerable<TSource> -> Task<TResult>. Callback receives an IAsyncEnumerable of items. + /// + public static Task RunParallelAsync( + IAsyncEnumerable source, + Func, TArgs, CancellationToken, Task> produceItems, + Func, TArgs, CancellationToken, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) + { + return RunParallelChannelAsync( + source, + produceItems: static (item, callback, args, cancellationToken) => args.produceItems(item, callback, args.args, cancellationToken), + consumeItems: static (reader, args, cancellationToken) => args.consumeItems(reader.ReadAllAsync(cancellationToken), args.args, cancellationToken), + args: (produceItems, consumeItems, args), + cancellationToken); + } + + #region helpers that return arrays + + /// + /// IEnumerable<TSource> -> Task<ImmutableArray<TResult>> + /// + public static Task> RunParallelAsync( + IEnumerable source, + Func, TArgs, CancellationToken, Task> produceItems, + TArgs args, + CancellationToken cancellationToken) + { + // Bridge to sibling helper that operates on an IAsyncEnumerable. + return RunParallelAsync(source.AsAsyncEnumerable(), produceItems, args, cancellationToken); + } + + /// + /// IAsyncEnumerable<TSource> -> Task<ImmutableArray<TResult>> + /// + public static async Task> RunParallelAsync( + IAsyncEnumerable source, + Func, TArgs, CancellationToken, Task> produceItems, + TArgs args, + CancellationToken cancellationToken) + { + // Bridge to sibling helper that takes a consumeItems that returns a value. + return await RunParallelAsync( + source, + produceItems: static (item, callback, args, cancellationToken) => args.produceItems(item, callback, args.args, cancellationToken), + consumeItems: static (stream, args, cancellationToken) => stream.ToImmutableArrayAsync(cancellationToken), + args: (produceItems, args), + cancellationToken).ConfigureAwait(false); + } + + #endregion + + #region Core channel-based impl + + private static Task RunParallelChannelAsync( + IAsyncEnumerable source, + Func, TArgs, CancellationToken, Task> produceItems, + Func, TArgs, CancellationToken, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) + { + return RunChannelAsync( + // We're running in parallel, so we def have multiple writers + ProducerConsumerOptions.SingleReaderOptions, + produceItems: static (callback, args, cancellationToken) => + RoslynParallel.ForEachAsync( + args.source, + cancellationToken, + async (source, cancellationToken) => + await args.produceItems(source, callback, args.args, cancellationToken).ConfigureAwait(false)), + consumeItems: static (enumerable, args, cancellationToken) => args.consumeItems(enumerable, args.args, cancellationToken), + args: (source, produceItems, consumeItems, args), + cancellationToken); + } + + /// + /// Helper utility for the pattern of a pair of a production routine and consumption routine using a channel to + /// coordinate data transfer. The provided are used to create a , which will then then manage the rules and behaviors around the routines. Importantly, the + /// channel handles backpressure, ensuring that if the consumption routine cannot keep up, that the production + /// routine will be throttled. + /// + /// is the routine called to actually produce the items. It will be passed an + /// action that can be used to write items to the channel. Note: the channel itself will have rules depending on if + /// that writing can happen concurrently multiple write threads or just a single writer. See for control of this when creating the channel. + /// + /// is the routine called to consume the items. Similarly, reading can have just a + /// single reader or multiple readers, depending on the value passed into . + /// + private static async Task RunChannelAsync( + ProducerConsumerOptions options, + Func, TArgs, CancellationToken, Task> produceItems, + Func, TArgs, CancellationToken, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) + { + var channel = Channel.CreateUnbounded(new() + { + SingleReader = options.SingleReader, + SingleWriter = options.SingleWriter, + }); + + // When cancellation happens, attempt to close the channel. That will unblock the task processing the items. + // Capture-free version is only available on netcore unfortunately. + using var _ = cancellationToken.Register( +#if NET + static (obj, cancellationToken) => ((Channel)obj!).Writer.TryComplete(new OperationCanceledException(cancellationToken)), + state: channel); +#else + () => channel.Writer.TryComplete(new OperationCanceledException(cancellationToken))); +#endif + + var writeTask = ProduceItemsAndWriteToChannelAsync(); + var readTask = ReadFromChannelAndConsumeItemsAsync(); + await Task.WhenAll(writeTask, readTask).ConfigureAwait(false); + + return await readTask.ConfigureAwait(false); + + async Task ReadFromChannelAndConsumeItemsAsync() + { + await Task.Yield().ConfigureAwait(false); + return await consumeItems(channel.Reader, args, cancellationToken).ConfigureAwait(false); + } + + async Task ProduceItemsAndWriteToChannelAsync() + { + Exception? exception = null; + try + { + await Task.Yield().ConfigureAwait(false); + + // It's ok to use TryWrite here. TryWrite always succeeds unless the channel is completed. And the + // channel is only ever completed by us (after produceItems completes or throws an exception) or if the + // cancellationToken is triggered above in RunAsync. In that latter case, it's ok for writing to the + // channel to do nothing as we no longer need to write out those assets to the pipe. + await produceItems(item => channel.Writer.TryWrite(item), args, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) when ((exception = ex) == null) + { + throw ExceptionUtilities.Unreachable(); + } + finally + { + // No matter what path we take (exceptional or non-exceptional), always complete the channel so the + // writing task knows it's done. + channel.Writer.TryComplete(exception); + } + } + } + + #endregion +} diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.NetFramework.cs b/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.NetFramework.cs new file mode 100644 index 0000000000000..d24e62bb1e4eb --- /dev/null +++ b/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.NetFramework.cs @@ -0,0 +1,652 @@ +// 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. + +#if !NET + +#pragma warning disable CA1068 // CancellationToken parameters must come last +#pragma warning disable IDE0007 // Use implicit type +#pragma warning disable IDE2003 // Blank line required between block and subsequent statement +#pragma warning disable IDE2004 // Blank line not allowed after constructor initializer colon +#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods + +// Ported from +// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/Parallel.ForEachAsync.cs +// With only changes to make the code work on NetFx. Where changes have been made, the original code is kept around in +// an ifdef'ed block to see what it was doing. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Shared.Utilities; + +internal static partial class RoslynParallel +{ + private static class NetFramework + { + /// Executes a for each operation on an in which iterations may run in parallel. + /// The type of the data in the source. + /// An enumerable data source. + /// An asynchronous delegate that is invoked once per element in the data source. + /// The argument or argument is . + /// A task that represents the entire for each operation. + /// The operation will execute at most operations in parallel. + public static Task ForEachAsync(IEnumerable source, Func body) + { +#if false + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(body); +#endif + + return ForEachAsync(source, DefaultDegreeOfParallelism, TaskScheduler.Default, default(CancellationToken), body); + } + + /// Executes a for each operation on an in which iterations may run in parallel. + /// The type of the data in the source. + /// An enumerable data source. + /// A cancellation token that may be used to cancel the for each operation. + /// An asynchronous delegate that is invoked once per element in the data source. + /// The argument or argument is . + /// A task that represents the entire for each operation. + /// The operation will execute at most operations in parallel. + public static Task ForEachAsync(IEnumerable source, CancellationToken cancellationToken, Func body) + { +#if false + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(body); +#endif + + return ForEachAsync(source, DefaultDegreeOfParallelism, TaskScheduler.Default, cancellationToken, body); + } + + /// Executes a for each operation on an in which iterations may run in parallel. + /// The type of the data in the source. + /// An enumerable data source. + /// An object that configures the behavior of this operation. + /// An asynchronous delegate that is invoked once per element in the data source. + /// The argument or argument is . + /// A task that represents the entire for each operation. + public static Task ForEachAsync(IEnumerable source, ParallelOptions parallelOptions, Func body) + { +#if false + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(parallelOptions); + ArgumentNullException.ThrowIfNull(body); + + return ForEachAsync(source, parallelOptions.EffectiveMaxConcurrencyLevel, parallelOptions.EffectiveTaskScheduler, parallelOptions.CancellationToken, body); +#else + return ForEachAsync(source, EffectiveMaxConcurrencyLevel(parallelOptions), EffectiveTaskScheduler(parallelOptions), parallelOptions.CancellationToken, body); +#endif + } + + // Copied from https://github.com/dotnet/runtime/blob/6f18b5ef46a8fbc6675b07d4b256c35b36fc4e3c/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/Parallel.cs#L64 + // Convenience property used by TPL logic + private static TaskScheduler EffectiveTaskScheduler(ParallelOptions options) => options.TaskScheduler ?? TaskScheduler.Current; + + private static int EffectiveMaxConcurrencyLevel(ParallelOptions options) + { + int rval = options.MaxDegreeOfParallelism; + int schedulerMax = EffectiveTaskScheduler(options).MaximumConcurrencyLevel; + if ((schedulerMax > 0) && (schedulerMax != int.MaxValue)) + { + rval = (rval == -1) ? schedulerMax : Math.Min(schedulerMax, rval); + } + return rval; + } + + /// Executes a for each operation on an in which iterations may run in parallel. + /// The type of the data in the source. + /// An enumerable data source. + /// A integer indicating how many operations to allow to run in parallel. + /// The task scheduler on which all code should execute. + /// A cancellation token that may be used to cancel the for each operation. + /// An asynchronous delegate that is invoked once per element in the data source. + /// The argument or argument is . + /// A task that represents the entire for each operation. + private static Task ForEachAsync(IEnumerable source, int dop, TaskScheduler scheduler, CancellationToken cancellationToken, Func body) + { + Debug.Assert(source != null); + Debug.Assert(scheduler != null); + Debug.Assert(body != null); + + // One fast up-front check for cancellation before we start the whole operation. + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + // The worker body. Each worker will execute this same body. + Func taskBody = static async o => + { + var state = (SyncForEachAsyncState)o; + bool launchedNext = false; + +#pragma warning disable CA2007 // Explicitly don't use ConfigureAwait, as we want to perform all work on the specified scheduler that's now current + try + { + // Continue to loop while there are more elements to be processed. + while (!state.Cancellation.IsCancellationRequested) + { + // Get the next element from the enumerator. This requires asynchronously locking around MoveNext/Current. + TSource element; + await state.AcquireLock(); + try + { + if (state.Cancellation.IsCancellationRequested || // check now that the lock has been acquired + !state.Enumerator.MoveNext()) + { + break; + } + + element = state.Enumerator.Current; + } + finally + { + state.ReleaseLock(); + } + + // If the remaining dop allows it and we've not yet queued the next worker, do so now. We wait + // until after we've grabbed an item from the enumerator to a) avoid unnecessary contention on the + // serialized resource, and b) avoid queueing another work if there aren't any more items. Each worker + // is responsible only for creating the next worker, which in turn means there can't be any contention + // on creating workers (though it's possible one worker could be executing while we're creating the next). + if (!launchedNext) + { + launchedNext = true; + state.QueueWorkerIfDopAvailable(); + } + + // Process the loop body. + await state.LoopBody(element, state.Cancellation.Token); + } + } + catch (Exception e) + { + // Record the failure and then don't let the exception propagate. The last worker to complete + // will propagate exceptions as is appropriate to the top-level task. + state.RecordException(e); + } + finally + { + // If we're the last worker to complete, clean up and complete the operation. + if (state.SignalWorkerCompletedIterating()) + { + try + { + state.Dispose(); + } + catch (Exception e) + { + state.RecordException(e); + } + + // Finally, complete the task returned to the ForEachAsync caller. + // This must be the very last thing done. + state.Complete(); + } + } +#pragma warning restore CA2007 + }; + + try + { + // Construct a state object that encapsulates all state to be passed and shared between + // the workers, and queues the first worker. + var state = new SyncForEachAsyncState(source, taskBody, dop, scheduler, cancellationToken, body); + state.QueueWorkerIfDopAvailable(); + return state.Task; + } + catch (Exception e) + { + return Task.FromException(e); + } + } + + /// Executes a for each operation on an in which iterations may run in parallel. + /// The type of the data in the source. + /// An asynchronous enumerable data source. + /// An asynchronous delegate that is invoked once per element in the data source. + /// The argument or argument is . + /// A task that represents the entire for each operation. + /// The operation will execute at most operations in parallel. + public static Task ForEachAsync(IAsyncEnumerable source, Func body) + { +#if false + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(body); +#endif + + return ForEachAsync(source, DefaultDegreeOfParallelism, TaskScheduler.Default, default(CancellationToken), body); + } + + /// Executes a for each operation on an in which iterations may run in parallel. + /// The type of the data in the source. + /// An asynchronous enumerable data source. + /// A cancellation token that may be used to cancel the for each operation. + /// An asynchronous delegate that is invoked once per element in the data source. + /// The argument or argument is . + /// A task that represents the entire for each operation. + /// The operation will execute at most operations in parallel. + public static Task ForEachAsync(IAsyncEnumerable source, CancellationToken cancellationToken, Func body) + { +#if false + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(body); +#endif + + return ForEachAsync(source, DefaultDegreeOfParallelism, TaskScheduler.Default, cancellationToken, body); + } + + /// Executes a for each operation on an in which iterations may run in parallel. + /// The type of the data in the source. + /// An asynchronous enumerable data source. + /// An object that configures the behavior of this operation. + /// An asynchronous delegate that is invoked once per element in the data source. + /// The argument or argument is . + /// A task that represents the entire for each operation. + public static Task ForEachAsync(IAsyncEnumerable source, ParallelOptions parallelOptions, Func body) + { +#if false + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(parallelOptions); + ArgumentNullException.ThrowIfNull(body); +#endif + +#if false + return ForEachAsync(source, parallelOptions.EffectiveMaxConcurrencyLevel, parallelOptions.EffectiveTaskScheduler, parallelOptions.CancellationToken, body); +#else + return ForEachAsync(source, EffectiveMaxConcurrencyLevel(parallelOptions), EffectiveTaskScheduler(parallelOptions), parallelOptions.CancellationToken, body); +#endif + } + + /// Executes a for each operation on an in which iterations may run in parallel. + /// The type of the data in the source. + /// An asynchronous enumerable data source. + /// A integer indicating how many operations to allow to run in parallel. + /// The task scheduler on which all code should execute. + /// A cancellation token that may be used to cancel the for each operation. + /// An asynchronous delegate that is invoked once per element in the data source. + /// The argument or argument is . + /// A task that represents the entire for each operation. + private static Task ForEachAsync(IAsyncEnumerable source, int dop, TaskScheduler scheduler, CancellationToken cancellationToken, Func body) + { + Debug.Assert(source != null); + Debug.Assert(scheduler != null); + Debug.Assert(body != null); + + // One fast up-front check for cancellation before we start the whole operation. + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + // The worker body. Each worker will execute this same body. + Func taskBody = static async o => + { + var state = (AsyncForEachAsyncState)o; + bool launchedNext = false; + +#pragma warning disable CA2007 // Explicitly don't use ConfigureAwait, as we want to perform all work on the specified scheduler that's now current + try + { + // Continue to loop while there are more elements to be processed. + while (!state.Cancellation.IsCancellationRequested) + { + // Get the next element from the enumerator. This requires asynchronously locking around MoveNextAsync/Current. + TSource element; + await state.AcquireLock(); + try + { + if (state.Cancellation.IsCancellationRequested || // check now that the lock has been acquired + !await state.Enumerator.MoveNextAsync()) + { + break; + } + + element = state.Enumerator.Current; + } + finally + { + state.ReleaseLock(); + } + + // If the remaining dop allows it and we've not yet queued the next worker, do so now. We wait + // until after we've grabbed an item from the enumerator to a) avoid unnecessary contention on the + // serialized resource, and b) avoid queueing another work if there aren't any more items. Each worker + // is responsible only for creating the next worker, which in turn means there can't be any contention + // on creating workers (though it's possible one worker could be executing while we're creating the next). + if (!launchedNext) + { + launchedNext = true; + state.QueueWorkerIfDopAvailable(); + } + + // Process the loop body. + await state.LoopBody(element, state.Cancellation.Token); + } + } + catch (Exception e) + { + // Record the failure and then don't let the exception propagate. The last worker to complete + // will propagate exceptions as is appropriate to the top-level task. + state.RecordException(e); + } + finally + { + // If we're the last worker to complete, clean up and complete the operation. + if (state.SignalWorkerCompletedIterating()) + { + try + { + await state.DisposeAsync(); + } + catch (Exception e) + { + state.RecordException(e); + } + + // Finally, complete the task returned to the ForEachAsync caller. + // This must be the very last thing done. + state.Complete(); + } + } +#pragma warning restore CA2007 + }; + + try + { + // Construct a state object that encapsulates all state to be passed and shared between + // the workers, and queues the first worker. + var state = new AsyncForEachAsyncState(source, taskBody, dop, scheduler, cancellationToken, body); + state.QueueWorkerIfDopAvailable(); + return state.Task; + } + catch (Exception e) + { + return Task.FromException(e); + } + } + + /// Gets the default degree of parallelism to use when none is explicitly provided. + private static int DefaultDegreeOfParallelism => Environment.ProcessorCount; + + /// Stores the state associated with a ForEachAsync operation, shared between all its workers. + /// Specifies the type of data being enumerated. +#if false + private abstract class ForEachAsyncState : TaskCompletionSource, IThreadPoolWorkItem +#else + private abstract class ForEachAsyncState : TaskCompletionSource +#endif + { + /// The caller-provided cancellation token. + private readonly CancellationToken _externalCancellationToken; + /// Registration with caller-provided cancellation token. + protected readonly CancellationTokenRegistration _registration; + /// + /// The delegate to invoke on each worker to run the enumerator processing loop. + /// + /// + /// This could have been an action rather than a func, but it returns a task so that the task body is an async Task + /// method rather than async void, even though the worker body catches all exceptions and the returned Task is ignored. + /// + private readonly Func _taskBody; + /// The on which all work should be performed. + private readonly TaskScheduler _scheduler; +#if false + /// The present at the time of the ForEachAsync invocation. This is only used if on the default scheduler. + private readonly ExecutionContext? _executionContext; +#endif + /// Semaphore used to provide exclusive access to the enumerator. + private readonly SemaphoreSlim? _lock; + + /// The number of outstanding workers. When this hits 0, the operation has completed. + private int _completionRefCount; + /// Any exceptions incurred during execution. + private List? _exceptions; + /// The number of workers that may still be created. + private int _remainingDop; + + /// The delegate to invoke for each element yielded by the enumerator. + public readonly Func LoopBody; + /// The internal token source used to cancel pending work. + public readonly CancellationTokenSource Cancellation = new CancellationTokenSource(); + + /// Initializes the state object. + protected ForEachAsyncState(Func taskBody, bool needsLock, int dop, TaskScheduler scheduler, CancellationToken cancellationToken, Func body) + { + _taskBody = taskBody; + _lock = needsLock ? new SemaphoreSlim(initialCount: 1, maxCount: 1) : null; + _remainingDop = dop < 0 ? DefaultDegreeOfParallelism : dop; + LoopBody = body; + _scheduler = scheduler; + +#if false + if (scheduler == TaskScheduler.Default) + { + _executionContext = ExecutionContext.Capture(); + } +#endif + + _externalCancellationToken = cancellationToken; +#if false + _registration = cancellationToken.UnsafeRegister(static o => ((ForEachAsyncState)o!).Cancellation.Cancel(), this); +#else + _registration = cancellationToken.Register(static o => ((ForEachAsyncState)o!).Cancellation.Cancel(), this); +#endif + } + + /// Queues another worker if allowed by the remaining degree of parallelism permitted. + /// This is not thread-safe and must only be invoked by one worker at a time. + public void QueueWorkerIfDopAvailable() + { + if (_remainingDop > 0) + { + _remainingDop--; + + // Queue the invocation of the worker/task body. Note that we explicitly do not pass a cancellation token here, + // as the task body is what's responsible for completing the ForEachAsync task, for decrementing the reference count + // on pending tasks, and for cleaning up state. If a token were passed to StartNew (which simply serves to stop the + // task from starting to execute if it hasn't yet by the time cancellation is requested), all of that logic could be + // skipped, and bad things could ensue, e.g. deadlocks, leaks, etc. Also note that we need to increment the pending + // work item ref count prior to queueing the worker in order to avoid race conditions that could lead to temporarily + // and erroneously bouncing at zero, which would trigger completion too early. + Interlocked.Increment(ref _completionRefCount); +#if false + if (_scheduler == TaskScheduler.Default) + { + // If the scheduler is the default, we can avoid the overhead of the StartNew Task by just queueing + // this state object as the work item. + ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false); + } + else +#endif + { + // We're targeting a non-default TaskScheduler, so queue the task body to it. + System.Threading.Tasks.Task.Factory.StartNew(_taskBody!, this, default(CancellationToken), TaskCreationOptions.DenyChildAttach, _scheduler); + } + } + } + + /// Signals that the worker has completed iterating. + /// true if this is the last worker to complete iterating; otherwise, false. + public bool SignalWorkerCompletedIterating() => Interlocked.Decrement(ref _completionRefCount) == 0; + + /// Asynchronously acquires exclusive access to the enumerator. + public Task AcquireLock() + { + // We explicitly don't pass this.Cancellation to WaitAsync. Doing so adds overhead, and it isn't actually + // necessary. All of the operations that monitor the lock are part of the same ForEachAsync operation, and the Task + // returned from ForEachAsync can't complete until all of the constituent operations have completed, including whoever + // holds the lock while this worker is waiting on the lock. Thus, the lock will need to be released for the overall + // operation to complete. Passing the token would allow the overall operation to potentially complete a bit faster in + // the face of cancellation, in exchange for making it a bit slower / more overhead in the common case of cancellation + // not being requested. We want to optimize for the latter. This also then avoids an exception throw / catch when + // cancellation is requested. + Debug.Assert(_lock is not null, "Should only be invoked when _lock is non-null"); + return _lock.WaitAsync(CancellationToken.None); + } + + /// Relinquishes exclusive access to the enumerator. + public void ReleaseLock() + { + Debug.Assert(_lock is not null, "Should only be invoked when _lock is non-null"); + _lock.Release(); + } + + /// Stores an exception and triggers cancellation in order to alert all workers to stop as soon as possible. + /// The exception. + public void RecordException(Exception e) + { + // Store the exception. + lock (this) + { + (_exceptions ??= new List()).Add(e); + } + + // Trigger cancellation of all workers. If cancellation has already been triggered + // due to a previous exception occurring, this is a nop. + try + { + Cancellation.Cancel(); + } + catch (AggregateException ae) + { + // If cancellation callbacks erroneously throw exceptions, include those exceptions in the list. + lock (this) + { + _exceptions.AddRange(ae.InnerExceptions); + } + } + } + + /// Completes the ForEachAsync task based on the status of this state object. + public void Complete() + { + Debug.Assert(_completionRefCount == 0, $"Expected {nameof(_completionRefCount)} == 0, got {_completionRefCount}"); + + bool taskSet; + if (_externalCancellationToken.IsCancellationRequested) + { + // The externally provided token had cancellation requested. Assume that any exceptions + // then are due to that, and just cancel the resulting task. + taskSet = TrySetCanceled(_externalCancellationToken); + } + else if (_exceptions is null) + { + // Everything completed successfully. + Debug.Assert(!Cancellation.IsCancellationRequested); +#if false + taskSet = TrySetResult(); +#else + taskSet = TrySetResult(default(VoidResult)); +#endif + } + else + { + // Fail the task with the resulting exceptions. The first should be the initial + // exception that triggered the operation to shut down. The others, if any, may + // include cancellation exceptions from other concurrent operations being canceled + // in response to the primary exception. + taskSet = TrySetException(_exceptions); + } + + Debug.Assert(taskSet, "Complete should only be called once."); + } + +#if false + /// Executes the task body using the captured when ForEachAsync was invoked. + void IThreadPoolWorkItem.Execute() + { + Debug.Assert(_scheduler == TaskScheduler.Default, $"Expected {nameof(_scheduler)} == TaskScheduler.Default, got {_scheduler}"); + + if (_executionContext is null) + { + _taskBody(this); + } + else + { + ExecutionContext.Run(_executionContext, static o => ((ForEachAsyncState)o!)._taskBody(o), this); + } + } +#endif + } + + /// Stores the state associated with an IEnumerable ForEachAsync operation, shared between all its workers. + /// Specifies the type of data being enumerated. + private sealed class SyncForEachAsyncState : ForEachAsyncState, IDisposable + { + public readonly IEnumerator Enumerator; + + public SyncForEachAsyncState( + IEnumerable source, Func taskBody, + int dop, TaskScheduler scheduler, CancellationToken cancellationToken, + Func body) : + base(taskBody, needsLock: true, dop, scheduler, cancellationToken, body) + { +#if false + Enumerator = source.GetEnumerator() ?? throw new InvalidOperationException(SR.Parallel_ForEach_NullEnumerator); +#else + Enumerator = source.GetEnumerator() ?? throw new InvalidOperationException(); +#endif + } + + public void Dispose() + { + _registration.Dispose(); + Enumerator.Dispose(); + } + } + + /// Stores the state associated with an IAsyncEnumerable ForEachAsync operation, shared between all its workers. + /// Specifies the type of data being enumerated. + private sealed class AsyncForEachAsyncState : ForEachAsyncState, IAsyncDisposable + { + public readonly IAsyncEnumerator Enumerator; + + public AsyncForEachAsyncState( + IAsyncEnumerable source, Func taskBody, + int dop, TaskScheduler scheduler, CancellationToken cancellationToken, + Func body) : + base(taskBody, needsLock: true, dop, scheduler, cancellationToken, body) + { +#if false + Enumerator = source.GetAsyncEnumerator(Cancellation.Token) ?? throw new InvalidOperationException(SR.Parallel_ForEach_NullEnumerator); +#else + Enumerator = source.GetAsyncEnumerator(Cancellation.Token) ?? throw new InvalidOperationException(); +#endif + } + + public ValueTask DisposeAsync() + { + _registration.Dispose(); + return Enumerator.DisposeAsync(); + } + } + + /// Stores the state associated with an IAsyncEnumerable ForEachAsync operation, shared between all its workers. + /// Specifies the type of data being enumerated. + private sealed class ForEachState : ForEachAsyncState, IDisposable + { + public T NextAvailable; + public readonly T ToExclusive; + + public ForEachState( + T fromExclusive, T toExclusive, Func taskBody, + bool needsLock, int dop, TaskScheduler scheduler, CancellationToken cancellationToken, + Func body) : + base(taskBody, needsLock, dop, scheduler, cancellationToken, body) + { + NextAvailable = fromExclusive; + ToExclusive = toExclusive; + } + + public void Dispose() => _registration.Dispose(); + } + } +} + +#endif diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs b/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs new file mode 100644 index 0000000000000..5760eec512e1d --- /dev/null +++ b/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs @@ -0,0 +1,66 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.Shared.Utilities; + +#pragma warning disable CA1068 // CancellationToken parameters must come last + +internal static partial class RoslynParallel +{ + // For all these helpers, we defer to the native .net core version if we're on .net core. Otherwise, we defer to + // our ported version of that code when on .net framework. + + public static Task ForEachAsync( + IEnumerable source, + CancellationToken cancellationToken, + Func body) + { +#if NET + return Parallel.ForEachAsync(source, cancellationToken, body); +#else + return NetFramework.ForEachAsync(source, cancellationToken, body); +#endif + } + + public static Task ForEachAsync( + IEnumerable source, + ParallelOptions parallelOptions, + Func body) + { +#if NET + return Parallel.ForEachAsync(source, parallelOptions, body); +#else + return NetFramework.ForEachAsync(source, parallelOptions, body); +#endif + } + + public static Task ForEachAsync( + IAsyncEnumerable source, + CancellationToken cancellationToken, + Func body) + { +#if NET + return Parallel.ForEachAsync(source, cancellationToken, body); +#else + return NetFramework.ForEachAsync(source, cancellationToken, body); +#endif + } + + public static Task ForEachAsync( + IAsyncEnumerable source, + ParallelOptions parallelOptions, + Func body) + { +#if NET + return Parallel.ForEachAsync(source, parallelOptions, body); +#else + return NetFramework.ForEachAsync(source, parallelOptions, body); +#endif + } +} diff --git a/src/Workspaces/Core/Portable/Simplification/AbstractSimplificationService.cs b/src/Workspaces/Core/Portable/Simplification/AbstractSimplificationService.cs index 3a3bf94dfb4a5..f852e4a0ef741 100644 --- a/src/Workspaces/Core/Portable/Simplification/AbstractSimplificationService.cs +++ b/src/Workspaces/Core/Portable/Simplification/AbstractSimplificationService.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -184,102 +185,109 @@ private async Task ReduceAsync( Contract.ThrowIfFalse(nodesAndTokensToReduce.Any()); - // Reduce each node or token in the given list by running it through each reducer. - var simplifyTasks = new Task[nodesAndTokensToReduce.Length]; - for (var i = 0; i < nodesAndTokensToReduce.Length; i++) + if (executeSerially) { - var nodeOrTokenToReduce = nodesAndTokensToReduce[i]; - simplifyTasks[i] = Task.Run(async () => - { - var nodeOrToken = nodeOrTokenToReduce.OriginalNodeOrToken; - var simplifyAllDescendants = nodeOrTokenToReduce.SimplifyAllDescendants; - var semanticModelForReduce = semanticModel; - var currentNodeOrToken = nodeOrTokenToReduce.NodeOrToken; - var isNode = nodeOrToken.IsNode; + foreach (var nodeOrTokenToReduce in nodesAndTokensToReduce) + await ReduceOneNodeOrTokenAsync(nodeOrTokenToReduce, cancellationToken).ConfigureAwait(false); + } + else + { + await RoslynParallel.ForEachAsync( + source: nodesAndTokensToReduce, + cancellationToken, + ReduceOneNodeOrTokenAsync).ConfigureAwait(false); + } - foreach (var reducer in reducers) - { - cancellationToken.ThrowIfCancellationRequested(); + return; + + async ValueTask ReduceOneNodeOrTokenAsync( + NodeOrTokenToReduce nodeOrTokenToReduce, CancellationToken cancellationToken) + { + // Reduce each node or token in the given list by running it through each reducer. - using var rewriter = reducer.GetOrCreateRewriter(); - rewriter.Initialize(document.Project.ParseOptions, options, cancellationToken); + var nodeOrToken = nodeOrTokenToReduce.OriginalNodeOrToken; + var simplifyAllDescendants = nodeOrTokenToReduce.SimplifyAllDescendants; + var semanticModelForReduce = semanticModel; + var currentNodeOrToken = nodeOrTokenToReduce.NodeOrToken; + var isNode = nodeOrToken.IsNode; - do + foreach (var reducer in reducers) + { + cancellationToken.ThrowIfCancellationRequested(); + + using var rewriter = reducer.GetOrCreateRewriter(); + rewriter.Initialize(document.Project.ParseOptions, options, cancellationToken); + + do + { + if (currentNodeOrToken.SyntaxTree != semanticModelForReduce.SyntaxTree) { - if (currentNodeOrToken.SyntaxTree != semanticModelForReduce.SyntaxTree) + // currentNodeOrToken was simplified either by a previous reducer or + // a previous iteration of the current reducer. + // Create a speculative semantic model for the simplified node for semantic queries. + + // Certain node kinds (expressions/statements) require non-null parent nodes during simplification. + // However, the reduced nodes haven't been parented yet, so do the required parenting using the original node's parent. + if (currentNodeOrToken.Parent == null && + nodeOrToken.Parent != null && + (currentNodeOrToken.IsToken || + currentNodeOrToken.AsNode() is TExpressionSyntax || + currentNodeOrToken.AsNode() is TStatementSyntax || + currentNodeOrToken.AsNode() is TCrefSyntax)) { - // currentNodeOrToken was simplified either by a previous reducer or - // a previous iteration of the current reducer. - // Create a speculative semantic model for the simplified node for semantic queries. - - // Certain node kinds (expressions/statements) require non-null parent nodes during simplification. - // However, the reduced nodes haven't been parented yet, so do the required parenting using the original node's parent. - if (currentNodeOrToken.Parent == null && - nodeOrToken.Parent != null && - (currentNodeOrToken.IsToken || - currentNodeOrToken.AsNode() is TExpressionSyntax || - currentNodeOrToken.AsNode() is TStatementSyntax || - currentNodeOrToken.AsNode() is TCrefSyntax)) - { - var annotation = new SyntaxAnnotation(); - currentNodeOrToken = currentNodeOrToken.WithAdditionalAnnotations(annotation); + var annotation = new SyntaxAnnotation(); + currentNodeOrToken = currentNodeOrToken.WithAdditionalAnnotations(annotation); - var replacedParent = isNode - ? nodeOrToken.Parent.ReplaceNode(nodeOrToken.AsNode()!, currentNodeOrToken.AsNode()!) - : nodeOrToken.Parent.ReplaceToken(nodeOrToken.AsToken(), currentNodeOrToken.AsToken()); + var replacedParent = isNode + ? nodeOrToken.Parent.ReplaceNode(nodeOrToken.AsNode()!, currentNodeOrToken.AsNode()!) + : nodeOrToken.Parent.ReplaceToken(nodeOrToken.AsToken(), currentNodeOrToken.AsToken()); - currentNodeOrToken = replacedParent - .ChildNodesAndTokens() - .Single(c => c.HasAnnotation(annotation)); - } + currentNodeOrToken = replacedParent + .ChildNodesAndTokens() + .Single(c => c.HasAnnotation(annotation)); + } - if (isNode) + if (isNode) + { + var currentNode = currentNodeOrToken.AsNode()!; + if (this.NodeRequiresNonSpeculativeSemanticModel(nodeOrToken.AsNode()!)) { - var currentNode = currentNodeOrToken.AsNode()!; - if (this.NodeRequiresNonSpeculativeSemanticModel(nodeOrToken.AsNode()!)) - { - // Since this node cannot be speculated, we are replacing the Document with the changes and get a new SemanticModel - var marker = new SyntaxAnnotation(); - var newRoot = root.ReplaceNode(nodeOrToken.AsNode()!, currentNode.WithAdditionalAnnotations(marker)); - var newDocument = document.WithSyntaxRoot(newRoot); - semanticModelForReduce = await newDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - newRoot = await semanticModelForReduce.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - currentNodeOrToken = newRoot.DescendantNodes().Single(c => c.HasAnnotation(marker)); - } - else - { - // Create speculative semantic model for simplified node. - semanticModelForReduce = GetSpeculativeSemanticModel(ref currentNode, semanticModel, nodeOrToken.AsNode()!); - currentNodeOrToken = currentNode; - } + // Since this node cannot be speculated, we are replacing the Document with the changes and get a new SemanticModel + var marker = new SyntaxAnnotation(); + var newRoot = root.ReplaceNode(nodeOrToken.AsNode()!, currentNode.WithAdditionalAnnotations(marker)); + var newDocument = document.WithSyntaxRoot(newRoot); + semanticModelForReduce = await newDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + newRoot = await semanticModelForReduce.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); + currentNodeOrToken = newRoot.DescendantNodes().Single(c => c.HasAnnotation(marker)); + } + else + { + // Create speculative semantic model for simplified node. + semanticModelForReduce = GetSpeculativeSemanticModel(ref currentNode, semanticModel, nodeOrToken.AsNode()!); + currentNodeOrToken = currentNode; } } - - // Reduce the current node or token. - currentNodeOrToken = rewriter.VisitNodeOrToken(currentNodeOrToken, semanticModelForReduce, simplifyAllDescendants); } - while (rewriter.HasMoreWork); + + // Reduce the current node or token. + currentNodeOrToken = rewriter.VisitNodeOrToken(currentNodeOrToken, semanticModelForReduce, simplifyAllDescendants); } + while (rewriter.HasMoreWork); + } - // If nodeOrToken was simplified, add it to the appropriate dictionary of replaced nodes/tokens. - if (currentNodeOrToken != nodeOrToken) + // If nodeOrToken was simplified, add it to the appropriate dictionary of replaced nodes/tokens. + if (currentNodeOrToken != nodeOrToken) + { + if (isNode) { - if (isNode) - { - reducedNodesMap[nodeOrToken.AsNode()!] = currentNodeOrToken.AsNode()!; - } - else - { - reducedTokensMap[nodeOrToken.AsToken()] = currentNodeOrToken.AsToken(); - } + reducedNodesMap[nodeOrToken.AsNode()!] = currentNodeOrToken.AsNode()!; } - }, cancellationToken); - - if (executeSerially) - await simplifyTasks[i].ConfigureAwait(false); + else + { + reducedTokensMap[nodeOrToken.AsToken()] = currentNodeOrToken.AsToken(); + } + } } - - await Task.WhenAll(simplifyTasks).ConfigureAwait(false); } // find any namespace imports / using directives marked for simplification in the specified spans diff --git a/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs b/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs index 0e6feed57a96e..15a9ef2cd4aa3 100644 --- a/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs @@ -17,19 +17,12 @@ namespace Microsoft.CodeAnalysis.Storage; /// A service that enables storing and retrieving of information associated with solutions, /// projects or documents across runtime sessions. /// -internal abstract partial class AbstractPersistentStorageService : IChecksummedPersistentStorageService +internal abstract partial class AbstractPersistentStorageService(IPersistentStorageConfiguration configuration) : IChecksummedPersistentStorageService { - protected readonly IPersistentStorageConfiguration Configuration; + protected readonly IPersistentStorageConfiguration Configuration = configuration; - /// - /// This lock guards all mutable fields in this type. - /// private readonly SemaphoreSlim _lock = new(initialCount: 1); - private ReferenceCountedDisposable? _currentPersistentStorage; - private SolutionId? _currentPersistentStorageSolutionId; - - protected AbstractPersistentStorageService(IPersistentStorageConfiguration configuration) - => Configuration = configuration; + private IChecksummedPersistentStorage? _currentPersistentStorage; protected abstract string GetDatabaseFilePath(string workingFolderPath); @@ -38,88 +31,65 @@ protected AbstractPersistentStorageService(IPersistentStorageConfiguration confi /// to delete the database and retry opening one more time. If that fails again, the instance will be used. /// - protected abstract ValueTask TryOpenDatabaseAsync(SolutionKey solutionKey, string workingFolderPath, string databaseFilePath, CancellationToken cancellationToken); - protected abstract bool ShouldDeleteDatabase(Exception exception); + protected abstract ValueTask TryOpenDatabaseAsync(SolutionKey solutionKey, string workingFolderPath, string databaseFilePath, IPersistentStorageFaultInjector? faultInjector, CancellationToken cancellationToken); public ValueTask GetStorageAsync(SolutionKey solutionKey, CancellationToken cancellationToken) - { - return solutionKey.FilePath == null - ? new(NoOpPersistentStorage.GetOrThrow(Configuration.ThrowOnFailure)) - : GetStorageWorkerAsync(solutionKey, cancellationToken); - } + => GetStorageAsync(solutionKey, this.Configuration, faultInjector: null, cancellationToken); - internal async ValueTask GetStorageWorkerAsync(SolutionKey solutionKey, CancellationToken cancellationToken) + public async ValueTask GetStorageAsync( + SolutionKey solutionKey, + IPersistentStorageConfiguration configuration, + IPersistentStorageFaultInjector? faultInjector, + CancellationToken cancellationToken) { - using (await _lock.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) - { - // Do we already have storage for this? - if (solutionKey.Id == _currentPersistentStorageSolutionId) - { - // We do, great. Increment our ref count for our caller. They'll decrement it - // when done with it. - return PersistentStorageReferenceCountedDisposableWrapper.AddReferenceCountToAndCreateWrapper(_currentPersistentStorage!); - } - - var workingFolder = Configuration.TryGetStorageLocation(solutionKey); - if (workingFolder == null) - return NoOpPersistentStorage.GetOrThrow(Configuration.ThrowOnFailure); - - // If we already had some previous cached service, let's let it start cleaning up - if (_currentPersistentStorage != null) - { - var storageToDispose = _currentPersistentStorage; - - // Kick off a task to actually go dispose the previous cached storage instance. - // This will remove the single ref count we ourselves added when we cached the - // instance. Then once all other existing clients who are holding onto this - // instance let go, it will finally get truly disposed. - // This operation is not safe to cancel (as dispose must happen). - _ = Task.Run(storageToDispose.Dispose, CancellationToken.None); + if (solutionKey.FilePath == null) + return NoOpPersistentStorage.GetOrThrow(solutionKey, Configuration.ThrowOnFailure); - _currentPersistentStorage = null; - _currentPersistentStorageSolutionId = null; - } + // Without taking the lock, see if we can lookup a storage for this key. + var existing = _currentPersistentStorage; + if (existing?.SolutionKey == solutionKey) + return existing; - var storage = await CreatePersistentStorageAsync(solutionKey, workingFolder, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfNull(storage); + var workingFolder = configuration.TryGetStorageLocation(solutionKey); + if (workingFolder == null) + return NoOpPersistentStorage.GetOrThrow(solutionKey, Configuration.ThrowOnFailure); - // Create and cache a new storage instance associated with this particular solution. - // It will initially have a ref-count of 1 due to our reference to it. - _currentPersistentStorage = new ReferenceCountedDisposable(storage); - _currentPersistentStorageSolutionId = solutionKey.Id; + using (await _lock.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + { + // Recheck if we have a storage for this key after taking the lock. + if (_currentPersistentStorage?.SolutionKey != solutionKey) + _currentPersistentStorage = await CreatePersistentStorageAsync(solutionKey, workingFolder, faultInjector, cancellationToken).ConfigureAwait(false); - // Now increment the reference count and return to our caller. The current ref - // count for this instance will be 2. Until all the callers *and* us decrement - // the refcounts, this instance will not be actually disposed. - return PersistentStorageReferenceCountedDisposableWrapper.AddReferenceCountToAndCreateWrapper(_currentPersistentStorage); + return _currentPersistentStorage; } } private async ValueTask CreatePersistentStorageAsync( - SolutionKey solutionKey, string workingFolderPath, CancellationToken cancellationToken) + SolutionKey solutionKey, string workingFolderPath, IPersistentStorageFaultInjector? faultInjector, CancellationToken cancellationToken) { // Attempt to create the database up to two times. The first time we may encounter // some sort of issue (like DB corruption). We'll then try to delete the DB and can // try to create it again. If we can't create it the second time, then there's nothing // we can do and we have to store things in memory. - var result = await TryCreatePersistentStorageAsync(solutionKey, workingFolderPath, cancellationToken).ConfigureAwait(false) ?? - await TryCreatePersistentStorageAsync(solutionKey, workingFolderPath, cancellationToken).ConfigureAwait(false); + var result = await TryCreatePersistentStorageAsync(solutionKey, workingFolderPath, faultInjector, cancellationToken).ConfigureAwait(false) ?? + await TryCreatePersistentStorageAsync(solutionKey, workingFolderPath, faultInjector, cancellationToken).ConfigureAwait(false); if (result != null) return result; - return NoOpPersistentStorage.GetOrThrow(Configuration.ThrowOnFailure); + return NoOpPersistentStorage.GetOrThrow(solutionKey, Configuration.ThrowOnFailure); } private async ValueTask TryCreatePersistentStorageAsync( SolutionKey solutionKey, string workingFolderPath, + IPersistentStorageFaultInjector? faultInjector, CancellationToken cancellationToken) { var databaseFilePath = GetDatabaseFilePath(workingFolderPath); try { - return await TryOpenDatabaseAsync(solutionKey, workingFolderPath, databaseFilePath, cancellationToken).ConfigureAwait(false); + return await TryOpenDatabaseAsync(solutionKey, workingFolderPath, databaseFilePath, faultInjector, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (Recover(e)) { @@ -131,133 +101,14 @@ bool Recover(Exception ex) StorageDatabaseLogger.LogException(ex); if (Configuration.ThrowOnFailure) - { return false; - } - if (ShouldDeleteDatabase(ex)) - { - // this was not a normal exception that we expected during DB open. - // Report this so we can try to address whatever is causing this. - FatalError.ReportAndCatch(ex); - IOUtilities.PerformIO(() => Directory.Delete(Path.GetDirectoryName(databaseFilePath)!, recursive: true)); - } + // this was not a normal exception that we expected during DB open. + // Report this so we can try to address whatever is causing this. + FatalError.ReportAndCatch(ex); + IOUtilities.PerformIO(() => Directory.Delete(Path.GetDirectoryName(databaseFilePath)!, recursive: true)); return true; } } - - private void Shutdown() - { - ReferenceCountedDisposable? storage = null; - - lock (_lock) - { - // We will transfer ownership in a thread-safe way out so we can dispose outside the lock - storage = _currentPersistentStorage; - _currentPersistentStorage = null; - _currentPersistentStorageSolutionId = null; - } - - // Dispose storage outside of the lock. Note this only removes our reference count; clients who are still - // using this will still be holding a reference count. - storage?.Dispose(); - } - - internal TestAccessor GetTestAccessor() - => new(this); - - internal readonly struct TestAccessor(AbstractPersistentStorageService service) - { - public void Shutdown() - => service.Shutdown(); - } - - /// - /// A trivial wrapper that we can hand out for instances from the - /// that wraps the underlying singleton. - /// - private sealed class PersistentStorageReferenceCountedDisposableWrapper : IChecksummedPersistentStorage - { - private readonly ReferenceCountedDisposable _storage; - - private PersistentStorageReferenceCountedDisposableWrapper(ReferenceCountedDisposable storage) - => _storage = storage; - - public static IChecksummedPersistentStorage AddReferenceCountToAndCreateWrapper(ReferenceCountedDisposable storage) - { - // This should only be called from a caller that has a non-null storage that it - // already has a reference on. So .TryAddReference cannot fail. - return new PersistentStorageReferenceCountedDisposableWrapper(storage.TryAddReference() ?? throw ExceptionUtilities.Unreachable()); - } - - public void Dispose() - => _storage.Dispose(); - - public ValueTask DisposeAsync() - => _storage.DisposeAsync(); - - public Task ChecksumMatchesAsync(string name, Checksum checksum, CancellationToken cancellationToken) - => _storage.Target.ChecksumMatchesAsync(name, checksum, cancellationToken); - - public Task ChecksumMatchesAsync(Project project, string name, Checksum checksum, CancellationToken cancellationToken) - => _storage.Target.ChecksumMatchesAsync(project, name, checksum, cancellationToken); - - public Task ChecksumMatchesAsync(Document document, string name, Checksum checksum, CancellationToken cancellationToken) - => _storage.Target.ChecksumMatchesAsync(document, name, checksum, cancellationToken); - - public Task ChecksumMatchesAsync(ProjectKey project, string name, Checksum checksum, CancellationToken cancellationToken) - => _storage.Target.ChecksumMatchesAsync(project, name, checksum, cancellationToken); - - public Task ChecksumMatchesAsync(DocumentKey document, string name, Checksum checksum, CancellationToken cancellationToken) - => _storage.Target.ChecksumMatchesAsync(document, name, checksum, cancellationToken); - - public Task ReadStreamAsync(string name, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(name, cancellationToken); - - public Task ReadStreamAsync(Project project, string name, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(project, name, cancellationToken); - - public Task ReadStreamAsync(Document document, string name, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(document, name, cancellationToken); - - public Task ReadStreamAsync(string name, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(name, checksum, cancellationToken); - - public Task ReadStreamAsync(Project project, string name, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(project, name, checksum, cancellationToken); - - public Task ReadStreamAsync(Document document, string name, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(document, name, checksum, cancellationToken); - - public Task ReadStreamAsync(ProjectKey project, string name, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(project, name, checksum, cancellationToken); - - public Task ReadStreamAsync(DocumentKey document, string name, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.ReadStreamAsync(document, name, checksum, cancellationToken); - - public Task WriteStreamAsync(string name, Stream stream, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(name, stream, cancellationToken); - - public Task WriteStreamAsync(Project project, string name, Stream stream, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(project, name, stream, cancellationToken); - - public Task WriteStreamAsync(Document document, string name, Stream stream, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(document, name, stream, cancellationToken); - - public Task WriteStreamAsync(string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(name, stream, checksum, cancellationToken); - - public Task WriteStreamAsync(Project project, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(project, name, stream, checksum, cancellationToken); - - public Task WriteStreamAsync(Document document, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(document, name, stream, checksum, cancellationToken); - - public Task WriteStreamAsync(ProjectKey projectKey, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(projectKey, name, stream, checksum, cancellationToken); - - public Task WriteStreamAsync(DocumentKey documentKey, string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) - => _storage.Target.WriteStreamAsync(documentKey, name, stream, checksum, cancellationToken); - } } diff --git a/src/Workspaces/Core/Portable/Storage/LegacyPersistentStorageService.cs b/src/Workspaces/Core/Portable/Storage/LegacyPersistentStorageService.cs index e9a3d231cd09f..4418282eb1cb0 100644 --- a/src/Workspaces/Core/Portable/Storage/LegacyPersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Storage/LegacyPersistentStorageService.cs @@ -36,5 +36,5 @@ public LegacyPersistentStorageService() } public IPersistentStorage GetStorage(Solution solution) - => NoOpPersistentStorage.GetOrThrow(throwOnFailure: false); + => NoOpPersistentStorage.GetOrThrow(SolutionKey.ToSolutionKey(solution), throwOnFailure: false); } diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/AbstractPersistentStorageService+SQLiteTestAccessor.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/AbstractPersistentStorageService+SQLiteTestAccessor.cs new file mode 100644 index 0000000000000..57f78ef598176 --- /dev/null +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/AbstractPersistentStorageService+SQLiteTestAccessor.cs @@ -0,0 +1,22 @@ +// 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 Microsoft.CodeAnalysis.SQLite.v2; + +namespace Microsoft.CodeAnalysis.Storage; + +internal partial class AbstractPersistentStorageService +{ + internal TestAccessor GetTestAccessor() + => new(this); + + internal readonly struct TestAccessor(AbstractPersistentStorageService service) + { + public void Shutdown() + { + (service._currentPersistentStorage as SQLitePersistentStorage)?.DatabaseOwnership.Dispose(); + service._currentPersistentStorage = null; + } + } +} diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs index 440b6409cd496..c710a86dd43d2 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/Interop/SqlConnection.cs @@ -21,16 +21,16 @@ namespace Microsoft.CodeAnalysis.SQLite.v2.Interop; /// Encapsulates a connection to a sqlite database. On construction an attempt will be made /// to open the DB if it exists, or create it if it does not. /// -/// Connections are considered relatively heavyweight and are pooled until the -/// is d. Connections can be used by different threads, -/// but only as long as they are used by one thread at a time. They are not safe for concurrent -/// use by several threads. +/// Connections are considered relatively heavyweight and are pooled (see ). Connections can be used by different +/// threads, but only as long as they are used by one thread at a time. They are not safe for concurrent use by several +/// threads. /// /// s can be created through the user of . /// These statements are cached for the lifetime of the connection and are only finalized /// (i.e. destroyed) when the connection is closed. /// -internal class SqlConnection +internal sealed class SqlConnection { // Cached UTF-8 (and null terminated) versions of the common strings we need to pass to sqlite. Used to prevent // having to convert these names to/from utf16 to UTF-8 on every call. Sqlite requires these be null terminated. diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool+PooledConnection.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool+PooledConnection.cs deleted file mode 100644 index 1b3750e7a9289..0000000000000 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool+PooledConnection.cs +++ /dev/null @@ -1,19 +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.SQLite.v2.Interop; - -namespace Microsoft.CodeAnalysis.SQLite.v2; - -internal partial class SQLiteConnectionPool -{ - internal readonly struct PooledConnection(SQLiteConnectionPool connectionPool, SqlConnection sqlConnection) : IDisposable - { - public readonly SqlConnection Connection = sqlConnection; - - public void Dispose() - => connectionPool.ReleaseConnection(Connection); - } -} diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPoolService.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPoolService.cs deleted file mode 100644 index bafe6a92c2e1f..0000000000000 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPoolService.cs +++ /dev/null @@ -1,159 +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 System.IO; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Microsoft.CodeAnalysis.SQLite.v2.Interop; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.SQLite.v2; - -[Export] -[Shared] -internal sealed class SQLiteConnectionPoolService : IDisposable -{ - private const string LockFile = "db.lock"; - - private readonly object _gate = new(); - - /// - /// Maps from database file path to connection pool. - /// - /// - /// Access to this field is synchronized through . - /// - private readonly Dictionary> _connectionPools = []; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public SQLiteConnectionPoolService() - { - } - - /// - /// Use a to simulate a reader-writer lock. - /// Read operations are performed on the - /// and writes are performed on the . - /// - /// We use this as a condition of using the in-memory shared-cache sqlite DB. This DB - /// doesn't busy-wait when attempts are made to lock the tables in it, which can lead to - /// deadlocks. Specifically, consider two threads doing the following: - /// - /// Thread A starts a transaction that starts as a reader, and later attempts to perform a - /// write. Thread B is a writer (either started that way, or started as a reader and - /// promoted to a writer first). B holds a RESERVED lock, waiting for readers to clear so it - /// can start writing. A holds a SHARED lock (it's a reader) and tries to acquire RESERVED - /// lock (so it can start writing). The only way to make progress in this situation is for - /// one of the transactions to roll back. No amount of waiting will help, so when SQLite - /// detects this situation, it doesn't honor the busy timeout. - /// - /// To prevent this scenario, we control our access to the db explicitly with operations that - /// can concurrently read, and operations that exclusively write. - /// - /// All code that reads or writes from the db should go through this. - /// - public ConcurrentExclusiveSchedulerPair Scheduler { get; } = new(); - - public void Dispose() - { - lock (_gate) - { - foreach (var (_, pool) in _connectionPools) - pool.Dispose(); - - _connectionPools.Clear(); - } - } - - public ReferenceCountedDisposable? TryOpenDatabase( - string databaseFilePath, - IPersistentStorageFaultInjector? faultInjector, - Action initializer, - CancellationToken cancellationToken) - { - lock (_gate) - { - if (_connectionPools.TryGetValue(databaseFilePath, out var pool)) - { - return pool.TryAddReference() ?? throw ExceptionUtilities.Unreachable(); - } - - // try to get db ownership lock. if someone else already has the lock. it will throw - var ownershipLock = TryGetDatabaseOwnership(databaseFilePath); - if (ownershipLock == null) - { - return null; - } - - try - { - pool = new ReferenceCountedDisposable( - new SQLiteConnectionPool(this, faultInjector, databaseFilePath, ownershipLock)); - - pool.Target.Initialize(initializer, cancellationToken); - - // Place the initial ownership reference in _connectionPools, and return another - _connectionPools.Add(databaseFilePath, pool); - return pool.TryAddReference() ?? throw ExceptionUtilities.Unreachable(); - } - catch (Exception ex) when (FatalError.ReportAndCatchUnlessCanceled(ex, cancellationToken)) - { - if (pool is not null) - { - // Dispose of the connection pool, releasing the ownership lock. - pool.Dispose(); - } - else - { - // The storage was not created so nothing owns the lock. - // Dispose the lock to allow reuse. - ownershipLock.Dispose(); - } - - throw; - } - } - } - - /// - /// Returns null in the case where an IO exception prevented us from being able to acquire - /// the db lock file. - /// - private static IDisposable? TryGetDatabaseOwnership(string databaseFilePath) - { - return IOUtilities.PerformIO(() => - { - // make sure directory exist first. - EnsureDirectory(databaseFilePath); - - var directoryName = Path.GetDirectoryName(databaseFilePath); - Contract.ThrowIfNull(directoryName); - - return File.Open( - Path.Combine(directoryName, LockFile), - FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); - }, defaultValue: null); - } - - private static void EnsureDirectory(string databaseFilePath) - { - var directory = Path.GetDirectoryName(databaseFilePath); - Contract.ThrowIfNull(directory); - - if (Directory.Exists(directory)) - { - return; - } - - Directory.CreateDirectory(directory); - } -} diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage+PooledConnection.cs similarity index 55% rename from src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool.cs rename to src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage+PooledConnection.cs index f961393cb00e5..bc0cd558919b9 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage+PooledConnection.cs @@ -3,61 +3,20 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SQLite.v2.Interop; namespace Microsoft.CodeAnalysis.SQLite.v2; -internal sealed partial class SQLiteConnectionPool(SQLiteConnectionPoolService connectionPoolService, IPersistentStorageFaultInjector? faultInjector, string databasePath, IDisposable ownershipLock) : IDisposable +internal partial class SQLitePersistentStorage { - // We pool connections to the DB so that we don't have to take the hit of - // reconnecting. The connections also cache the prepared statements used - // to get/set data from the db. A connection is safe to use by one thread - // at a time, but is not safe for simultaneous use by multiple threads. - private readonly object _connectionGate = new(); - private readonly Stack _connectionsPool = new(); - - private readonly CancellationTokenSource _shutdownTokenSource = new(); - - internal void Initialize( - Action initializer, - CancellationToken cancellationToken) - { - // This is our startup path. No other code can be running. So it's safe for us to access a connection that - // can talk to the db without having to be on the reader/writer scheduler queue. - using var _ = GetPooledConnection(checkScheduler: false, out var connection); - - initializer(connection, cancellationToken); - } - - public void Dispose() + private readonly struct PooledConnection(SQLitePersistentStorage storage, SqlConnection sqlConnection) : IDisposable { - // Flush all pending writes so that all data our features wanted written - // are definitely persisted to the DB. - try - { - _shutdownTokenSource.Cancel(); - CloseWorker(); - } - finally - { - // let the lock go - ownershipLock.Dispose(); - } - } + public readonly SqlConnection Connection = sqlConnection; - private void CloseWorker() - { - lock (_connectionGate) - { - // Go through all our pooled connections and close them. - while (_connectionsPool.TryPop(out var connection)) - connection.Close_OnlyForUseBySQLiteConnectionPool(); - } + public void Dispose() + => storage.ReleaseConnection(Connection); } /// @@ -68,7 +27,7 @@ private void CloseWorker() /// longer in use. In particular, make sure to avoid letting a connection lease cross an /// boundary, as it will prevent code in the asynchronous operation from using the existing connection. /// - internal PooledConnection GetPooledConnection(out SqlConnection connection) + private PooledConnection GetPooledConnection(out SqlConnection connection) => GetPooledConnection(checkScheduler: true, out connection); /// @@ -81,8 +40,8 @@ private PooledConnection GetPooledConnection(bool checkScheduler, out SqlConnect if (checkScheduler) { var scheduler = TaskScheduler.Current; - if (scheduler != connectionPoolService.Scheduler.ConcurrentScheduler && scheduler != connectionPoolService.Scheduler.ExclusiveScheduler) - throw new InvalidOperationException($"Cannot get a connection to the DB unless running on one of {nameof(SQLiteConnectionPoolService)}'s schedulers"); + if (scheduler != this.Scheduler.ConcurrentScheduler && scheduler != this.Scheduler.ExclusiveScheduler) + throw new InvalidOperationException($"Cannot get a connection to the DB unless running on one of {nameof(SQLitePersistentStorage)}'s schedulers"); } var result = new PooledConnection(this, GetConnection()); @@ -100,7 +59,7 @@ private SqlConnection GetConnection() } // Otherwise create a new connection. - return SqlConnection.Create(faultInjector, databasePath); + return SqlConnection.Create(_faultInjector, this.DatabaseFile); } private void ReleaseConnection(SqlConnection connection) diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.Accessor.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.Accessor.cs index 4ea118171b220..0765d555aa450 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.Accessor.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.Accessor.cs @@ -163,13 +163,13 @@ private Optional ReadColumn( // We're reading. All current scenarios have this happening under the concurrent/read-only scheduler. // If this assert fires either a bug has been introduced, or there is a valid scenario for a writing // codepath to read a column and this assert should be adjusted. - Contract.ThrowIfFalse(TaskScheduler.Current == Storage._connectionPoolService.Scheduler.ConcurrentScheduler); + Contract.ThrowIfFalse(TaskScheduler.Current == this.Storage.Scheduler.ConcurrentScheduler); cancellationToken.ThrowIfCancellationRequested(); if (!Storage._shutdownTokenSource.IsCancellationRequested) { - using var _ = Storage._connectionPool.Target.GetPooledConnection(out var connection); + using var _ = this.Storage.GetPooledConnection(out var connection); // We're in the reading-only scheduler path, so we can't allow TryGetDatabaseId to write. Note that // this is ok, and actually provides the semantics we want. Specifically, we can be trying to read @@ -222,13 +222,13 @@ public Task WriteStreamAsync(TKey key, string name, Stream stream, Checksu private bool WriteStream(TKey key, string dataName, Stream stream, Checksum? checksum, CancellationToken cancellationToken) { // We're writing. This better always be under the exclusive scheduler. - Contract.ThrowIfFalse(TaskScheduler.Current == Storage._connectionPoolService.Scheduler.ExclusiveScheduler); + Contract.ThrowIfFalse(TaskScheduler.Current == this.Storage.Scheduler.ExclusiveScheduler); cancellationToken.ThrowIfCancellationRequested(); if (!Storage._shutdownTokenSource.IsCancellationRequested) { - using var _ = Storage._connectionPool.Target.GetPooledConnection(out var connection); + using var _ = this.Storage.GetPooledConnection(out var connection); // Determine the appropriate data-id to store this stream at. We already are running // with an exclusive write lock on the DB, so it's safe for us to write the data id to @@ -361,7 +361,7 @@ private void InsertOrReplaceBlobIntoWriteCache( ReadOnlySpan dataBytes) { // We're writing. This better always be under the exclusive scheduler. - Contract.ThrowIfFalse(TaskScheduler.Current == Storage._connectionPoolService.Scheduler.ExclusiveScheduler); + Contract.ThrowIfFalse(TaskScheduler.Current == this.Storage.Scheduler.ExclusiveScheduler); using (var resettableStatement = connection.GetResettableStatement( _insert_or_replace_into_writecache_table_values_0primarykey_1checksum_2data)) diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs index 5d31591fd3d4a..9ea33b38eff0f 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.SQLite.Interop; using Microsoft.CodeAnalysis.SQLite.v2.Interop; using Microsoft.CodeAnalysis.Storage; @@ -23,15 +25,30 @@ namespace Microsoft.CodeAnalysis.SQLite.v2; /// internal sealed partial class SQLitePersistentStorage : AbstractPersistentStorage { + private const string LockFile = "db.lock"; + private readonly CancellationTokenSource _shutdownTokenSource = new(); - private readonly SolutionKey _solutionKey; private readonly string _solutionDirectory; - private readonly SQLiteConnectionPoolService _connectionPoolService; - private readonly ReferenceCountedDisposable _connectionPool; + // We pool connections to the DB so that we don't have to take the hit of + // reconnecting. The connections also cache the prepared statements used + // to get/set data from the db. A connection is safe to use by one thread + // at a time, but is not safe for simultaneous use by multiple threads. + private readonly object _connectionGate = new(); + private readonly Stack _connectionsPool = new(); private readonly Action _flushInMemoryDataToDisk; + /// + /// Lock file that ensures only one database is made per process per solution. + /// + public readonly IDisposable DatabaseOwnership; + + /// + /// For testing purposes. Allows us to test what happens when we fail to acquire the db lock file. + /// + private readonly IPersistentStorageFaultInjector? _faultInjector; + // Accessors that allow us to retrieve/store data into specific DB tables. The // core Accessor type has logic that we to share across all reading/writing, while // the derived types contain only enough logic to specify how to read/write from @@ -47,31 +64,48 @@ internal sealed partial class SQLitePersistentStorage : AbstractPersistentStorag private readonly string _select_star_from_string_table_where_0_limit_one = $"select * from {StringInfoTableName} where ({DataColumnName} = ?) limit 1"; private readonly string _select_star_from_string_table = $"select * from {StringInfoTableName}"; + /// + /// Use a to simulate a reader-writer lock. + /// Read operations are performed on the + /// and writes are performed on the . + /// + /// We use this as a condition of using the in-memory shared-cache sqlite DB. This DB + /// doesn't busy-wait when attempts are made to lock the tables in it, which can lead to + /// deadlocks. Specifically, consider two threads doing the following: + /// + /// Thread A starts a transaction that starts as a reader, and later attempts to perform a + /// write. Thread B is a writer (either started that way, or started as a reader and + /// promoted to a writer first). B holds a RESERVED lock, waiting for readers to clear so it + /// can start writing. A holds a SHARED lock (it's a reader) and tries to acquire RESERVED + /// lock (so it can start writing). The only way to make progress in this situation is for + /// one of the transactions to roll back. No amount of waiting will help, so when SQLite + /// detects this situation, it doesn't honor the busy timeout. + /// + /// To prevent this scenario, we control our access to the db explicitly with operations that + /// can concurrently read, and operations that exclusively write. + /// + /// All code that reads or writes from the db should go through this. + /// + private ConcurrentExclusiveSchedulerPair Scheduler { get; } = new(); + private SQLitePersistentStorage( - SQLiteConnectionPoolService connectionPoolService, SolutionKey solutionKey, string workingFolderPath, string databaseFile, IAsynchronousOperationListener asyncListener, - IPersistentStorageFaultInjector? faultInjector) - : base(workingFolderPath, solutionKey.FilePath!, databaseFile) + IPersistentStorageFaultInjector? faultInjector, + IDisposable databaseOwnership) + : base(solutionKey, workingFolderPath, databaseFile) { Contract.ThrowIfNull(solutionKey.FilePath); - _solutionKey = solutionKey; _solutionDirectory = PathUtilities.GetDirectoryName(solutionKey.FilePath); - _connectionPoolService = connectionPoolService; _solutionAccessor = new SolutionAccessor(this); _projectAccessor = new ProjectAccessor(this); _documentAccessor = new DocumentAccessor(this); - // This assignment violates the declared non-nullability of _connectionPool, but the caller ensures that - // the constructed object is only used if the nullability post-conditions are met. - _connectionPool = connectionPoolService.TryOpenDatabase( - databaseFile, - faultInjector, - Initialize, - CancellationToken.None)!; + _faultInjector = faultInjector; + DatabaseOwnership = databaseOwnership; // Create a delay to batch up requests to flush. We'll won't flush more than every FlushAllDelayMS. _flushInMemoryDataToDisk = FlushInMemoryDataToDisk; @@ -83,49 +117,52 @@ private SQLitePersistentStorage( } public static SQLitePersistentStorage? TryCreate( - SQLiteConnectionPoolService connectionPoolService, SolutionKey solutionKey, string workingFolderPath, string databaseFile, IAsynchronousOperationListener asyncListener, IPersistentStorageFaultInjector? faultInjector) { - var sqlStorage = new SQLitePersistentStorage( - connectionPoolService, solutionKey, workingFolderPath, databaseFile, asyncListener, faultInjector); - if (sqlStorage._connectionPool is null) - { - // The connection pool failed to initialize + var databaseOwnership = TryGetDatabaseOwnership(databaseFile); + if (databaseOwnership is null) return null; - } - return sqlStorage; + var storage = new SQLitePersistentStorage( + solutionKey, workingFolderPath, databaseFile, asyncListener, faultInjector, databaseOwnership); + storage.Initialize(); + return storage; } - public override void Dispose() + /// + /// Returns null in the case where an IO exception prevented us from being able to acquire + /// the db lock file. + /// + private static IDisposable? TryGetDatabaseOwnership(string databaseFilePath) { - var task = DisposeAsync().AsTask(); - task.Wait(); - } + return IOUtilities.PerformIO(() => + { + // make sure directory exist first. + EnsureDirectory(databaseFilePath); - public override async ValueTask DisposeAsync() - { - try + var directoryName = Path.GetDirectoryName(databaseFilePath); + Contract.ThrowIfNull(directoryName); + + return File.Open( + Path.Combine(directoryName, LockFile), + FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); + }, defaultValue: null); + + static void EnsureDirectory(string databaseFilePath) { - // Flush all pending writes so that all data our features wanted written are definitely - // persisted to the DB. - try - { - await FlushWritesOnCloseAsync().ConfigureAwait(false); - } - catch (Exception e) + var directory = Path.GetDirectoryName(databaseFilePath); + Contract.ThrowIfNull(directory); + + if (Directory.Exists(directory)) { - // Flushing may fail. We still have to close all our connections. - StorageDatabaseLogger.LogException(e); + return; } - } - finally - { - _connectionPool.Dispose(); + + Directory.CreateDirectory(directory); } } @@ -142,17 +179,11 @@ public static KeyValueLogMessage GetLogMessage(SqlException exception) d["Message"] = exception.Message; }); - private void Initialize(SqlConnection connection, CancellationToken cancellationToken) + private void Initialize() { - if (cancellationToken.IsCancellationRequested) - { - // Someone tried to get a connection *after* a call to Dispose the storage system - // happened. That should never happen. We only Dispose when the last ref to the - // storage system goes away. Once that happens, it's an error for there to be any - // future or existing consumers of the storage service. So nothing should be doing - // anything that wants to get an connection. - throw new InvalidOperationException(); - } + // This is our startup path. No other code can be running. So it's safe for us to access a connection that can + // talk to the db without having to be on the reader/writer scheduler queue. + using var _ = GetPooledConnection(checkScheduler: false, out var connection); // Ensure the database has tables for the types we care about. diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs index fe7686516eb3d..9f1570f00f3b6 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs @@ -17,7 +17,6 @@ namespace Microsoft.CodeAnalysis.SQLite.v2; internal sealed class SQLitePersistentStorageService( - SQLiteConnectionPoolService connectionPoolService, IPersistentStorageConfiguration configuration, IAsynchronousOperationListener asyncListener) : AbstractPersistentStorageService(configuration), IWorkspaceService { @@ -25,13 +24,12 @@ internal sealed class SQLitePersistentStorageService( [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class ServiceFactory( - SQLiteConnectionPoolService connectionPoolService, IAsynchronousOperationListenerProvider asyncOperationListenerProvider) : IWorkspaceServiceFactory { private readonly IAsynchronousOperationListener _asyncListener = asyncOperationListenerProvider.GetListener(FeatureAttribute.PersistentStorage); public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new SQLitePersistentStorageService(connectionPoolService, workspaceServices.GetRequiredService(), _asyncListener); + => new SQLitePersistentStorageService(workspaceServices.GetRequiredService(), _asyncListener); } private const string StorageExtension = "sqlite3"; @@ -61,18 +59,6 @@ private static bool TryInitializeLibrariesLazy() return true; } - private readonly IPersistentStorageFaultInjector? _faultInjector; - - public SQLitePersistentStorageService( - SQLiteConnectionPoolService connectionPoolService, - IPersistentStorageConfiguration configuration, - IAsynchronousOperationListener asyncListener, - IPersistentStorageFaultInjector? faultInjector) - : this(connectionPoolService, configuration, asyncListener) - { - _faultInjector = faultInjector; - } - protected override string GetDatabaseFilePath(string workingFolderPath) { Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(workingFolderPath)); @@ -80,7 +66,7 @@ protected override string GetDatabaseFilePath(string workingFolderPath) } protected override ValueTask TryOpenDatabaseAsync( - SolutionKey solutionKey, string workingFolderPath, string databaseFilePath, CancellationToken cancellationToken) + SolutionKey solutionKey, string workingFolderPath, string databaseFilePath, IPersistentStorageFaultInjector? faultInjector, CancellationToken cancellationToken) { if (!TryInitializeLibraries()) { @@ -89,17 +75,13 @@ protected override string GetDatabaseFilePath(string workingFolderPath) } if (solutionKey.FilePath == null) - return new(NoOpPersistentStorage.GetOrThrow(Configuration.ThrowOnFailure)); + return new(NoOpPersistentStorage.GetOrThrow(solutionKey, Configuration.ThrowOnFailure)); return new(SQLitePersistentStorage.TryCreate( - connectionPoolService, solutionKey, workingFolderPath, databaseFilePath, asyncListener, - _faultInjector)); + faultInjector)); } - - // Error occurred when trying to open this DB. Try to remove it so we can create a good DB. - protected override bool ShouldDeleteDatabase(Exception exception) => true; } diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs index 1988badfbe5db..e0d6ca9943104 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs @@ -29,47 +29,17 @@ private async ValueTask FlushInMemoryDataToDiskIfNotShutdownAsync(CancellationTo await PerformWriteAsync(_flushInMemoryDataToDisk, cancellationToken).ConfigureAwait(false); } - private Task FlushWritesOnCloseAsync() - { - // Issue a write task to write this all out to disk. - // - // Note: this only happens on close, so we don't try to avoid allocations here. - - return PerformWriteAsync( - () => - { - // Perform the actual write while having exclusive access to the scheduler. - FlushInMemoryDataToDisk(); - - // Now that we've done this, definitely cancel any further work. From this point on, it is now - // invalid for any codepaths to try to acquire a db connection for any purpose (beyond us - // disposing things below). - // - // This will also ensure that if we have a bg flush task still pending, when it wakes up it will - // see that we're shutdown and not proceed (and importantly won't acquire a connection). Because - // both the bg task and us run serialized, there is no way for it to miss this token - // cancellation. If it runs after us, then it sees this. If it runs before us, then we just - // block until it finishes. - // - // We don't have to worry about reads/writes getting connections either. - // The only way we can get disposed in the first place is if every user of this storage instance - // has released their ref on us. In that case, it would be an error on their part to ever try to - // read/write after releasing us. - _shutdownTokenSource.Cancel(); - }, CancellationToken.None); - } - private void FlushInMemoryDataToDisk() { // We're writing. This better always be under the exclusive scheduler. - Contract.ThrowIfFalse(TaskScheduler.Current == _connectionPoolService.Scheduler.ExclusiveScheduler); + Contract.ThrowIfFalse(TaskScheduler.Current == this.Scheduler.ExclusiveScheduler); // Don't flush from a bg task if we've been asked to shutdown. The shutdown logic in the storage service // will take care of the final writes to the main db. if (_shutdownTokenSource.IsCancellationRequested) return; - using var _ = _connectionPool.Target.GetPooledConnection(out var connection); + using var _ = this.GetPooledConnection(out var connection); var exception = connection.RunInTransaction(static state => { diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_SolutionSerialization.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_SolutionSerialization.cs index e17c3f8591134..5e4ef383324fb 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_SolutionSerialization.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_SolutionSerialization.cs @@ -16,13 +16,13 @@ namespace Microsoft.CodeAnalysis.SQLite.v2; internal partial class SQLitePersistentStorage { public override Task ChecksumMatchesAsync(string name, Checksum checksum, CancellationToken cancellationToken) - => _solutionAccessor.ChecksumMatchesAsync(_solutionKey, name, checksum, cancellationToken); + => _solutionAccessor.ChecksumMatchesAsync(this.SolutionKey, name, checksum, cancellationToken); public override Task ReadStreamAsync(string name, Checksum? checksum, CancellationToken cancellationToken) - => _solutionAccessor.ReadStreamAsync(_solutionKey, name, checksum, cancellationToken); + => _solutionAccessor.ReadStreamAsync(this.SolutionKey, name, checksum, cancellationToken); public override Task WriteStreamAsync(string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) - => _solutionAccessor.WriteStreamAsync(_solutionKey, name, stream, checksum, cancellationToken); + => _solutionAccessor.WriteStreamAsync(this.SolutionKey, name, stream, checksum, cancellationToken); private readonly record struct SolutionPrimaryKey(); diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_StringIds.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_StringIds.cs index be276efa9a39a..c9c4e8c126b2c 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_StringIds.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_StringIds.cs @@ -47,8 +47,8 @@ internal partial class SQLitePersistentStorage { // We're reading or writing. This can be under either of our schedulers. Contract.ThrowIfFalse( - TaskScheduler.Current == _connectionPoolService.Scheduler.ExclusiveScheduler || - TaskScheduler.Current == _connectionPoolService.Scheduler.ConcurrentScheduler); + TaskScheduler.Current == this.Scheduler.ExclusiveScheduler || + TaskScheduler.Current == this.Scheduler.ConcurrentScheduler); // First, check if we can find that string in the string table. var stringId = TryGetStringIdFromDatabaseWorker(connection, value, canReturnNull: true); @@ -65,7 +65,7 @@ internal partial class SQLitePersistentStorage return null; // We're writing. This better always be under the exclusive scheduler. - Contract.ThrowIfFalse(TaskScheduler.Current == _connectionPoolService.Scheduler.ExclusiveScheduler); + Contract.ThrowIfFalse(TaskScheduler.Current == this.Scheduler.ExclusiveScheduler); // The string wasn't in the db string table. Add it. Note: this may fail if some // other thread/process beats us there as this table has a 'unique' constraint on the diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs index 72edf6a941550..0ff47ae62e10f 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs @@ -38,7 +38,7 @@ private Task PerformReadAsync(Func func, // Dispose method that restores ExecutionContext flow must run on the same thread where SuppressFlow was // originally run. using var _ = FlowControlHelper.TrySuppressFlow(); - return PerformTaskAsync(func, arg, _connectionPoolService.Scheduler.ConcurrentScheduler, cancellationToken); + return PerformTaskAsync(func, arg, this.Scheduler.ConcurrentScheduler, cancellationToken); } // Write tasks go to the exclusive-scheduler so they run exclusively of all other threading @@ -53,7 +53,7 @@ public Task PerformWriteAsync(Func func, // Dispose method that restores ExecutionContext flow must run on the same thread where SuppressFlow was // originally run. using var _ = FlowControlHelper.TrySuppressFlow(); - return PerformTaskAsync(func, arg, _connectionPoolService.Scheduler.ExclusiveScheduler, cancellationToken); + return PerformTaskAsync(func, arg, this.Scheduler.ExclusiveScheduler, cancellationToken); } public Task PerformWriteAsync(Action action, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/SymbolSearch/ISymbolSearchUpdateEngine.cs b/src/Workspaces/Core/Portable/SymbolSearch/ISymbolSearchUpdateEngine.cs index 33a083cc0f12f..2c5f5529dff5d 100644 --- a/src/Workspaces/Core/Portable/SymbolSearch/ISymbolSearchUpdateEngine.cs +++ b/src/Workspaces/Core/Portable/SymbolSearch/ISymbolSearchUpdateEngine.cs @@ -4,6 +4,7 @@ #nullable disable +using System; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; @@ -14,7 +15,7 @@ namespace Microsoft.CodeAnalysis.SymbolSearch; /// Service that allows you to query the SymbolSearch database and which keeps /// the database up to date. /// -internal interface ISymbolSearchUpdateEngine +internal interface ISymbolSearchUpdateEngine : IDisposable { ValueTask UpdateContinuouslyAsync(string sourceName, string localSettingsDirectory, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.DirectMemoryAccessStreamReader.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.DirectMemoryAccessStreamReader.cs new file mode 100644 index 0000000000000..9918414654ea1 --- /dev/null +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.DirectMemoryAccessStreamReader.cs @@ -0,0 +1,76 @@ +// 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.Runtime.InteropServices; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Host; + +internal sealed partial class TemporaryStorageService +{ + private unsafe class DirectMemoryAccessStreamReader : TextReaderWithLength + { + private char* _position; + private readonly char* _end; + + public DirectMemoryAccessStreamReader(char* src, int length) + : base(length) + { + RoslynDebug.Assert(src != null); + RoslynDebug.Assert(length >= 0); + + _position = src; + _end = _position + length; + } + + public override int Peek() + { + if (_position >= _end) + { + return -1; + } + + return *_position; + } + + public override int Read() + { + if (_position >= _end) + { + return -1; + } + + return *_position++; + } + + public override int Read(char[] buffer, int index, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (index < 0 || index >= buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0 || (index + count) > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + count = Math.Min(count, (int)(_end - _position)); + if (count > 0) + { + Marshal.Copy((IntPtr)_position, buffer, index, count); + _position += count; + } + + return count; + } + } +} diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.Factory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.Factory.cs index 0d08d75a58210..e58f2b4935d22 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.Factory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.Factory.cs @@ -22,13 +22,7 @@ internal partial class Factory( public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { var textFactory = workspaceServices.GetRequiredService(); - - // MemoryMapped files which are used by the TemporaryStorageService are present in .NET Framework (including Mono) - // and .NET Core Windows. For non-Windows .NET Core scenarios, we can return the TrivialTemporaryStorageService - // until https://github.com/dotnet/runtime/issues/30878 is fixed. - return PlatformInformation.IsWindows || PlatformInformation.IsRunningOnMono - ? new TemporaryStorageService(workspaceThreadingService, textFactory) - : TrivialTemporaryStorageService.Instance; + return new TemporaryStorageService(workspaceThreadingService, textFactory); } } } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs index 7f6b5d80dc9e2..0e0c87e785c27 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs @@ -24,7 +24,7 @@ internal partial class TemporaryStorageService /// This class and its nested types have familiar APIs and predictable behavior when used in other code, but /// are non-trivial to work on. /// - internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string name, long offset, long size) + internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string? name, long offset, long size) { /// /// The memory mapped file. @@ -45,16 +45,14 @@ internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string /// private ReferenceCountedDisposable.WeakReference _weakReadAccessor; - public static MemoryMappedInfo OpenExisting(string name, long offset, long size) - => new(MemoryMappedFile.OpenExisting(name), name, offset, size); - - public static MemoryMappedInfo CreateNew(string name, long size) + public static MemoryMappedInfo CreateNew(string? name, long size) => new(MemoryMappedFile.CreateNew(name, size), name, offset: 0, size); /// - /// The name of the memory mapped file. + /// The name of the memory mapped file. Non null on systems that support named memory mapped files, null + /// otherwise.. /// - public string Name { get; } = name; + public string? Name { get; } = name; /// /// The offset into the memory mapped file of the region described by the current diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs index 62762d0ee869b..52d97d1bd762c 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs @@ -5,24 +5,28 @@ using System.IO; using System.IO.MemoryMappedFiles; using System.Threading; +using Microsoft.CodeAnalysis.Internal.Log; namespace Microsoft.CodeAnalysis.Host; internal sealed partial class TemporaryStorageService { public sealed class TemporaryStorageStreamHandle( - TemporaryStorageService storageService, MemoryMappedFile memoryMappedFile, TemporaryStorageIdentifier identifier) : ITemporaryStorageStreamHandle + MemoryMappedFile memoryMappedFile, + TemporaryStorageIdentifier identifier) + : ITemporaryStorageStreamHandle { public TemporaryStorageIdentifier Identifier => identifier; - Stream ITemporaryStorageStreamHandle.ReadFromTemporaryStorage(CancellationToken cancellationToken) - => ReadFromTemporaryStorage(cancellationToken); - public UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken) { - var storage = new TemporaryStreamStorage( - storageService, memoryMappedFile, this.Identifier.Name, this.Identifier.Offset, this.Identifier.Size); - return storage.ReadStream(cancellationToken); + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadStream, cancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + var info = new MemoryMappedInfo(memoryMappedFile, Identifier.Name, Identifier.Offset, Identifier.Size); + return info.CreateReadableStream(); + } } } } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index b537a951e38bd..56a205c39481b 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.IO; using System.IO.MemoryMappedFiles; -using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Threading; @@ -94,12 +93,48 @@ private TemporaryStorageService(IWorkspaceThreadingService? workspaceThreadingSe _textFactory = textFactory; } - public ITemporaryTextStorageInternal CreateTemporaryTextStorage() - => new TemporaryTextStorage(this); + ITemporaryStorageTextHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken) + => WriteToTemporaryStorage(text, cancellationToken); - public TemporaryTextStorage AttachTemporaryTextStorage( - string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) - => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); + async Task ITemporaryStorageServiceInternal.WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken) + => await WriteToTemporaryStorageAsync(text, cancellationToken).ConfigureAwait(false); + + public TemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken) + { + var memoryMappedInfo = WriteToMemoryMappedFile(); + var identifier = new TemporaryStorageIdentifier(memoryMappedInfo.Name, memoryMappedInfo.Offset, memoryMappedInfo.Size); + return new(this, memoryMappedInfo.MemoryMappedFile, identifier, text.ChecksumAlgorithm, text.Encoding, text.GetContentHash()); + + MemoryMappedInfo WriteToMemoryMappedFile() + { + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteText, cancellationToken)) + { + // the method we use to get text out of SourceText uses Unicode (2bytes per char). + var size = Encoding.Unicode.GetMaxByteCount(text.Length); + var memoryMappedInfo = this.CreateTemporaryStorage(size); + + // Write the source text out as Unicode. We expect that to be cheap. + using var stream = memoryMappedInfo.CreateWritableStream(); + { + using var writer = new StreamWriter(stream, Encoding.Unicode); + text.Write(writer, cancellationToken); + } + + return memoryMappedInfo; + } + } + } + + public async Task WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken) + { + if (this._workspaceThreadingService is { IsOnMainThread: true }) + { + await Task.Yield().ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } + + return WriteToTemporaryStorage(text, cancellationToken); + } ITemporaryStorageStreamHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) => WriteToTemporaryStorage(stream, cancellationToken); @@ -107,16 +142,51 @@ ITemporaryStorageStreamHandle ITemporaryStorageServiceInternal.WriteToTemporaryS public TemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { stream.Position = 0; - var storage = new TemporaryStreamStorage(this); - storage.WriteStream(stream, cancellationToken); - var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); - return new(this, storage.MemoryMappedInfo.MemoryMappedFile, identifier); + var memoryMappedInfo = WriteToMemoryMappedFile(); + var identifier = new TemporaryStorageIdentifier(memoryMappedInfo.Name, memoryMappedInfo.Offset, memoryMappedInfo.Size); + return new(memoryMappedInfo.MemoryMappedFile, identifier); + + MemoryMappedInfo WriteToMemoryMappedFile() + { + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteStream, cancellationToken)) + { + var size = stream.Length; + var memoryMappedInfo = this.CreateTemporaryStorage(size); + using var viewStream = memoryMappedInfo.CreateWritableStream(); + { + using var pooledObject = SharedPools.ByteArray.GetPooledObject(); + var buffer = pooledObject.Object; + while (true) + { + var count = stream.Read(buffer, 0, buffer.Length); + if (count == 0) + break; + + viewStream.Write(buffer, 0, count); + } + } + + return memoryMappedInfo; + } + } } - internal TemporaryStorageStreamHandle GetHandle(TemporaryStorageIdentifier storageIdentifier) + internal static TemporaryStorageStreamHandle GetStreamHandle(TemporaryStorageIdentifier storageIdentifier) { + Contract.ThrowIfNull(storageIdentifier.Name, $"{nameof(GetStreamHandle)} should only be called for VS on Windows (where named memory mapped files as supported)"); var memoryMappedFile = MemoryMappedFile.OpenExisting(storageIdentifier.Name); - return new(this, memoryMappedFile, storageIdentifier); + return new(memoryMappedFile, storageIdentifier); + } + + internal TemporaryStorageTextHandle GetTextHandle( + TemporaryStorageIdentifier storageIdentifier, + SourceHashAlgorithm checksumAlgorithm, + Encoding? encoding, + ImmutableArray contentHash) + { + Contract.ThrowIfNull(storageIdentifier.Name, $"{nameof(GetTextHandle)} should only be called for VS on Windows (where named memory mapped files as supported)"); + var memoryMappedFile = MemoryMappedFile.OpenExisting(storageIdentifier.Name); + return new(this, memoryMappedFile, storageIdentifier, checksumAlgorithm, encoding, contentHash); } /// @@ -154,85 +224,37 @@ private MemoryMappedInfo CreateTemporaryStorage(long size) else { // Reserve additional space in the existing storage location - Contract.ThrowIfNull(_name); _offset += size; return new MemoryMappedInfo(reference, _name, _offset - size, size); } } } - public static string CreateUniqueName(long size) - => "Roslyn Temp Storage " + size.ToString() + " " + Guid.NewGuid().ToString("N"); - - public sealed class TemporaryTextStorage : ITemporaryTextStorageInternal, ITemporaryStorageWithName + public static string? CreateUniqueName(long size) { - private readonly TemporaryStorageService _service; - private SourceHashAlgorithm _checksumAlgorithm; - private Encoding? _encoding; - private ImmutableArray _contentHash; - private MemoryMappedInfo? _memoryMappedInfo; - - public TemporaryTextStorage(TemporaryStorageService service) - => _service = service; - - public TemporaryTextStorage( - TemporaryStorageService service, - string storageName, - long offset, - long size, - SourceHashAlgorithm checksumAlgorithm, - Encoding? encoding, - ImmutableArray contentHash) - { - _service = service; - _checksumAlgorithm = checksumAlgorithm; - _encoding = encoding; - _contentHash = contentHash; - _memoryMappedInfo = MemoryMappedInfo.OpenExisting(storageName, offset, size); - } - - // TODO: cleanup https://github.com/dotnet/roslyn/issues/43037 - // Offset, Size not accessed if Name is null - public string? Name => _memoryMappedInfo?.Name; - public long Offset => _memoryMappedInfo!.Offset; - public long Size => _memoryMappedInfo!.Size; - - /// - /// Gets the value for the property for the - /// represented by this temporary storage. - /// - public SourceHashAlgorithm ChecksumAlgorithm => _checksumAlgorithm; - - /// - /// Gets the value for the property for the - /// represented by this temporary storage. - /// - public Encoding? Encoding => _encoding; - - /// - /// Gets the checksum for the represented by this temporary storage. This is equivalent - /// to calling . - /// - public ImmutableArray ContentHash => _contentHash; - - public SourceText ReadText(CancellationToken cancellationToken) - { - if (_memoryMappedInfo == null) - { - throw new InvalidOperationException(); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadText, cancellationToken)) - { - using var stream = _memoryMappedInfo.CreateReadableStream(); - using var reader = CreateTextReaderFromTemporaryStorage(stream); + // MemoryMapped files which are used by the TemporaryStorageService are present in .NET Framework (including + // Mono) and .NET Core Windows. For non-Windows .NET Core scenarios, we return null to enable create the memory + // mapped file (just not in a way that can be shared across processes). + return PlatformInformation.IsWindows || PlatformInformation.IsRunningOnMono + ? $"Roslyn Shared File: Size={size} Id={Guid.NewGuid():N}" + : null; + } - // we pass in encoding we got from original source text even if it is null. - return _service._textFactory.CreateText(reader, _encoding, _checksumAlgorithm, cancellationToken); - } - } + public sealed class TemporaryStorageTextHandle( + TemporaryStorageService storageService, + MemoryMappedFile memoryMappedFile, + TemporaryStorageIdentifier identifier, + SourceHashAlgorithm checksumAlgorithm, + Encoding? encoding, + ImmutableArray contentHash) + : ITemporaryStorageTextHandle + { + public TemporaryStorageIdentifier Identifier => identifier; + public SourceHashAlgorithm ChecksumAlgorithm => checksumAlgorithm; + public Encoding? Encoding => encoding; + public ImmutableArray ContentHash => contentHash; - public async Task ReadTextAsync(CancellationToken cancellationToken) + public async Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken) { // There is a reason for implementing it like this: proper async implementation // that reads the underlying memory mapped file stream in an asynchronous fashion @@ -244,53 +266,29 @@ public async Task ReadTextAsync(CancellationToken cancellationToken) // of a page fault. Therefore, if we're going to be blocking a thread, we should // just block one thread and do the whole thing at once vs. a fake "async" // implementation which will continue to requeue work back to the thread pool. - if (_service._workspaceThreadingService is { IsOnMainThread: true }) + if (storageService._workspaceThreadingService is { IsOnMainThread: true }) { await Task.Yield().ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); } - return ReadText(cancellationToken); + return ReadFromTemporaryStorage(cancellationToken); } - public void WriteText(SourceText text, CancellationToken cancellationToken) + public SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken) { - if (_memoryMappedInfo != null) - { - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteText, cancellationToken)) + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadText, cancellationToken)) { - _checksumAlgorithm = text.ChecksumAlgorithm; - _encoding = text.Encoding; - _contentHash = text.GetContentHash(); - - // the method we use to get text out of SourceText uses Unicode (2bytes per char). - var size = Encoding.Unicode.GetMaxByteCount(text.Length); - _memoryMappedInfo = _service.CreateTemporaryStorage(size); - - // Write the source text out as Unicode. We expect that to be cheap. - using var stream = _memoryMappedInfo.CreateWritableStream(); - using var writer = new StreamWriter(stream, Encoding.Unicode); - - text.Write(writer, cancellationToken); - } - } + var info = new MemoryMappedInfo(memoryMappedFile, Identifier.Name, Identifier.Offset, Identifier.Size); + using var stream = info.CreateReadableStream(); + using var reader = CreateTextReaderFromTemporaryStorage(stream); - public async Task WriteTextAsync(SourceText text, CancellationToken cancellationToken) - { - // See commentary in ReadTextAsync for why this is implemented this way. - if (_service._workspaceThreadingService is { IsOnMainThread: true }) - { - await Task.Yield().ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); + // we pass in encoding we got from original source text even if it is null. + return storageService._textFactory.CreateText(reader, encoding, checksumAlgorithm, cancellationToken); } - - WriteText(text, cancellationToken); } - private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedMemoryStream stream) + private static unsafe DirectMemoryAccessStreamReader CreateTextReaderFromTemporaryStorage(UnmanagedMemoryStream stream) { var src = (char*)stream.PositionPointer; @@ -301,128 +299,4 @@ private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedM return new DirectMemoryAccessStreamReader(src + 1, (int)stream.Length / sizeof(char) - 1); } } - - internal sealed class TemporaryStreamStorage - { - private readonly TemporaryStorageService _service; - private MemoryMappedInfo? _memoryMappedInfo; - - public TemporaryStreamStorage(TemporaryStorageService service) - => _service = service; - - public TemporaryStreamStorage( - TemporaryStorageService service, MemoryMappedFile file, string storageName, long offset, long size) - { - _service = service; - _memoryMappedInfo = new MemoryMappedInfo(file, storageName, offset, size); - } - - public MemoryMappedInfo MemoryMappedInfo => _memoryMappedInfo ?? throw new InvalidOperationException(); - - public string Name => this.MemoryMappedInfo.Name; - public long Offset => this.MemoryMappedInfo.Offset; - public long Size => this.MemoryMappedInfo.Size; - - public UnmanagedMemoryStream ReadStream(CancellationToken cancellationToken) - { - if (_memoryMappedInfo == null) - { - throw new InvalidOperationException(); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadStream, cancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - - return _memoryMappedInfo.CreateReadableStream(); - } - } - - public void WriteStream(Stream stream, CancellationToken cancellationToken) - { - if (_memoryMappedInfo != null) - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteStream, cancellationToken)) - { - var size = stream.Length; - _memoryMappedInfo = _service.CreateTemporaryStorage(size); - using var viewStream = _memoryMappedInfo.CreateWritableStream(); - - using var pooledObject = SharedPools.ByteArray.GetPooledObject(); - var buffer = pooledObject.Object; - while (true) - { - var count = stream.Read(buffer, 0, buffer.Length); - if (count == 0) - break; - - viewStream.Write(buffer, 0, count); - } - } - } - } -} - -internal unsafe class DirectMemoryAccessStreamReader : TextReaderWithLength -{ - private char* _position; - private readonly char* _end; - - public DirectMemoryAccessStreamReader(char* src, int length) - : base(length) - { - RoslynDebug.Assert(src != null); - RoslynDebug.Assert(length >= 0); - - _position = src; - _end = _position + length; - } - - public override int Peek() - { - if (_position >= _end) - { - return -1; - } - - return *_position; - } - - public override int Read() - { - if (_position >= _end) - { - return -1; - } - - return *_position++; - } - - public override int Read(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (index < 0 || index >= buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - if (count < 0 || (index + count) > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - count = Math.Min(count, (int)(_end - _position)); - if (count > 0) - { - Marshal.Copy((IntPtr)_position, buffer, index, count); - _position += count; - } - - return count; - } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs index cbefb3294b7c1..81913cc81c0cb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs @@ -13,8 +13,9 @@ namespace Microsoft.CodeAnalysis.Host; internal abstract class AbstractPersistentStorage : IChecksummedPersistentStorage { + public SolutionKey SolutionKey { get; } + public string WorkingFolderPath { get; } - public string SolutionFilePath { get; } public string DatabaseFile { get; } public string DatabaseDirectory => Path.GetDirectoryName(DatabaseFile) ?? throw ExceptionUtilities.UnexpectedValue(DatabaseFile); @@ -22,12 +23,12 @@ internal abstract class AbstractPersistentStorage : IChecksummedPersistentStorag private bool _isDisabled; protected AbstractPersistentStorage( + SolutionKey solutionKey, string workingFolderPath, - string solutionFilePath, string databaseFile) { + this.SolutionKey = solutionKey; this.WorkingFolderPath = workingFolderPath; - this.SolutionFilePath = solutionFilePath; this.DatabaseFile = databaseFile; if (!Directory.Exists(this.DatabaseDirectory)) @@ -42,9 +43,6 @@ private bool IsDisabled protected void DisableStorage() => Volatile.Write(ref _isDisabled, true); - public abstract void Dispose(); - public abstract ValueTask DisposeAsync(); - public abstract Task ChecksumMatchesAsync(string name, Checksum checksum, CancellationToken cancellationToken); public abstract Task ReadStreamAsync(string name, Checksum? checksum, CancellationToken cancellationToken); public abstract Task WriteStreamAsync(string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IChecksummedPersistentStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IChecksummedPersistentStorage.cs index 27512482e6872..a0feb6908ba70 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IChecksummedPersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IChecksummedPersistentStorage.cs @@ -12,6 +12,11 @@ namespace Microsoft.CodeAnalysis.Host; internal interface IChecksummedPersistentStorage : IPersistentStorage { + /// + /// The solution this is a storage instance for. + /// + SolutionKey SolutionKey { get; } + /// /// if the data we have for the solution with the given has the /// provided . diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs index 4b81585424cf5..9d5d44929fbc8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.Host; /// disposal should always be preferred as the implementation of synchronous disposal may end up blocking the caller /// on async work. /// -public interface IPersistentStorage : IDisposable, IAsyncDisposable +public interface IPersistentStorage { Task ReadStreamAsync(string name, CancellationToken cancellationToken = default); Task ReadStreamAsync(Project project, string name, CancellationToken cancellationToken = default); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorage.cs index 14840b4d8f5a9..c924931a44441 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorage.cs @@ -11,27 +11,14 @@ namespace Microsoft.CodeAnalysis.Host; -internal class NoOpPersistentStorage : IChecksummedPersistentStorage +internal class NoOpPersistentStorage(SolutionKey solutionKey) : IChecksummedPersistentStorage { - private static readonly IChecksummedPersistentStorage Instance = new NoOpPersistentStorage(); + public SolutionKey SolutionKey => solutionKey; - private NoOpPersistentStorage() - { - } - - public static IChecksummedPersistentStorage GetOrThrow(bool throwOnFailure) + public static IChecksummedPersistentStorage GetOrThrow(SolutionKey solutionKey, bool throwOnFailure) => throwOnFailure ? throw new InvalidOperationException("Database was not supported") - : Instance; - - public void Dispose() - { - } - - public ValueTask DisposeAsync() - { - return ValueTaskFactory.CompletedTask; - } + : new NoOpPersistentStorage(solutionKey); public Task ChecksumMatchesAsync(string name, Checksum checksum, CancellationToken cancellationToken) => SpecializedTasks.False; @@ -98,6 +85,6 @@ public Task WriteStreamAsync(DocumentKey documentKey, string name, Stream public readonly struct TestAccessor { - public static readonly IChecksummedPersistentStorage StorageInstance = Instance; + public static IChecksummedPersistentStorage GetStorageInstance(SolutionKey solutionKey) => new NoOpPersistentStorage(solutionKey); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs index b3015195fd3f3..5f5bc076f5df3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorageService.cs @@ -23,5 +23,5 @@ public static IChecksummedPersistentStorageService GetOrThrow(IPersistentStorage : Instance; public ValueTask GetStorageAsync(SolutionKey solutionKey, CancellationToken cancellationToken) - => new(NoOpPersistentStorage.GetOrThrow(throwOnFailure: false)); + => new(NoOpPersistentStorage.GetOrThrow(solutionKey, throwOnFailure: false)); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/SolutionKey.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/SolutionKey.cs index 13d48d4dd1aec..41ea22e495640 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/SolutionKey.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/SolutionKey.cs @@ -14,14 +14,10 @@ namespace Microsoft.CodeAnalysis.Storage; /// solution load), but querying the data is still desired. /// [DataContract] -internal readonly struct SolutionKey(SolutionId id, string? filePath) +internal readonly record struct SolutionKey( + [property: DataMember(Order = 0)] SolutionId Id, + [property: DataMember(Order = 1)] string? FilePath) { - [DataMember(Order = 0)] - public readonly SolutionId Id = id; - - [DataMember(Order = 1)] - public readonly string? FilePath = filePath; - public static SolutionKey ToSolutionKey(Solution solution) => ToSolutionKey(solution.SolutionState); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index aaac0a3ac1e6c..d458ba5ca481c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.Host; -[Obsolete("Roslyn no longer exports a mechanism to store arbitrary data in-memory.")] +[Obsolete("Roslyn no longer exports a mechanism to store arbitrary data in-memory.", error: true)] public interface ITemporaryTextStorage : IDisposable { SourceText ReadText(CancellationToken cancellationToken = default); @@ -21,7 +21,7 @@ public interface ITemporaryTextStorage : IDisposable Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); } -[Obsolete("Roslyn no longer exports a mechanism to store arbitrary data in-memory.")] +[Obsolete("Roslyn no longer exports a mechanism to store arbitrary data in-memory.", error: true)] public interface ITemporaryStreamStorage : IDisposable { Stream ReadStream(CancellationToken cancellationToken = default); @@ -29,14 +29,3 @@ public interface ITemporaryStreamStorage : IDisposable void WriteStream(Stream stream, CancellationToken cancellationToken = default); Task WriteStreamAsync(Stream stream, CancellationToken cancellationToken = default); } - -/// -/// TemporaryStorage can be used to read and write text to a temporary storage location. -/// -internal interface ITemporaryTextStorageInternal -{ - SourceText ReadText(CancellationToken cancellationToken = default); - Task ReadTextAsync(CancellationToken cancellationToken = default); - void WriteText(SourceText text, CancellationToken cancellationToken = default); - Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index b9d1f9d530efc..c19c7b99a852b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -5,10 +5,12 @@ using System; using System.IO; using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Host; -[Obsolete("API is no longer available")] +[Obsolete("API is no longer available", error: true)] public interface ITemporaryStorageService : IWorkspaceService { ITemporaryStreamStorage CreateTemporaryStreamStorage(CancellationToken cancellationToken = default); @@ -16,7 +18,13 @@ public interface ITemporaryStorageService : IWorkspaceService } /// -/// API to allow a client to write data to memory-mapped-file storage (allowing it to be shared across processes). +/// API to allow a client to write data to memory-mapped-file storage. That data can be read back in within the same +/// process using a handle returned from the writing call. The data can optionally be read back in from a different +/// process, using the information contained with the handle's Identifier (see ), but only on systems that support named memory mapped files. Currently, this +/// is any .net on Windows and mono on unix systems. This is not supported on .net core on unix systems (tracked here +/// https://github.com/dotnet/runtime/issues/30878). This is not a problem in practice as cross process sharing is only +/// needed by the VS host, which is windows only. /// internal interface ITemporaryStorageServiceInternal : IWorkspaceService { @@ -25,26 +33,26 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// be used to identify the data across processes allowing it to be read back in in any process. /// /// - /// This type is used for two purposes. - /// - /// - /// Dumping metadata to disk. This then allowing them to be read in by mapping their data into types like . It also allows them to be read in by our server process, without having to transmit - /// the data over the wire. For this use case, we never dispose of the handle, opting to keep things simple by - /// having the host and server not have to coordinate on the lifetime of the data. - /// - /// - /// Dumping large compiler command lines to disk to purge them from main memory. Some of these strings are enormous - /// (many MB large), and will get into the LOH. This allows us to dump the data, knowing we can perfectly - /// reconstruct it when needed. In this case, we do dispose of the handle, as we don't need to keep the data around - /// when we get the next large compiler command line. - /// - /// - /// Note: The stream provided must support . The stream will also be reset to 0 within this method. The caller does not need to reset the stream + /// This type is primarily used to allow dumping metadata to disk. This then allowing them to be read in by mapping + /// their data into types like . It also allows them to be read in by our server + /// process, without having to transmit the data over the wire. + /// Note: The stream provided must support . The stream will also be reset to + /// 0 within this method. The caller does not need to reset the stream /// itself. /// ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); - ITemporaryTextStorageInternal CreateTemporaryTextStorage(); + /// + /// Write the provided to a new memory-mapped-file. Returns a handle to the data that can + /// be used to identify the data across processes allowing it to be read back in in any process. + /// + /// + /// This type is primarily used to allow dumping source texts to disk. This then allowing them to be read in by + /// mapping their data into types like . It also allows them + /// to be read in by our server process, without having to transmit the data over the wire. + /// + ITemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken); + + /// "/> + Task WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs index e4689f2ba1381..a278f5a73ab30 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs @@ -10,9 +10,9 @@ namespace Microsoft.CodeAnalysis.Host; /// /// Represents a handle to data stored to temporary storage (generally a memory mapped file). As long as this handle is /// alive, the data should remain in storage and can be readable from any process using the information provided in . Use to write the data -/// to temporary storage and get a handle to it. Use to read the data back in -/// any process. +/// cref="Identifier"/>. Use to write the data to temporary storage and get a handle to it. Use to read the data back in any process. /// internal interface ITemporaryStorageStreamHandle { @@ -22,5 +22,5 @@ internal interface ITemporaryStorageStreamHandle /// Reads the data indicated to by this handle into a stream. This stream can be created in a different process /// than the one that wrote the data originally. /// - Stream ReadFromTemporaryStorage(CancellationToken cancellationToken); + UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs new file mode 100644 index 0000000000000..d2c1dcccc88b1 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Host; + +internal interface ITemporaryStorageTextHandle +{ + public TemporaryStorageIdentifier Identifier { get; } + + SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken); + Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken); +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs deleted file mode 100644 index 3d635adbe6c8d..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.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. - -namespace Microsoft.CodeAnalysis.Host; - -/// -/// TemporaryStorage can be used to read and write text to a temporary storage location. -/// -internal interface ITemporaryStorageWithName -{ - // TODO: clean up https://github.com/dotnet/roslyn/issues/43037 - // Name shouldn't be nullable. - - /// - /// Get name of the temporary storage - /// - string? Name { get; } - - /// - /// Get offset of the temporary storage - /// - long Offset { get; } - - /// - /// Get size of the temporary storage - /// - long Size { get; } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs index 2457297fca662..8ac99018bef6b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs @@ -2,21 +2,22 @@ // 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.Serialization; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host; /// -/// Identifier for a stream of data placed in a segment of temporary storage (generally a memory mapped file). Can be -/// used to identify that segment across processes, allowing for efficient sharing of data. +/// Identifier for a stream of data placed in a segment of a memory mapped file. Can be used to identify that segment +/// across processes (where supported), allowing for efficient sharing of data. /// +/// The name of the segment in the temporary storage. on platforms that don't +/// support cross process sharing of named memory mapped files. internal sealed record TemporaryStorageIdentifier( - string Name, long Offset, long Size) + string? Name, long Offset, long Size) { public static TemporaryStorageIdentifier ReadFrom(ObjectReader reader) => new( - reader.ReadRequiredString(), + reader.ReadString(), reader.ReadInt64(), reader.ReadInt64()); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs deleted file mode 100644 index 2b750b62a5807..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs +++ /dev/null @@ -1,22 +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.IO; -using System.Threading; -using Microsoft.CodeAnalysis.Host; - -namespace Microsoft.CodeAnalysis; - -internal sealed partial class TrivialTemporaryStorageService -{ - private sealed class TrivialStorageStreamHandle( - TemporaryStorageIdentifier storageIdentifier, - StreamStorage streamStorage) : ITemporaryStorageStreamHandle - { - public TemporaryStorageIdentifier Identifier => storageIdentifier; - - public Stream ReadFromTemporaryStorage(CancellationToken cancellationToken) - => streamStorage.ReadStream(); - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs deleted file mode 100644 index 7b09b0d5bc9df..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ /dev/null @@ -1,91 +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.IO; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis; - -internal sealed partial class TrivialTemporaryStorageService : ITemporaryStorageServiceInternal -{ - public static readonly TrivialTemporaryStorageService Instance = new(); - - private TrivialTemporaryStorageService() - { - } - - public ITemporaryTextStorageInternal CreateTemporaryTextStorage() - => new TextStorage(); - - public ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) - { - stream.Position = 0; - var storage = new StreamStorage(); - storage.WriteStream(stream); - var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: stream.Length); - var handle = new TrivialStorageStreamHandle(identifier, storage); - return handle; - } - - private sealed class StreamStorage - { - private MemoryStream? _stream; - - public Stream ReadStream() - { - var stream = _stream ?? throw new InvalidOperationException(); - - // Return a read-only view of the underlying buffer to prevent users from overwriting or directly - // disposing the backing storage. - return new MemoryStream(stream.GetBuffer(), 0, (int)stream.Length, writable: false); - } - - public void WriteStream(Stream stream) - { - var newStream = new MemoryStream(); - stream.CopyTo(newStream); - var existingValue = Interlocked.CompareExchange(ref _stream, newStream, null); - if (existingValue is not null) - { - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } - } - } - - private sealed class TextStorage : ITemporaryTextStorageInternal - { - private SourceText? _sourceText; - - public void Dispose() - => _sourceText = null; - - public SourceText ReadText(CancellationToken cancellationToken) - => _sourceText ?? throw new InvalidOperationException(); - - public Task ReadTextAsync(CancellationToken cancellationToken) - => Task.FromResult(ReadText(cancellationToken)); - - public void WriteText(SourceText text, CancellationToken cancellationToken) - { - // This is a trivial implementation, indeed. Note, however, that we retain a strong - // reference to the source text, which defeats the intent of RecoverableTextAndVersion, but - // is appropriate for this trivial implementation. - var existingValue = Interlocked.CompareExchange(ref _sourceText, text, null); - if (existingValue is not null) - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } - - public Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default) - { - WriteText(text, cancellationToken); - return Task.CompletedTask; - } - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index 9c92c71478ee4..8e7daaa323d16 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -31,7 +31,7 @@ internal partial class DocumentState : TextDocumentState private readonly ParseOptions? _options; // null if the document doesn't support syntax trees: - private readonly AsyncLazy? _treeSource; + private readonly ITreeAndVersionSource? _treeSource; protected DocumentState( LanguageServices languageServices, @@ -40,7 +40,7 @@ protected DocumentState( ParseOptions? options, ITextAndVersionSource textSource, LoadTextOptions loadTextOptions, - AsyncLazy? treeSource) + ITreeAndVersionSource? treeSource) : base(languageServices.SolutionServices, documentServiceProvider, attributes, textSource, loadTextOptions) { Contract.ThrowIfFalse(_options is null == _treeSource is null); @@ -79,7 +79,7 @@ public DocumentState( } } - public AsyncLazy? TreeSource => _treeSource; + public ITreeAndVersionSource? TreeSource => _treeSource; [MemberNotNullWhen(true, nameof(_treeSource))] [MemberNotNullWhen(true, nameof(TreeSource))] @@ -97,7 +97,7 @@ public SourceCodeKind SourceCodeKind public bool IsGenerated => Attributes.IsGenerated; - protected static AsyncLazy CreateLazyFullyParsedTree( + protected static ITreeAndVersionSource CreateLazyFullyParsedTree( ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions, string? filePath, @@ -105,7 +105,7 @@ protected static AsyncLazy CreateLazyFullyParsedTree( LanguageServices languageServices, PreservationMode mode = PreservationMode.PreserveValue) { - return AsyncLazy.Create( + return SimpleTreeAndVersionSource.Create( static (arg, c) => FullyParseTreeAsync(arg.newTextSource, arg.loadTextOptions, arg.filePath, arg.options, arg.languageServices, arg.mode, c), static (arg, c) => FullyParseTree(arg.newTextSource, arg.loadTextOptions, arg.filePath, arg.options, arg.languageServices, arg.mode, c), arg: (newTextSource, loadTextOptions, filePath, options, languageServices, mode)); @@ -163,19 +163,19 @@ private static TreeAndVersion CreateTreeAndVersion( return new TreeAndVersion(tree, textAndVersion.Version); } - private static AsyncLazy CreateLazyIncrementallyParsedTree( - AsyncLazy oldTreeSource, + private static ITreeAndVersionSource CreateLazyIncrementallyParsedTree( + ITreeAndVersionSource oldTreeSource, ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions) { - return AsyncLazy.Create( + return SimpleTreeAndVersionSource.Create( static (arg, c) => IncrementallyParseTreeAsync(arg.oldTreeSource, arg.newTextSource, arg.loadTextOptions, c), static (arg, c) => IncrementallyParseTree(arg.oldTreeSource, arg.newTextSource, arg.loadTextOptions, c), arg: (oldTreeSource, newTextSource, loadTextOptions)); } private static async Task IncrementallyParseTreeAsync( - AsyncLazy oldTreeSource, + ITreeAndVersionSource oldTreeSource, ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions, CancellationToken cancellationToken) @@ -197,7 +197,7 @@ private static async Task IncrementallyParseTreeAsync( } private static TreeAndVersion IncrementallyParseTree( - AsyncLazy oldTreeSource, + ITreeAndVersionSource oldTreeSource, ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions, CancellationToken cancellationToken) @@ -346,7 +346,7 @@ private DocumentState SetParseOptions(ParseOptions options, bool onlyPreprocesso throw new InvalidOperationException(); } - AsyncLazy? newTreeSource = null; + ITreeAndVersionSource? newTreeSource = null; // Optimization: if we are only changing preprocessor directives, and we've already parsed the existing tree // and it didn't have any, we can avoid a reparse since the tree will be parsed the same. @@ -368,7 +368,7 @@ private DocumentState SetParseOptions(ParseOptions options, bool onlyPreprocesso } if (newTree is not null) - newTreeSource = AsyncLazy.Create(new TreeAndVersion(newTree, existingTreeAndVersion.Version)); + newTreeSource = SimpleTreeAndVersionSource.Create(new TreeAndVersion(newTree, existingTreeAndVersion.Version)); } // If we weren't able to reuse in a smart way, just reparse @@ -457,7 +457,7 @@ public DocumentState UpdateFilePath(string? filePath) protected override TextDocumentState UpdateText(ITextAndVersionSource newTextSource, PreservationMode mode, bool incremental) { - AsyncLazy? newTreeSource; + ITreeAndVersionSource? newTreeSource; if (!SupportsSyntaxTree) { @@ -528,7 +528,7 @@ internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode) _options, textSource: text, LoadTextOptions, - treeSource: AsyncLazy.Create(treeAndVersion)); + treeSource: SimpleTreeAndVersionSource.Create(treeAndVersion)); // use static method so we don't capture references to this static (ITextAndVersionSource, TreeAndVersion) CreateTreeWithLazyText( diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 9353d1f140a87..8c481c1b4a3be 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -13,6 +13,29 @@ namespace Microsoft.CodeAnalysis; internal partial class DocumentState { + /// + /// when we're linked to another file (a 'sibling') and will attempt to reuse + /// that sibling's tree as our own. Note: we won't know if we can actually use the contents of that sibling file + /// until we actually go and realize it, as it may contains constructs (like pp-directives) that prevent use. In + /// that case, we'll fall back to a normal incremental parse between our original and the latest text contents of our sibling's file. + /// + private sealed class LinkedFileReuseTreeAndVersionSource( + ITreeAndVersionSource originalTreeSource, + AsyncLazy lazyComputation) : ITreeAndVersionSource + { + public readonly ITreeAndVersionSource OriginalTreeSource = originalTreeSource; + + public Task GetValueAsync(CancellationToken cancellationToken) + => lazyComputation.GetValueAsync(cancellationToken); + + public TreeAndVersion GetValue(CancellationToken cancellationToken) + => lazyComputation.GetValue(cancellationToken); + + public bool TryGetValue([NotNullWhen(true)] out TreeAndVersion? value) + => lazyComputation.TryGetValue(out value); + } + /// /// 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 public DocumentState UpdateTextAndTreeContents( ITextAndVersionSource siblingTextSource, - AsyncLazy? siblingTreeSource, + ITreeAndVersionSource? siblingTreeSource, bool forceEvenIfTreesWouldDiffer) { if (!SupportsSyntaxTree) @@ -38,50 +61,52 @@ public DocumentState UpdateTextAndTreeContents( Contract.ThrowIfNull(siblingTreeSource); + // We don't want to point at a long chain of transformations as our sibling files change, deferring to each next + // link of the chain to potentially do the work (or potentially failing out). So, if we're about to do this, + // instead return our original tree-source so that in the case we are unable to use the sibling file's root, we + // can do a single step incremental parse between our original tree and the final sibling text. + // + // We only need to look one deep here as we'll pull that tree source forward to our level. If another link is + // later added to us, it will do the same thing. + var originalTreeSource = this.TreeSource; + if (originalTreeSource is LinkedFileReuseTreeAndVersionSource linkedFileTreeAndVersionSource) + originalTreeSource = linkedFileTreeAndVersionSource.OriginalTreeSource; + // 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. + // Defer to static helper to make sure we don't accidentally capture anything else we don't want off of 'this' + // (like "this.TreeSource"). + return UpdateTextAndTreeContentsWorker( + this.Attributes, this.LanguageServices, this.Services, this.LoadTextOptions, this.ParseOptions, + originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer); + } - // copy data from this entity, and pass to static helper, so we don't keep this green node alive. + private static DocumentState UpdateTextAndTreeContentsWorker( + DocumentInfo.DocumentAttributes attributes, + LanguageServices languageServices, + IDocumentServiceProvider services, + LoadTextOptions loadTextOptions, + ParseOptions parseOptions, + ITreeAndVersionSource originalTreeSource, + ITextAndVersionSource siblingTextSource, + ITreeAndVersionSource siblingTreeSource, + bool forceEvenIfTreesWouldDiffer) + { + // 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 tree-source that defers to the + // provided source, gets the tree from it, and then wraps its root in a new tree for us. - 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 lazyComputation = AsyncLazy.Create( + static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + arg: (filePath: attributes.SyntaxTreeFilePath, languageServices, loadTextOptions, parseOptions, originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); - var newTreeSource = GetReuseTreeSource( - filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer); + var newTreeSource = new LinkedFileReuseTreeAndVersionSource(originalTreeSource, lazyComputation); return new DocumentState( - languageServices, - Services, - Attributes, - _options, - siblingTextSource, - LoadTextOptions, - newTreeSource); - - static AsyncLazy GetReuseTreeSource( - string filePath, - LanguageServices languageServices, - LoadTextOptions loadTextOptions, - ParseOptions parseOptions, - AsyncLazy treeSource, - ITextAndVersionSource siblingTextSource, - AsyncLazy siblingTreeSource, - bool forceEvenIfTreesWouldDiffer) - { - return AsyncLazy.Create( - static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - arg: (filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); - } + languageServices, services, attributes, parseOptions, siblingTextSource, loadTextOptions, newTreeSource); static bool TryReuseSiblingRoot( string filePath, @@ -186,9 +211,9 @@ static async Task TryReuseSiblingTreeAsync( LanguageServices languageServices, LoadTextOptions loadTextOptions, ParseOptions parseOptions, - AsyncLazy treeSource, + ITreeAndVersionSource treeSource, ITextAndVersionSource siblingTextSource, - AsyncLazy siblingTreeSource, + ITreeAndVersionSource siblingTreeSource, bool forceEvenIfTreesWouldDiffer, CancellationToken cancellationToken) { @@ -209,9 +234,9 @@ static TreeAndVersion TryReuseSiblingTree( LanguageServices languageServices, LoadTextOptions loadTextOptions, ParseOptions parseOptions, - AsyncLazy treeSource, + ITreeAndVersionSource treeSource, ITextAndVersionSource siblingTextSource, - AsyncLazy siblingTreeSource, + ITreeAndVersionSource siblingTreeSource, bool forceEvenIfTreesWouldDiffer, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs index 1ead6e28e9b29..7cbe5fd3bbb63 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; @@ -287,9 +288,13 @@ public async ValueTask> GetSourceGeneratedD ImmutableInterlocked.GetOrAdd(ref _idToSourceGeneratedDocumentMap, state.Id, s_createSourceGeneratedDocumentFunction, (state, this))); } - internal async ValueTask> GetAllRegularAndSourceGeneratedDocumentsAsync(CancellationToken cancellationToken = default) + internal async IAsyncEnumerable GetAllRegularAndSourceGeneratedDocumentsAsync([EnumeratorCancellation] CancellationToken cancellationToken) { - return Documents.Concat(await GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false)); + foreach (var document in this.Documents) + yield return document; + + foreach (var document in await GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false)) + yield return document; } public async ValueTask GetSourceGeneratedDocumentAsync(DocumentId documentId, CancellationToken cancellationToken = default) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs index 013c0f9aaa830..10b51a65ae47c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs @@ -185,28 +185,35 @@ private static async Task ComputeLatestDocumentVersionAsync(TextDo } private AsyncLazy CreateLazyLatestDocumentTopLevelChangeVersion( - TextDocumentState newDocument, + ImmutableArray newDocuments, TextDocumentStates newDocumentStates, TextDocumentStates newAdditionalDocumentStates) { if (_lazyLatestDocumentTopLevelChangeVersion.TryGetValue(out var oldVersion)) { - return AsyncLazy.Create(static (arg, c) => - ComputeTopLevelChangeTextVersionAsync(arg.oldVersion, arg.newDocument, c), - arg: (oldVersion, newDocument)); + return AsyncLazy.Create(static (arg, cancellationToken) => + ComputeTopLevelChangeTextVersionAsync(arg.oldVersion, arg.newDocuments, cancellationToken), + arg: (oldVersion, newDocuments)); } else { - return AsyncLazy.Create(static (arg, c) => - ComputeLatestDocumentTopLevelChangeVersionAsync(arg.newDocumentStates, arg.newAdditionalDocumentStates, c), + return AsyncLazy.Create(static (arg, cancellationToken) => + ComputeLatestDocumentTopLevelChangeVersionAsync(arg.newDocumentStates, arg.newAdditionalDocumentStates, cancellationToken), arg: (newDocumentStates, newAdditionalDocumentStates)); } } - private static async Task ComputeTopLevelChangeTextVersionAsync(VersionStamp oldVersion, TextDocumentState newDocument, CancellationToken cancellationToken) + private static async Task ComputeTopLevelChangeTextVersionAsync( + VersionStamp oldVersion, ImmutableArray newDocuments, CancellationToken cancellationToken) { - var newVersion = await newDocument.GetTopLevelChangeTextVersionAsync(cancellationToken).ConfigureAwait(false); - return newVersion.GetNewerVersion(oldVersion); + var finalVersion = oldVersion; + foreach (var newDocument in newDocuments) + { + var newVersion = await newDocument.GetTopLevelChangeTextVersionAsync(cancellationToken).ConfigureAwait(false); + finalVersion = newVersion.GetNewerVersion(finalVersion); + } + + return finalVersion; } private static async Task ComputeLatestDocumentTopLevelChangeVersionAsync(TextDocumentStates documentStates, TextDocumentStates additionalDocumentStates, CancellationToken cancellationToken) @@ -834,16 +841,25 @@ public ProjectState RemoveAllNormalDocuments() } public ProjectState UpdateDocument(DocumentState newDocument, bool contentChanged) + => UpdateDocuments([newDocument], contentChanged); + + public ProjectState UpdateDocuments(ImmutableArray newDocuments, bool contentChanged) { - var oldDocument = DocumentStates.GetRequiredState(newDocument.Id); - if (oldDocument == newDocument) - { + var oldDocuments = newDocuments.SelectAsArray(d => DocumentStates.GetRequiredState(d.Id)); + if (oldDocuments.SequenceEqual(newDocuments)) return this; - } - var newDocumentStates = DocumentStates.SetState(newDocument.Id, newDocument); + // Must not be empty as we would have otherwise bailed out in the check above. + Contract.ThrowIfTrue(newDocuments.IsEmpty); + + var newDocumentStates = DocumentStates.SetStates(newDocuments); + + // When computing the latest dependent version, we just need to know how GetLatestDependentVersions( - newDocumentStates, AdditionalDocumentStates, oldDocument, newDocument, contentChanged, + newDocumentStates, AdditionalDocumentStates, + oldDocuments.CastArray(), + newDocuments.CastArray(), + contentChanged, out var dependentDocumentVersion, out var dependentSemanticVersion); return With( @@ -860,9 +876,9 @@ public ProjectState UpdateAdditionalDocument(AdditionalDocumentState newDocument return this; } - var newDocumentStates = AdditionalDocumentStates.SetState(newDocument.Id, newDocument); + var newDocumentStates = AdditionalDocumentStates.SetState(newDocument); GetLatestDependentVersions( - DocumentStates, newDocumentStates, oldDocument, newDocument, contentChanged, + DocumentStates, newDocumentStates, [oldDocument], [newDocument], contentChanged, out var dependentDocumentVersion, out var dependentSemanticVersion); return this.With( @@ -879,7 +895,7 @@ public ProjectState UpdateAnalyzerConfigDocument(AnalyzerConfigDocumentState new return this; } - var newDocumentStates = AnalyzerConfigDocumentStates.SetState(newDocument.Id, newDocument); + var newDocumentStates = AnalyzerConfigDocumentStates.SetState(newDocument); return CreateNewStateForChangedAnalyzerConfigDocuments(newDocumentStates); } @@ -899,46 +915,71 @@ public ProjectState UpdateDocumentsOrder(ImmutableList documentIds) private void GetLatestDependentVersions( TextDocumentStates newDocumentStates, TextDocumentStates newAdditionalDocumentStates, - TextDocumentState oldDocument, TextDocumentState newDocument, + ImmutableArray oldDocuments, + ImmutableArray newDocuments, bool contentChanged, - out AsyncLazy dependentDocumentVersion, out AsyncLazy dependentSemanticVersion) + out AsyncLazy dependentDocumentVersion, + out AsyncLazy dependentSemanticVersion) { var recalculateDocumentVersion = false; var recalculateSemanticVersion = false; if (contentChanged) { - if (oldDocument.TryGetTextVersion(out var oldVersion)) + foreach (var oldDocument in oldDocuments) { - if (!_lazyLatestDocumentVersion.TryGetValue(out var documentVersion) || documentVersion == oldVersion) + if (oldDocument.TryGetTextVersion(out var oldVersion)) { - recalculateDocumentVersion = true; - } + if (!_lazyLatestDocumentVersion.TryGetValue(out var documentVersion) || documentVersion == oldVersion) + recalculateDocumentVersion = true; - if (!_lazyLatestDocumentTopLevelChangeVersion.TryGetValue(out var semanticVersion) || semanticVersion == oldVersion) - { - recalculateSemanticVersion = true; + if (!_lazyLatestDocumentTopLevelChangeVersion.TryGetValue(out var semanticVersion) || semanticVersion == oldVersion) + recalculateSemanticVersion = true; } + + if (recalculateDocumentVersion && recalculateSemanticVersion) + break; } } - dependentDocumentVersion = recalculateDocumentVersion - ? AsyncLazy.Create(static (arg, c) => - ComputeLatestDocumentVersionAsync(arg.newDocumentStates, arg.newAdditionalDocumentStates, c), - arg: (newDocumentStates, newAdditionalDocumentStates)) - : contentChanged - ? AsyncLazy.Create(static (newDocument, c) => - newDocument.GetTextVersionAsync(c), - arg: newDocument) - : _lazyLatestDocumentVersion; - - dependentSemanticVersion = recalculateSemanticVersion - ? AsyncLazy.Create(static (arg, c) => - ComputeLatestDocumentTopLevelChangeVersionAsync(arg.newDocumentStates, arg.newAdditionalDocumentStates, c), - arg: (newDocumentStates, newAdditionalDocumentStates)) - : contentChanged - ? CreateLazyLatestDocumentTopLevelChangeVersion(newDocument, newDocumentStates, newAdditionalDocumentStates) - : _lazyLatestDocumentTopLevelChangeVersion; + if (recalculateDocumentVersion) + { + dependentDocumentVersion = AsyncLazy.Create(static (arg, cancellationToken) => + ComputeLatestDocumentVersionAsync(arg.newDocumentStates, arg.newAdditionalDocumentStates, cancellationToken), + arg: (newDocumentStates, newAdditionalDocumentStates)); + } + else if (contentChanged) + { + dependentDocumentVersion = AsyncLazy.Create( + static async (newDocuments, cancellationToken) => + { + var finalVersion = await newDocuments[0].GetTextVersionAsync(cancellationToken).ConfigureAwait(false); + for (var i = 1; i < newDocuments.Length; i++) + finalVersion = finalVersion.GetNewerVersion(await newDocuments[i].GetTextVersionAsync(cancellationToken).ConfigureAwait(false)); + + return finalVersion; + }, + arg: newDocuments); + } + else + { + dependentDocumentVersion = _lazyLatestDocumentVersion; + } + + if (recalculateSemanticVersion) + { + dependentSemanticVersion = AsyncLazy.Create(static (arg, cancellationToken) => + ComputeLatestDocumentTopLevelChangeVersionAsync(arg.newDocumentStates, arg.newAdditionalDocumentStates, cancellationToken), + arg: (newDocumentStates, newAdditionalDocumentStates)); + } + else if (contentChanged) + { + dependentSemanticVersion = CreateLazyLatestDocumentTopLevelChangeVersion(newDocuments, newDocumentStates, newAdditionalDocumentStates); + } + else + { + dependentSemanticVersion = _lazyLatestDocumentTopLevelChangeVersion; + } } public void AddDocumentIdsWithFilePath(ref TemporaryArray temporaryArray, string filePath) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index 3d6f1098fbd04..978f704c4154c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -194,6 +194,14 @@ private static Project CreateProject(ProjectId projectId, Solution solution) internal Project? GetOriginatingProject(ISymbol symbol) => GetProject(GetOriginatingProjectId(symbol)); + /// + /// + /// Returns the that produced the symbol. In the case of a symbol that was retargetted + /// this will be the compilation it was retargtted into, not the original compilation that it was retargetted from. + /// + internal Compilation? GetOriginatingCompilation(ISymbol symbol) + => _compilationState.GetOriginatingProjectInfo(symbol)?.Compilation; + /// /// True if the solution contains the document in one of its projects /// @@ -1173,20 +1181,25 @@ public Solution WithDocumentFilePath(DocumentId documentId, string filePath) /// specified. /// public Solution WithDocumentText(DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveValue) - { - CheckContainsDocument(documentId); + => WithDocumentTexts([(documentId, text, mode)]); - if (text == null) - { - throw new ArgumentNullException(nameof(text)); - } + internal Solution WithDocumentTexts(ImmutableArray<(DocumentId documentId, SourceText text)> texts) + => WithDocumentTexts(texts.SelectAsArray(t => (t.documentId, t.text, PreservationMode.PreserveValue))); - if (!mode.IsValid()) + internal Solution WithDocumentTexts(ImmutableArray<(DocumentId documentId, SourceText text, PreservationMode mode)> texts) + { + foreach (var (documentId, text, mode) in texts) { - throw new ArgumentOutOfRangeException(nameof(mode)); + CheckContainsDocument(documentId); + + if (text == null) + throw new ArgumentNullException(nameof(text)); + + if (!mode.IsValid()) + throw new ArgumentOutOfRangeException(nameof(mode)); } - return WithCompilationState(_compilationState.WithDocumentText(documentId, text, mode)); + return WithCompilationState(_compilationState.WithDocumentTexts(texts)); } /// @@ -1299,20 +1312,27 @@ public Solution WithAnalyzerConfigDocumentText(DocumentId documentId, TextAndVer /// rooted by the specified syntax node. /// public Solution WithDocumentSyntaxRoot(DocumentId documentId, SyntaxNode root, PreservationMode mode = PreservationMode.PreserveValue) - { - CheckContainsDocument(documentId); + => WithDocumentSyntaxRoots([(documentId, root, mode)]); - if (root == null) - { - throw new ArgumentNullException(nameof(root)); - } + /// . + internal Solution WithDocumentSyntaxRoots(ImmutableArray<(DocumentId documentId, SyntaxNode root)> syntaxRoots) + => WithDocumentSyntaxRoots(syntaxRoots.SelectAsArray(t => (t.documentId, t.root, PreservationMode.PreserveValue))); - if (!mode.IsValid()) + /// . + internal Solution WithDocumentSyntaxRoots(ImmutableArray<(DocumentId documentId, SyntaxNode root, PreservationMode mode)> syntaxRoots) + { + foreach (var (documentId, root, mode) in syntaxRoots) { - throw new ArgumentOutOfRangeException(nameof(mode)); + CheckContainsDocument(documentId); + + if (root == null) + throw new ArgumentNullException(nameof(root)); + + if (!mode.IsValid()) + throw new ArgumentOutOfRangeException(nameof(mode)); } - return WithCompilationState(_compilationState.WithDocumentSyntaxRoot(documentId, root, mode)); + return WithCompilationState(_compilationState.WithDocumentSyntaxRoots(syntaxRoots)); } internal Solution WithDocumentContentsFrom(DocumentId documentId, DocumentState documentState, bool forceEvenIfTreesWouldDiffer) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs index abffc060a6830..347957df7875f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs @@ -147,12 +147,9 @@ private sealed class FinalCompilationTrackerState : CompilationTrackerState public readonly bool HasSuccessfullyLoaded; /// - /// Weak set of the assembly, module and dynamic symbols that this compilation tracker has created. - /// This can be used to determine which project an assembly symbol came from after the fact. This is - /// needed as the compilation an assembly came from can be GC'ed and further requests to get that - /// compilation (or any of it's assemblies) may produce new assembly symbols. + /// Used to determine which project an assembly symbol came from after the fact. /// - public readonly UnrootedSymbolSet UnrootedSymbolSet; + private SingleInitNullable _rootedSymbolSet; /// /// The final compilation, with all references and source generators run. This is distinct from _rootedSymbolSet.Initialize( + static finalCompilationWithGeneratedDocuments => RootedSymbolSet.Create(finalCompilationWithGeneratedDocuments), + this.FinalCompilationWithGeneratedDocuments); + public FinalCompilationTrackerState WithCreationPolicy(CreationPolicy creationPolicy) => creationPolicy == this.CreationPolicy ? this @@ -235,8 +232,7 @@ public FinalCompilationTrackerState WithCreationPolicy(CreationPolicy creationPo FinalCompilationWithGeneratedDocuments, CompilationWithoutGeneratedDocuments, HasSuccessfullyLoaded, - GeneratorInfo, - UnrootedSymbolSet); + GeneratorInfo); private static void RecordAssemblySymbols(ProjectId projectId, Compilation compilation, Dictionary? metadataReferenceToProjectId) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs index 11b1bd8884d08..5f1b44eaf6799 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs @@ -100,23 +100,22 @@ public GeneratorDriver? GeneratorDriver } } - public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary, out MetadataReferenceInfo? referencedThrough) + public bool ContainsAssemblyOrModuleOrDynamic( + ISymbol symbol, bool primary, + [NotNullWhen(true)] out Compilation? compilation, + out MetadataReferenceInfo? referencedThrough) { - Debug.Assert(symbol.Kind is SymbolKind.Assembly or - SymbolKind.NetModule or - SymbolKind.DynamicType); - var state = this.ReadState(); - - var unrootedSymbolSet = (state as FinalCompilationTrackerState)?.UnrootedSymbolSet; - if (unrootedSymbolSet == null) + Debug.Assert(symbol.Kind is SymbolKind.Assembly or SymbolKind.NetModule or SymbolKind.DynamicType); + if (this.ReadState() is not FinalCompilationTrackerState finalState) { // this was not a tracker that has handed out a compilation (all compilations handed out must be // owned by a 'FinalState'). So this symbol could not be from us. + compilation = null; referencedThrough = null; return false; } - return unrootedSymbolSet.Value.ContainsAssemblyOrModuleOrDynamic(symbol, primary, out referencedThrough); + return finalState.RootedSymbolSet.ContainsAssemblyOrModuleOrDynamic(symbol, primary, out compilation, out referencedThrough); } /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.cs index 4aea9e09b1162..3c9bd134dca24 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker_Generators.cs @@ -144,7 +144,7 @@ private partial class CompilationTracker : ICompilationTracker foreach (var (documentIdentity, _, generationDateTime) in infos) { var documentId = documentIdentity.DocumentId; - oldGeneratedDocuments = oldGeneratedDocuments.SetState(documentId, oldGeneratedDocuments.GetRequiredState(documentId).WithGenerationDateTime(generationDateTime)); + oldGeneratedDocuments = oldGeneratedDocuments.SetState(oldGeneratedDocuments.GetRequiredState(documentId).WithGenerationDateTime(generationDateTime)); } // If there are no generated documents though, then just use the compilationWithoutGeneratedFiles so we diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs index 83dd3fa0a8ce6..da0ca13cc63d8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs @@ -50,18 +50,21 @@ public GeneratedFileReplacingCompilationTracker( _skeletonReferenceCache = underlyingTracker.GetClonedSkeletonReferenceCache(); } - public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary, out MetadataReferenceInfo? referencedThrough) + public bool ContainsAssemblyOrModuleOrDynamic( + ISymbol symbol, bool primary, + [NotNullWhen(true)] out Compilation? compilation, + out MetadataReferenceInfo? referencedThrough) { if (_compilationWithReplacements == null) { // We don't have a compilation yet, so this couldn't have came from us + compilation = null; referencedThrough = null; return false; } - else - { - return UnrootedSymbolSet.Create(_compilationWithReplacements).ContainsAssemblyOrModuleOrDynamic(symbol, primary, out referencedThrough); - } + + return RootedSymbolSet.Create(_compilationWithReplacements).ContainsAssemblyOrModuleOrDynamic( + symbol, primary, out compilation, out referencedThrough); } public ICompilationTracker Fork(ProjectState newProject, TranslationAction? translate) @@ -159,7 +162,7 @@ public async ValueTask> GetSour { // The generated file still exists in the underlying compilation, but the contents may not match the open file if the open file // is stale. Replace the syntax tree so we have a tree that matches the text. - newStates = newStates.SetState(id, replacementState); + newStates = newStates.SetState(replacementState); } else { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs index 30455ec7cfd49..80ef8db9493ac 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs @@ -30,7 +30,10 @@ private interface ICompilationTracker /// of the symbols returned by for /// any of the references of the . /// - bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary, out MetadataReferenceInfo? referencedThrough); + bool ContainsAssemblyOrModuleOrDynamic( + ISymbol symbol, bool primary, + [NotNullWhen(true)] out Compilation? compilation, + out MetadataReferenceInfo? referencedThrough); ICompilationTracker Fork(ProjectState newProject, TranslationAction? translate); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs new file mode 100644 index 0000000000000..c33523099f78e --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs @@ -0,0 +1,152 @@ +// 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.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.PooledObjects; +using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; + +namespace Microsoft.CodeAnalysis; + +using SecondaryReferencedSymbol = (int hashCode, ISymbol symbol, SolutionCompilationState.MetadataReferenceInfo referenceInfo); + +internal partial class SolutionCompilationState +{ + internal readonly record struct MetadataReferenceInfo(MetadataReferenceProperties Properties, string? FilePath) + { + internal static MetadataReferenceInfo From(MetadataReference reference) + => new(reference.Properties, (reference as PortableExecutableReference)?.FilePath); + } + + /// + /// Information maintained for unrooted symbols. + /// + /// + /// The project the symbol originated from, i.e. the symbol is defined in the project or its metadata reference. + /// + /// + /// The Compilation that produced the symbol. + /// + /// + /// If the symbol is defined in a metadata reference of , information about the + /// reference. + /// + internal sealed record class OriginatingProjectInfo( + ProjectId ProjectId, + Compilation? Compilation, + MetadataReferenceInfo? ReferencedThrough); + + /// + /// A helper type for mapping back to an originating /. + /// + /// + /// In IDE scenarios we have the need to map from an to the that + /// contained a that could have produced that symbol. This is especially needed with OOP + /// scenarios where we have to communicate to OOP from VS (And vice versa) what symbol we are referring to. To do + /// this, we pass along a project where this symbol could be found, and enough information (a ) to resolve that symbol back in that that . + /// + private readonly struct RootedSymbolSet + { + public readonly Compilation Compilation; + + /// + /// The s or s produced through for all the references exposed by . Sorted by the hash code produced by so that it can be binary searched efficiently. + /// + public readonly ImmutableArray SecondaryReferencedSymbols; + + private RootedSymbolSet( + Compilation compilation, + ImmutableArray secondaryReferencedSymbols) + { + Compilation = compilation; + SecondaryReferencedSymbols = secondaryReferencedSymbols; + } + + public static RootedSymbolSet Create(Compilation compilation) + { + // PERF: Preallocate this array so we don't have to resize it as we're adding assembly symbols. + using var _ = ArrayBuilder.GetInstance( + compilation.ExternalReferences.Length + compilation.DirectiveReferences.Length, out var secondarySymbols); + + foreach (var reference in compilation.References) + { + var symbol = compilation.GetAssemblyOrModuleSymbol(reference); + if (symbol == null) + continue; + + secondarySymbols.Add((ReferenceEqualityComparer.GetHashCode(symbol), symbol, MetadataReferenceInfo.From(reference))); + } + + // Sort all the secondary symbols by their hash. This will allow us to easily binary search for them + // afterwards. Note: it is fine for multiple symbols to have the same reference hash. The search algorithm + // will account for that. + secondarySymbols.Sort(static (x, y) => x.hashCode.CompareTo(y.hashCode)); + return new RootedSymbolSet(compilation, secondarySymbols.ToImmutable()); + } + + public bool ContainsAssemblyOrModuleOrDynamic( + ISymbol symbol, bool primary, + [NotNullWhen(true)] out Compilation? compilation, + out MetadataReferenceInfo? referencedThrough) + { + if (primary) + { + if (this.Compilation.Assembly.Equals(symbol)) + { + compilation = this.Compilation; + referencedThrough = null; + return true; + } + + if (this.Compilation.Language == LanguageNames.CSharp && + this.Compilation.DynamicType.Equals(symbol)) + { + compilation = this.Compilation; + referencedThrough = null; + return true; + } + } + else + { + var secondarySymbols = this.SecondaryReferencedSymbols; + + var symbolHash = ReferenceEqualityComparer.GetHashCode(symbol); + + // The secondary symbol array is sorted by the symbols' hash codes. So do a binary search to find + // the location we should start looking at. + var index = secondarySymbols.BinarySearch(symbolHash, static (item, symbolHash) => item.hashCode.CompareTo(symbolHash)); + if (index >= 0) + { + // Could have multiple symbols with the same hash. They will all be placed next to each other, + // so walk backward to hit the first. + while (index > 0 && secondarySymbols[index - 1].hashCode == symbolHash) + index--; + + // Now, walk forward through the stored symbols with the same hash looking to see if any are a reference match. + while (index < secondarySymbols.Length && secondarySymbols[index].hashCode == symbolHash) + { + var cached = secondarySymbols[index]; + if (cached.symbol.Equals(symbol)) + { + referencedThrough = cached.referenceInfo; + compilation = this.Compilation; + return true; + } + + index++; + } + } + } + + compilation = null; + referencedThrough = null; + return false; + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs index 52a13061db566..5e86a295e2ef4 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -172,13 +173,16 @@ internal partial class SolutionCompilationState return projectId; } - else if (symbol.IsKind(SymbolKind.TypeParameter, out ITypeParameterSymbol? typeParameter) && - typeParameter.TypeParameterKind == TypeParameterKind.Cref) + else if (symbol is ITypeParameterSymbol + { + TypeParameterKind: TypeParameterKind.Cref, + Locations: [{ SourceTree: var typeParameterSourceTree }, ..], + }) { // Cref type parameters don't belong to any containing symbol. But we can map them to a doc/project // using the declaring syntax of the type parameter itself. - if (GetDocumentState(typeParameter.Locations[0].SourceTree, projectId: null) is { } document) - return new OriginatingProjectInfo(document.Id.ProjectId, ReferencedThrough: null); + if (GetDocumentState(typeParameterSourceTree, projectId: null) is { } document) + return new OriginatingProjectInfo(document.Id.ProjectId, Compilation: null, ReferencedThrough: null); } return null; @@ -187,8 +191,8 @@ internal partial class SolutionCompilationState { foreach (var (id, tracker) in _projectIdToTrackerMap) { - if (tracker.ContainsAssemblyOrModuleOrDynamic(symbol, primary, out var referencedThrough)) - return new OriginatingProjectInfo(id, referencedThrough); + if (tracker.ContainsAssemblyOrModuleOrDynamic(symbol, primary, out var compilation, out var referencedThrough)) + return new OriginatingProjectInfo(id, compilation, referencedThrough); } return null; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.TranslationAction_Actions.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.TranslationAction_Actions.cs index 30961e91913ed..6d624d7c540f0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.TranslationAction_Actions.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.TranslationAction_Actions.cs @@ -2,13 +2,13 @@ // 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.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; @@ -17,27 +17,34 @@ internal partial class SolutionCompilationState { private abstract partial class TranslationAction { - internal sealed class TouchDocumentAction( + internal sealed class TouchDocumentsAction( ProjectState oldProjectState, ProjectState newProjectState, - DocumentState oldState, - DocumentState newState) - : TranslationAction(oldProjectState, newProjectState) + ImmutableArray newStates) : TranslationAction(oldProjectState, newProjectState) { - private readonly DocumentState _oldState = oldState; - private readonly DocumentState _newState = newState; + private readonly ImmutableArray _oldStates = newStates.SelectAsArray(s => oldProjectState.DocumentStates.GetRequiredState(s.Id)); + private readonly ImmutableArray _newStates = newStates; public override async Task TransformCompilationAsync(Compilation oldCompilation, CancellationToken cancellationToken) { - return oldCompilation.ReplaceSyntaxTree( - await _oldState.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false), - await _newState.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false)); - } + var finalCompilation = oldCompilation; + for (int i = 0, n = _newStates.Length; i < n; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + var newState = _newStates[i]; + var oldState = _oldStates[i]; + finalCompilation = finalCompilation.ReplaceSyntaxTree( + await oldState.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false), + await newState.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false)); + } - public DocumentId DocumentId => _newState.Attributes.Id; + return finalCompilation; + } - // Replacing a single tree doesn't impact the generated trees in a compilation, so we can use this against - // compilations that have generated trees. + /// + /// Replacing a single tree doesn't impact the generated trees in a compilation, so we can use this against + /// compilations that have generated trees. + /// public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true; public override GeneratorDriver TransformGeneratorDriver(GeneratorDriver generatorDriver) @@ -45,12 +52,12 @@ public override GeneratorDriver TransformGeneratorDriver(GeneratorDriver generat public override TranslationAction? TryMergeWithPrior(TranslationAction priorAction) { - if (priorAction is TouchDocumentAction priorTouchAction && - priorTouchAction._newState == _oldState) + if (priorAction is TouchDocumentsAction priorTouchAction && + priorTouchAction._newStates.SequenceEqual(_oldStates)) { // As we're merging ourselves with the prior touch action, we want to keep the old project state // that we are translating from. - return new TouchDocumentAction(priorAction.OldProjectState, this.NewProjectState, priorTouchAction._oldState, _newState); + return new TouchDocumentsAction(priorAction.OldProjectState, this.NewProjectState, _newStates); } return null; @@ -138,37 +145,23 @@ internal sealed class AddDocumentsAction( public override async Task TransformCompilationAsync(Compilation oldCompilation, CancellationToken cancellationToken) { -#if NETSTANDARD - using var _1 = ArrayBuilder.GetInstance(this.Documents.Length, out var tasks); - - // We want to parse in parallel. But we don't want to have too many parses going on at the same time. - // So we use a semaphore here to only allow that many in at a time. Once we hit that amount, it will - // block further parallel work. However, as the semaphore is released, new work will be let in. - var semaphore = new SemaphoreSlim(initialCount: AddDocumentsBatchSize); - foreach (var document in this.Documents) - { - tasks.Add(Task.Run(async () => - { - using (await semaphore.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) - await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - }, cancellationToken)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); -#else - await Parallel.ForEachAsync( - this.Documents, - cancellationToken, - static async (document, cancellationToken) => - await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); -#endif - - using var _2 = ArrayBuilder.GetInstance(this.Documents.Length, out var trees); - + // TODO(cyrusn): Do we need to ensure that the syntax trees we add to the compilation are in the same + // order as the documents array we have added to the project? If not, we can remove this map and the + // sorting below. + using var _ = PooledDictionary.GetInstance(out var documentToIndex); foreach (var document in this.Documents) - trees.Add(await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false)); - - return oldCompilation.AddSyntaxTrees(trees); + documentToIndex.Add(document, documentToIndex.Count); + + var documentsAndTrees = await ProducerConsumer<(DocumentState document, SyntaxTree tree)>.RunParallelAsync( + source: this.Documents, + produceItems: static async (document, callback, _, cancellationToken) => + callback((document, await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false))), + args: default(VoidResult), + cancellationToken).ConfigureAwait(false); + + return oldCompilation.AddSyntaxTrees(documentsAndTrees + .Sort((dt1, dt2) => documentToIndex[dt1.document] - documentToIndex[dt2.document]) + .Select(static dt => dt.tree)); } // This action adds the specified trees, but leaves the generated trees untouched. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs deleted file mode 100644 index 06837aab31a62..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs +++ /dev/null @@ -1,156 +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; -using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; - -namespace Microsoft.CodeAnalysis; - -using SecondaryReferencedSymbol = (int hashCode, WeakReference symbol, SolutionCompilationState.MetadataReferenceInfo referenceInfo); - -internal partial class SolutionCompilationState -{ - internal readonly record struct MetadataReferenceInfo(MetadataReferenceProperties Properties, string? FilePath) - { - internal static MetadataReferenceInfo From(MetadataReference reference) - => new(reference.Properties, (reference as PortableExecutableReference)?.FilePath); - } - - /// - /// Information maintained for unrooted symbols. - /// - /// - /// The project the symbol originated from, i.e. the symbol is defined in the project or its metadata reference. - /// - /// - /// If the symbol is defined in a metadata reference of , information about the reference. - /// - internal sealed record class OriginatingProjectInfo(ProjectId ProjectId, MetadataReferenceInfo? ReferencedThrough); - - /// - /// A helper type for mapping back to an originating . - /// - /// - /// In IDE scenarios we have the need to map from an to the that - /// contained a that could have produced that symbol. This is especially needed with - /// OOP scenarios where we have to communicate to OOP from VS (And vice versa) what symbol we are referring to. - /// To do this, we pass along a project where this symbol could be found, and enough information (a ) to resolve that symbol back in that that . - /// - /// This is challenging however as symbols do not necessarily have back-pointers to s, - /// and as such, we can't just see which Project produced the that produced that . In other words, the doesn't root the compilation. Because - /// of that we keep track of those symbols per project in a weak fashion. Then, we can later see if a - /// symbol came from a particular project by checking if it is one of those weak symbols. We use weakly held - /// symbols to that a instance doesn't hold symbols alive. But, we know if we are - /// holding the symbol itself, then the weak-ref will stay alive such that we can do this containment check. - /// - /// - private readonly struct UnrootedSymbolSet - { - /// - /// The produced directly by . - /// - public readonly WeakReference PrimaryAssemblySymbol; - - /// - /// The produced directly by . Only - /// valid for . - /// - public readonly WeakReference PrimaryDynamicSymbol; - - /// - /// The s or s produced through for all the references exposed by . Sorted by the hash code produced by so that it can be binary searched efficiently. - /// - public readonly ImmutableArray SecondaryReferencedSymbols; - - private UnrootedSymbolSet( - WeakReference primaryAssemblySymbol, - WeakReference primaryDynamicSymbol, - ImmutableArray secondaryReferencedSymbols) - { - PrimaryAssemblySymbol = primaryAssemblySymbol; - PrimaryDynamicSymbol = primaryDynamicSymbol; - SecondaryReferencedSymbols = secondaryReferencedSymbols; - } - - public static UnrootedSymbolSet Create(Compilation compilation) - { - var primaryAssembly = new WeakReference(compilation.Assembly); - - // The dynamic type is also unrooted (i.e. doesn't point back at the compilation or source - // assembly). So we have to keep track of it so we can get back from it to a project in case the - // underlying compilation is GC'ed. - var primaryDynamic = new WeakReference( - compilation.Language == LanguageNames.CSharp ? compilation.DynamicType : null); - - // PERF: Preallocate this array so we don't have to resize it as we're adding assembly symbols. - using var _ = ArrayBuilder.GetInstance( - compilation.ExternalReferences.Length + compilation.DirectiveReferences.Length, out var secondarySymbols); - - foreach (var reference in compilation.References) - { - var symbol = compilation.GetAssemblyOrModuleSymbol(reference); - if (symbol == null) - continue; - - secondarySymbols.Add((ReferenceEqualityComparer.GetHashCode(symbol), new WeakReference(symbol), MetadataReferenceInfo.From(reference))); - } - - // Sort all the secondary symbols by their hash. This will allow us to easily binary search for - // them afterwards. Note: it is fine for multiple symbols to have the same reference hash. The - // search algorithm will account for that. - secondarySymbols.Sort(static (x, y) => x.hashCode.CompareTo(y.hashCode)); - return new UnrootedSymbolSet(primaryAssembly, primaryDynamic, secondarySymbols.ToImmutable()); - } - - public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary, out MetadataReferenceInfo? referencedThrough) - { - referencedThrough = null; - - if (primary) - { - return symbol.Equals(this.PrimaryAssemblySymbol.GetTarget()) || - symbol.Equals(this.PrimaryDynamicSymbol.GetTarget()); - } - - var secondarySymbols = this.SecondaryReferencedSymbols; - - var symbolHash = ReferenceEqualityComparer.GetHashCode(symbol); - - // The secondary symbol array is sorted by the symbols' hash codes. So do a binary search to find - // the location we should start looking at. - var index = secondarySymbols.BinarySearch(symbolHash, static (item, symbolHash) => item.hashCode.CompareTo(symbolHash)); - if (index < 0) - return false; - - // Could have multiple symbols with the same hash. They will all be placed next to each other, - // so walk backward to hit the first. - while (index > 0 && secondarySymbols[index - 1].hashCode == symbolHash) - index--; - - // Now, walk forward through the stored symbols with the same hash looking to see if any are a reference match. - while (index < secondarySymbols.Length && secondarySymbols[index].hashCode == symbolHash) - { - var cached = secondarySymbols[index]; - if (cached.symbol.TryGetTarget(out var otherSymbol) && otherSymbol == symbol) - { - referencedThrough = cached.referenceInfo; - return true; - } - - index++; - } - - return false; - } - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs index 058055eb643fe..4bcb509819238 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -709,11 +709,50 @@ public SolutionCompilationState WithDocumentFilePath( } /// - public SolutionCompilationState WithDocumentText( - DocumentId documentId, SourceText text, PreservationMode mode) + public SolutionCompilationState WithDocumentText(DocumentId documentId, SourceText text, PreservationMode mode) + => WithDocumentTexts([(documentId, text, mode)]); + + internal SolutionCompilationState WithDocumentTexts( + ImmutableArray<(DocumentId documentId, SourceText text, PreservationMode mode)> texts) { - return UpdateDocumentState( - this.SolutionState.WithDocumentText(documentId, text, mode), documentId); + return WithDocumentContents( + texts, IsUnchanged, + static (documentState, text, mode) => documentState.UpdateText(text, mode)); + + static bool IsUnchanged(DocumentState oldDocument, SourceText text) + => oldDocument.TryGetText(out var oldText) && text == oldText; + } + + private SolutionCompilationState WithDocumentContents( + ImmutableArray<(DocumentId documentId, TContent content, PreservationMode mode)> texts, + Func isUnchanged, + Func updateContent) + { + return UpdateDocumentsInMultipleProjects( + texts.GroupBy(d => d.documentId.ProjectId).Select(g => + { + var projectId = g.Key; + var projectState = this.SolutionState.GetRequiredProjectState(projectId); + + using var _ = ArrayBuilder.GetInstance(out var newDocumentStates); + foreach (var (documentId, content, mode) in g) + { + var documentState = projectState.DocumentStates.GetRequiredState(documentId); + if (isUnchanged(documentState, content)) + continue; + + newDocumentStates.Add(updateContent(documentState, content, mode)); + } + + return (projectId, newDocumentStates.ToImmutableAndClear()); + }), + static (projectState, newDocumentStates) => + { + return new TranslationAction.TouchDocumentsAction( + projectState, + projectState.UpdateDocuments(newDocumentStates, contentChanged: true), + newDocumentStates); + }); } public SolutionCompilationState WithDocumentState( @@ -762,12 +801,19 @@ public SolutionCompilationState WithAnalyzerConfigDocumentText( this.SolutionState.WithAnalyzerConfigDocumentText(documentId, textAndVersion, mode)); } - /// - public SolutionCompilationState WithDocumentSyntaxRoot( - DocumentId documentId, SyntaxNode root, PreservationMode mode) + /// + public SolutionCompilationState WithDocumentSyntaxRoots(ImmutableArray<(DocumentId documentId, SyntaxNode root, PreservationMode mode)> syntaxRoots) { - return UpdateDocumentState( - this.SolutionState.WithDocumentSyntaxRoot(documentId, root, mode), documentId); + return WithDocumentContents( + syntaxRoots, IsUnchanged, + static (documentState, root, mode) => documentState.UpdateTree(root, mode)); + + static bool IsUnchanged(DocumentState oldDocument, SyntaxNode root) + { + return oldDocument.TryGetSyntaxTree(out var oldTree) && + oldTree.TryGetRoot(out var oldRoot) && + oldRoot == root; + } } public SolutionCompilationState WithDocumentContentsFrom( @@ -839,10 +885,10 @@ private SolutionCompilationState UpdateDocumentState(StateChange stateChange, Do // This function shouldn't have been called if the document has not changed Debug.Assert(stateChange.OldProjectState != stateChange.NewProjectState); - var oldDocument = stateChange.OldProjectState.DocumentStates.GetRequiredState(documentId); var newDocument = stateChange.NewProjectState.DocumentStates.GetRequiredState(documentId); - return new TranslationAction.TouchDocumentAction(stateChange.OldProjectState, stateChange.NewProjectState, oldDocument, newDocument); + return new TranslationAction.TouchDocumentsAction( + stateChange.OldProjectState, stateChange.NewProjectState, [newDocument]); }, forkTracker: true, arg: documentId); @@ -1408,7 +1454,7 @@ static SolutionCompilationState ComputeFrozenPartialState( } // Now, add all missing documents per project. - currentState = currentState.AddDocumentsToMultipleProjects( + currentState = currentState.UpdateDocumentsInMultipleProjects( // Do a SelectAsArray here to ensure that we realize the array once, and as such only call things like // ToImmutableAndFree once per ArrayBuilder. missingDocumentStates.SelectAsArray(kvp => (kvp.Key, kvp.Value.ToImmutableAndFree())), @@ -1482,7 +1528,7 @@ private SolutionCompilationState AddDocumentsToMultipleProjects( // The documents might be contributing to multiple different projects; split them by project and then we'll // process one project at a time. - return AddDocumentsToMultipleProjects( + return UpdateDocumentsInMultipleProjects( documentInfos.GroupBy(d => d.Id.ProjectId).Select(g => { var projectId = g.Key; @@ -1493,17 +1539,17 @@ private SolutionCompilationState AddDocumentsToMultipleProjects( addDocumentsToProjectState); } - private SolutionCompilationState AddDocumentsToMultipleProjects( - IEnumerable<(ProjectId projectId, ImmutableArray newDocumentStates)> projectIdAndNewDocuments, - Func, TranslationAction> addDocumentsToProjectState) + private SolutionCompilationState UpdateDocumentsInMultipleProjects( + IEnumerable<(ProjectId projectId, ImmutableArray updatedDocumentState)> projectIdAndUpdatedDocuments, + Func, TranslationAction> getTranslationAction) where TDocumentState : TextDocumentState { var newCompilationState = this; - foreach (var (projectId, newDocumentStates) in projectIdAndNewDocuments) + foreach (var (projectId, newDocumentStates) in projectIdAndUpdatedDocuments) { var oldProjectState = newCompilationState.SolutionState.GetRequiredProjectState(projectId); - var compilationTranslationAction = addDocumentsToProjectState(oldProjectState, newDocumentStates); + var compilationTranslationAction = getTranslationAction(oldProjectState, newDocumentStates); var newProjectState = compilationTranslationAction.NewProjectState; var stateChange = newCompilationState.SolutionState.ForkProject( diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 9600215792ec1..9e9663b46e49b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -988,24 +988,6 @@ public StateChange WithAnalyzerConfigDocumentText(DocumentId documentId, TextAnd return UpdateAnalyzerConfigDocumentState(oldDocument.UpdateText(textAndVersion, mode)); } - /// - /// Creates a new solution instance with the document specified updated to have a syntax tree - /// rooted by the specified syntax node. - /// - public StateChange WithDocumentSyntaxRoot(DocumentId documentId, SyntaxNode root, PreservationMode mode = PreservationMode.PreserveValue) - { - var oldDocument = GetRequiredDocumentState(documentId); - if (oldDocument.TryGetSyntaxTree(out var oldTree) && - oldTree.TryGetRoot(out var oldRoot) && - oldRoot == root) - { - var oldProject = GetRequiredProjectState(documentId.ProjectId); - return new(this, oldProject, oldProject); - } - - return UpdateDocumentState(oldDocument.UpdateTree(root, mode), contentChanged: true); - } - /// Whether or not the specified document is forced to have the same text and /// green-tree-root from . If , then they will share /// these values. If , then they will only be shared when safe to do so (for example, diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs index e404b3a91c413..cb1591b0eb256 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs @@ -105,7 +105,7 @@ private SourceGeneratedDocumentState( ITextAndVersionSource textSource, SourceText text, LoadTextOptions loadTextOptions, - AsyncLazy treeSource, + ITreeAndVersionSource treeSource, Lazy lazyContentHash, DateTime generationDateTime) : base(languageServices, documentServiceProvider, attributes, options, textSource, loadTextOptions, treeSource) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index 53c6008610ccc..63fa8cb9f446d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -82,8 +82,8 @@ private static ITextAndVersionSource CreateRecoverableText(TextAndVersion text, : new RecoverableTextAndVersion(new ConstantTextAndVersionSource(text), services); } - public ITemporaryTextStorageInternal? Storage - => (TextAndVersionSource as RecoverableTextAndVersion)?.Storage; + public ITemporaryStorageTextHandle? StorageHandle + => (TextAndVersionSource as RecoverableTextAndVersion)?.StorageHandle; public bool TryGetText([NotNullWhen(returnValue: true)] out SourceText? text) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index d9a24fa7d891a..6a4fee023332c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -168,14 +168,27 @@ public TextDocumentStates RemoveRange(ImmutableArray ids) return new(_ids.RemoveRange(enumerableIds), _map.RemoveRange(enumerableIds), filePathToDocumentIds: null); } - internal TextDocumentStates SetState(DocumentId id, TState state) + internal TextDocumentStates SetState(TState state) + => SetStates([state]); + + internal TextDocumentStates SetStates(ImmutableArray states) { - var oldState = _map[id]; - var filePathToDocumentIds = oldState.FilePath != state.FilePath - ? null - : _filePathToDocumentIds; + var builder = _map.ToBuilder(); + var filePathToDocumentIds = _filePathToDocumentIds; + + foreach (var state in states) + { + var id = state.Id; + var oldState = _map[id]; + + // If any file paths have changed, don't preseve the computed map. We'll regenerate the new map on demand when needed. + if (filePathToDocumentIds != null && oldState.FilePath != state.FilePath) + filePathToDocumentIds = null; + + builder[id] = state; + } - return new(_ids, _map.SetItem(id, state), filePathToDocumentIds); + return new(_ids, builder.ToImmutable(), filePathToDocumentIds); } public TextDocumentStates UpdateStates(Func transformation, TArg arg) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITextAndVersionSource.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITextAndVersionSource.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITreeAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITreeAndVersionSource.cs new file mode 100644 index 0000000000000..75e0d0c670b33 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITreeAndVersionSource.cs @@ -0,0 +1,20 @@ +// 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; + +namespace Microsoft.CodeAnalysis; + +/// +/// Similar to , but for trees. Allows hiding (or introspecting) the details of how +/// a tree is created for a particular document. +/// +internal interface ITreeAndVersionSource +{ + Task GetValueAsync(CancellationToken cancellationToken); + TreeAndVersion GetValue(CancellationToken cancellationToken); + bool TryGetValue([NotNullWhen(true)] out TreeAndVersion? value); +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/LoadableTextAndVersionSource.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/LoadableTextAndVersionSource.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.RecoverableText.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.RecoverableText.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.RecoverableText.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.RecoverableText.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.cs similarity index 90% rename from src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.cs index b4b327a618e44..7314888e4c9ba 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.cs @@ -48,8 +48,8 @@ private bool TryGetInitialSourceOrRecoverableText([NotNullWhen(true)] out ITextA public TextLoader? TextLoader => (_initialSourceOrRecoverableText as ITextAndVersionSource)?.TextLoader; - public ITemporaryTextStorageInternal? Storage - => (_initialSourceOrRecoverableText as RecoverableText)?.Storage; + public ITemporaryStorageTextHandle? StorageHandle + => (_initialSourceOrRecoverableText as RecoverableText)?.StorageHandle; public bool TryGetValue(LoadTextOptions options, [MaybeNullWhen(false)] out TextAndVersion value) { @@ -143,7 +143,7 @@ private sealed partial class RecoverableText public readonly ITextAndVersionSource? InitialSource; public readonly LoadTextOptions LoadTextOptions; - public ITemporaryTextStorageInternal? _storage; + public ITemporaryStorageTextHandle? _storageHandle; public RecoverableText(ITextAndVersionSource source, TextAndVersion textAndVersion, LoadTextOptions options, SolutionServices services) { @@ -166,37 +166,36 @@ public RecoverableText(ITextAndVersionSource source, TextAndVersion textAndVersi public TextAndVersion ToTextAndVersion(SourceText text) => TextAndVersion.Create(text, Version, LoadDiagnostic); - public ITemporaryTextStorageInternal? Storage => _storage; + public ITemporaryStorageTextHandle? StorageHandle => _storageHandle; private async Task RecoverAsync(CancellationToken cancellationToken) { - Contract.ThrowIfNull(_storage); + Contract.ThrowIfNull(_storageHandle); using (Logger.LogBlock(FunctionId.Workspace_Recoverable_RecoverTextAsync, cancellationToken)) { - return await _storage.ReadTextAsync(cancellationToken).ConfigureAwait(false); + return await _storageHandle.ReadFromTemporaryStorageAsync(cancellationToken).ConfigureAwait(false); } } private SourceText Recover(CancellationToken cancellationToken) { - Contract.ThrowIfNull(_storage); + Contract.ThrowIfNull(_storageHandle); using (Logger.LogBlock(FunctionId.Workspace_Recoverable_RecoverText, cancellationToken)) { - return _storage.ReadText(cancellationToken); + return _storageHandle.ReadFromTemporaryStorage(cancellationToken); } } private async Task SaveAsync(SourceText text, CancellationToken cancellationToken) { - Contract.ThrowIfFalse(_storage == null); // Cannot save more than once + Contract.ThrowIfFalse(_storageHandle == null); // Cannot save more than once - var storage = _storageService.CreateTemporaryTextStorage(); - await storage.WriteTextAsync(text, cancellationToken).ConfigureAwait(false); + var handle = await _storageService.WriteToTemporaryStorageAsync(text, cancellationToken).ConfigureAwait(false); - // make sure write is done before setting _storage field - Interlocked.CompareExchange(ref _storage, storage, null); + // make sure write is done before setting _storageHandle field + Interlocked.CompareExchange(ref _storageHandle, handle, null); // Only set _initialValue to null once writing to the storage service completes fully. If the save did not // complete, we want to keep it around to service future requests. Once we do clear out this value, then diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/SimpleTreeAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/SimpleTreeAndVersionSource.cs new file mode 100644 index 0000000000000..46be64b1bd3ec --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/SimpleTreeAndVersionSource.cs @@ -0,0 +1,44 @@ +// 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.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis; + +/// +/// Simple implementation of backed by an opaque ."/> +/// +internal sealed class SimpleTreeAndVersionSource : ITreeAndVersionSource +{ + private readonly AsyncLazy _source; + + private SimpleTreeAndVersionSource(AsyncLazy source) + { + _source = source; + } + + public Task GetValueAsync(CancellationToken cancellationToken) + => _source.GetValueAsync(cancellationToken); + + public TreeAndVersion GetValue(CancellationToken cancellationToken) + => _source.GetValue(cancellationToken); + + public bool TryGetValue([NotNullWhen(true)] out TreeAndVersion? value) + => _source.TryGetValue(out value); + + public static SimpleTreeAndVersionSource Create( + Func> asynchronousComputeFunction, + Func? synchronousComputeFunction, TArg arg) + { + return new(AsyncLazy.Create(asynchronousComputeFunction, synchronousComputeFunction, arg)); + } + + public static SimpleTreeAndVersionSource Create(TreeAndVersion source) + => new(AsyncLazy.Create(source)); +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/VersionStamp.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionStamp.cs index f0a4fc6bf828a..34872c1b0c2d7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/VersionStamp.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionStamp.cs @@ -168,41 +168,6 @@ public bool Equals(VersionStamp version) public static bool operator !=(VersionStamp left, VersionStamp right) => !left.Equals(right); - /// - /// Check whether given persisted version is re-usable. Used by VS for Mac - /// - internal static bool CanReusePersistedVersion(VersionStamp baseVersion, VersionStamp persistedVersion) - { - if (baseVersion == persistedVersion) - { - return true; - } - - // there was a collision, we can't use these - if (baseVersion._localIncrement != 0 || persistedVersion._localIncrement != 0) - { - return false; - } - - return baseVersion._utcLastModified == persistedVersion._utcLastModified; - } - - internal void WriteTo(ObjectWriter writer) - { - writer.WriteInt64(_utcLastModified.ToBinary()); - writer.WriteInt32(_localIncrement); - writer.WriteInt32(_globalIncrement); - } - - internal static VersionStamp ReadFrom(ObjectReader reader) - { - var raw = reader.ReadInt64(); - var localIncrement = reader.ReadInt32(); - var globalIncrement = reader.ReadInt32(); - - return new VersionStamp(DateTime.FromBinary(raw), localIncrement, globalIncrement); - } - private static int GetGlobalVersion(VersionStamp version) { // global increment < 0 means it is a global version which has its global increment in local increment diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 201d28946e432..a68324a694c30 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; @@ -1466,6 +1467,7 @@ internal virtual bool TryApplyChanges(Solution newSolution, IProgress Unexpected value '{0}' in DocumentKinds array. + + Running code cleanup on fixed documents + + + Applying changes to {0} + \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf index 93569dd7abbc2..c0c7da7547edc 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf @@ -17,6 +17,11 @@ Došlo k chybě při čtení zadaného konfiguračního souboru: {0} + + Applying changes to {0} + Applying changes to {0} + + C# files Soubory C# @@ -127,6 +132,11 @@ Přejmenovat {0} na {1} + + Running code cleanup on fixed documents + Running code cleanup on fixed documents + + Solution does not contain specified reference Řešení neobsahuje zadaný odkaz. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf index 4dbc4bbf1268c..c8e0e89c76867 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf @@ -17,6 +17,11 @@ Beim Lesen der angegebenen Konfigurationsdatei ist ein Fehler aufgetreten: {0} + + Applying changes to {0} + Applying changes to {0} + + C# files C#-Dateien @@ -127,6 +132,11 @@ "{0}" in "{1}" umbenennen + + Running code cleanup on fixed documents + Running code cleanup on fixed documents + + Solution does not contain specified reference Der angegebene Verweis ist nicht in der Projektmappe enthalten. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf index d79f326dae933..688d652b9a210 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf @@ -17,6 +17,11 @@ Error al leer el archivo de configuración especificado: {0} + + Applying changes to {0} + Applying changes to {0} + + C# files Archivos de C# @@ -127,6 +132,11 @@ Cambiar el nombre de '{0}' a '{1}' + + Running code cleanup on fixed documents + Running code cleanup on fixed documents + + Solution does not contain specified reference La solución no contiene la referencia especificada diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf index 4437971ba7614..9cf1df47b76d2 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf @@ -17,6 +17,11 @@ Une erreur s'est produite lors de la lecture du fichier de configuration spécifié : {0} + + Applying changes to {0} + Applying changes to {0} + + C# files Fichiers C# @@ -127,6 +132,11 @@ Renommer '{0}' en '{1}' + + Running code cleanup on fixed documents + Running code cleanup on fixed documents + + Solution does not contain specified reference La solution ne contient pas la référence spécifiée diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf index 77eab2b223826..8132baa38dafd 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf @@ -17,6 +17,11 @@ Si è verificato un errore durante la lettura del file di configurazione specificato: {0} + + Applying changes to {0} + Applying changes to {0} + + C# files File C# @@ -127,6 +132,11 @@ Rinomina '{0}' in '{1}' + + Running code cleanup on fixed documents + Running code cleanup on fixed documents + + Solution does not contain specified reference La soluzione non contiene il riferimento specificato diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf index 7f16149e410ce..fb0c1c676b2e5 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf @@ -17,6 +17,11 @@ 指定した構成ファイルの読み取り中にエラーが発生しました: {0} + + Applying changes to {0} + Applying changes to {0} + + C# files C# ファイル @@ -127,6 +132,11 @@ '{0}' を '{1}' に名前変更 + + Running code cleanup on fixed documents + Running code cleanup on fixed documents + + Solution does not contain specified reference 指定された参照がソリューションに含まれていません diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf index ab6a3d0da4853..f00171fca507c 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf @@ -17,6 +17,11 @@ 지정한 구성 파일을 읽는 동안 오류가 발생했습니다({0}). + + Applying changes to {0} + Applying changes to {0} + + C# files C# 파일 @@ -127,6 +132,11 @@ '{1}'(으)로 '{0}' 이름 바꾸기 + + Running code cleanup on fixed documents + Running code cleanup on fixed documents + + Solution does not contain specified reference 지정된 참조가 솔루션에 포함되어 있지 않습니다. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf index 5d6df9750c27b..baef2a6e60e1c 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf @@ -17,6 +17,11 @@ Wystąpił błąd podczas odczytywania określonego pliku konfiguracji: {0} + + Applying changes to {0} + Applying changes to {0} + + C# files Pliki C# @@ -127,6 +132,11 @@ Zmień nazwę elementu „{0}” na „{1}” + + Running code cleanup on fixed documents + Running code cleanup on fixed documents + + Solution does not contain specified reference Rozwiązanie nie zawiera określonego odwołania diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf index d61231dbb8017..5d21e0d33d94c 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf @@ -17,6 +17,11 @@ Ocorreu um erro ao ler o arquivo de configuração especificado: {0} + + Applying changes to {0} + Applying changes to {0} + + C# files Arquivos C# @@ -127,6 +132,11 @@ Renomear "{0}" para "{1}" + + Running code cleanup on fixed documents + Running code cleanup on fixed documents + + Solution does not contain specified reference A solução não contém a referência especificada diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf index a59fe061c71d0..9f26b33ad0eec 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf @@ -17,6 +17,11 @@ Произошла ошибка при чтении указанного файла конфигурации: {0} + + Applying changes to {0} + Applying changes to {0} + + C# files Файлы C# @@ -127,6 +132,11 @@ Переименовать "{0}" в "{1}" + + Running code cleanup on fixed documents + Running code cleanup on fixed documents + + Solution does not contain specified reference Решение не содержит указанную ссылку. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf index a27884ea21b81..a80f53668d945 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf @@ -17,6 +17,11 @@ Belirtilen yapılandırma dosyası okunurken bir hata oluştu: {0} + + Applying changes to {0} + Applying changes to {0} + + C# files C# dosyalarını @@ -127,6 +132,11 @@ '{0}' öğesini '{1}' olarak yeniden adlandır + + Running code cleanup on fixed documents + Running code cleanup on fixed documents + + Solution does not contain specified reference Çözüm belirtilen başvuruyu içermiyor diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf index 282030d47ddc5..d3a06528147db 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf @@ -17,6 +17,11 @@ 读取指定的配置文件时出错: {0} + + Applying changes to {0} + Applying changes to {0} + + C# files c# 文件 @@ -127,6 +132,11 @@ 将“{0}” 重命名为“{1}” + + Running code cleanup on fixed documents + Running code cleanup on fixed documents + + Solution does not contain specified reference 解决方案不包含指定的引用 diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf index fa3e4431600d7..bffa74e3d2aa5 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf @@ -17,6 +17,11 @@ 讀取指定的組態檔時發生錯誤: {0} + + Applying changes to {0} + Applying changes to {0} + + C# files C# 檔案 @@ -127,6 +132,11 @@ 將 '{0}' 重新命名為 '{1}' + + Running code cleanup on fixed documents + Running code cleanup on fixed documents + + Solution does not contain specified reference 解決方案未包含指定的參考 diff --git a/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs b/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs index b5d5d44d9e1dd..bab48686708c0 100644 --- a/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs +++ b/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs @@ -74,7 +74,7 @@ private static void VerifyResults(IEnumerable declarations, string[] ex } } - private Workspace CreateWorkspace(TestHost testHost = TestHost.InProcess) + private Workspace CreateWorkspace(TestHost testHost = TestHost.OutOfProcess) { var composition = FeaturesTestCompositions.Features.WithTestHostParts(testHost); var workspace = new AdhocWorkspace(composition.GetHostServices()); @@ -122,7 +122,7 @@ private Workspace CreateWorkspaceWithMultipleProjectSolution(TestHost testHost, return workspace; } - private Workspace CreateWorkspaceWithSolution(SolutionKind solutionKind, out Solution solution, TestHost testHost = TestHost.InProcess) + private Workspace CreateWorkspaceWithSolution(SolutionKind solutionKind, out Solution solution, TestHost testHost = TestHost.OutOfProcess) => solutionKind switch { SolutionKind.SingleClass => CreateWorkspaceWithSingleProjectSolution(testHost, [SingleClass], out solution), @@ -137,7 +137,7 @@ private Workspace CreateWorkspaceWithSolution(SolutionKind solutionKind, out Sol _ => throw ExceptionUtilities.UnexpectedValue(solutionKind), }; - private Workspace CreateWorkspaceWithProject(SolutionKind solutionKind, out Project project, TestHost testHost = TestHost.InProcess) + private Workspace CreateWorkspaceWithProject(SolutionKind solutionKind, out Project project, TestHost testHost = TestHost.OutOfProcess) { var workspace = CreateWorkspaceWithSolution(solutionKind, out var solution, testHost); project = solution.Projects.First(); diff --git a/src/Workspaces/CoreTest/Formatter/FormatterTests.cs b/src/Workspaces/CoreTest/Formatter/FormatterTests.cs index abf74a8dd1a2b..f5f087ef9efda 100644 --- a/src/Workspaces/CoreTest/Formatter/FormatterTests.cs +++ b/src/Workspaces/CoreTest/Formatter/FormatterTests.cs @@ -59,8 +59,7 @@ public async Task FormatAsync_ForeignLanguageWithFormattingSupport() AssertEx.Equal(@"Formatted with options: LineFormattingOptions { UseTabs = False, TabSize = 4, IndentationSize = 4, NewLine = \r\n }", formattedText.ToString()); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task FormatAsync_ForeignLanguageWithFormattingSupport_Options(bool passExplicitOptions) { var hostServices = s_composition.AddParts([typeof(NoCompilationLanguageService), typeof(TestFormattingService)]).GetHostServices(); diff --git a/src/Workspaces/CoreTest/ObjectSerializationTests.cs b/src/Workspaces/CoreTest/ObjectSerializationTests.cs index 1afaa16289fda..91a75a4968f72 100644 --- a/src/Workspaces/CoreTest/ObjectSerializationTests.cs +++ b/src/Workspaces/CoreTest/ObjectSerializationTests.cs @@ -246,8 +246,7 @@ public void TestCompressedUInt() Assert.Throws(() => TestRoundTripCompressedUint(0xC0000000u)); // both high bits set not allowed } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void TestByteSpan([CombinatorialValues(0, 1, 2, 3, 1000, 1000000)] int size) { var data = new byte[size]; @@ -597,7 +596,7 @@ private static void TestRoundTripChar(Char ch) [Fact] public void TestRoundTripGuid() { - void test(Guid guid) + static void test(Guid guid) { TestRoundTrip(guid, (w, v) => w.WriteGuid(v), r => r.ReadGuid()); } diff --git a/src/Workspaces/CoreTest/SerializationTests.cs b/src/Workspaces/CoreTest/SerializationTests.cs deleted file mode 100644 index ee8e276515d90..0000000000000 --- a/src/Workspaces/CoreTest/SerializationTests.cs +++ /dev/null @@ -1,37 +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.IO; -using Microsoft.CodeAnalysis.Test.Utilities; -using Roslyn.Test.Utilities; -using Roslyn.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.UnitTests -{ - [UseExportProvider] - public partial class SerializationTests : TestBase - { - [Fact] - public void VersionStamp_RoundTripText() - { - var versionStamp = VersionStamp.Create(); - - using var writerStream = new MemoryStream(); - - using (var writer = new ObjectWriter(writerStream, leaveOpen: true)) - { - versionStamp.WriteTo(writer); - } - - using var readerStream = new MemoryStream(writerStream.ToArray()); - using var reader = ObjectReader.TryGetReader(readerStream); - var deserializedVersionStamp = VersionStamp.ReadFrom(reader); - - Assert.Equal(versionStamp, deserializedVersionStamp); - } - } -} diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 67e9cabedec10..a98d7291ae18e 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -1227,8 +1227,7 @@ public void WithProjectCompilationOptionsExceptionHandling() Assert.Throws(() => solution.WithProjectCompilationOptions(ProjectId.CreateNewId(), options)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void WithProjectCompilationOptionsReplacesSyntaxTreeOptionProvider([CombinatorialValues(LanguageNames.CSharp, LanguageNames.VisualBasic)] string languageName) { var projectId = ProjectId.CreateNewId(); @@ -2639,11 +2638,8 @@ public void TestUpdatingFilePathUpdatesSyntaxTree() } } -#if NETCOREAPP - [SupportedOSPlatform("windows")] -#endif [MethodImpl(MethodImplOptions.NoInlining)] - [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542736")] + [ConditionalFact(typeof(WindowsOnly)), WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542736")] public void TestDocumentChangedOnDiskIsNotObserved() { var text1 = "public class A {}"; @@ -2669,17 +2665,7 @@ public void TestDocumentChangedOnDiskIsNotObserved() Assert.Equal(text2, textOnDisk); // stop observing it and let GC reclaim it - if (PlatformInformation.IsWindows || PlatformInformation.IsRunningOnMono) - { - Assert.IsType(workspace.Services.GetService()); - observedText.AssertReleased(); - } - else - { - // If this assertion fails, it means a new target supports the true temporary storage service, and the - // condition above should be updated to ensure 'AssertReleased' is called for this target. - Assert.IsType(workspace.Services.GetService()); - } + observedText.AssertReleased(); // if we ask for the same text again we should get the original content var observedText2 = sol.GetDocument(did).GetTextAsync().Result; @@ -3715,8 +3701,7 @@ public async Task TestFrozenPartialSemanticsAfterSingleTextEdit() Assert.Contains(await frozenDocument.GetSyntaxTreeAsync(), (await frozenDocument.Project.GetCompilationAsync()).SyntaxTrees); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestFrozenPartialSemanticsWithMulitipleUnrelatedEdits([CombinatorialValues(1, 2, 3)] int documentToFreeze) { using var workspace = CreateWorkspaceWithPartialSemantics(); @@ -4036,8 +4021,7 @@ public void TestUpdateDocumentsOrderExceptions() Assert.Throws(() => solution = solution.WithProjectDocumentsOrder(pid, ImmutableList.CreateRange(new[] { did3, did2, did1 }))); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAddingEditorConfigFileWithDiagnosticSeverity([CombinatorialValues(LanguageNames.CSharp, LanguageNames.VisualBasic)] string languageName) { using var workspace = CreateWorkspace(); @@ -4073,8 +4057,7 @@ public async Task TestAddingEditorConfigFileWithDiagnosticSeverity([Combinatoria Assert.Equal(ReportDiagnostic.Error, severity); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestAddingAndRemovingEditorConfigFileWithDiagnosticSeverity([CombinatorialValues(LanguageNames.CSharp, LanguageNames.VisualBasic)] string languageName) { using var workspace = CreateWorkspace(); @@ -4115,8 +4098,7 @@ public async Task TestAddingAndRemovingEditorConfigFileWithDiagnosticSeverity([C Assert.True(finalCompilation.ContainsSyntaxTree(syntaxTreeAfterRemovingEditorConfig)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task TestChangingAnEditorConfigFile([CombinatorialValues(LanguageNames.CSharp, LanguageNames.VisualBasic)] string languageName) { using var workspace = CreateWorkspace(); @@ -5080,5 +5062,62 @@ public async Task TestFrozenPartialSolutionOtherLanguage() var frozenCompilation = await frozenProject.GetCompilationAsync(); Assert.Null(frozenCompilation); } + + [Theory] + [InlineData(1000)] + [InlineData(2000)] + [InlineData(4000)] + [InlineData(8000)] + public async Task TestLargeLinkedFileChain(int intermediatePullCount) + { + using var workspace = CreateWorkspace(); + + var project1 = workspace.CurrentSolution + .AddProject($"Project1", $"Project1", LanguageNames.CSharp) + .WithParseOptions(CSharpParseOptions.Default.WithPreprocessorSymbols("DEBUG")) + .AddDocument($"Document", SourceText.From("class C { }"), filePath: @"c:\test\Document.cs").Project; + var documentId1 = project1.DocumentIds.Single(); + + // make another project, give a separate set of pp directives, so that we do *not* try to use the sibling + // root (from project1), but instead incrementally parse using the *contents* of the file in project1 again + // our actual tree. This used to stack overflow since we'd create a long chain of incremental parsing steps + // for each edit made to the sibling file. + var project2 = project1.Solution + .AddProject($"Project2", $"Project2", LanguageNames.CSharp) + .WithParseOptions(CSharpParseOptions.Default.WithPreprocessorSymbols("RELEASE")) + .AddDocument($"Document", SourceText.From("class C { }"), filePath: @"c:\test\Document.cs").Project; + var documentId2 = project2.DocumentIds.Single(); + + workspace.SetCurrentSolution( + _ => project2.Solution, + (_, _) => (WorkspaceChangeKind.SolutionAdded, null, null)); + + for (var i = 1; i <= 8000; i++) + { + var lastContents = $"#if true //{new string('.', i)}//"; + workspace.SetCurrentSolution( + old => old.WithDocumentText(documentId1, SourceText.From(lastContents)), + (_, _) => (WorkspaceChangeKind.DocumentChanged, documentId1.ProjectId, documentId1)); + + // Ensure that the first document is fine, and we're not stack overflowing on simply getting the tree + // from it. Do this on a disparate cadence from our pulls of the second document to ensure we are + // testing the case where we haven't necessarily immediately pulled on hte first doc before pulling on + // the second. + if (i % 33 == 0) + { + var document1 = workspace.CurrentSolution.GetRequiredDocument(documentId1); + await document1.GetSyntaxRootAsync(); + } + + if (i % intermediatePullCount == 0) + { + var document2 = workspace.CurrentSolution.GetRequiredDocument(documentId2); + + // Getting the second document should both be fine, and have contents equivalent to what is in the first document. + var root = await document2.GetSyntaxRootAsync(); + Assert.Equal(lastContents, root.ToFullString()); + } + } + } } } diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs index f8daa66a477e5..bb8a74e919e5a 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs @@ -70,7 +70,7 @@ public async Task WithReferencesMethodCorrectlyUpdatesWithEqualReferences(TestHo // reference with another reference that's equal, we correctly update generators. We'll have the underlying generators // be different since two AnalyzerFileReferences that are value equal but different instances would have their own generators as well. const string SharedPath = "Z:\\Generator.dll"; - ISourceGenerator CreateGenerator() => new SingleFileTestGenerator("// StaticContent", hintName: "generated"); + static ISourceGenerator CreateGenerator() => new SingleFileTestGenerator("// StaticContent", hintName: "generated"); var analyzerReference1 = new TestGeneratorReferenceWithFilePathEquality(CreateGenerator(), SharedPath); var analyzerReference2 = new TestGeneratorReferenceWithFilePathEquality(CreateGenerator(), SharedPath); diff --git a/src/Workspaces/CoreTest/UtilityTest/AsyncLazyTests.cs b/src/Workspaces/CoreTest/UtilityTest/AsyncLazyTests.cs index c8f23e41194a1..ff97ddfac212b 100644 --- a/src/Workspaces/CoreTest/UtilityTest/AsyncLazyTests.cs +++ b/src/Workspaces/CoreTest/UtilityTest/AsyncLazyTests.cs @@ -220,8 +220,7 @@ public void GetValueAsyncThatIsCancelledReturnsTaskCancelledWithCorrectToken() } } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] private static void CancellationDuringInlinedComputationFromGetValueOrGetValueAsyncStillCachesResult(bool includeSynchronousComputation) { var computations = 0; @@ -272,8 +271,7 @@ public void SynchronousRequestShouldCacheValueWithAsynchronousComputeFunction() Assert.Same(secondRequestResult, firstRequestResult); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public async Task AwaitingProducesCorrectException(bool producerAsync, bool consumerAsync) { var exception = new ArgumentException(); diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/GlobalOptionServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/GlobalOptionServiceTests.cs index cf34c47dcdd13..5a50bd52ddc6a 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/GlobalOptionServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/GlobalOptionServiceTests.cs @@ -50,8 +50,7 @@ public void LegacyGlobalOptions_SetGet() Assert.Equal(3, optionService.GetExternallyDefinedOption(optionKey)); } - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] public void ExternallyDefinedOption(bool subclass) { using var workspace1 = new AdhocWorkspace(); diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index 2bd1e0f417690..053cad8235405 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -73,41 +73,17 @@ public void TestTemporaryStorageStream() private static void TestTemporaryStorage(ITemporaryStorageServiceInternal temporaryStorageService, SourceText text) { - // create a temporary storage location - var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); - // write text into it - temporaryStorage.WriteTextAsync(text).Wait(); + var handle = temporaryStorageService.WriteToTemporaryStorage(text, CancellationToken.None); // read text back from it - var text2 = temporaryStorage.ReadTextAsync().Result; + var text2 = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.NotSame(text, text2); Assert.Equal(text.ToString(), text2.ToString()); Assert.Equal(text.Encoding, text2.Encoding); } - [ConditionalFact(typeof(WindowsOnly))] - public void TestTemporaryTextStorageExceptions() - { - using var workspace = new AdhocWorkspace(); - var textFactory = Assert.IsType(workspace.Services.GetService()); - var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryTextStorage(); - - // Nothing has been written yet - Assert.Throws(() => storage.ReadText()); - Assert.Throws(() => storage.ReadTextAsync().Result); - - // write a normal string - var text = SourceText.From(new string(' ', 4096) + "public class A {}"); - storage.WriteTextAsync(text).Wait(); - - // Writing multiple times is not allowed - Assert.Throws(() => storage.WriteText(text)); - Assert.Throws(() => storage.WriteTextAsync(text).Wait()); - } - [ConditionalFact(typeof(WindowsOnly))] public void TestZeroLengthStreams() { diff --git a/src/Workspaces/CoreTestUtilities/Formatting/FormattingTestBase.cs b/src/Workspaces/CoreTestUtilities/Formatting/FormattingTestBase.cs index 84c250d2915d1..6f738bcc3613f 100644 --- a/src/Workspaces/CoreTestUtilities/Formatting/FormattingTestBase.cs +++ b/src/Workspaces/CoreTestUtilities/Formatting/FormattingTestBase.cs @@ -72,7 +72,7 @@ private protected async Task AssertFormatAsync( internal void AssertFormatWithTransformation( SolutionServices services, string expected, SyntaxNode root, IEnumerable spans, SyntaxFormattingOptions options, bool treeCompare = true, ParseOptions? parseOptions = null) { - var newRootNode = Formatter.Format(root, spans, services, options, rules: null, CancellationToken.None); + var newRootNode = Formatter.Format(root, spans, services, options, rules: default, CancellationToken.None); Assert.Equal(expected, newRootNode.ToFullString()); diff --git a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs index 8808a28046312..83cc1ba8d7589 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs @@ -29,8 +29,6 @@ public static RemoteHostClient Create(SolutionServices services, RemoteServiceCa var inprocServices = new InProcRemoteServices(services, traceListener, testData); var instance = new InProcRemoteHostClient(services, inprocServices, callbackDispatchers); - instance.Started(); - // return instance return instance; } @@ -76,8 +74,6 @@ public override RemoteServiceConnection CreateConnection(object? callbackT public override void Dispose() { _inprocServices.Dispose(); - - base.Dispose(); } public sealed class ServiceProvider : IServiceProvider @@ -166,7 +162,7 @@ public InProcRemoteServices(SolutionServices workspaceServices, TraceListener? t { var remoteLogger = new TraceSource("InProcRemoteClient") { - Switch = { Level = SourceLevels.Verbose }, + Switch = { Level = SourceLevels.Warning }, }; if (traceListener != null) @@ -184,36 +180,36 @@ public InProcRemoteServices(SolutionServices workspaceServices, TraceListener? t RegisterInProcBrokeredService(SolutionAssetProvider.ServiceDescriptor, () => new SolutionAssetProvider(workspaceServices)); RegisterRemoteBrokeredService(new RemoteAssetSynchronizationService.Factory()); RegisterRemoteBrokeredService(new RemoteAsynchronousOperationListenerService.Factory()); - RegisterRemoteBrokeredService(new RemoteSymbolSearchUpdateService.Factory()); + RegisterRemoteBrokeredService(new RemoteCodeLensReferencesService.Factory()); + RegisterRemoteBrokeredService(new RemoteConvertTupleToStructCodeRefactoringService.Factory()); + RegisterRemoteBrokeredService(new RemoteDependentTypeFinderService.Factory()); RegisterRemoteBrokeredService(new RemoteDesignerAttributeDiscoveryService.Factory()); - RegisterRemoteBrokeredService(new RemoteTaskListService.Factory()); RegisterRemoteBrokeredService(new RemoteDiagnosticAnalyzerService.Factory()); - RegisterRemoteBrokeredService(new RemoteSemanticClassificationService.Factory()); RegisterRemoteBrokeredService(new RemoteDocumentHighlightsService.Factory()); + RegisterRemoteBrokeredService(new RemoteEditAndContinueService.Factory()); RegisterRemoteBrokeredService(new RemoteEncapsulateFieldService.Factory()); - RegisterRemoteBrokeredService(new RemoteKeepAliveService.Factory()); - RegisterRemoteBrokeredService(new RemoteRenamerService.Factory()); - RegisterRemoteBrokeredService(new RemoteConvertTupleToStructCodeRefactoringService.Factory()); + RegisterRemoteBrokeredService(new RemoteExtensionMethodImportCompletionService.Factory()); RegisterRemoteBrokeredService(new RemoteFindUsagesService.Factory()); RegisterRemoteBrokeredService(new RemoteFullyQualifyService.Factory()); - RegisterRemoteBrokeredService(new RemoteSymbolFinderService.Factory()); - RegisterRemoteBrokeredService(new RemoteNavigateToSearchService.Factory()); - RegisterRemoteBrokeredService(new RemoteNavigationBarItemService.Factory()); - RegisterRemoteBrokeredService(new RemoteMissingImportDiscoveryService.Factory()); - RegisterRemoteBrokeredService(new RemoteExtensionMethodImportCompletionService.Factory()); - RegisterRemoteBrokeredService(new RemoteDependentTypeFinderService.Factory()); RegisterRemoteBrokeredService(new RemoteGlobalNotificationDeliveryService.Factory()); - RegisterRemoteBrokeredService(new RemoteCodeLensReferencesService.Factory()); - RegisterRemoteBrokeredService(new RemoteEditAndContinueService.Factory()); - RegisterRemoteBrokeredService(new RemoteValueTrackingService.Factory()); RegisterRemoteBrokeredService(new RemoteInheritanceMarginService.Factory()); - RegisterRemoteBrokeredService(new RemoteUnusedReferenceAnalysisService.Factory()); + RegisterRemoteBrokeredService(new RemoteKeepAliveService.Factory()); RegisterRemoteBrokeredService(new RemoteLegacySolutionEventsAggregationService.Factory()); + RegisterRemoteBrokeredService(new RemoteMissingImportDiscoveryService.Factory()); + RegisterRemoteBrokeredService(new RemoteNavigateToSearchService.Factory()); + RegisterRemoteBrokeredService(new RemoteNavigationBarItemService.Factory()); + RegisterRemoteBrokeredService(new RemoteProcessTelemetryService.Factory()); + RegisterRemoteBrokeredService(new RemoteRenamerService.Factory()); + RegisterRemoteBrokeredService(new RemoteSemanticClassificationService.Factory()); + RegisterRemoteBrokeredService(new RemoteSemanticSearchService.Factory()); + RegisterRemoteBrokeredService(new RemoteSourceGenerationService.Factory()); RegisterRemoteBrokeredService(new RemoteStackTraceExplorerService.Factory()); + RegisterRemoteBrokeredService(new RemoteSymbolFinderService.Factory()); + RegisterRemoteBrokeredService(new RemoteSymbolSearchUpdateService.Factory()); + RegisterRemoteBrokeredService(new RemoteTaskListService.Factory()); RegisterRemoteBrokeredService(new RemoteUnitTestingSearchService.Factory()); - RegisterRemoteBrokeredService(new RemoteSourceGenerationService.Factory()); - RegisterRemoteBrokeredService(new RemoteSemanticSearchService.Factory()); - RegisterRemoteBrokeredService(new RemoteProcessTelemetryService.Factory()); + RegisterRemoteBrokeredService(new RemoteUnusedReferenceAnalysisService.Factory()); + RegisterRemoteBrokeredService(new RemoteValueTrackingService.Factory()); } public void Dispose() diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs index af62f5337cb67..eda0bcadb27f4 100644 --- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs +++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs @@ -22,6 +22,7 @@ using Microsoft.CodeAnalysis.MetadataAsSource; using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.CodeAnalysis.UnitTests; @@ -54,8 +55,6 @@ public abstract partial class TestWorkspace : Wo internal override bool IgnoreUnchangeableDocumentsWhenApplyingChanges { get; } - private readonly IMetadataAsSourceFileService? _metadataAsSourceFileService; - private readonly string _workspaceKind; private readonly bool _supportsLspMutation; @@ -115,8 +114,6 @@ internal TestWorkspace( throw new InvalidOperationException($"{severityText} {fullMessage}"); }; } - - _metadataAsSourceFileService = ExportProvider.GetExportedValues().FirstOrDefault(); } private static HostServices GetHostServices([NotNull] ref TestComposition? composition, bool hasWorkspaceConfigurationOptions) @@ -202,12 +199,6 @@ public TDocument DocumentWithCursor public new void RegisterText(SourceTextContainer text) => base.RegisterText(text); - protected override void Dispose(bool finalize) - { - _metadataAsSourceFileService?.CleanupGeneratedFiles(); - base.Dispose(finalize); - } - internal void AddTestSolution(TSolution solution) => this.OnSolutionAdded(SolutionInfo.Create(solution.Id, solution.Version, solution.FilePath, projects: solution.Projects.Select(p => p.ToProjectInfo()))); @@ -757,5 +748,24 @@ private IList CreateSubmissions( return submissions; } + + public override bool TryApplyChanges(Solution newSolution) + { + var result = base.TryApplyChanges(newSolution); + + // Ensure that any in-memory analyzer references in this test workspace are known by the serializer service + // so that we can validate OOP scenarios involving analyzers. + foreach (var analyzer in this.CurrentSolution.AnalyzerReferences) + { + if (analyzer is AnalyzerImageReference analyzerImageReference) + { +#pragma warning disable CA1416 // Validate platform compatibility + SerializerService.TestAccessor.AddAnalyzerImageReference(analyzerImageReference); +#pragma warning restore CA1416 // Validate platform compatibility + } + } + + return result; + } } } diff --git a/src/Workspaces/MSBuildTest/Microsoft.CodeAnalysis.Workspaces.MSBuild.UnitTests.csproj b/src/Workspaces/MSBuildTest/Microsoft.CodeAnalysis.Workspaces.MSBuild.UnitTests.csproj index 0908804fdba4a..fb8224323dc55 100644 --- a/src/Workspaces/MSBuildTest/Microsoft.CodeAnalysis.Workspaces.MSBuild.UnitTests.csproj +++ b/src/Workspaces/MSBuildTest/Microsoft.CodeAnalysis.Workspaces.MSBuild.UnitTests.csproj @@ -7,29 +7,16 @@ $(NetRoslyn);net472 - - - - - - - - - - - - - - + diff --git a/src/Workspaces/MSBuildTest/NetCoreTests.cs b/src/Workspaces/MSBuildTest/NetCoreTests.cs index d58776c25f9d9..1af40888ed5e5 100644 --- a/src/Workspaces/MSBuildTest/NetCoreTests.cs +++ b/src/Workspaces/MSBuildTest/NetCoreTests.cs @@ -6,15 +6,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.CSharp; -using Microsoft.CodeAnalysis.MSBuild.Build; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.UnitTests; using Microsoft.CodeAnalysis.UnitTests.TestFiles; using Microsoft.CodeAnalysis.VisualBasic; using Roslyn.Test.Utilities; @@ -443,7 +439,7 @@ public async Task TestOpenProject_OverrideTFM() DotNetRestore(@"Library\Library.csproj"); // Override the TFM properties defined in the file - using (var workspace = CreateMSBuildWorkspace((PropertyNames.TargetFramework, ""), (PropertyNames.TargetFrameworks, "net6;net5"))) + using (var workspace = CreateMSBuildWorkspace(("TargetFramework", ""), ("TargetFrameworks", "net6;net5"))) { await workspace.OpenProjectAsync(projectFilePath); diff --git a/src/Workspaces/MSBuildTest/RpcTests.cs b/src/Workspaces/MSBuildTest/RpcTests.cs index ef05081a1ddd9..fd0e2a55f18ae 100644 --- a/src/Workspaces/MSBuildTest/RpcTests.cs +++ b/src/Workspaces/MSBuildTest/RpcTests.cs @@ -6,8 +6,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.MSBuild.Rpc; -using Microsoft.VisualStudio.Telemetry; using Nerdbank.Streams; using Xunit; diff --git a/src/Workspaces/MSBuildTest/VisualStudioMSBuildWorkspaceTests.cs b/src/Workspaces/MSBuildTest/VisualStudioMSBuildWorkspaceTests.cs index 732ff37083097..ea9cf6ad02c76 100644 --- a/src/Workspaces/MSBuildTest/VisualStudioMSBuildWorkspaceTests.cs +++ b/src/Workspaces/MSBuildTest/VisualStudioMSBuildWorkspaceTests.cs @@ -17,7 +17,6 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.MSBuild.Build; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.UnitTests; @@ -2432,7 +2431,7 @@ public async Task TestProjectReferenceWithExternAlias() } [ConditionalFact(typeof(VisualStudioMSBuildInstalled))] - public async Task TestProjectReferenceWithReferenceOutputAssemblyFalse() + public async Task TestProjectReferenceWithReferenceOutputAssemblyFalse_SolutionRoot() { var files = GetProjectReferenceSolutionFiles(); files = VisitProjectReferences( @@ -2451,6 +2450,29 @@ public async Task TestProjectReferenceWithReferenceOutputAssemblyFalse() } } + [ConditionalFact(typeof(VisualStudioMSBuildInstalled))] + public async Task TestProjectReferenceWithReferenceOutputAssemblyFalse_ProjectRoot() + { + var files = GetProjectReferenceSolutionFiles(); + files = VisitProjectReferences( + files, + r => r.Add(new XElement(XName.Get("ReferenceOutputAssembly", MSBuildNamespace), "false"))); + + CreateFiles(files); + + var referencingProjectPath = GetSolutionFileName(@"CSharpProject\CSharpProject_ProjectReference.csproj"); + var referencedProjectPath = GetSolutionFileName(@"CSharpProject\CSharpProject.csproj"); + + using var workspace = CreateMSBuildWorkspace(); + var project = await workspace.OpenProjectAsync(referencingProjectPath); + + Assert.Empty(project.ProjectReferences); + + // Project referenced through ProjectReference with ReferenceOutputAssembly=false + // should be present in the solution. + Assert.NotNull(project.Solution.GetProjectsByName("CSharpProject").SingleOrDefault()); + } + private static FileSet VisitProjectReferences(FileSet files, Action visitProjectReference) { var result = new List<(string, object)>(); @@ -3093,7 +3115,6 @@ public async Task TestOpenProject_CommandLineArgsHaveNoErrors() CreateFiles(GetSimpleCSharpSolutionFiles()); using var workspace = CreateMSBuildWorkspace(); - var loader = new CSharpProjectFileLoader(); var projectFilePath = GetSolutionFileName(@"CSharpProject\CSharpProject.csproj"); @@ -3104,7 +3125,7 @@ public async Task TestOpenProject_CommandLineArgsHaveNoErrors() var projectFileInfo = (await projectFile.GetProjectFileInfosAsync(CancellationToken.None)).Single(); var commandLineParser = workspace.Services - .GetLanguageServices(loader.Language) + .GetLanguageServices(LanguageNames.CSharp) .GetRequiredService(); var projectDirectory = Path.GetDirectoryName(projectFilePath); diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 2cc7bf9e2192a..ac97eb85341e4 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -189,23 +189,41 @@ public static async Task GetAssetsAsync( await assetProvider.GetAssetsAsync(assetPath, checksumSet, callback, arg, cancellationToken).ConfigureAwait(false); } + /// + /// Returns an array of assets, corresponding to all the checksums found in the given . + /// The assets will be returned in the order corresponding to their checksum in . + /// public static async Task> GetAssetsArrayAsync( this AbstractAssetProvider assetProvider, AssetPath assetPath, ChecksumCollection checksums, CancellationToken cancellationToken) where T : class { + // Note: nothing stops 'checksums' from having multiple identical checksums in it. First, collapse this down to + // a set so we're only asking about unique checksums. using var _1 = PooledHashSet.GetInstance(out var checksumSet); #if NET checksumSet.EnsureCapacity(checksums.Children.Length); #endif checksumSet.AddAll(checksums.Children); - using var _ = ArrayBuilder.GetInstance(checksumSet.Count, out var builder); + using var _2 = PooledDictionary.GetInstance(out var checksumToAsset); await assetProvider.GetAssetHelper().GetAssetsAsync( assetPath, checksumSet, - static (checksum, asset, builder) => builder.Add(asset), - builder, + // Calling .Add here is safe. As checksum-set is a unique set of checksums, we'll never have collions here. + static (checksum, asset, checksumToAsset) => checksumToAsset.Add(checksum, asset), + checksumToAsset, cancellationToken).ConfigureAwait(false); - return builder.ToImmutableAndClear(); + // Note: GetAssetsAsync will only succeed if we actually found all our assets (it crashes otherwise). So we can + // just safely assume we can index into checksumToAsset here. + Contract.ThrowIfTrue(checksumToAsset.Count != checksumSet.Count); + + // The result of GetAssetsArrayAsync wants the returned assets to be in the exact order of the checksums that + // were in 'checksums'. So now fetch the assets in that order, even if we found them in an entirely different + // order. + var result = new FixedSizeArrayBuilder(checksums.Children.Length); + foreach (var checksum in checksums.Children) + result.Add(checksumToAsset[checksum]); + + return result.MoveToImmutable(); } } diff --git a/src/Workspaces/Remote/Core/BrokeredServiceDescriptors.cs b/src/Workspaces/Remote/Core/BrokeredServiceDescriptors.cs index 100a69202bfb6..d66c72ff84200 100644 --- a/src/Workspaces/Remote/Core/BrokeredServiceDescriptors.cs +++ b/src/Workspaces/Remote/Core/BrokeredServiceDescriptors.cs @@ -74,7 +74,8 @@ protected override JsonRpcConnection CreateConnection(JsonRpc jsonRpc) public static readonly ServiceRpcDescriptor HotReloadLoggerService = CreateDebuggerServiceDescriptor("HotReloadLogger", new Version(0, 1)); public static readonly ServiceRpcDescriptor HotReloadSessionNotificationService = CreateDebuggerServiceDescriptor("HotReloadSessionNotificationService", new Version(0, 1)); public static readonly ServiceRpcDescriptor ManagedHotReloadAgentManagerService = CreateDebuggerServiceDescriptor("ManagedHotReloadAgentManagerService", new Version(0, 1)); - public static readonly ServiceRpcDescriptor MauiLaunchCustomizerServiceDescriptor = CreateMauiServiceDescriptor("MauiLaunchCustomizerService", new Version(0, 1)); + public static readonly ServiceRpcDescriptor HotReloadOptionService = CreateDebuggerClientServiceDescriptor("HotReloadOptionService", new Version(0, 1)); + public static readonly ServiceRpcDescriptor MauiLaunchCustomizerService = CreateMauiServiceDescriptor("MauiLaunchCustomizerService", new Version(0, 1)); public static ServiceMoniker CreateMoniker(string namespaceName, string componentName, string serviceName, Version? version) => new(namespaceName + "." + componentName + "." + serviceName, version); @@ -98,6 +99,13 @@ public static ServiceJsonRpcDescriptor CreateServerServiceDescriptor(string serv public static ServiceJsonRpcDescriptor CreateDebuggerServiceDescriptor(string serviceName, Version? version = null) => CreateDescriptor(CreateMoniker(VisualStudioComponentNamespace, DebuggerComponentName, serviceName, version)); + /// + /// Descriptor for services proferred by the debugger server (implemented in TypeScript). + /// + public static ServiceJsonRpcDescriptor CreateDebuggerClientServiceDescriptor(string serviceName, Version? version = null) + => new ClientServiceDescriptor(CreateMoniker(VisualStudioComponentNamespace, DebuggerComponentName, serviceName, version), clientInterface: null) + .WithExceptionStrategy(ExceptionProcessing.ISerializable); + /// /// Descriptor for services proferred by the MAUI extension (implemented in TypeScript). /// diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index d2d31c20e7efd..f1548eb3fbcb6 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -4,13 +4,13 @@ using System; using System.Buffers.Binary; +using System.Collections.Generic; using System.IO.Pipelines; using System.Threading; -using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; -using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote; @@ -62,79 +62,29 @@ internal readonly struct RemoteHostAssetWriter( private static readonly ObjectPool s_streamPool = new(() => new()); - private static readonly UnboundedChannelOptions s_channelOptions = new() - { - // We have a single task reading the data from the channel and writing it to the pipe. This option allows the - // channel to operate in a more efficient manner knowing it won't have to synchronize data for multiple readers. - SingleReader = true, - - // Currently we only have a single writer writing to the channel when we call _scope.FindAssetsAsync. However, - // we could change this in the future to allow the search to happen in parallel. - SingleWriter = true, - }; - private readonly PipeWriter _pipeWriter = pipeWriter; private readonly Scope _scope = scope; private readonly AssetPath _assetPath = assetPath; private readonly ReadOnlyMemory _checksums = checksums; private readonly ISerializerService _serializer = serializer; - public async ValueTask WriteDataAsync(CancellationToken cancellationToken) - { - // Create a channel to communicate between the searching and writing tasks. This allows the searching task to - // find items, add them to the channel synchronously, and immediately continue searching for more items. - // Concurrently, the writing task can read from the channel and write the items to the pipe-writer. - var channel = Channel.CreateUnbounded(s_channelOptions); - - // When cancellation happens, attempt to close the channel. That will unblock the task writing the assets to - // the pipe. Capture-free version is only available on netcore unfortunately. - using var _ = cancellationToken.Register( -#if NET - static (obj, cancellationToken) => ((Channel)obj!).Writer.TryComplete(new OperationCanceledException(cancellationToken)), - state: channel); -#else - () => channel.Writer.TryComplete(new OperationCanceledException(cancellationToken))); -#endif - - // Spin up a task to go find all the requested checksums, adding results to the channel. - // Spin up a task to read from the channel, writing out the assets to the pipe-writer. - await Task.WhenAll( - FindAssetsFromScopeAndWriteToChannelAsync(channel.Writer, cancellationToken), - ReadAssetsFromChannelAndWriteToPipeAsync(channel.Reader, cancellationToken)).ConfigureAwait(false); - } - - private async Task FindAssetsFromScopeAndWriteToChannelAsync(ChannelWriter channelWriter, CancellationToken cancellationToken) - { - Exception? exception = null; - try - { - await Task.Yield(); - - await _scope.FindAssetsAsync( - _assetPath, _checksums, - // It's ok to use TryWrite here. TryWrite always succeeds unless the channel is completed. And the - // channel is only ever completed by us (after FindAssetsAsync completes or throws an exception) or if - // the cancellationToken is triggered above in WriteDataAsync. In that latter case, it's ok for writing - // to the channel to do nothing as we no longer need to write out those assets to the pipe. - static (checksum, asset, channelWriter) => channelWriter.TryWrite((checksum, asset)), - channelWriter, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) when ((exception = ex) == null) - { - throw ExceptionUtilities.Unreachable(); - } - finally - { - // No matter what path we take (exceptional or non-exceptional), always complete the channel so the - // writing task knows it's done. - channelWriter.TryComplete(exception); - } - } - - private async Task ReadAssetsFromChannelAndWriteToPipeAsync(ChannelReader channelReader, CancellationToken cancellationToken) + public Task WriteDataAsync(CancellationToken cancellationToken) + => ProducerConsumer.RunAsync( + ProducerConsumerOptions.SingleReaderWriterOptions, + produceItems: static (onItemFound, @this, cancellationToken) => @this.FindAssetsAsync(onItemFound, cancellationToken), + consumeItems: static (items, @this, cancellationToken) => @this.WriteBatchToPipeAsync(items, cancellationToken), + args: this, + cancellationToken); + + private Task FindAssetsAsync(Action onItemFound, CancellationToken cancellationToken) + => _scope.FindAssetsAsync( + _assetPath, _checksums, + static (checksum, asset, onItemFound) => onItemFound((checksum, asset)), + onItemFound, cancellationToken); + + private async Task WriteBatchToPipeAsync( + IAsyncEnumerable checksumsAndAssets, CancellationToken cancellationToken) { - await Task.Yield(); - // Get the in-memory buffer and object-writer we'll use to serialize the assets into. Don't write any // validation bytes at this point in time. We'll write them between each asset we write out. Using a single // object writer across all assets means we get the benefit of string deduplication across all assets we write @@ -151,7 +101,7 @@ private async Task ReadAssetsFromChannelAndWriteToPipeAsync(ChannelReader( (service, cancellationToken) => service.EnableAsync(AsynchronousOperationListenerProvider.IsEnabled, listenerProvider.DiagnosticTokensEnabled, cancellationToken), cancellationToken).ConfigureAwait(false); - client.Started(); return client; } } @@ -141,8 +140,6 @@ public override void Dispose() _hubClient.Dispose(); _serviceBrokerClient.Dispose(); - - base.Dispose(); } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteAnalyzerAssemblyLoader.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteAnalyzerAssemblyLoader.cs index 7417225aaebba..78d093bdb1e57 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteAnalyzerAssemblyLoader.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteAnalyzerAssemblyLoader.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.IO; +using System.Collections.Immutable; namespace Microsoft.CodeAnalysis.Remote.Diagnostics { @@ -15,7 +16,8 @@ internal sealed class RemoteAnalyzerAssemblyLoader : AnalyzerAssemblyLoader { private readonly string _baseDirectory; - public RemoteAnalyzerAssemblyLoader(string baseDirectory) + public RemoteAnalyzerAssemblyLoader(string baseDirectory, ImmutableArray? externalResolvers = null) + : base(externalResolvers ?? []) { _baseDirectory = baseDirectory; } diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteAnalyzerAssemblyLoaderService.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteAnalyzerAssemblyLoaderService.cs index d1046f8960c44..52bac4993d404 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteAnalyzerAssemblyLoaderService.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteAnalyzerAssemblyLoaderService.cs @@ -4,11 +4,13 @@ using System; using System.Composition; +using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Reflection; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using System.Collections.Generic; namespace Microsoft.CodeAnalysis.Remote.Diagnostics { @@ -23,13 +25,14 @@ internal sealed class RemoteAnalyzerAssemblyLoaderService : IAnalyzerAssemblyLoa [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public RemoteAnalyzerAssemblyLoaderService() + public RemoteAnalyzerAssemblyLoaderService([ImportMany] IEnumerable externalResolvers) { var baseDirectory = Path.GetDirectoryName(Path.GetFullPath(typeof(RemoteAnalyzerAssemblyLoader).GetTypeInfo().Assembly.Location)); Debug.Assert(baseDirectory != null); - _loader = new(baseDirectory); - _shadowCopyLoader = new(Path.Combine(Path.GetTempPath(), "VS", "AnalyzerAssemblyLoader")); + var resolvers = externalResolvers.ToImmutableArray(); + _loader = new(baseDirectory, resolvers); + _shadowCopyLoader = new(Path.Combine(Path.GetTempPath(), "VS", "AnalyzerAssemblyLoader"), resolvers); } public IAnalyzerAssemblyLoader GetLoader(in AnalyzerAssemblyLoaderOptions options) diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs index 11067ca427033..b409b3b8e28db 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs @@ -267,5 +267,14 @@ public Entry(object @object) Object = @object; } } + + public TestAccessor GetTestAccessor() + => new(this); + + public readonly struct TestAccessor(SolutionAssetCache cache) + { + public void Clear() + => cache._assets.Clear(); + } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs b/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs index d7cdc206882f5..c1d32bb09e5f3 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -79,18 +80,22 @@ private static async Task CleanupAsync(Solution oldSolution, Solution var changes = newSolution.GetChangedDocuments(oldSolution); var final = newSolution; - foreach (var docId in changes) - { - var document = newSolution.GetRequiredDocument(docId); + var changedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync( + source: changes, + produceItems: static async (docId, callback, args, cancellationToken) => + { + var document = args.newSolution.GetRequiredDocument(docId); - var options = await document.GetCodeCleanupOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); - var cleaned = await CodeAction.CleanupDocumentAsync(document, options, cancellationToken).ConfigureAwait(false); + var options = await document.GetCodeCleanupOptionsAsync(args.fallbackOptions, cancellationToken).ConfigureAwait(false); + var cleaned = await CodeAction.CleanupDocumentAsync(document, options, cancellationToken).ConfigureAwait(false); - var cleanedRoot = await cleaned.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - final = final.WithDocumentSyntaxRoot(docId, cleanedRoot); - } + var cleanedRoot = await cleaned.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + callback((docId, cleanedRoot)); + }, + args: (newSolution, fallbackOptions), + cancellationToken).ConfigureAwait(false); - return final; + return newSolution.WithDocumentSyntaxRoots(changedDocuments); } } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs b/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs index e267243f8c32d..27ea35c52107e 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs @@ -13,7 +13,10 @@ namespace Microsoft.CodeAnalysis.Remote { - internal sealed class RemoteNavigateToSearchService : BrokeredServiceBase, IRemoteNavigateToSearchService + internal sealed class RemoteNavigateToSearchService( + in BrokeredServiceBase.ServiceConstructionArguments arguments, + RemoteCallback callback) + : BrokeredServiceBase(arguments), IRemoteNavigateToSearchService { internal sealed class Factory : FactoryBase { @@ -22,26 +25,14 @@ protected override IRemoteNavigateToSearchService CreateService( => new RemoteNavigateToSearchService(arguments, callback); } - private readonly RemoteCallback _callback; + private readonly RemoteCallback _callback = callback; - public RemoteNavigateToSearchService(in ServiceConstructionArguments arguments, RemoteCallback callback) - : base(arguments) - { - _callback = callback; - } - - private (Func onItemFound, Func onProjectCompleted) GetCallbacks( + private (Func, VoidResult, CancellationToken, Task> onItemsFound, Func onProjectCompleted) GetCallbacks( RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) { - Func onItemFound = async i => await _callback.InvokeAsync((callback, _) => - callback.OnResultFoundAsync(callbackId, i), - cancellationToken).ConfigureAwait(false); - - Func onProjectCompleted = async () => await _callback.InvokeAsync((callback, _) => - callback.OnProjectCompletedAsync(callbackId), - cancellationToken).ConfigureAwait(false); - - return (onItemFound, onProjectCompleted); + return ( + async (array, _, cancellationToken) => await _callback.InvokeAsync((callback, cancellationToken) => callback.OnItemsFoundAsync(callbackId, array), cancellationToken).ConfigureAwait(false), + async () => await _callback.InvokeAsync((callback, cancellationToken) => callback.OnProjectCompletedAsync(callbackId), cancellationToken).ConfigureAwait(false)); } public ValueTask HydrateAsync(Checksum solutionChecksum, CancellationToken cancellationToken) @@ -64,10 +55,10 @@ public ValueTask SearchDocumentAsync( return RunServiceAsync(solutionChecksum, async solution => { var document = solution.GetRequiredDocument(documentId); - var (onItemFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); + var (onItemsFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); await AbstractNavigateToSearchService.SearchDocumentInCurrentProcessAsync( - document, searchPattern, kinds.ToImmutableHashSet(), onItemFound, cancellationToken).ConfigureAwait(false); + document, searchPattern, kinds.ToImmutableHashSet(), onItemsFound, cancellationToken).ConfigureAwait(false); }, cancellationToken); } @@ -83,12 +74,12 @@ public ValueTask SearchProjectsAsync( return RunServiceAsync(solutionChecksum, async solution => { var projects = projectIds.SelectAsArray(solution.GetRequiredProject); - var (onItemFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); + var (onItemsFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); var priorityDocuments = priorityDocumentIds.SelectAsArray(d => solution.GetRequiredDocument(d)); await AbstractNavigateToSearchService.SearchProjectsInCurrentProcessAsync( - projects, priorityDocuments, searchPattern, kinds.ToImmutableHashSet(), onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + projects, priorityDocuments, searchPattern, kinds.ToImmutableHashSet(), onItemsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); }, cancellationToken); } @@ -103,10 +94,10 @@ public ValueTask SearchGeneratedDocumentsAsync( return RunServiceAsync(solutionChecksum, async solution => { var projects = projectIds.SelectAsArray(solution.GetRequiredProject); - var (onItemFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); + var (onItemsFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); await AbstractNavigateToSearchService.SearchGeneratedDocumentsInCurrentProcessAsync( - projects, searchPattern, kinds.ToImmutableHashSet(), onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + projects, searchPattern, kinds.ToImmutableHashSet(), onItemsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); }, cancellationToken); } @@ -123,10 +114,10 @@ public ValueTask SearchCachedDocumentsAsync( // Intentionally do not call GetSolutionAsync here. We do not want the cost of // synchronizing the solution over to the remote side. Instead, we just directly // check whatever cached data we have from the previous vs session. - var (onItemFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); + var (onItemsFound, onProjectCompleted) = GetCallbacks(callbackId, cancellationToken); var storageService = GetWorkspaceServices().GetPersistentStorageService(); await AbstractNavigateToSearchService.SearchCachedDocumentsInCurrentProcessAsync( - storageService, documentKeys, priorityDocumentKeys, searchPattern, kinds.ToImmutableHashSet(), onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + storageService, documentKeys, priorityDocumentKeys, searchPattern, kinds.ToImmutableHashSet(), onItemsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); }, cancellationToken); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs index 5db02e1df8e57..6bb8113b40e18 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs @@ -117,7 +117,6 @@ private static async Task CacheClassificationsAsync( var persistenceService = solution.Services.GetPersistentStorageService(); var storage = await persistenceService.GetStorageAsync(SolutionKey.ToSolutionKey(solution), cancellationToken).ConfigureAwait(false); - await using var _1 = storage.ConfigureAwait(false); if (storage == null) return; @@ -280,7 +279,6 @@ private async Task> TryReadCachedSemanticClassifi { var persistenceService = GetWorkspaceServices().GetPersistentStorageService(); var storage = await persistenceService.GetStorageAsync(documentKey.Project.Solution, cancellationToken).ConfigureAwait(false); - await using var _ = storage.ConfigureAwait(false); if (storage == null) return default; diff --git a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs index 43be04c3b336c..83aef0424bc2b 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs @@ -218,12 +218,6 @@ public ValueTask OnStartedAsync(CancellationToken cancellationToken) public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => _callback.InvokeAsync((callback, cancellationToken) => callback.OnCompletedAsync(_callbackId, cancellationToken), cancellationToken); - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) - => _callback.InvokeAsync((callback, cancellationToken) => callback.OnFindInDocumentStartedAsync(_callbackId, document.Id, cancellationToken), cancellationToken); - - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) - => _callback.InvokeAsync((callback, cancellationToken) => callback.OnFindInDocumentCompletedAsync(_callbackId, document.Id, cancellationToken), cancellationToken); - public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) { var dehydratedGroup = SerializableSymbolGroup.Dehydrate(_solution, group, cancellationToken); @@ -231,15 +225,18 @@ public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken can (callback, cancellationToken) => callback.OnDefinitionFoundAsync(_callbackId, dehydratedGroup, cancellationToken), cancellationToken); } - public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, ReferenceLocation reference, CancellationToken cancellationToken) + public ValueTask OnReferencesFoundAsync( + ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, + CancellationToken cancellationToken) { - var dehydratedGroup = SerializableSymbolGroup.Dehydrate(_solution, group, cancellationToken); - var dehydratedDefinition = SerializableSymbolAndProjectId.Dehydrate(_solution, definition, cancellationToken); - var dehydratedReference = SerializableReferenceLocation.Dehydrate(reference, cancellationToken); + var dehydrated = references.SelectAsArray(t => + (SerializableSymbolGroup.Dehydrate(_solution, t.group, cancellationToken), + SerializableSymbolAndProjectId.Dehydrate(_solution, t.symbol, cancellationToken), + SerializableReferenceLocation.Dehydrate(t.location, cancellationToken))); return _callback.InvokeAsync( - (callback, cancellationToken) => callback.OnReferenceFoundAsync( - _callbackId, dehydratedGroup, dehydratedDefinition, dehydratedReference, cancellationToken), cancellationToken); + (callback, cancellationToken) => callback.OnReferencesFoundAsync( + _callbackId, dehydrated, cancellationToken), cancellationToken); } public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) diff --git a/src/Workspaces/Remote/ServiceHubTest/TelemetryLoggerTests.cs b/src/Workspaces/Remote/ServiceHubTest/TelemetryLoggerTests.cs index 8f4b733cd8b9d..c44212acc8ada 100644 --- a/src/Workspaces/Remote/ServiceHubTest/TelemetryLoggerTests.cs +++ b/src/Workspaces/Remote/ServiceHubTest/TelemetryLoggerTests.cs @@ -74,8 +74,7 @@ private static string InspectPropertyValue(object? value) _ => value.ToString()! }; - [Theory] - [CombinatorialData] + [Theory, CombinatorialData] internal void IgnoredSeverity(LogLevel level) { var logger = new TestLogger(); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/TypeStyle/TypeStyleHelper.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/TypeStyle/TypeStyleHelper.cs index 645e538e6d928..1cef0f1a6c039 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/TypeStyle/TypeStyleHelper.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/TypeStyle/TypeStyleHelper.cs @@ -69,7 +69,7 @@ public static bool IsTypeApparentInAssignmentExpression( } // literals, use var if options allow usage here. - if (initializerExpression.IsAnyLiteralExpression()) + if (initializerExpression is LiteralExpressionSyntax) { return stylePreferences.HasFlag(UseVarPreference.ForBuiltInTypes); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs index af8de6ed49ff5..ae491e6c78840 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs @@ -166,9 +166,6 @@ private static bool AddSimpleName(SimpleNameSyntax simpleName, List part return true; } - public static bool IsAnyLiteralExpression(this ExpressionSyntax expression) - => expression is LiteralExpressionSyntax; - public static bool IsInConstantContext([NotNullWhen(true)] this ExpressionSyntax? expression) { if (expression == null) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs index 539db54381412..0eb50260e6875 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs @@ -219,7 +219,15 @@ public static bool CanRemoveParentheses( // (null) -> null // (default) -> default; // (1) -> 1 - if (expression.IsAnyLiteralExpression()) + if (expression is LiteralExpressionSyntax) + return true; + + // (typeof(int)) -> typeof(int) + // (default(int)) -> default(int) + // (checked(1)) -> checked(1) + // (unchecked(1)) -> unchecked(1) + // (sizeof(int)) -> sizeof(int) + if (expression is TypeOfExpressionSyntax or DefaultExpressionSyntax or CheckedExpressionSyntax or SizeOfExpressionSyntax) return true; // (this) -> this diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/CSharpSyntaxFormatting.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/CSharpSyntaxFormatting.cs index e4006fad174b4..d56f6b058ed0e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/CSharpSyntaxFormatting.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/CSharpSyntaxFormatting.cs @@ -7,10 +7,8 @@ using System.Threading; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; -using Microsoft.CodeAnalysis.Shared.Collections; -using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Collections; namespace Microsoft.CodeAnalysis.CSharp.Formatting; @@ -47,6 +45,6 @@ public override SyntaxFormattingOptions GetFormattingOptions(IOptionsReader opti protected override IFormattingResult CreateAggregatedFormattingResult(SyntaxNode node, IList results, TextSpanIntervalTree? formattingSpans = null) => new AggregatedFormattingResult(node, results, formattingSpans); - protected override AbstractFormattingResult Format(SyntaxNode node, SyntaxFormattingOptions options, IEnumerable formattingRules, SyntaxToken startToken, SyntaxToken endToken, CancellationToken cancellationToken) + protected override AbstractFormattingResult Format(SyntaxNode node, SyntaxFormattingOptions options, ImmutableArray formattingRules, SyntaxToken startToken, SyntaxToken endToken, CancellationToken cancellationToken) => new CSharpFormatEngine(node, options, formattingRules, startToken, endToken).Format(cancellationToken); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/DefaultOperationProvider.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/DefaultOperationProvider.cs index 8b392f7679a57..67fa60bca9850 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/DefaultOperationProvider.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/DefaultOperationProvider.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.CSharp.Formatting; @@ -19,7 +20,7 @@ private DefaultOperationProvider() { } - public override void AddSuppressOperations(List list, SyntaxNode node, in NextSuppressOperationAction nextOperation) + public override void AddSuppressOperations(ArrayBuilder list, SyntaxNode node, in NextSuppressOperationAction nextOperation) { } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Engine/CSharpFormatEngine.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Engine/CSharpFormatEngine.cs index 8800b22006f67..11c3eb47cef74 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Engine/CSharpFormatEngine.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Engine/CSharpFormatEngine.cs @@ -2,9 +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.Collections.Generic; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.LanguageService; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.LanguageService; @@ -16,7 +15,7 @@ internal class CSharpFormatEngine : AbstractFormatEngine public CSharpFormatEngine( SyntaxNode node, SyntaxFormattingOptions options, - IEnumerable formattingRules, + ImmutableArray formattingRules, SyntaxToken startToken, SyntaxToken endToken) : base(TreeData.Create(node), diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/BaseFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/BaseFormattingRule.cs index d2c5f95c7cc6b..d8259513c82c9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/BaseFormattingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/BaseFormattingRule.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -116,13 +117,13 @@ protected static void SetAlignmentBlockOperation( list.Add(FormattingOperations.CreateRelativeIndentBlockOperation(baseToken, startToken, endToken, indentationDelta: 0, option: option)); } - protected static void AddSuppressWrappingIfOnSingleLineOperation(List list, SyntaxToken startToken, SyntaxToken endToken, SuppressOption extraOption = SuppressOption.None) + protected static void AddSuppressWrappingIfOnSingleLineOperation(ArrayBuilder list, SyntaxToken startToken, SyntaxToken endToken, SuppressOption extraOption = SuppressOption.None) => AddSuppressOperation(list, startToken, endToken, SuppressOption.NoWrappingIfOnSingleLine | extraOption); - protected static void AddSuppressAllOperationIfOnMultipleLine(List list, SyntaxToken startToken, SyntaxToken endToken, SuppressOption extraOption = SuppressOption.None) + protected static void AddSuppressAllOperationIfOnMultipleLine(ArrayBuilder list, SyntaxToken startToken, SyntaxToken endToken, SuppressOption extraOption = SuppressOption.None) => AddSuppressOperation(list, startToken, endToken, SuppressOption.NoSpacingIfOnMultipleLine | SuppressOption.NoWrapping | extraOption); - protected static void AddSuppressOperation(List list, SyntaxToken startToken, SyntaxToken endToken, SuppressOption option) + protected static void AddSuppressOperation(ArrayBuilder list, SyntaxToken startToken, SyntaxToken endToken, SuppressOption option) { if (startToken.Kind() == SyntaxKind.None || endToken.Kind() == SyntaxKind.None) { @@ -158,7 +159,7 @@ protected static AdjustNewLinesOperation CreateAdjustNewLinesOperation(int line, protected static AdjustSpacesOperation CreateAdjustSpacesOperation(int space, AdjustSpacesOption option) => FormattingOperations.CreateAdjustSpacesOperation(space, option); - protected static void AddBraceSuppressOperations(List list, SyntaxNode node) + protected static void AddBraceSuppressOperations(ArrayBuilder list, SyntaxNode node) { var bracePair = node.GetBracePair(); if (!bracePair.IsValidBracketOrBracePair()) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs index f3b79019b4238..977c5ebd4978a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Utilities; using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; @@ -21,7 +22,7 @@ internal class ElasticTriviaFormattingRule : BaseFormattingRule { internal const string Name = "CSharp Elastic trivia Formatting Rule"; - public override void AddSuppressOperations(List list, SyntaxNode node, in NextSuppressOperationAction nextOperation) + public override void AddSuppressOperations(ArrayBuilder list, SyntaxNode node, in NextSuppressOperationAction nextOperation) { nextOperation.Invoke(); @@ -37,7 +38,7 @@ public override void AddSuppressOperations(List list, SyntaxN AddCollectionExpressionSuppressOperations(list, node); } - private static void AddPropertyDeclarationSuppressOperations(List list, SyntaxNode node) + private static void AddPropertyDeclarationSuppressOperations(ArrayBuilder list, SyntaxNode node) { if (node is BasePropertyDeclarationSyntax basePropertyDeclaration && basePropertyDeclaration.AccessorList != null && basePropertyDeclaration.AccessorList.Accessors.All(a => a.Body == null) && @@ -49,7 +50,7 @@ private static void AddPropertyDeclarationSuppressOperations(List list, SyntaxNode node) + private static void AddInitializerSuppressOperations(ArrayBuilder list, SyntaxNode node) { var initializer = GetInitializerNode(node); var lastTokenOfType = GetLastTokenOfType(node); @@ -66,7 +67,7 @@ private static void AddInitializerSuppressOperations(List lis } } - private static void AddCollectionExpressionSuppressOperations(List list, SyntaxNode node) + private static void AddCollectionExpressionSuppressOperations(ArrayBuilder list, SyntaxNode node) { if (node is CollectionExpressionSyntax { OpenBracketToken.IsMissing: false, CloseBracketToken.IsMissing: false } collectionExpression) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/QueryExpressionFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/QueryExpressionFormattingRule.cs index 32bdf04b114db..e9628ccd59744 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/QueryExpressionFormattingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/QueryExpressionFormattingRule.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CSharp.Formatting; @@ -38,7 +39,7 @@ public override AbstractFormattingRule WithOptions(SyntaxFormattingOptions optio return new QueryExpressionFormattingRule(newOptions); } - public override void AddSuppressOperations(List list, SyntaxNode node, in NextSuppressOperationAction nextOperation) + public override void AddSuppressOperations(ArrayBuilder list, SyntaxNode node, in NextSuppressOperationAction nextOperation) { nextOperation.Invoke(); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/SpacingFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/SpacingFormattingRule.cs index 68df98b39dfce..ccf10986adf30 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/SpacingFormattingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/SpacingFormattingRule.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Formatting; @@ -557,7 +558,7 @@ SyntaxKind.InterpolatedSingleLineRawStringStartToken or return nextOperation.Invoke(in previousToken, in currentToken); } - public override void AddSuppressOperations(List list, SyntaxNode node, in NextSuppressOperationAction nextOperation) + public override void AddSuppressOperations(ArrayBuilder list, SyntaxNode node, in NextSuppressOperationAction nextOperation) { nextOperation.Invoke(); @@ -570,7 +571,7 @@ private static bool IsEmptyForStatement(ForStatementSyntax forStatement) && forStatement.Condition == null && forStatement.Incrementors.Count == 0; - private void SuppressVariableDeclaration(List list, SyntaxNode node) + private void SuppressVariableDeclaration(ArrayBuilder list, SyntaxNode node) { if (node.Kind() is SyntaxKind.FieldDeclaration diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/SuppressFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/SuppressFormattingRule.cs index d649ba761a44c..dd4aecbdbe93c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/SuppressFormattingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/SuppressFormattingRule.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp.Formatting; @@ -17,7 +18,7 @@ internal class SuppressFormattingRule : BaseFormattingRule { internal const string Name = "CSharp Suppress Formatting Rule"; - public override void AddSuppressOperations(List list, SyntaxNode node, in NextSuppressOperationAction nextOperation) + public override void AddSuppressOperations(ArrayBuilder list, SyntaxNode node, in NextSuppressOperationAction nextOperation) { nextOperation.Invoke(); @@ -32,7 +33,7 @@ public override void AddSuppressOperations(List list, SyntaxN AddSpecificNodesSuppressOperations(list, node); } - private static void AddSpecificNodesSuppressOperations(List list, SyntaxNode node) + private static void AddSpecificNodesSuppressOperations(ArrayBuilder list, SyntaxNode node) { if (node is IfStatementSyntax ifStatementNode) { @@ -259,7 +260,7 @@ private static void AddSpecificNodesSuppressOperations(List l } } - private static void AddStatementExceptBlockSuppressOperations(List list, SyntaxNode node) + private static void AddStatementExceptBlockSuppressOperations(ArrayBuilder list, SyntaxNode node) { if (node is not StatementSyntax statementNode || statementNode.Kind() == SyntaxKind.Block) { @@ -272,7 +273,7 @@ private static void AddStatementExceptBlockSuppressOperations(List list, SyntaxNode node) + private static void AddFormatSuppressOperations(ArrayBuilder list, SyntaxNode node) { if (!node.ContainsDirectives) { @@ -293,7 +294,7 @@ private static void AddFormatSuppressOperations(List list, Sy return; // Local functions - static void ProcessTriviaList(List list, SyntaxTriviaList triviaList) + static void ProcessTriviaList(ArrayBuilder list, SyntaxTriviaList triviaList) { foreach (var trivia in triviaList) { @@ -301,7 +302,7 @@ static void ProcessTriviaList(List list, SyntaxTriviaList tri } } - static void ProcessTrivia(List list, SyntaxTrivia trivia) + static void ProcessTrivia(ArrayBuilder list, SyntaxTrivia trivia) { if (!(trivia.HasStructure)) { @@ -311,7 +312,7 @@ static void ProcessTrivia(List list, SyntaxTrivia trivia) ProcessStructuredTrivia(list, trivia.GetStructure()!); } - static void ProcessStructuredTrivia(List list, SyntaxNode structure) + static void ProcessStructuredTrivia(ArrayBuilder list, SyntaxNode structure) { if (structure is not PragmaWarningDirectiveTriviaSyntax pragmaWarningDirectiveTrivia) { @@ -365,7 +366,7 @@ private static bool IsFormatDirective(DirectiveTriviaSyntax? trivia, SyntaxKind return false; } - private static void AddInitializerSuppressOperations(List list, SyntaxNode node) + private static void AddInitializerSuppressOperations(ArrayBuilder list, SyntaxNode node) { // array or collection initializer case if (node.IsInitializerForArrayOrCollectionCreationExpression()) @@ -395,7 +396,7 @@ private static void AddInitializerSuppressOperations(List lis } } - private static void AddInitializerSuppressOperations(List list, SyntaxNode parent, IEnumerable items) + private static void AddInitializerSuppressOperations(ArrayBuilder list, SyntaxNode parent, IEnumerable items) { // make creation node itself to not break into multiple line, if it is on same line AddSuppressWrappingIfOnSingleLineOperation(list, parent.GetFirstToken(includeZeroWidth: true), parent.GetLastToken(includeZeroWidth: true)); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/WrappingFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/WrappingFormattingRule.cs index 7332ca7cbdf80..1e41f578424e3 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/WrappingFormattingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/WrappingFormattingRule.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -42,7 +43,7 @@ public override AbstractFormattingRule WithOptions(SyntaxFormattingOptions optio return new WrappingFormattingRule(newOptions); } - public override void AddSuppressOperations(List list, SyntaxNode node, in NextSuppressOperationAction nextOperation) + public override void AddSuppressOperations(ArrayBuilder list, SyntaxNode node, in NextSuppressOperationAction nextOperation) { nextOperation.Invoke(); @@ -88,7 +89,7 @@ private static (SyntaxToken firstToken, SyntaxToken lastToken) GetSpecificNodeSu }; } - private static void AddSpecificNodesSuppressOperations(List list, SyntaxNode node) + private static void AddSpecificNodesSuppressOperations(ArrayBuilder list, SyntaxNode node) { var (firstToken, lastToken) = GetSpecificNodeSuppressionTokenRange(node); if (!firstToken.IsKind(SyntaxKind.None) || !lastToken.IsKind(SyntaxKind.None)) @@ -97,7 +98,7 @@ private static void AddSpecificNodesSuppressOperations(List l } } - private static void AddStatementExceptBlockSuppressOperations(List list, SyntaxNode node) + private static void AddStatementExceptBlockSuppressOperations(ArrayBuilder list, SyntaxNode node) { if (node is not StatementSyntax statementNode || statementNode.Kind() == SyntaxKind.Block) { @@ -110,7 +111,7 @@ private static void AddStatementExceptBlockSuppressOperations(List list, SyntaxNode node) + private static void RemoveSuppressOperationForStatementMethodDeclaration(ArrayBuilder list, SyntaxNode node) { if (!(node is not StatementSyntax statementNode || statementNode.Kind() == SyntaxKind.Block)) { @@ -133,7 +134,7 @@ private static void RemoveSuppressOperationForStatementMethodDeclaration(List list, SyntaxNode node) + private static void RemoveSuppressOperationForBlock(ArrayBuilder list, SyntaxNode node) { var bracePair = GetBracePair(node); if (!bracePair.IsValidBracketOrBracePair()) @@ -175,7 +176,7 @@ private static (SyntaxToken openBrace, SyntaxToken closeBrace) GetBracePair(Synt } private static void RemoveSuppressOperation( - List list, + ArrayBuilder list, SyntaxToken startToken, SyntaxToken endToken) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs index 2e538e86dd901..afd0a3984bc56 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Indentation; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -103,7 +104,7 @@ public IList FormatToken(SyntaxToken token, CancellationToken cancel } } - var smartTokenformattingRules = new SmartTokenFormattingRule().Concat(_formattingRules); + ImmutableArray smartTokenFormattingRules = [new SmartTokenFormattingRule(), .. _formattingRules]; var adjustedStartPosition = previousToken.SpanStart; if (token.IsKind(SyntaxKind.OpenBraceToken) && _options.IndentStyle != FormattingOptions2.IndentStyle.Smart) @@ -117,7 +118,7 @@ public IList FormatToken(SyntaxToken token, CancellationToken cancel var formatter = CSharpSyntaxFormatting.Instance; var result = formatter.GetFormattingResult( - _root, [TextSpan.FromBounds(adjustedStartPosition, adjustedEndPosition)], _options.FormattingOptions, smartTokenformattingRules, cancellationToken); + _root, [TextSpan.FromBounds(adjustedStartPosition, adjustedEndPosition)], _options.FormattingOptions, smartTokenFormattingRules, cancellationToken); return result.GetTextChanges(cancellationToken); } @@ -146,7 +147,7 @@ private class NoLineChangeFormattingRule : AbstractFormattingRule private class SmartTokenFormattingRule : NoLineChangeFormattingRule { - public override void AddSuppressOperations(List list, SyntaxNode node, in NextSuppressOperationAction nextOperation) + public override void AddSuppressOperations(ArrayBuilder list, SyntaxNode node, in NextSuppressOperationAction nextOperation) { // don't suppress anything } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTree`1.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTree`1.cs index 0e0c3b9ca927a..f90939d8824f0 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTree`1.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTree`1.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -22,17 +23,18 @@ internal partial class IntervalTree : IEnumerable { public static readonly IntervalTree Empty = new(); - protected Node? root; + private static readonly ObjectPool> s_stackPool + = SharedPools.Default>(); + + /// + /// Keep around a fair number of these as we often use them in parallel algorithms. + /// + private static readonly ObjectPool> s_nodePool = new(() => new(), 128); private delegate bool TestInterval(T value, int start, int length, in TIntrospector introspector) where TIntrospector : struct, IIntervalIntrospector; - private static readonly ObjectPool> s_stackPool - = SharedPools.Default>(); - - public IntervalTree() - { - } + protected Node? root; public static IntervalTree Create(in TIntrospector introspector, IEnumerable values) where TIntrospector : struct, IIntervalIntrospector @@ -139,9 +141,33 @@ public bool HasIntervalThatContains(int start, int length, in TIn private bool Any(int start, int length, TestInterval testInterval, in TIntrospector introspector) where TIntrospector : struct, IIntervalIntrospector { - using var result = TemporaryArray.Empty; - var matches = FillWithIntervalsThatMatch(start, length, testInterval, ref result.AsRef(), in introspector, stopAfterFirst: true); - return matches > 0; + // Inlined version of FillWithIntervalsThatMatch, optimized to do less work and stop once it finds a match. + if (root is null) + return false; + + using var pooledObject = s_nodePool.GetPooledObject(); + var candidates = pooledObject.Object; + + var end = start + length; + + candidates.Push(root); + + while (candidates.TryPop(out var currentNode)) + { + // Check the nodes as we go down. That way we can stop immediately when we find something that matches, + // instead of having to do an entire in-order walk, which might end up hitting a lot of nodes we don't care + // about and placing a lot into the stack. + if (testInterval(currentNode.Value, start, length, in introspector)) + return true; + + if (ShouldExamineRight(start, end, currentNode, in introspector, out var right)) + candidates.Push(right); + + if (ShouldExamineLeft(start, currentNode, in introspector, out var left)) + candidates.Push(left); + } + + return false; } private ImmutableArray GetIntervalsThatMatch( @@ -161,28 +187,11 @@ private int FillWithIntervalsThatMatch( where TIntrospector : struct, IIntervalIntrospector { if (root == null) - { return 0; - } using var pooledObject = s_stackPool.GetPooledObject(); var candidates = pooledObject.Object; - var matches = FillWithIntervalsThatMatch( - start, length, testInterval, - ref builder, in introspector, - stopAfterFirst, candidates); - - return matches; - } - - /// The number of matching intervals found by the method. - private int FillWithIntervalsThatMatch( - int start, int length, TestInterval testInterval, - ref TemporaryArray builder, in TIntrospector introspector, - bool stopAfterFirst, Stack<(Node? node, bool firstTime)> candidates) - where TIntrospector : struct, IIntervalIntrospector - { var matches = 0; var end = start + length; @@ -191,11 +200,8 @@ private int FillWithIntervalsThatMatch( while (candidates.TryPop(out var currentTuple)) { var currentNode = currentTuple.node; - RoslynDebug.Assert(currentNode != null); - - var firstTime = currentTuple.firstTime; - if (!firstTime) + if (!currentTuple.firstTime) { // We're seeing this node for the second time (as we walk back up the left // side of it). Now see if it matches our test, and if so return it out. @@ -205,45 +211,62 @@ private int FillWithIntervalsThatMatch( builder.Add(currentNode.Value); if (stopAfterFirst) - { return 1; - } } } else { - // First time we're seeing this node. In order to see the node 'in-order', - // we push the right side, then the node again, then the left side. This - // time we mark the current node with 'false' to indicate that it's the - // second time we're seeing it the next time it comes around. - - // right children's starts will never be to the left of the parent's start - // so we should consider right subtree only if root's start overlaps with - // interval's End, - if (introspector.GetStart(currentNode.Value) <= end) - { - var right = currentNode.Right; - if (right != null && GetEnd(right.MaxEndNode.Value, in introspector) >= start) - { - candidates.Push((right, firstTime: true)); - } - } + // First time we're seeing this node. In order to see the node 'in-order', we push the right side, then + // the node again, then the left side. This time we mark the current node with 'false' to indicate that + // it's the second time we're seeing it the next time it comes around. + + if (ShouldExamineRight(start, end, currentNode, in introspector, out var right)) + candidates.Push((right, firstTime: true)); candidates.Push((currentNode, firstTime: false)); - // only if left's maxVal overlaps with interval's start, we should consider - // left subtree - var left = currentNode.Left; - if (left != null && GetEnd(left.MaxEndNode.Value, in introspector) >= start) - { + if (ShouldExamineLeft(start, currentNode, in introspector, out var left)) candidates.Push((left, firstTime: true)); - } } } return matches; } + private static bool ShouldExamineRight( + int start, int end, + Node currentNode, + in TIntrospector introspector, + [NotNullWhen(true)] out Node? right) where TIntrospector : struct, IIntervalIntrospector + { + // right children's starts will never be to the left of the parent's start so we should consider right + // subtree only if root's start overlaps with interval's End, + if (introspector.GetStart(currentNode.Value) <= end) + { + right = currentNode.Right; + if (right != null && GetEnd(right.MaxEndNode.Value, in introspector) >= start) + return true; + } + + right = null; + return false; + } + + private static bool ShouldExamineLeft( + int start, + Node currentNode, + in TIntrospector introspector, + [NotNullWhen(true)] out Node? left) where TIntrospector : struct, IIntervalIntrospector + { + // only if left's maxVal overlaps with interval's start, we should consider + // left subtree + left = currentNode.Left; + if (left != null && GetEnd(left.MaxEndNode.Value, in introspector) >= start) + return true; + + return false; + } + public bool IsEmpty() => this.root == null; protected static Node Insert(Node? root, Node newNode, in TIntrospector introspector) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ListExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ListExtensions.cs index 535a7e3fdb495..4a05b3ffbfe13 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ListExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ListExtensions.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Shared.Extensions; @@ -58,6 +59,25 @@ public static void RemoveOrTransformAll(this List list, Func(this ArrayBuilder list, Func transform, TArg arg) + where T : struct + { + RoslynDebug.AssertNotNull(list); + RoslynDebug.AssertNotNull(transform); + + var targetIndex = 0; + for (var sourceIndex = 0; sourceIndex < list.Count; sourceIndex++) + { + var newValue = transform(list[sourceIndex], arg); + if (newValue is null) + continue; + + list[targetIndex++] = newValue.Value; + } + + list.RemoveRange(targetIndex, list.Count - targetIndex); + } + /// /// Attempts to remove the first item selected by . /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/AbstractSyntaxFormatting.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/AbstractSyntaxFormatting.cs index 662e591c5012e..22865e0751e08 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/AbstractSyntaxFormatting.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/AbstractSyntaxFormatting.cs @@ -7,14 +7,12 @@ using System.Collections.Immutable; using System.Linq; using System.Threading; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Formatting; @@ -22,10 +20,6 @@ internal abstract class AbstractSyntaxFormatting : ISyntaxFormatting { private static readonly Func s_notEmpty = s => !s.IsEmpty; - protected AbstractSyntaxFormatting() - { - } - public abstract SyntaxFormattingOptions DefaultOptions { get; } public abstract SyntaxFormattingOptions GetFormattingOptions(IOptionsReader options, SyntaxFormattingOptions? fallbackOptions); @@ -33,9 +27,9 @@ protected AbstractSyntaxFormatting() protected abstract IFormattingResult CreateAggregatedFormattingResult(SyntaxNode node, IList results, TextSpanIntervalTree? formattingSpans = null); - protected abstract AbstractFormattingResult Format(SyntaxNode node, SyntaxFormattingOptions options, IEnumerable rules, SyntaxToken startToken, SyntaxToken endToken, CancellationToken cancellationToken); + protected abstract AbstractFormattingResult Format(SyntaxNode node, SyntaxFormattingOptions options, ImmutableArray rules, SyntaxToken startToken, SyntaxToken endToken, CancellationToken cancellationToken); - public IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerable? spans, SyntaxFormattingOptions options, IEnumerable? rules, CancellationToken cancellationToken) + public IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerable? spans, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken) { IReadOnlyList spansToFormat; @@ -53,7 +47,8 @@ public IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerable? results = null; foreach (var (startToken, endToken) in node.ConvertToTokenPairs(spansToFormat)) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/BottomUpBaseIndentationFinder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/BottomUpBaseIndentationFinder.cs index d47e5050dadfa..d0a46391100f9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/BottomUpBaseIndentationFinder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/BottomUpBaseIndentationFinder.cs @@ -219,7 +219,7 @@ private List GetParentIndentBlockOperations(SyntaxToken to allNodes.Do(n => _formattingRules.AddIndentBlockOperations(list, n)); // sort them in right order - list.RemoveAll(CommonFormattingHelpers.IsNull); + list.RemoveAll(static o => o is null); list.Sort(CommonFormattingHelpers.IndentBlockOperationComparer); return list; @@ -296,7 +296,7 @@ private SyntaxToken GetAlignmentBaseTokenFor(SyntaxToken token) } // well, found no appropriate one - list.RemoveAll(CommonFormattingHelpers.IsNull); + list.RemoveAll(static o => o is null); if (list.Count == 0) { return null; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Context/FormattingContext.InitialContextFinder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Context/FormattingContext.InitialContextFinder.cs index b49d180422640..641931ca4e693 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Context/FormattingContext.InitialContextFinder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Context/FormattingContext.InitialContextFinder.cs @@ -4,12 +4,14 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; @@ -39,7 +41,7 @@ public InitialContextFinder( _rootNode = rootNode; } - public (List indentOperations, List? suppressOperations) Do(SyntaxToken startToken, SyntaxToken endToken) + public (List indentOperations, ImmutableArray suppressOperations) Do(SyntaxToken startToken, SyntaxToken endToken) { // we are formatting part of document, try to find initial context that formatting will be based on such as // initial indentation and etc. @@ -53,7 +55,6 @@ public InitialContextFinder( if (initialSuppressOperations != null) { Debug.Assert( - initialSuppressOperations.IsEmpty() || initialSuppressOperations.All( o => o.TextSpan.Contains(startToken.SpanStart) || o.TextSpan.Contains(endToken.SpanStart))); @@ -121,71 +122,60 @@ private List GetInitialIndentBlockOperations(SyntaxToken s return operations; } - private List? GetInitialSuppressOperations(SyntaxToken startToken, SyntaxToken endToken) + private ImmutableArray GetInitialSuppressOperations(SyntaxToken startToken, SyntaxToken endToken) { - var noWrapList = this.GetInitialSuppressOperations(startToken, endToken, SuppressOption.NoWrapping); - var noSpaceList = this.GetInitialSuppressOperations(startToken, endToken, SuppressOption.NoSpacing); + using var _ = ArrayBuilder.GetInstance(out var result); - var list = noWrapList.Combine(noSpaceList); - if (list == null) - { - return null; - } + this.AddInitialSuppressOperations(startToken, endToken, SuppressOption.NoWrapping, result); + this.AddInitialSuppressOperations(startToken, endToken, SuppressOption.NoSpacing, result); - list.Sort(CommonFormattingHelpers.SuppressOperationComparer); - return list; + result.Sort(CommonFormattingHelpers.SuppressOperationComparer); + return result.ToImmutable(); } - private List? GetInitialSuppressOperations(SyntaxToken startToken, SyntaxToken endToken, SuppressOption mask) + private void AddInitialSuppressOperations( + SyntaxToken startToken, SyntaxToken endToken, SuppressOption mask, ArrayBuilder result) { - var startList = this.GetInitialSuppressOperations(startToken, mask); - var endList = this.GetInitialSuppressOperations(endToken, mask); - - return startList.Combine(endList); + this.AddInitialSuppressOperations(startToken, mask, result); + this.AddInitialSuppressOperations(endToken, mask, result); } - private List? GetInitialSuppressOperations(SyntaxToken token, SuppressOption mask) + private void AddInitialSuppressOperations(SyntaxToken token, SuppressOption mask, ArrayBuilder result) { var startNode = token.Parent; var startPosition = token.SpanStart; // starting from given token, move up to root until the first meaningful // operation has found - var list = new List(); + using var _ = ArrayBuilder.GetInstance(out var buffer); var currentIndentationNode = startNode; - Predicate predicate = Predicate; while (currentIndentationNode != null) { - _formattingRules.AddSuppressOperations(list, currentIndentationNode); + _formattingRules.AddSuppressOperations(buffer, currentIndentationNode); - list.RemoveAll(predicate); - if (list.Count > 0) + buffer.RemoveAll(Predicate, (startPosition, _tokenStream, mask)); + if (buffer.Count > 0) { - return list; + result.AddRange(buffer); + return; } currentIndentationNode = currentIndentationNode.Parent; } - return null; + return; - bool Predicate(SuppressOperation operation) + static bool Predicate(SuppressOperation operation, (int startPosition, TokenStream tokenStream, SuppressOption mask) tuple) { - if (!operation.TextSpan.Contains(startPosition)) - { + if (!operation.TextSpan.Contains(tuple.startPosition)) return true; - } - if (operation.ContainsElasticTrivia(_tokenStream) && !operation.Option.IsOn(SuppressOption.IgnoreElasticWrapping)) - { + if (operation.ContainsElasticTrivia(tuple.tokenStream) && !operation.Option.IsOn(SuppressOption.IgnoreElasticWrapping)) return true; - } - if (!operation.Option.IsMaskOn(mask)) - { + if (!operation.Option.IsMaskOn(tuple.mask)) return true; - } return false; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Context/FormattingContext.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Context/FormattingContext.cs index b408318b2916e..965a3a2b38a6f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Context/FormattingContext.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Context/FormattingContext.cs @@ -115,7 +115,8 @@ public void Initialize( _initialIndentBlockOperations = indentationOperations; } - suppressOperations?.Do(this.AddInitialSuppressOperation); + foreach (var suppressOperation in suppressOperations) + this.AddInitialSuppressOperation(suppressOperation); } public void AddIndentBlockOperations( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs index 60cd1f2e7f4fa..83886f8fdf3b6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/AbstractFormatEngine.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Threading; using Microsoft.CodeAnalysis.Collections; @@ -45,21 +46,44 @@ internal abstract partial class AbstractFormatEngine internal readonly SyntaxFormattingOptions Options; internal readonly TreeData TreeData; + /// + /// It is very common to be formatting lots of documents at teh same time, with the same set of formatting rules and + /// options. To help with that, cache the last set of ChainedFormattingRules that was produced, as it is not a cheap + /// type to create. + /// + /// + /// Stored as a instead of a so we don't have + /// to worry about torn write concerns. + /// + private static Tuple, SyntaxFormattingOptions, ChainedFormattingRules>? s_lastRulesAndOptions; + public AbstractFormatEngine( TreeData treeData, SyntaxFormattingOptions options, - IEnumerable formattingRules, + ImmutableArray formattingRules, SyntaxToken startToken, SyntaxToken endToken) : this( treeData, options, - new ChainedFormattingRules(formattingRules, options), + GetChainedFormattingRules(formattingRules, options), startToken, endToken) { } + private static ChainedFormattingRules GetChainedFormattingRules(ImmutableArray formattingRules, SyntaxFormattingOptions options) + { + var lastRulesAndOptions = s_lastRulesAndOptions; + if (formattingRules != lastRulesAndOptions?.Item1 || options != s_lastRulesAndOptions?.Item2) + { + lastRulesAndOptions = Tuple.Create(formattingRules, options, new ChainedFormattingRules(formattingRules, options)); + s_lastRulesAndOptions = lastRulesAndOptions; + } + + return lastRulesAndOptions.Item3; + } + internal AbstractFormatEngine( TreeData treeData, SyntaxFormattingOptions options, @@ -133,10 +157,10 @@ protected virtual NodeOperations CreateNodeOperations(CancellationToken cancella var nodeOperations = new NodeOperations(); - var indentBlockOperation = new List(); - var suppressOperation = new List(); - var alignmentOperation = new List(); - var anchorIndentationOperations = new List(); + var indentBlockOperationScratch = new List(); + var alignmentOperationScratch = new List(); + var anchorIndentationOperationsScratch = new List(); + using var _ = ArrayBuilder.GetInstance(out var suppressOperationScratch); // Cache delegates out here to avoid allocation overhead. @@ -150,14 +174,14 @@ protected virtual NodeOperations CreateNodeOperations(CancellationToken cancella { cancellationToken.ThrowIfCancellationRequested(); - AddOperations(nodeOperations.IndentBlockOperation, indentBlockOperation, node, addIndentBlockOperations); - AddOperations(nodeOperations.SuppressOperation, suppressOperation, node, addSuppressOperation); - AddOperations(nodeOperations.AlignmentOperation, alignmentOperation, node, addAlignTokensOperations); - AddOperations(nodeOperations.AnchorIndentationOperations, anchorIndentationOperations, node, addAnchorIndentationOperations); + AddOperations(nodeOperations.IndentBlockOperation, indentBlockOperationScratch, node, addIndentBlockOperations); + AddOperations(nodeOperations.SuppressOperation, suppressOperationScratch, node, addSuppressOperation); + AddOperations(nodeOperations.AlignmentOperation, alignmentOperationScratch, node, addAlignTokensOperations); + AddOperations(nodeOperations.AnchorIndentationOperations, anchorIndentationOperationsScratch, node, addAnchorIndentationOperations); } // make sure we order align operation from left to right - alignmentOperation.Sort(static (o1, o2) => o1.BaseToken.Span.CompareTo(o2.BaseToken.Span)); + nodeOperations.AlignmentOperation.Sort(static (o1, o2) => o1.BaseToken.Span.CompareTo(o2.BaseToken.Span)); return nodeOperations; } @@ -176,6 +200,20 @@ private static void AddOperations(SegmentedList operations, List scratc scratch.Clear(); } + private static void AddOperations(SegmentedList operations, ArrayBuilder scratch, SyntaxNode node, Action, SyntaxNode> addOperations) + { + Debug.Assert(scratch.Count == 0); + + addOperations(scratch, node); + foreach (var operation in scratch) + { + if (operation is not null) + operations.Add(operation); + } + + scratch.Clear(); + } + private void AddTokenOperations( TokenStream tokenStream, SegmentedList list, diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/ChainedFormattingRules.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/ChainedFormattingRules.cs index b0dbfa8d1204c..68f2d1c86db2d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/ChainedFormattingRules.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/ChainedFormattingRules.cs @@ -10,6 +10,7 @@ using System.Reflection; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Formatting; @@ -32,7 +33,7 @@ public ChainedFormattingRules(IEnumerable formattingRule { Contract.ThrowIfNull(formattingRules); - _formattingRules = formattingRules.Select(rule => rule.WithOptions(options)).ToImmutableArray(); + _formattingRules = formattingRules.SelectAsArray(rule => rule.WithOptions(options)); _options = options; _addSuppressOperationsRules = FilterToRulesImplementingMethod(_formattingRules, nameof(AbstractFormattingRule.AddSuppressOperations)); @@ -43,7 +44,7 @@ public ChainedFormattingRules(IEnumerable formattingRule _getAdjustSpacesOperationRules = FilterToRulesImplementingMethod(_formattingRules, nameof(AbstractFormattingRule.GetAdjustSpacesOperation)); } - public void AddSuppressOperations(List list, SyntaxNode currentNode) + public void AddSuppressOperations(ArrayBuilder list, SyntaxNode currentNode) { var action = new NextSuppressOperationAction(_addSuppressOperationsRules, index: 0, currentNode, list); action.Invoke(); @@ -81,25 +82,21 @@ public void AddAlignTokensOperations(List list, SyntaxNode private static ImmutableArray FilterToRulesImplementingMethod(ImmutableArray rules, string name) { - return rules.Where(rule => + return rules.WhereAsArray(rule => { var type = GetTypeImplementingMethod(rule, name); if (type == typeof(AbstractFormattingRule)) - { return false; - } if (type == typeof(CompatAbstractFormattingRule)) { type = GetTypeImplementingMethod(rule, name + "Slow"); if (type == typeof(CompatAbstractFormattingRule)) - { return false; - } } return true; - }).ToImmutableArray(); + }); } private static Type? GetTypeImplementingMethod(object obj, string name) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingExtensions.cs index 8073cdc2c43eb..0ab7048ad4140 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingExtensions.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -53,7 +54,8 @@ public static bool ContainsElasticTrivia(this SuppressOperation operation, Token var endToken = tokenStream.GetTokenData(operation.EndToken); var previousToken = endToken.GetPreviousTokenData(); - return tokenStream.GetTriviaData(startToken, nextToken).TreatAsElastic || tokenStream.GetTriviaData(previousToken, endToken).TreatAsElastic; + return CommonFormattingHelpers.HasAnyWhitespaceElasticTrivia(startToken.Token, nextToken.Token) || + CommonFormattingHelpers.HasAnyWhitespaceElasticTrivia(previousToken.Token, endToken.Token); } public static bool HasAnyWhitespaceElasticTrivia(this SyntaxTriviaList list) @@ -62,9 +64,7 @@ public static bool HasAnyWhitespaceElasticTrivia(this SyntaxTriviaList list) foreach (var trivia in list) { if (trivia.IsElastic()) - { return true; - } } return false; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/ISyntaxFormatting.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/ISyntaxFormatting.cs index ef25534b4e233..aa3bf201c3742 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/ISyntaxFormatting.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/ISyntaxFormatting.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Text; @@ -18,5 +17,5 @@ internal interface ISyntaxFormatting SyntaxFormattingOptions GetFormattingOptions(IOptionsReader options, SyntaxFormattingOptions? fallbackOptions); ImmutableArray GetDefaultFormattingRules(); - IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerable? spans, SyntaxFormattingOptions options, IEnumerable? rules, CancellationToken cancellationToken); + IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerable? spans, SyntaxFormattingOptions options, ImmutableArray rules, CancellationToken cancellationToken); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/AbstractFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/AbstractFormattingRule.cs index 33620eea5d158..8af68c34064e7 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/AbstractFormattingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/AbstractFormattingRule.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.Formatting.Rules; @@ -20,7 +20,7 @@ public virtual AbstractFormattingRule WithOptions(SyntaxFormattingOptions option /// Returns SuppressWrappingIfOnSingleLineOperations under a node either by itself or by /// filtering/replacing operations returned by NextOperation /// - public virtual void AddSuppressOperations(List list, SyntaxNode node, in NextSuppressOperationAction nextOperation) + public virtual void AddSuppressOperations(ArrayBuilder list, SyntaxNode node, in NextSuppressOperationAction nextOperation) => nextOperation.Invoke(); /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/CompatAbstractFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/CompatAbstractFormattingRule.cs index 0b5609b7364b2..08e9a2da873c4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/CompatAbstractFormattingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/CompatAbstractFormattingRule.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.Formatting.Rules; @@ -13,7 +14,7 @@ internal abstract class CompatAbstractFormattingRule : AbstractFormattingRule #pragma warning disable CS0809 // Obsolete member overrides non-obsolete member [Obsolete("Do not call this method directly (it will Stack Overflow).", error: true)] [EditorBrowsable(EditorBrowsableState.Never)] - public sealed override void AddSuppressOperations(List list, SyntaxNode node, in NextSuppressOperationAction nextOperation) + public sealed override void AddSuppressOperations(ArrayBuilder list, SyntaxNode node, in NextSuppressOperationAction nextOperation) { var nextOperationCopy = nextOperation; AddSuppressOperationsSlow(list, node, ref nextOperationCopy); @@ -68,7 +69,7 @@ public sealed override void AddAlignTokensOperations(List /// Returns SuppressWrappingIfOnSingleLineOperations under a node either by itself or by /// filtering/replacing operations returned by NextOperation /// - public virtual void AddSuppressOperationsSlow(List list, SyntaxNode node, ref NextSuppressOperationAction nextOperation) + public virtual void AddSuppressOperationsSlow(ArrayBuilder list, SyntaxNode node, ref NextSuppressOperationAction nextOperation) => base.AddSuppressOperations(list, node, in nextOperation); /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/NextSuppressOperationAction.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/NextSuppressOperationAction.cs index a3e051ab705c8..f0a02813eb808 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/NextSuppressOperationAction.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/NextSuppressOperationAction.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Immutable; +using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Formatting.Rules; @@ -13,7 +14,7 @@ internal readonly struct NextSuppressOperationAction( ImmutableArray formattingRules, int index, SyntaxNode node, - List list) + ArrayBuilder list) { private NextSuppressOperationAction NextAction => new(formattingRules, index + 1, node, list); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/Operations/FormattingOperations.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/Operations/FormattingOperations.cs index 0b2d965c14a0f..ea13eaf68014c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/Operations/FormattingOperations.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Rules/Operations/FormattingOperations.cs @@ -141,18 +141,6 @@ public static AdjustSpacesOperation CreateAdjustSpacesOperation(int space, Adjus return new AdjustSpacesOperation(space, option); } - /// - /// return SuppressOperation for the node provided by the given formatting rules - /// - internal static IEnumerable GetSuppressOperations(IEnumerable formattingRules, SyntaxNode node, SyntaxFormattingOptions options) - { - var chainedFormattingRules = new ChainedFormattingRules(formattingRules, options); - - var list = new List(); - chainedFormattingRules.AddSuppressOperations(list, node); - return list; - } - /// /// return AnchorIndentationOperation for the node provided by the given formatting rules /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs index e9a169c4ce9e8..b99371005305b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs @@ -626,5 +626,10 @@ internal enum FunctionId // 800-850 for Copilot performance logging. Copilot_Suggestion_Dismissed = 800, + Copilot_On_The_Fly_Docs_Showed_Link = 810, + Copilot_On_The_Fly_Docs_Loading_State_Entered = 811, + Copilot_On_The_Fly_Docs_Results_Displayed = 812, + Copilot_On_The_Fly_Docs_Error_Displayed = 813, + Copilot_On_The_Fly_Docs_Results_Canceled = 814, Copilot_Rename = 851 } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CommonFormattingHelpers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CommonFormattingHelpers.cs index 62dcc67daf5cc..aa20832c3047f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CommonFormattingHelpers.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CommonFormattingHelpers.cs @@ -346,21 +346,15 @@ public static int GetStartPositionOfSpan(SyntaxToken token) public static bool HasAnyWhitespaceElasticTrivia(SyntaxToken previousToken, SyntaxToken currentToken) { - if ((!previousToken.ContainsAnnotations && !currentToken.ContainsAnnotations) || - (!previousToken.HasTrailingTrivia && !currentToken.HasLeadingTrivia)) - { + if (!previousToken.ContainsAnnotations && !currentToken.ContainsAnnotations) + return false; + + if (!previousToken.HasTrailingTrivia && !currentToken.HasLeadingTrivia) return false; - } return previousToken.TrailingTrivia.HasAnyWhitespaceElasticTrivia() || currentToken.LeadingTrivia.HasAnyWhitespaceElasticTrivia(); } - public static bool IsNull(T t) where T : class - => t == null; - - public static bool IsNotNull(T t) where T : class - => !IsNull(t); - public static TextSpan GetFormattingSpan(SyntaxNode root, TextSpan span) { Contract.ThrowIfNull(root); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpRemoveUnnecessaryImportsService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpRemoveUnnecessaryImportsService.cs index 74df6ecf17b13..f59a9424eb31b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpRemoveUnnecessaryImportsService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpRemoveUnnecessaryImportsService.cs @@ -79,7 +79,7 @@ public override async Task RemoveUnnecessaryImportsAsync( #endif var spans = new List(); AddFormattingSpans(newRoot, spans, cancellationToken); - var formattedRoot = Formatter.Format(newRoot, spans, provider, formattingOptions, rules: null, cancellationToken); + var formattedRoot = Formatter.Format(newRoot, spans, provider, formattingOptions, rules: default, cancellationToken); return document.WithSyntaxRoot(formattedRoot); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Indentation/SpecialFormattingOperation.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Indentation/SpecialFormattingOperation.vb index f62b444c2c872..0fd8a219aa27c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Indentation/SpecialFormattingOperation.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Indentation/SpecialFormattingOperation.vb @@ -5,6 +5,7 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Formatting.Rules +Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -18,7 +19,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Indentation _indentStyle = indentStyle End Sub - Public Overrides Sub AddSuppressOperationsSlow(list As List(Of SuppressOperation), node As SyntaxNode, ByRef nextOperation As NextSuppressOperationAction) + Public Overrides Sub AddSuppressOperationsSlow(list As ArrayBuilder(Of SuppressOperation), node As SyntaxNode, ByRef nextOperation As NextSuppressOperationAction) ' don't suppress anything End Sub diff --git a/src/Workspaces/VisualBasic/Portable/Formatting/DefaultOperationProvider.vb b/src/Workspaces/VisualBasic/Portable/Formatting/DefaultOperationProvider.vb index f581265ed4cf0..49c635c13a72f 100644 --- a/src/Workspaces/VisualBasic/Portable/Formatting/DefaultOperationProvider.vb +++ b/src/Workspaces/VisualBasic/Portable/Formatting/DefaultOperationProvider.vb @@ -5,6 +5,7 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Formatting.Rules +Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.CodeAnalysis.VisualBasic.Utilities @@ -36,7 +37,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting Return New DefaultOperationProvider(options) End Function - Public Overrides Sub AddSuppressOperationsSlow(operations As List(Of SuppressOperation), node As SyntaxNode, ByRef nextAction As NextSuppressOperationAction) + Public Overrides Sub AddSuppressOperationsSlow(operations As ArrayBuilder(Of SuppressOperation), node As SyntaxNode, ByRef nextAction As NextSuppressOperationAction) End Sub Public Overrides Sub AddAnchorIndentationOperationsSlow(operations As List(Of AnchorIndentationOperation), node As SyntaxNode, ByRef nextAction As NextAnchorIndentationOperationAction) diff --git a/src/Workspaces/VisualBasic/Portable/Formatting/Engine/VisualBasicFormatEngine.vb b/src/Workspaces/VisualBasic/Portable/Formatting/Engine/VisualBasicFormatEngine.vb index ab3418217b4c8..d6b00a74ecdb5 100644 --- a/src/Workspaces/VisualBasic/Portable/Formatting/Engine/VisualBasicFormatEngine.vb +++ b/src/Workspaces/VisualBasic/Portable/Formatting/Engine/VisualBasicFormatEngine.vb @@ -2,7 +2,7 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports Microsoft.CodeAnalysis.Diagnostics +Imports System.Collections.Immutable Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Formatting.Rules Imports Microsoft.CodeAnalysis.LanguageService @@ -14,7 +14,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting Public Sub New(node As SyntaxNode, options As SyntaxFormattingOptions, - formattingRules As IEnumerable(Of AbstractFormattingRule), + formattingRules As ImmutableArray(Of AbstractFormattingRule), startToken As SyntaxToken, endToken As SyntaxToken) MyBase.New(TreeData.Create(node), diff --git a/src/Workspaces/VisualBasic/Portable/Formatting/Rules/ElasticTriviaFormattingRule.vb b/src/Workspaces/VisualBasic/Portable/Formatting/Rules/ElasticTriviaFormattingRule.vb index 9c87a6a33365c..eac504169c6df 100644 --- a/src/Workspaces/VisualBasic/Portable/Formatting/Rules/ElasticTriviaFormattingRule.vb +++ b/src/Workspaces/VisualBasic/Portable/Formatting/Rules/ElasticTriviaFormattingRule.vb @@ -4,6 +4,7 @@ Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Formatting.Rules +Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting @@ -14,7 +15,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting Public Sub New() End Sub - Public Overrides Sub AddSuppressOperationsSlow(list As List(Of SuppressOperation), node As SyntaxNode, ByRef nextOperation As NextSuppressOperationAction) + Public Overrides Sub AddSuppressOperationsSlow(list As ArrayBuilder(Of SuppressOperation), node As SyntaxNode, ByRef nextOperation As NextSuppressOperationAction) nextOperation.Invoke() End Sub diff --git a/src/Workspaces/VisualBasic/Portable/Formatting/VisualBasicSyntaxFormatting.vb b/src/Workspaces/VisualBasic/Portable/Formatting/VisualBasicSyntaxFormatting.vb index f0b7fee2a86b6..598ce18a2d40b 100644 --- a/src/Workspaces/VisualBasic/Portable/Formatting/VisualBasicSyntaxFormatting.vb +++ b/src/Workspaces/VisualBasic/Portable/Formatting/VisualBasicSyntaxFormatting.vb @@ -43,7 +43,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting Return New AggregatedFormattingResult(node, results, formattingSpans) End Function - Protected Overrides Function Format(root As SyntaxNode, options As SyntaxFormattingOptions, formattingRules As IEnumerable(Of AbstractFormattingRule), startToken As SyntaxToken, endToken As SyntaxToken, cancellationToken As CancellationToken) As AbstractFormattingResult + Protected Overrides Function Format(root As SyntaxNode, options As SyntaxFormattingOptions, formattingRules As ImmutableArray(Of AbstractFormattingRule), startToken As SyntaxToken, endToken As SyntaxToken, cancellationToken As CancellationToken) As AbstractFormattingResult Return New VisualBasicFormatEngine(root, options, formattingRules, startToken, endToken).Format(cancellationToken) End Function End Class