diff --git a/Roslyn.sln b/Roslyn.sln index 016b90b55746c..f1940655bbe97 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -500,6 +500,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSha EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Remote.ServiceHub.UnitTests", "src\Workspaces\Remote\ServiceHubTest\Microsoft.CodeAnalysis.Remote.ServiceHub.UnitTests.csproj", "{8D830CBB-CA6E-47D8-9FB8-9230AAD272F3}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.LanguageServices.New.IntegrationTests", "src\VisualStudio\IntegrationTest\New.IntegrationTests\Microsoft.VisualStudio.LanguageServices.New.IntegrationTests.csproj", "{6272739B-31E4-483E-A3A5-2ABB5040ABF0}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Analyzers\VisualBasic\CodeFixes\VisualBasicCodeFixes.projitems*{0141285d-8f6c-42c7-baf3-3c0ccd61c716}*SharedItemsImports = 5 @@ -1235,10 +1237,10 @@ Global {8D22FC91-BDFE-4342-999B-D695E1C57E85}.Debug|Any CPU.Build.0 = Debug|Any CPU {8D22FC91-BDFE-4342-999B-D695E1C57E85}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D22FC91-BDFE-4342-999B-D695E1C57E85}.Release|Any CPU.Build.0 = Release|Any CPU - {2801F82B-78CE-4BAE-B06F-537574751E2E}.Debug|Any CPU.ActiveCfg = Debug|x86 - {2801F82B-78CE-4BAE-B06F-537574751E2E}.Debug|Any CPU.Build.0 = Debug|x86 - {2801F82B-78CE-4BAE-B06F-537574751E2E}.Release|Any CPU.ActiveCfg = Release|x86 - {2801F82B-78CE-4BAE-B06F-537574751E2E}.Release|Any CPU.Build.0 = Release|x86 + {2801F82B-78CE-4BAE-B06F-537574751E2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2801F82B-78CE-4BAE-B06F-537574751E2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2801F82B-78CE-4BAE-B06F-537574751E2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2801F82B-78CE-4BAE-B06F-537574751E2E}.Release|Any CPU.Build.0 = Release|Any CPU {9B25E472-DF94-4E24-9F5D-E487CE5A91FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9B25E472-DF94-4E24-9F5D-E487CE5A91FB}.Debug|Any CPU.Build.0 = Debug|Any CPU {9B25E472-DF94-4E24-9F5D-E487CE5A91FB}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1303,6 +1305,10 @@ Global {8D830CBB-CA6E-47D8-9FB8-9230AAD272F3}.Debug|Any CPU.Build.0 = Debug|Any CPU {8D830CBB-CA6E-47D8-9FB8-9230AAD272F3}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D830CBB-CA6E-47D8-9FB8-9230AAD272F3}.Release|Any CPU.Build.0 = Release|Any CPU + {6272739B-31E4-483E-A3A5-2ABB5040ABF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6272739B-31E4-483E-A3A5-2ABB5040ABF0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6272739B-31E4-483E-A3A5-2ABB5040ABF0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6272739B-31E4-483E-A3A5-2ABB5040ABF0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1531,6 +1537,7 @@ Global {8FCD1B85-BE63-4A2F-8E19-37244F19BE0F} = {55A62CFA-1155-46F1-ADF3-BEEE51B58AB5} {2B7DC612-1B37-41F7-BE31-4D600930EAC9} = {32A48625-F0AD-419D-828B-A50BDABA38EA} {8D830CBB-CA6E-47D8-9FB8-9230AAD272F3} = {55A62CFA-1155-46F1-ADF3-BEEE51B58AB5} + {6272739B-31E4-483E-A3A5-2ABB5040ABF0} = {CC126D03-7EAC-493F-B187-DCDEE1EF6A70} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {604E6B91-7BC0-4126-AE07-D4D2FEFC3D29} diff --git a/azure-pipelines-integration-corehost.yml b/azure-pipelines-integration-corehost.yml index 8423888c6ed22..d34995411c3cc 100644 --- a/azure-pipelines-integration-corehost.yml +++ b/azure-pipelines-integration-corehost.yml @@ -18,11 +18,15 @@ pr: - features/* - demos/* +variables: + - name: XUNIT_LOGS + value: $(Build.SourcesDirectory)\artifacts\log\$(_configuration) + jobs: - job: VS_Integration_CoreHost pool: - name: NetCorePublic-Pool - queue: $(queueName) + name: NetCore1ESPool-Public + demands: ImageOverride -equals $(queueName) strategy: maxParallel: 2 matrix: diff --git a/azure-pipelines-integration-lsp.yml b/azure-pipelines-integration-lsp.yml index 0270799d3b21d..85ba4c6ffbe37 100644 --- a/azure-pipelines-integration-lsp.yml +++ b/azure-pipelines-integration-lsp.yml @@ -18,11 +18,15 @@ pr: - features/* - demos/* +variables: + - name: XUNIT_LOGS + value: $(Build.SourcesDirectory)\artifacts\log\$(_configuration) + jobs: - job: VS_Integration_LSP pool: - name: NetCore1ESPool-Svc-Public - demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Pre.Open + name: NetCore1ESPool-Public + demands: ImageOverride -equals $(queueName) timeoutInMinutes: 135 steps: diff --git a/azure-pipelines-integration.yml b/azure-pipelines-integration.yml index 251f8641c4feb..d47ecd4d8087a 100644 --- a/azure-pipelines-integration.yml +++ b/azure-pipelines-integration.yml @@ -14,11 +14,15 @@ pr: - features/* - demos/* +variables: + - name: XUNIT_LOGS + value: $(Build.SourcesDirectory)\artifacts\log\$(_configuration) + jobs: - job: VS_Integration pool: - name: NetCore1ESPool-Svc-Public - demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Pre.Open + name: $(poolName) + demands: ImageOverride -equals $(queueName) strategy: maxParallel: 4 matrix: diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml index 6572395e03a4c..aec790a57e107 100644 --- a/azure-pipelines-official.yml +++ b/azure-pipelines-official.yml @@ -82,7 +82,7 @@ stages: displayName: Official Build timeoutInMinutes: 360 pool: - name: NetCore1ESPool-Svc-Internal + name: NetCore1ESPool-Internal demands: ImageOverride -equals Build.Server.Amd64.VS2019 steps: diff --git a/azure-pipelines-pr-validation.yml b/azure-pipelines-pr-validation.yml index 494eceff5837c..720361ea86da7 100644 --- a/azure-pipelines-pr-validation.yml +++ b/azure-pipelines-pr-validation.yml @@ -49,7 +49,7 @@ stages: timeoutInMinutes: 360 # Conditionally set build pool so we can share this YAML when building with different pipeline pool: - name: VSEngSS-MicroBuild2017 + name: VSEngSS-MicroBuild2019-1ES demands: - msbuild - visualstudio @@ -89,6 +89,14 @@ stages: inputs: nuGetServiceConnections: azure-public/vs-impl, azure-public/vssdk + # Needed because the build fails the NuGet Tools restore without it + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + packageType: sdk + useGlobalJson: true + workingDirectory: '$(Build.SourcesDirectory)' + # Needed to restore the Microsoft.DevDiv.Optimization.Data.PowerShell package - task: NuGetCommand@2 displayName: Restore internal tools @@ -210,7 +218,7 @@ stages: displayName: Create Insertion dependsOn: - build - + jobs: - job: insert displayName: Insert to VS @@ -220,7 +228,7 @@ stages: - powershell: Write-Host "##vso[task.setvariable variable=SourceBranchName]$('$(Build.SourceBranch)'.Substring('refs/heads/'.Length))" displayName: Setting SourceBranchName variable condition: succeeded() - + - template: eng/pipelines/insert.yml parameters: createDraftPR: true @@ -232,4 +240,4 @@ stages: titlePrefix: ${{ parameters.OptionalTitlePrefix }} sourceBranch: $(SourceBranchName) publishDataURI: "https://raw.githubusercontent.com/dotnet/roslyn/main/eng/config/PublishData.json" - + diff --git a/azure-pipelines-richnav.yml b/azure-pipelines-richnav.yml index 4bf52240ca699..e68c88ad6c78e 100644 --- a/azure-pipelines-richnav.yml +++ b/azure-pipelines-richnav.yml @@ -19,8 +19,8 @@ pr: none jobs: - job: RichCodeNav_Indexing pool: - name: NetCore1ESPool-Svc-Public - demands: ImageOverride -equals Build.Windows.10.Amd64.Open + name: NetCore1ESPool-Public + demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Pre.Open variables: EnableRichCodeNavigation: true timeoutInMinutes: 200 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d4db92b9a244b..14b3d3cb339aa 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -21,14 +21,14 @@ jobs: jobName: Build_Windows_Debug testArtifactName: Transport_Artifacts_Windows_Debug configuration: Debug - queueName: Build.Windows.10.Amd64.Open + queueName: Build.Windows.Amd64.VS2022.Pre.Open - template: eng/pipelines/build-windows-job.yml parameters: jobName: Build_Windows_Release testArtifactName: Transport_Artifacts_Windows_Release configuration: Release - queueName: Build.Windows.10.Amd64.Open + queueName: Build.Windows.Amd64.VS2022.Pre.Open - template: eng/pipelines/test-windows-job.yml parameters: @@ -153,7 +153,7 @@ jobs: - job: Correctness_Determinism pool: - name: NetCore1ESPool-Svc-Public + name: NetCore1ESPool-Public demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Pre.Open timeoutInMinutes: 90 steps: @@ -169,7 +169,7 @@ jobs: - job: Correctness_Build pool: - name: NetCore1ESPool-Svc-Public + name: NetCore1ESPool-Public demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Pre.Open timeoutInMinutes: 90 steps: @@ -201,8 +201,8 @@ jobs: - job: Correctness_Rebuild pool: - name: NetCore1ESPool-Svc-Public - demands: ImageOverride -equals Build.Windows.10.Amd64.Open + name: NetCore1ESPool-Public + demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Pre.Open timeoutInMinutes: 90 steps: - template: eng/pipelines/checkout-windows-task.yml diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 6.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 6.md index 4dab166db115b..a530a0ab2d7dc 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 6.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 6.md @@ -108,4 +108,3 @@ These are _function_type_conversions_. } } ``` - diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md index 483e0973945c6..dd8a7a6b5b2af 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md @@ -34,3 +34,25 @@ public CustomHandler(int literalLength, int formattedCount, C c) {} } ``` + + +3. In Visual Studio 17.1, `ref`/`ref readonly`/`in`/`out` are not allowed to be used on return/parameters of a method attributed with `UnmanagedCallersOnly`. +https://github.com/dotnet/roslyn/issues/57025 + + ```cs + using System.Runtime.InteropServices; + [UnmanagedCallersOnly] + static ref int M1() => throw null; // error CS8977: Cannot use 'ref', 'in', or 'out' in a method attributed with 'UnmanagedCallersOnly'. + + [UnmanagedCallersOnly] + static ref readonly int M2() => throw null; // error CS8977: Cannot use 'ref', 'in', or 'out' in a method attributed with 'UnmanagedCallersOnly'. + + [UnmanagedCallersOnly] + static void M3(ref int o) => throw null; // error CS8977: Cannot use 'ref', 'in', or 'out' in a method attributed with 'UnmanagedCallersOnly'. + + [UnmanagedCallersOnly] + static void M4(in int o) => throw null; // error CS8977: Cannot use 'ref', 'in', or 'out' in a method attributed with 'UnmanagedCallersOnly'. + + [UnmanagedCallersOnly] + static void M5(out int o) => throw null; // error CS8977: Cannot use 'ref', 'in', or 'out' in a method attributed with 'UnmanagedCallersOnly'. + ``` diff --git a/docs/features/pdb-compilation-options.md b/docs/features/pdb-compilation-options.md index 9e6bd4dcf0985..29c62897cfa3c 100644 --- a/docs/features/pdb-compilation-options.md +++ b/docs/features/pdb-compilation-options.md @@ -133,7 +133,7 @@ foreach (var handle in metadataReader.GetCustomDebugInformation(EntityHandle.Mod ### Compiler Options custom debug information -The remaining values will be stored as key value pairs in the pdb. The storage format will be UTF8 encoded key value pairs that are null terminated. Order is not guaranteed. Any values left out can be assumed to be the default for the type. Keys may be different for Visual Basic and CSharp. They are serialized to reflect the command line arguments representing the same values +The remaining values will be stored as key value pairs in the pdb. The storage format will be UTF8 encoded key value pairs that are null terminated. Order is not guaranteed. Any values left out can be assumed to be the default for the type. Keys may be different for Visual Basic and C#. They are serialized to reflect the command line arguments representing the same values Example: @@ -141,7 +141,7 @@ Example: ## List of Compiler Flags -#### CSharp Flags That Can Be Derived From PDB or Assembly +#### C# Flags That Can Be Derived From PDB or Assembly * baseaddress * checksumalgorithm @@ -171,7 +171,7 @@ Example: * win32manifest * win32res -#### CSharp Flags Not Included +#### C# Flags Not Included * bugreport * delaysign @@ -255,11 +255,11 @@ Example: * verbose * warnaserror -#### Shared Options for CSharp and Visual Basic +#### Shared Options for C# and Visual Basic | PDB Key | Format | Default | Description | | ---------------------- | --------------------------------------- | --------- | ------------ | -| language | `CSharp\|Visual Basic` | required | Language name. | +| language | `C#\|Visual Basic` | required | Language name. | | compiler-version | [SemVer2](https://semver.org/spec/v2.0.0.html) string | required | Full version with SHA | | runtime-version | [SemVer2](https://semver.org/spec/v2.0.0.html) string | required | [runtime version](#runtime-version) | | source-file-count | int32 | required | Count of files in the document table that are source files | @@ -270,7 +270,7 @@ Example: | output-kind | string | require | The value passed to `/target` | | platform | string | require | The value passed to `/platform` | -#### Options For CSharp +#### Options For C\# See [compiler options](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/listed-alphabetically) documentation diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 9ac64a192283c..73a54c1c90cb8 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -6,25 +6,25 @@ 7e80445ee82adbf9a8e6ae601ac5e239d982afaa - + https://github.com/dotnet/source-build - a9515db097e05728fcc2169d074eae3c6a4580d0 + 50f26e9983277650d82d193296b2ccc50683f6b6 - + https://github.com/dotnet/arcade - 3ea0d860c6973f2cbadc9e895c7ec2cbdaec4ad5 + fecf65bedcee9036b8ba9d8d7feef5413f294914 - + https://github.com/dotnet/roslyn - c1d8c6f043bc80425c6828455eb57f8a404759c6 + 4ba1d9d2984e212af9d6ebff95dedb8a3739c661 - + https://github.com/dotnet/arcade - 3ea0d860c6973f2cbadc9e895c7ec2cbdaec4ad5 + fecf65bedcee9036b8ba9d8d7feef5413f294914 diff --git a/eng/Versions.props b/eng/Versions.props index 9b720af774648..1ed0f66147c21 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -23,7 +23,7 @@ - 4.0.0-5.21469.2 + 4.1.0-1.21553.8 @@ -33,11 +33,11 @@ 4.0.0-3.final 16.10.230 - 17.0.391-preview-g5e248c9073 + 17.0.487 5.0.0-alpha1.19409.1 5.0.0-preview.1.20112.8 17.0.5133-g7b8c8bd49d - 17.0.0-previews-4-31709-430 + 17.0.31723.112 16.5.0 13.0.1 - 2.8.21 + 2.8.28 Y for the purposes of determining the best // source type of a set of operators. // - // We perpetuate these fictions here. + // We perpetuate these fictions here, except when X or Y is not a valid + // type argument to `Nullable`. - if (target.IsNullableType() && convertsTo.IsNonNullableValueType()) + if (target.IsNullableType() && convertsTo.IsValidNullableTypeArgument()) { convertsTo = MakeNullableType(convertsTo); toConversion = EncompassingExplicitConversion(null, convertsTo, target, ref useSiteInfo); } - if ((object)source != null && source.IsNullableType() && convertsFrom.IsNonNullableValueType()) + if ((object)source != null && source.IsNullableType() && convertsFrom.IsValidNullableTypeArgument()) { convertsFrom = MakeNullableType(convertsFrom); fromConversion = EncompassingExplicitConversion(null, convertsFrom, source, ref useSiteInfo); diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedImplicitConversions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedImplicitConversions.cs index 03b3cffe5c1b0..a1e447450d3af 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedImplicitConversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedImplicitConversions.cs @@ -304,9 +304,12 @@ void addCandidatesFromType( // actually X-->Y? in source for the purposes of determining the best target // type of an operator. // - // We perpetuate this fiction here. + // We perpetuate this fiction here, except for cases when Y is not a valid type + // argument for Nullable. This scenario should only be possible when the corlib + // defines a type such as int or long to be a ref struct (see + // LiftedConversion_InvalidTypeArgument02). - if ((object)target != null && target.IsNullableType() && convertsTo.IsNonNullableValueType()) + if ((object)target != null && target.IsNullableType() && convertsTo.IsValidNullableTypeArgument()) { convertsTo = MakeNullableType(convertsTo); toConversion = allowAnyTarget ? Conversion.Identity : @@ -315,7 +318,7 @@ void addCandidatesFromType( u.Add(UserDefinedConversionAnalysis.Normal(constrainedToTypeOpt, op, fromConversion, toConversion, convertsFrom, convertsTo)); } - else if ((object)source != null && source.IsNullableType() && convertsFrom.IsNonNullableValueType() && + else if ((object)source != null && source.IsNullableType() && convertsFrom.IsValidNullableTypeArgument() && (allowAnyTarget || target.CanBeAssignedNull())) { // As mentioned above, here we diverge from the specification, in two ways. @@ -334,7 +337,7 @@ void addCandidatesFromType( // If the answer to all those questions is "yes" then we lift to nullable // and see if the resulting operator is applicable. TypeSymbol nullableFrom = MakeNullableType(convertsFrom); - TypeSymbol nullableTo = convertsTo.IsNonNullableValueType() ? MakeNullableType(convertsTo) : convertsTo; + TypeSymbol nullableTo = convertsTo.IsValidNullableTypeArgument() ? MakeNullableType(convertsTo) : convertsTo; Conversion liftedFromConversion = EncompassingImplicitConversion(sourceExpression, source, nullableFrom, ref useSiteInfo); Conversion liftedToConversion = !allowAnyTarget ? EncompassingImplicitConversion(null, nullableTo, target, ref useSiteInfo) : diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs index 77c94f0db82a2..82fe0fb80460a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs @@ -929,10 +929,8 @@ private static LiftingResult UserDefinedBinaryOperatorCanBeLifted(TypeSymbol lef // SPEC: types and if the result type is bool. The lifted form is // SPEC: constructed by adding a single ? modifier to each operand type. - if (!left.IsValueType || - left.IsNullableType() || - !right.IsValueType || - right.IsNullableType()) + if (!left.IsValidNullableTypeArgument() || + !right.IsValidNullableTypeArgument()) { return LiftingResult.NotLifted; } @@ -953,7 +951,7 @@ private static LiftingResult UserDefinedBinaryOperatorCanBeLifted(TypeSymbol lef LiftingResult.LiftOperandsButNotResult : LiftingResult.NotLifted; default: - return result.IsValueType && !result.IsNullableType() ? + return result.IsValidNullableTypeArgument() ? LiftingResult.LiftOperandsAndResult : LiftingResult.NotLifted; } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorOverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorOverloadResolution.cs index 68f3de987afe9..d0078360916e2 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorOverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorOverloadResolution.cs @@ -500,8 +500,8 @@ private void GetUserDefinedUnaryOperatorsFromType( case UnaryOperatorKind.PostfixIncrement: case UnaryOperatorKind.LogicalNegation: case UnaryOperatorKind.BitwiseComplement: - if (operandType.IsValueType && !operandType.IsNullableType() && - resultType.IsValueType && !resultType.IsNullableType()) + if (operandType.IsValidNullableTypeArgument() && + resultType.IsValidNullableTypeArgument()) { operators.Add(new UnaryOperatorSignature( UnaryOperatorKind.Lifted | UnaryOperatorKind.UserDefined | kind, diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MemberResolutionResult.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MemberResolutionResult.cs index 17960c0c15134..b719b9604f3ef 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MemberResolutionResult.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MemberResolutionResult.cs @@ -22,7 +22,7 @@ namespace Microsoft.CodeAnalysis.CSharp /// internal readonly bool HasTypeArgumentInferredFromFunctionType; - internal MemberResolutionResult(TMember member, TMember leastOverriddenMember, MemberAnalysisResult result, bool hasTypeArgumentInferredFromFunctionType = false) + internal MemberResolutionResult(TMember member, TMember leastOverriddenMember, MemberAnalysisResult result, bool hasTypeArgumentInferredFromFunctionType) { _member = member; _leastOverriddenMember = leastOverriddenMember; @@ -30,6 +30,11 @@ internal MemberResolutionResult(TMember member, TMember leastOverriddenMember, M HasTypeArgumentInferredFromFunctionType = hasTypeArgumentInferredFromFunctionType; } + internal MemberResolutionResult WithResult(MemberAnalysisResult result) + { + return new MemberResolutionResult(Member, LeastOverriddenMember, result, HasTypeArgumentInferredFromFunctionType); + } + internal bool IsNull { get { return (object)_member == null; } @@ -92,12 +97,12 @@ public bool IsApplicable internal MemberResolutionResult Worse() { - return new MemberResolutionResult(Member, LeastOverriddenMember, MemberAnalysisResult.Worse()); + return WithResult(MemberAnalysisResult.Worse()); } internal MemberResolutionResult Worst() { - return new MemberResolutionResult(Member, LeastOverriddenMember, MemberAnalysisResult.Worst()); + return WithResult(MemberAnalysisResult.Worst()); } internal bool HasUseSiteDiagnosticToReport diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs index fd6e17709465f..07965c314f1c3 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -385,7 +385,7 @@ private static void RemoveStaticInstanceMismatches(ArrayBuilder(member, result.LeastOverriddenMember, MemberAnalysisResult.StaticInstanceMismatch()); + results[f] = result.WithResult(MemberAnalysisResult.StaticInstanceMismatch()); } } } @@ -401,7 +401,7 @@ private static void RemoveMethodsNotDeclaredStatic(ArrayBuilder(member, result.LeastOverriddenMember, MemberAnalysisResult.StaticInstanceMismatch()); + results[f] = result.WithResult(MemberAnalysisResult.StaticInstanceMismatch()); } } } @@ -426,8 +426,7 @@ private void RemoveConstraintViolations(ArrayBuilder constraintFailureDiagnosticsOpt, template)) { - results[f] = new MemberResolutionResult( - result.Member, result.LeastOverriddenMember, + results[f] = result.WithResult( MemberAnalysisResult.ConstraintFailure(constraintFailureDiagnosticsOpt.ToImmutableAndFree())); } } @@ -559,7 +558,7 @@ private void RemoveCallingConventionMismatches(ArrayBuilder makeWrongCallingConvention(MemberResolutionResult result) - => new MemberResolutionResult(result.Member, result.LeastOverriddenMember, MemberAnalysisResult.WrongCallingConvention()); + => result.WithResult(MemberAnalysisResult.WrongCallingConvention()); } #nullable disable @@ -649,13 +648,11 @@ private void RemoveDelegateConversionsWithWrongReturnType( if (!returnsMatch) { - results[f] = new MemberResolutionResult( - result.Member, result.LeastOverriddenMember, MemberAnalysisResult.WrongReturnType()); + results[f] = result.WithResult(MemberAnalysisResult.WrongReturnType()); } else if (method.RefKind != returnRefKind) { - results[f] = new MemberResolutionResult( - result.Member, result.LeastOverriddenMember, MemberAnalysisResult.WrongRefKind()); + results[f] = result.WithResult(MemberAnalysisResult.WrongRefKind()); } } } @@ -699,7 +696,7 @@ private void AddConstructorToCandidateSet(MethodSymbol constructor, ArrayBuilder Debug.Assert(!MemberAnalysisResult.UnsupportedMetadata().HasUseSiteDiagnosticToReportFor(constructor)); if (completeResults) { - results.Add(new MemberResolutionResult(constructor, constructor, MemberAnalysisResult.UnsupportedMetadata())); + results.Add(new MemberResolutionResult(constructor, constructor, MemberAnalysisResult.UnsupportedMetadata(), hasTypeArgumentInferredFromFunctionType: false)); } return; } @@ -721,7 +718,7 @@ private void AddConstructorToCandidateSet(MethodSymbol constructor, ArrayBuilder // If the constructor has a use site diagnostic, we don't want to discard it because we'll have to report the diagnostic later. if (result.IsValid || completeResults || result.HasUseSiteDiagnosticToReportFor(constructor)) { - results.Add(new MemberResolutionResult(constructor, constructor, result)); + results.Add(new MemberResolutionResult(constructor, constructor, result, hasTypeArgumentInferredFromFunctionType: false)); } } @@ -905,7 +902,7 @@ private void AddMemberToCandidateSet( Debug.Assert(!MemberAnalysisResult.UnsupportedMetadata().HasUseSiteDiagnosticToReportFor(member)); if (completeResults) { - results.Add(new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.UnsupportedMetadata())); + results.Add(new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.UnsupportedMetadata(), hasTypeArgumentInferredFromFunctionType: false)); } return; } @@ -1141,7 +1138,7 @@ private void RemoveInaccessibleTypeArguments(ArrayBuilder(result.Member, result.LeastOverriddenMember, MemberAnalysisResult.InaccessibleTypeArgument()); + results[f] = result.WithResult(MemberAnalysisResult.InaccessibleTypeArgument()); } } } @@ -1272,7 +1269,7 @@ private static void RemoveLessDerivedMembers(ArrayBuilder(result.Member, result.LeastOverriddenMember, MemberAnalysisResult.LessDerived()); + results[f] = result.WithResult(MemberAnalysisResult.LessDerived()); } } } @@ -1383,7 +1380,7 @@ private static void RemoveAllInterfaceMembers(ArrayBuilder(member, result.LeastOverriddenMember, MemberAnalysisResult.LessDerived()); + results[f] = result.WithResult(MemberAnalysisResult.LessDerived()); } } } @@ -3284,7 +3281,7 @@ private MemberResolutionResult IsMemberApplicableInNormalForm( // thus improving the API and intellisense experience. break; default: - return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.ArgumentParameterMismatch(argumentAnalysis)); + return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.ArgumentParameterMismatch(argumentAnalysis), hasTypeArgumentInferredFromFunctionType: false); } } @@ -3292,7 +3289,7 @@ private MemberResolutionResult IsMemberApplicableInNormalForm( // NOTE: The diagnostic may not be reported (e.g. if the member is later removed as less-derived). if (member.HasUseSiteError) { - return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.UseSiteError()); + return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.UseSiteError(), hasTypeArgumentInferredFromFunctionType: false); } bool hasAnyRefOmittedArgument; @@ -3334,7 +3331,7 @@ private MemberResolutionResult IsMemberApplicableInNormalForm( // type inference and lambda binding. In that case we still need to return the argument mismatch failure here. if (completeResults && !argumentAnalysis.IsValid) { - return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.ArgumentParameterMismatch(argumentAnalysis)); + return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.ArgumentParameterMismatch(argumentAnalysis), hasTypeArgumentInferredFromFunctionType: false); } return applicableResult; @@ -3355,14 +3352,14 @@ private MemberResolutionResult IsMemberApplicableInExpandedForm(member, leastOverriddenMember, MemberAnalysisResult.ArgumentParameterMismatch(argumentAnalysis)); + return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.ArgumentParameterMismatch(argumentAnalysis), hasTypeArgumentInferredFromFunctionType: false); } // Check after argument analysis, but before more complicated type inference and argument type validation. // NOTE: The diagnostic may not be reported (e.g. if the member is later removed as less-derived). if (member.HasUseSiteError) { - return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.UseSiteError()); + return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.UseSiteError(), hasTypeArgumentInferredFromFunctionType: false); } bool hasAnyRefOmittedArgument; @@ -3401,9 +3398,7 @@ private MemberResolutionResult IsMemberApplicableInExpandedForm( - result.Member, - result.LeastOverriddenMember, + result.WithResult( MemberAnalysisResult.ExpandedForm(result.Result.ArgsToParamsOpt, result.Result.ConversionsOpt, hasAnyRefOmittedArgument)) : result; } @@ -3465,7 +3460,7 @@ private MemberResolutionResult IsApplicable( ref useSiteInfo); if (typeArguments.IsDefault) { - return new MemberResolutionResult(member, leastOverriddenMember, inferenceError); + return new MemberResolutionResult(member, leastOverriddenMember, inferenceError, hasTypeArgumentInferredFromFunctionType: false); } } @@ -3505,7 +3500,7 @@ private MemberResolutionResult IsApplicable( { if (!parameterTypes[i].Type.CheckAllConstraints(Compilation, Conversions)) { - return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.ConstructedParameterFailedConstraintsCheck(i)); + return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.ConstructedParameterFailedConstraintsCheck(i), hasTypeArgumentsInferredFromFunctionType); } } diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index afbf2313b0fb9..a3e6707d02dbf 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -6902,10 +6902,16 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The operation may overflow at runtime (use 'unchecked' syntax to override) + + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + newlines in interpolations Interpolated string handler conversions that reference the instance being indexed cannot be used in indexer member initializers. + + '{0}' cannot be made nullable. + diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index b992330d3f9f5..e4ee33961c90b 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2003,6 +2003,10 @@ internal enum ErrorCode ERR_LambdaExplicitReturnTypeVar = 8975, ERR_InterpolatedStringsReferencingInstanceCannotBeInObjectInitializers = 8976, + // Added in VS 17.1. Technically a breaking change, but the code it breaks was already guaranteed to throw + // at runtime. + ERR_CannotUseRefInUnmanagedCallersOnly = 8977, + ERR_IncorrectNullCheckSyntax = 8990, ERR_MustNullCheckInImplementation = 8991, ERR_NonNullableValueTypeIsNullChecked = 8992, @@ -2012,6 +2016,8 @@ internal enum ErrorCode #endregion + ERR_CannotBeMadeNullable = 8978, + // Note: you will need to re-generate compiler code after adding warnings (eng\generate-compiler-code.cmd) } } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.PendingBranchesCollection.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.PendingBranchesCollection.cs new file mode 100644 index 0000000000000..0dbdec0318d9b --- /dev/null +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.PendingBranchesCollection.cs @@ -0,0 +1,153 @@ +// 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 Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class AbstractFlowPass + { + internal sealed class PendingBranchesCollection + { + private ArrayBuilder _unlabeledBranches; + private PooledDictionary>? _labeledBranches; + + internal PendingBranchesCollection() + { + _unlabeledBranches = ArrayBuilder.GetInstance(); + } + + internal void Free() + { + _unlabeledBranches.Free(); + _unlabeledBranches = null!; + FreeLabeledBranches(); + } + + internal void Clear() + { + _unlabeledBranches.Clear(); + FreeLabeledBranches(); + } + + private void FreeLabeledBranches() + { + if (_labeledBranches is { }) + { + foreach (var branches in _labeledBranches.Values) + { + branches.Free(); + } + _labeledBranches.Free(); + _labeledBranches = null; + } + } + + /// + /// Returns the unordered collection of branches. + /// + internal ImmutableArray ToImmutable() + { + return _labeledBranches is null ? + _unlabeledBranches.ToImmutable() : + ImmutableArray.CreateRange(AsEnumerable()); + } + + internal ArrayBuilder? GetAndRemoveBranches(LabelSymbol? label) + { + ArrayBuilder? result; + if (label is null) + { + if (_unlabeledBranches.Count == 0) + { + result = null; + } + else + { + result = _unlabeledBranches; + _unlabeledBranches = ArrayBuilder.GetInstance(); + } + } + else if (_labeledBranches is { } && _labeledBranches.TryGetValue(label, out result)) + { + _labeledBranches.Remove(label); + } + else + { + result = null; + } + return result; + } + + internal void Add(PendingBranch branch) + { + var label = branch.Label; + if (label is null) + { + _unlabeledBranches.Add(branch); + } + else + { + var branches = GetOrAddLabeledBranches(label); + branches.Add(branch); + } + } + + internal void AddRange(PendingBranchesCollection collection) + { + _unlabeledBranches.AddRange(collection._unlabeledBranches); + if (collection._labeledBranches is { }) + { + foreach (var pair in collection._labeledBranches) + { + var branches = GetOrAddLabeledBranches(pair.Key); + branches.AddRange(pair.Value); + } + } + } + + private ArrayBuilder GetOrAddLabeledBranches(LabelSymbol label) + { + if (_labeledBranches is null) + { + _labeledBranches = PooledDictionary>.GetInstance(); + } + if (!_labeledBranches.TryGetValue(label, out var branches)) + { + branches = ArrayBuilder.GetInstance(); + _labeledBranches.Add(label, branches); + } + return branches; + } + + /// + /// Returns the unordered collection of branches. + /// + internal IEnumerable AsEnumerable() + { + return _labeledBranches is null ? + _unlabeledBranches : + asEnumerableCore(); + + IEnumerable asEnumerableCore() + { + foreach (var branch in _unlabeledBranches) + { + yield return branch; + } + foreach (var branches in _labeledBranches.Values) + { + foreach (var branch in branches) + { + yield return branch; + } + } + } + } + } + } +} diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index 3f839eec29016..57bce8634b8b2 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -99,7 +99,7 @@ internal abstract partial class AbstractFlowPass - protected ArrayBuilder PendingBranches { get; private set; } + protected PendingBranchesCollection PendingBranches { get; private set; } /// /// The definite assignment and/or reachability state at the point currently being analyzed. @@ -205,7 +205,7 @@ protected AbstractFlowPass( this.RegionSpan = new TextSpan(startLocation, length); } - PendingBranches = ArrayBuilder.GetInstance(); + PendingBranches = new PendingBranchesCollection(); _labelsSeen = PooledHashSet.GetInstance(); _labels = PooledDictionary.GetInstance(); this.Diagnostics = DiagnosticBag.GetInstance(); @@ -390,14 +390,16 @@ protected override bool ConvertInsufficientExecutionStackExceptionToCancelledByS /// pending branches to a label in that block we process the branch. Otherwise we relay it /// up to the enclosing construct as a pending branch of the enclosing construct. /// - internal class PendingBranch + internal sealed class PendingBranch { public readonly BoundNode Branch; public bool IsConditionalState; public TLocalState State; public TLocalState StateWhenTrue; public TLocalState StateWhenFalse; - public readonly LabelSymbol Label; +#nullable enable + public readonly LabelSymbol? Label; +#nullable disable public PendingBranch(BoundNode branch, TLocalState state, LabelSymbol label, bool isConditionalState = false, TLocalState stateWhenTrue = default, TLocalState stateWhenFalse = default) { @@ -708,38 +710,14 @@ private void LoopTail(BoundLoopStatement node) } } +#nullable enable /// /// Used to resolve break statements in each statement form that has a break statement /// (loops, switch). /// private void ResolveBreaks(TLocalState breakState, LabelSymbol label) { - var pendingBranches = PendingBranches; - var count = pendingBranches.Count; - - if (count != 0) - { - int stillPending = 0; - for (int i = 0; i < count; i++) - { - var pending = pendingBranches[i]; - if (pending.Label == label) - { - Join(ref breakState, ref pending.State); - } - else - { - if (stillPending != i) - { - pendingBranches[stillPending] = pending; - } - stillPending++; - } - } - - pendingBranches.Clip(stillPending); - } - + JoinPendingBranches(ref breakState, label); SetState(breakState); } @@ -748,38 +726,27 @@ private void ResolveBreaks(TLocalState breakState, LabelSymbol label) /// private void ResolveContinues(LabelSymbol continueLabel) { - var pendingBranches = PendingBranches; - var count = pendingBranches.Count; + // Technically, nothing in the language specification depends on the state + // at the continue label, so we could just discard them instead of merging + // the states. In fact, we need not have added continue statements to the + // pending jump queue in the first place if we were interested solely in the + // flow analysis. However, region analysis (in support of extract method) + // and other forms of more precise analysis + // depend on continue statements appearing in the pending branch queue, so + // we process them from the queue here. + JoinPendingBranches(ref this.State, continueLabel); + } - if (count != 0) + private void JoinPendingBranches(ref TLocalState state, LabelSymbol label) + { + var pendingBranches = PendingBranches.GetAndRemoveBranches(label); + if (pendingBranches is { }) { - int stillPending = 0; - for (int i = 0; i < count; i++) + foreach (var pending in pendingBranches) { - var pending = pendingBranches[i]; - if (pending.Label == continueLabel) - { - // Technically, nothing in the language specification depends on the state - // at the continue label, so we could just discard them instead of merging - // the states. In fact, we need not have added continue statements to the - // pending jump queue in the first place if we were interested solely in the - // flow analysis. However, region analysis (in support of extract method) - // and other forms of more precise analysis - // depend on continue statements appearing in the pending branch queue, so - // we process them from the queue here. - Join(ref this.State, ref pending.State); - } - else - { - if (stillPending != i) - { - pendingBranches[stillPending] = pending; - } - stillPending++; - } + Join(ref state, ref pending.State); } - - pendingBranches.Clip(stillPending); + pendingBranches.Free(); } } @@ -798,41 +765,26 @@ protected virtual void NoteBranch(PendingBranch pending, BoundNode gotoStmt, Bou /// /// Target label /// Statement containing the target label - private bool ResolveBranches(LabelSymbol label, BoundStatement target) + private bool ResolveBranches(LabelSymbol label, BoundStatement? target) { target?.AssertIsLabeledStatementWithLabel(label); bool labelStateChanged = false; - var pendingBranches = PendingBranches; - var count = pendingBranches.Count; - if (count != 0) + var pendingBranches = PendingBranches.GetAndRemoveBranches(label); + if (pendingBranches is { }) { - int stillPending = 0; - for (int i = 0; i < count; i++) + foreach (var pending in pendingBranches) { - var pending = pendingBranches[i]; - if (pending.Label == label) - { - ResolveBranch(pending, label, target, ref labelStateChanged); - } - else - { - if (stillPending != i) - { - pendingBranches[stillPending] = pending; - } - stillPending++; - } + ResolveBranch(pending, label, target, ref labelStateChanged); } - - pendingBranches.Clip(stillPending); + pendingBranches.Free(); } return labelStateChanged; } - protected virtual void ResolveBranch(PendingBranch pending, LabelSymbol label, BoundStatement target, ref bool labelStateChanged) + protected virtual void ResolveBranch(PendingBranch pending, LabelSymbol label, BoundStatement? target, ref bool labelStateChanged) { var state = LabelState(label); if (target != null) @@ -848,12 +800,12 @@ protected virtual void ResolveBranch(PendingBranch pending, LabelSymbol label, B } } - protected struct SavedPending + protected readonly struct SavedPending { - public readonly ArrayBuilder PendingBranches; + public readonly PendingBranchesCollection PendingBranches; public readonly PooledHashSet LabelsSeen; - public SavedPending(ArrayBuilder pendingBranches, PooledHashSet labelsSeen) + public SavedPending(PendingBranchesCollection pendingBranches, PooledHashSet labelsSeen) { this.PendingBranches = pendingBranches; this.LabelsSeen = labelsSeen; @@ -870,7 +822,7 @@ protected SavedPending SavePending() Debug.Assert(!this.IsConditionalState); var result = new SavedPending(PendingBranches, _labelsSeen); - PendingBranches = ArrayBuilder.GetInstance(); + PendingBranches = new PendingBranchesCollection(); _labelsSeen = PooledHashSet.GetInstance(); return result; @@ -927,6 +879,7 @@ protected void RestorePending(SavedPending oldPending) _labelsSeen.Free(); _labelsSeen = oldPending.LabelsSeen; } +#nullable disable #region visitors @@ -1541,7 +1494,6 @@ public override BoundNode VisitBadStatement(BoundBadStatement node) return null; } - // Can be called as part of a bad expression. public override BoundNode VisitArrayInitialization(BoundArrayInitialization node) { foreach (var child in node.Initializers) @@ -1727,7 +1679,7 @@ public override BoundNode VisitTryStatement(BoundTryStatement node) var tryAndCatchPending = SavePending(); var stateMovedUpInFinally = ReachableBottomState(); VisitFinallyBlockWithAnyTransferFunction(node.FinallyBlockOpt, ref stateMovedUpInFinally); - foreach (var pend in tryAndCatchPending.PendingBranches) + foreach (var pend in tryAndCatchPending.PendingBranches.AsEnumerable()) { if (pend.Branch == null) { @@ -2648,29 +2600,10 @@ public override BoundNode VisitArrayCreation(BoundArrayCreation node) VisitRvalue(expr); } - if (node.InitializerOpt != null) - { - VisitArrayInitializationInternal(node, node.InitializerOpt); - } - + VisitRvalue(node.InitializerOpt); return null; } - private void VisitArrayInitializationInternal(BoundArrayCreation arrayCreation, BoundArrayInitialization node) - { - foreach (var child in node.Initializers) - { - if (child.Kind == BoundKind.ArrayInitialization) - { - VisitArrayInitializationInternal(arrayCreation, (BoundArrayInitialization)child); - } - else - { - VisitRvalue(child); - } - } - } - public override BoundNode VisitForStatement(BoundForStatement node) { if (node.Initializer != null) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AlwaysAssignedWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AlwaysAssignedWalker.cs index c29551afe1292..3056295f7c01b 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AlwaysAssignedWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AlwaysAssignedWalker.cs @@ -126,7 +126,7 @@ protected override void LeaveRegion() _endOfRegionState = this.State.Clone(); } - foreach (var branch in base.PendingBranches) + foreach (var branch in PendingBranches.AsEnumerable()) { if (branch.Branch != null && RegionContains(branch.Branch.Syntax.Span) && !_labelsInside.Contains(branch.Label)) { diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowAnalysis.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowAnalysis.cs index a467bdf6efbc9..c937819fae8bb 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowAnalysis.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowAnalysis.cs @@ -61,7 +61,7 @@ public override ImmutableArray ExitPoints if (_exitPoints == null) { var result = Succeeded - ? ((IEnumerable)ExitPointsWalker.Analyze(_context.Compilation, _context.Member, _context.BoundNode, _context.FirstInRegion, _context.LastInRegion)).ToImmutableArray() + ? ImmutableArray.CastUp(ExitPointsWalker.Analyze(_context.Compilation, _context.Member, _context.BoundNode, _context.FirstInRegion, _context.LastInRegion)) : ImmutableArray.Empty; ImmutableInterlocked.InterlockedInitialize(ref _exitPoints, result); } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs index 1a1c503bef253..7ff832f2f54f3 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/ControlFlowPass.cs @@ -294,7 +294,7 @@ protected override void VisitFinallyBlock(BoundStatement finallyBlock, ref Local var oldPending2 = SavePending(); // track only the branches out of the finally block base.VisitFinallyBlock(finallyBlock, ref endState); RestorePending(oldPending2); // resolve branches that remain within the finally block - foreach (var branch in PendingBranches) + foreach (var branch in PendingBranches.AsEnumerable()) { if (branch.Branch == null) continue; // a tracked exception var location = new SourceLocation(branch.Branch.Syntax.GetFirstToken()); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/ExitPointsWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/ExitPointsWalker.cs index ef51ef436b2db..c70043503a082 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/ExitPointsWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/ExitPointsWalker.cs @@ -4,13 +4,10 @@ #nullable disable -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp @@ -21,7 +18,7 @@ namespace Microsoft.CodeAnalysis.CSharp internal class ExitPointsWalker : AbstractRegionControlFlowPass { private readonly ArrayBuilder _labelsInside; - private ArrayBuilder _branchesOutOf; + private readonly ArrayBuilder _branchesOutOf; private ExitPointsWalker(CSharpCompilation compilation, Symbol member, BoundNode node, BoundNode firstInRegion, BoundNode lastInRegion) : base(compilation, member, node, firstInRegion, lastInRegion) @@ -41,16 +38,12 @@ protected override void Free() base.Free(); } - internal static IEnumerable Analyze(CSharpCompilation compilation, Symbol member, BoundNode node, BoundNode firstInRegion, BoundNode lastInRegion) + internal static ImmutableArray Analyze(CSharpCompilation compilation, Symbol member, BoundNode node, BoundNode firstInRegion, BoundNode lastInRegion) { var walker = new ExitPointsWalker(compilation, member, node, firstInRegion, lastInRegion); try { - bool badRegion = false; - walker.Analyze(ref badRegion); - var result = walker._branchesOutOf.ToImmutableAndFree(); - walker._branchesOutOf = null; - return badRegion ? SpecializedCollections.EmptyEnumerable() : result; + return walker.Analyze(); } finally { @@ -58,10 +51,20 @@ internal static IEnumerable Analyze(CSharpCompilation compilati } } - private void Analyze(ref bool badRegion) + private ImmutableArray Analyze() { + bool badRegion = false; + // only one pass is needed. Scan(ref badRegion); + + if (badRegion) + { + return ImmutableArray.Empty; + } + + _branchesOutOf.Sort((x, y) => x.SpanStart - y.SpanStart); + return _branchesOutOf.ToImmutable(); } public override BoundNode VisitLabelStatement(BoundLabelStatement node) @@ -116,7 +119,7 @@ protected override void EnterRegion() protected override void LeaveRegion() { - foreach (var pending in PendingBranches) + foreach (var pending in PendingBranches.AsEnumerable()) { if (pending.Branch == null || !RegionContains(pending.Branch.Syntax.Span)) continue; switch (pending.Branch.Kind) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index bc26b7b69c766..c3045f7fd410f 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -699,10 +699,12 @@ public override BoundNode VisitArrayAccess(BoundArrayAccess node) VisitExpression(node.Expression), out BoundAssignmentOperator arrayAssign); + BoundExpression makeOffsetInput = DetermineMakePatternIndexOffsetExpressionStrategy(node.Indices[0], out PatternIndexOffsetLoweringStrategy strategy); + var indexOffsetExpr = MakePatternIndexOffsetExpression( - node.Indices[0], + makeOffsetInput, F.ArrayLength(arrayLocal), - out _); + strategy); resultExpr = F.Sequence( ImmutableArray.Create(arrayLocal.LocalSymbol), diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs index 4b6bc86dd17e2..8bf9121b538ec 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs @@ -1052,7 +1052,7 @@ private BoundExpression RewriteUserDefinedConversion( private BoundExpression MakeLiftedUserDefinedConversionConsequence(BoundCall call, TypeSymbol resultType) { - if (call.Method.ReturnType.IsNonNullableValueType()) + if (call.Method.ReturnType.IsValidNullableTypeArgument()) { Debug.Assert(resultType.IsNullableType() && TypeSymbol.Equals(resultType.GetNullableUnderlyingType(), call.Method.ReturnType, TypeCompareKind.ConsiderEverything2)); MethodSymbol ctor = UnsafeGetNullableMethod(call.Syntax, resultType, SpecialMember.System_Nullable_T__ctor); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs index 796d87af34465..273b03a19c035 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs @@ -208,17 +208,50 @@ private BoundSequence VisitIndexPatternIndexerAccess( { var F = _factory; + var locals = ArrayBuilder.GetInstance(2); + var sideeffects = ArrayBuilder.GetInstance(2); + Debug.Assert(receiver.Type is { }); var receiverLocal = F.StoreToTemp( VisitExpression(receiver), out var receiverStore, // Store the receiver as a ref local if it's a value type to ensure side effects are propagated receiver.Type.IsReferenceType ? RefKind.None : RefKind.Ref); - var indexAccess = MakePatternIndexOffsetExpression(argument, F.Property(receiverLocal, lengthOrCountProperty), out _); + locals.Add(receiverLocal.LocalSymbol); + sideeffects.Add(receiverStore); + + BoundExpression makeOffsetInput = DetermineMakePatternIndexOffsetExpressionStrategy(argument, out PatternIndexOffsetLoweringStrategy strategy); + BoundExpression indexAccess; + + switch (strategy) + { + case PatternIndexOffsetLoweringStrategy.SubtractFromLength: + // ensure we evaluate the input before accessing length + if (makeOffsetInput.ConstantValue is null) + { + makeOffsetInput = F.StoreToTemp(makeOffsetInput, out BoundAssignmentOperator inputStore); + locals.Add(((BoundLocal)makeOffsetInput).LocalSymbol); + sideeffects.Add(inputStore); + } + + indexAccess = MakePatternIndexOffsetExpression(makeOffsetInput, F.Property(receiverLocal, lengthOrCountProperty), strategy); + break; + + case PatternIndexOffsetLoweringStrategy.UseAsIs: + indexAccess = MakePatternIndexOffsetExpression(makeOffsetInput, lengthAccess: null, strategy); + break; + + case PatternIndexOffsetLoweringStrategy.UseGetOffsetAPI: + indexAccess = MakePatternIndexOffsetExpression(makeOffsetInput, F.Property(receiverLocal, lengthOrCountProperty), strategy); + break; + + default: + throw ExceptionUtilities.UnexpectedValue(strategy); + } return (BoundSequence)F.Sequence( - ImmutableArray.Create(receiverLocal.LocalSymbol), - ImmutableArray.Create(receiverStore), + locals.ToImmutableAndFree(), + sideeffects.ToImmutableAndFree(), MakeIndexerAccess( syntax, receiverLocal, @@ -235,55 +268,108 @@ private BoundSequence VisitIndexPatternIndexerAccess( } /// - /// Used to construct a pattern index offset expression, of the form - /// `unloweredExpr.GetOffset(lengthAccess)` - /// where unloweredExpr is an expression of type System.Index and the - /// lengthAccess retrieves the length of the indexing target. + /// Used to produce an expression translating to an integer offset + /// according to the . + /// The implementation should be in sync with . /// - /// The unlowered argument to the indexing expression + /// The lowered input for the translation /// /// An expression accessing the length of the indexing target. This should /// be a non-side-effecting operation. /// - /// - /// True if we were able to optimize the - /// to use the operation directly on the receiver, instead of - /// using System.Index helpers. - /// + /// The translation strategy private BoundExpression MakePatternIndexOffsetExpression( + BoundExpression? loweredExpr, + BoundExpression? lengthAccess, + PatternIndexOffsetLoweringStrategy strategy) + { + switch (strategy) + { + case PatternIndexOffsetLoweringStrategy.Zero: + return _factory.Literal(0); + + case PatternIndexOffsetLoweringStrategy.Length: + Debug.Assert(lengthAccess is not null); + return lengthAccess; + + case PatternIndexOffsetLoweringStrategy.SubtractFromLength: + Debug.Assert(loweredExpr is not null); + Debug.Assert(lengthAccess is not null); + Debug.Assert(loweredExpr.Type!.SpecialType == SpecialType.System_Int32); + + if (loweredExpr.ConstantValue?.Int32Value == 0) + { + return lengthAccess; + } + + return _factory.IntSubtract(lengthAccess, loweredExpr); + + case PatternIndexOffsetLoweringStrategy.UseAsIs: + Debug.Assert(loweredExpr is not null); + Debug.Assert(loweredExpr.Type!.SpecialType == SpecialType.System_Int32); + return loweredExpr; + + case PatternIndexOffsetLoweringStrategy.UseGetOffsetAPI: + Debug.Assert(loweredExpr is not null); + Debug.Assert(lengthAccess is not null); + Debug.Assert(TypeSymbol.Equals( + loweredExpr.Type, + _compilation.GetWellKnownType(WellKnownType.System_Index), + TypeCompareKind.ConsiderEverything)); + + return _factory.Call( + loweredExpr, + WellKnownMember.System_Index__GetOffset, + lengthAccess); + + default: + throw ExceptionUtilities.UnexpectedValue(strategy); + } + } + + private enum PatternIndexOffsetLoweringStrategy + { + Zero, + Length, + SubtractFromLength, + UseAsIs, + UseGetOffsetAPI + } + + /// + /// Determine the lowering strategy for translating a System.Index value to an integer offset value + /// and prepare the lowered input for the translation process handled by . + /// The implementation should be in sync with . + /// + private BoundExpression DetermineMakePatternIndexOffsetExpressionStrategy( BoundExpression unloweredExpr, - BoundExpression lengthAccess, - out bool usedLength) + out PatternIndexOffsetLoweringStrategy strategy) { Debug.Assert(TypeSymbol.Equals( unloweredExpr.Type, _compilation.GetWellKnownType(WellKnownType.System_Index), TypeCompareKind.ConsiderEverything)); - var F = _factory; - if (unloweredExpr is BoundFromEndIndexExpression hatExpression) { // If the System.Index argument is `^index`, we can replace the // `argument.GetOffset(length)` call with `length - index` Debug.Assert(hatExpression.Operand is { Type: { SpecialType: SpecialType.System_Int32 } }); - usedLength = true; - return F.IntSubtract(lengthAccess, VisitExpression(hatExpression.Operand)); + strategy = PatternIndexOffsetLoweringStrategy.SubtractFromLength; + return VisitExpression(hatExpression.Operand); } else if (unloweredExpr is BoundConversion { Operand: { Type: { SpecialType: SpecialType.System_Int32 } } operand }) { // If the System.Index argument is a conversion from int to Index we // can return the int directly - usedLength = false; + strategy = PatternIndexOffsetLoweringStrategy.UseAsIs; return VisitExpression(operand); } else { - usedLength = true; - return F.Call( - VisitExpression(unloweredExpr), - WellKnownMember.System_Index__GetOffset, - lengthAccess); + // `argument.GetOffset(length)` + strategy = PatternIndexOffsetLoweringStrategy.UseGetOffsetAPI; + return VisitExpression(unloweredExpr); } } @@ -300,8 +386,8 @@ private BoundSequence VisitRangePatternIndexerAccess( // Lowered code without optimizations: // var receiver = receiverExpr; - // int length = receiver.length; // Range range = argumentExpr; + // int length = receiver.length; // int start = range.Start.GetOffset(length) // int rangeSize = range.End.GetOffset(length) - start // receiver.Slice(start, rangeSize) @@ -312,7 +398,6 @@ private BoundSequence VisitRangePatternIndexerAccess( var sideEffectsBuilder = ArrayBuilder.GetInstance(); var receiverLocal = F.StoreToTemp(VisitExpression(receiver), out var receiverStore); - var lengthLocal = F.StoreToTemp(F.Property(receiverLocal, lengthOrCountProperty), out var lengthStore); localsBuilder.Add(receiverLocal.LocalSymbol); sideEffectsBuilder.Add(receiverStore); @@ -333,63 +418,159 @@ private BoundSequence VisitRangePatternIndexerAccess( // int start = start.GetOffset(length) // int rangeSize = end.GetOffset(length) - start - bool usedLength = false; + BoundExpression? startMakeOffsetInput; + PatternIndexOffsetLoweringStrategy startStrategy; if (rangeExpr.LeftOperandOpt is BoundExpression left) { - var startLocal = F.StoreToTemp( - MakePatternIndexOffsetExpression(rangeExpr.LeftOperandOpt, lengthLocal, out usedLength), - out var startStore); - - localsBuilder.Add(startLocal.LocalSymbol); - sideEffectsBuilder.Add(startStore); - startExpr = startLocal; + startMakeOffsetInput = DetermineMakePatternIndexOffsetExpressionStrategy(left, out startStrategy); } else { - startExpr = F.Literal(0); + startStrategy = PatternIndexOffsetLoweringStrategy.Zero; + startMakeOffsetInput = null; } - BoundExpression endExpr; + BoundExpression? endMakeOffsetInput; + PatternIndexOffsetLoweringStrategy endStrategy; + if (rangeExpr.RightOperandOpt is BoundExpression right) { - endExpr = MakePatternIndexOffsetExpression( - right, - lengthLocal, - out bool usedLengthTemp); - usedLength |= usedLengthTemp; + endMakeOffsetInput = DetermineMakePatternIndexOffsetExpressionStrategy(right, out endStrategy); } else { - usedLength = true; - endExpr = lengthLocal; + endStrategy = PatternIndexOffsetLoweringStrategy.Length; + endMakeOffsetInput = null; } - if (usedLength) + const int captureStartOffset = 1 << 0; + const int captureEndOffset = 1 << 1; + const int useLength = 1 << 2; + const int captureLength = 1 << 3; + const int captureStartValue = 1 << 4; + + int rewriteFlags; + + switch ((startStrategy, endStrategy)) { - // If we used the length, it needs to be calculated after the receiver (the - // first bound node in the builder) and before the first use, which could be the - // second or third node in the builder - localsBuilder.Insert(1, lengthLocal.LocalSymbol); - sideEffectsBuilder.Insert(1, lengthStore); + case (PatternIndexOffsetLoweringStrategy.Zero, PatternIndexOffsetLoweringStrategy.Length): + case (PatternIndexOffsetLoweringStrategy.Zero, PatternIndexOffsetLoweringStrategy.UseGetOffsetAPI): + rewriteFlags = useLength; + break; + case (PatternIndexOffsetLoweringStrategy.Zero, PatternIndexOffsetLoweringStrategy.SubtractFromLength): + rewriteFlags = captureEndOffset | useLength; + break; + case (PatternIndexOffsetLoweringStrategy.Zero, PatternIndexOffsetLoweringStrategy.UseAsIs): + rewriteFlags = 0; + break; + case (PatternIndexOffsetLoweringStrategy.UseAsIs, PatternIndexOffsetLoweringStrategy.Length): + case (PatternIndexOffsetLoweringStrategy.UseAsIs, PatternIndexOffsetLoweringStrategy.UseGetOffsetAPI): + rewriteFlags = useLength | captureStartValue; + break; + case (PatternIndexOffsetLoweringStrategy.UseAsIs, PatternIndexOffsetLoweringStrategy.SubtractFromLength): + rewriteFlags = captureStartOffset | captureEndOffset | useLength; + break; + case (PatternIndexOffsetLoweringStrategy.UseAsIs, PatternIndexOffsetLoweringStrategy.UseAsIs): + rewriteFlags = captureStartValue; + break; + case (PatternIndexOffsetLoweringStrategy.SubtractFromLength, PatternIndexOffsetLoweringStrategy.Length): + case (PatternIndexOffsetLoweringStrategy.UseGetOffsetAPI, PatternIndexOffsetLoweringStrategy.Length): + rewriteFlags = captureStartOffset | useLength | captureLength | captureStartValue; + break; + case (PatternIndexOffsetLoweringStrategy.SubtractFromLength, PatternIndexOffsetLoweringStrategy.SubtractFromLength): + case (PatternIndexOffsetLoweringStrategy.SubtractFromLength, PatternIndexOffsetLoweringStrategy.UseGetOffsetAPI): + case (PatternIndexOffsetLoweringStrategy.UseGetOffsetAPI, PatternIndexOffsetLoweringStrategy.SubtractFromLength): + case (PatternIndexOffsetLoweringStrategy.UseGetOffsetAPI, PatternIndexOffsetLoweringStrategy.UseGetOffsetAPI): + rewriteFlags = captureStartOffset | captureEndOffset | useLength | captureLength | captureStartValue; + break; + case (PatternIndexOffsetLoweringStrategy.SubtractFromLength, PatternIndexOffsetLoweringStrategy.UseAsIs): + case (PatternIndexOffsetLoweringStrategy.UseGetOffsetAPI, PatternIndexOffsetLoweringStrategy.UseAsIs): + rewriteFlags = captureStartOffset | captureEndOffset | useLength | captureStartValue; + break; + + default: + throw ExceptionUtilities.UnexpectedValue(startStrategy); } - var rangeSizeLocal = F.StoreToTemp( - F.IntSubtract(endExpr, startExpr), - out var rangeStore); + Debug.Assert(startStrategy != PatternIndexOffsetLoweringStrategy.Zero || (rewriteFlags & captureStartOffset) == 0); + Debug.Assert(startStrategy != PatternIndexOffsetLoweringStrategy.Zero || (rewriteFlags & captureStartValue) == 0); + Debug.Assert((rewriteFlags & captureEndOffset) == 0 || (rewriteFlags & captureStartOffset) != 0 || startStrategy == PatternIndexOffsetLoweringStrategy.Zero); + Debug.Assert((rewriteFlags & captureStartOffset) == 0 || (rewriteFlags & captureEndOffset) != 0 || endStrategy == PatternIndexOffsetLoweringStrategy.Length); + Debug.Assert(endStrategy != PatternIndexOffsetLoweringStrategy.Length || (rewriteFlags & captureEndOffset) == 0); + Debug.Assert((rewriteFlags & captureLength) == 0 || (rewriteFlags & useLength) != 0); - localsBuilder.Add(rangeSizeLocal.LocalSymbol); - sideEffectsBuilder.Add(rangeStore); - rangeSizeExpr = rangeSizeLocal; + if ((rewriteFlags & captureStartOffset) != 0) + { + Debug.Assert(startMakeOffsetInput is not null); + if (startMakeOffsetInput.ConstantValue is null) + { + startMakeOffsetInput = F.StoreToTemp(startMakeOffsetInput, out BoundAssignmentOperator inputStore); + localsBuilder.Add(((BoundLocal)startMakeOffsetInput).LocalSymbol); + sideEffectsBuilder.Add(inputStore); + } + } + + if ((rewriteFlags & captureEndOffset) != 0) + { + Debug.Assert(endMakeOffsetInput is not null); + if (endMakeOffsetInput.ConstantValue is null) + { + endMakeOffsetInput = F.StoreToTemp(endMakeOffsetInput, out BoundAssignmentOperator inputStore); + localsBuilder.Add(((BoundLocal)endMakeOffsetInput).LocalSymbol); + sideEffectsBuilder.Add(inputStore); + } + } + + BoundExpression? lengthAccess = null; + + if ((rewriteFlags & useLength) != 0) + { + lengthAccess = F.Property(receiverLocal, lengthOrCountProperty); + + if ((rewriteFlags & captureLength) != 0) + { + var lengthLocal = F.StoreToTemp(lengthAccess, out var lengthStore); + localsBuilder.Add(lengthLocal.LocalSymbol); + sideEffectsBuilder.Add(lengthStore); + lengthAccess = lengthLocal; + } + } + + startExpr = MakePatternIndexOffsetExpression(startMakeOffsetInput, lengthAccess, startStrategy); + + if ((rewriteFlags & captureStartValue) != 0 && startExpr.ConstantValue is null) + { + var startLocal = F.StoreToTemp(startExpr, out var startStore); + localsBuilder.Add(startLocal.LocalSymbol); + sideEffectsBuilder.Add(startStore); + startExpr = startLocal; + } + + BoundExpression endExpr = MakePatternIndexOffsetExpression(endMakeOffsetInput, lengthAccess, endStrategy); + + if (startExpr.ConstantValue?.Int32Value == 0) + { + rangeSizeExpr = endExpr; + } + else if (startExpr.ConstantValue is { Int32Value: var startConst } && endExpr.ConstantValue is { Int32Value: var endConst }) + { + rangeSizeExpr = F.Literal(unchecked(endConst - startConst)); + } + else + { + rangeSizeExpr = F.IntSubtract(endExpr, startExpr); + } } else { var rangeLocal = F.StoreToTemp(VisitExpression(rangeArg), out var rangeStore); + localsBuilder.Add(rangeLocal.LocalSymbol); + sideEffectsBuilder.Add(rangeStore); + var lengthLocal = F.StoreToTemp(F.Property(receiverLocal, lengthOrCountProperty), out var lengthStore); localsBuilder.Add(lengthLocal.LocalSymbol); sideEffectsBuilder.Add(lengthStore); - localsBuilder.Add(rangeLocal.LocalSymbol); - sideEffectsBuilder.Add(rangeStore); var startLocal = F.StoreToTemp( F.Call( diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs index c78eba58a7774..ec682017ee0d0 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs @@ -314,10 +314,13 @@ public CSharpOperationFactory(SemanticModel semanticModel) break; } } - ImmutableArray children = GetIOperationChildren(boundNode); - return new NoneOperation(children, _semanticModel, boundNode.Syntax, type: null, constantValue, isImplicit: isImplicit); - + ITypeSymbol? type = boundNode switch + { + BoundExpression boundExpr => boundExpr.GetPublicTypeSymbol(), + _ => null + }; + return new NoneOperation(children, _semanticModel, boundNode.Syntax, type: type, constantValue, isImplicit: isImplicit); default: // If you're hitting this because the IOperation test hook has failed, see // /docs/Compilers/IOperation Test Hook.md for instructions on how to fix. @@ -453,16 +456,16 @@ private IOperation CreateBoundFunctionPointerInvocationOperation(BoundFunctionPo ITypeSymbol? type = boundFunctionPointerInvocation.GetPublicTypeSymbol(); SyntaxNode syntax = boundFunctionPointerInvocation.Syntax; bool isImplicit = boundFunctionPointerInvocation.WasCompilerGenerated; - ImmutableArray children; if (boundFunctionPointerInvocation.ResultKind != LookupResultKind.Viable) { - children = CreateFromArray(((IBoundInvalidNode)boundFunctionPointerInvocation).InvalidNodeChildren); + ImmutableArray children = CreateFromArray(((IBoundInvalidNode)boundFunctionPointerInvocation).InvalidNodeChildren); return new InvalidOperation(children, _semanticModel, syntax, type, constantValue: null, isImplicit); } - children = GetIOperationChildren(boundFunctionPointerInvocation); - return new NoneOperation(children, _semanticModel, syntax, type, constantValue: null, isImplicit); + var pointer = Create(boundFunctionPointerInvocation.InvokedExpression); + var arguments = DeriveArguments(boundFunctionPointerInvocation); + return new FunctionPointerInvocationOperation(pointer, arguments, _semanticModel, syntax, type, isImplicit); } private IOperation CreateBoundUnconvertedAddressOfOperatorOperation(BoundUnconvertedAddressOfOperator boundUnconvertedAddressOf) diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory_Methods.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory_Methods.cs index d8f22dac45f64..29949cecd5018 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory_Methods.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory_Methods.cs @@ -259,7 +259,17 @@ internal ImmutableArray DeriveArguments(BoundNode containing boundCollectionElementInitializer.Syntax, boundCollectionElementInitializer.InvokedAsExtensionMethod); } - + case BoundKind.FunctionPointerInvocation: + { + var boundFunctionPointerInvocation = (BoundFunctionPointerInvocation)containingExpression; + return DeriveArguments(boundFunctionPointerInvocation.FunctionPointer.Signature, + boundFunctionPointerInvocation.Arguments, + default, + BitVector.Empty, + false, + boundFunctionPointerInvocation.Syntax, + false); + } default: throw ExceptionUtilities.UnexpectedValue(containingExpression.Kind); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs index f10521a2f2eea..60a068e423039 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs @@ -918,14 +918,19 @@ private void DecodeUnmanagedCallersOnlyAttribute(ref DecodeWellKnownAttributeArg return; } - checkAndReportManagedTypes(ReturnType, returnTypeSyntax, isParam: false, diagnostics); + checkAndReportManagedTypes(ReturnType, this.RefKind, returnTypeSyntax, isParam: false, diagnostics); foreach (var param in Parameters) { - checkAndReportManagedTypes(param.Type, param.GetNonNullSyntaxNode(), isParam: true, diagnostics); + checkAndReportManagedTypes(param.Type, param.RefKind, param.GetNonNullSyntaxNode(), isParam: true, diagnostics); } - static void checkAndReportManagedTypes(TypeSymbol type, SyntaxNode syntax, bool isParam, BindingDiagnosticBag diagnostics) + static void checkAndReportManagedTypes(TypeSymbol type, RefKind refKind, SyntaxNode syntax, bool isParam, BindingDiagnosticBag diagnostics) { + if (refKind != RefKind.None) + { + diagnostics.Add(ErrorCode.ERR_CannotUseRefInUnmanagedCallersOnly, syntax.Location); + } + // use-site diagnostics will be reported at actual parameter declaration site, we're only interested // in reporting managed types being used switch (type.ManagedKindNoUseSiteDiagnostics) diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index 3272323630c12..6b0f2459f47c4 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -132,6 +132,14 @@ public static bool IsNullableType(this TypeSymbol type) return type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T; } + public static bool IsValidNullableTypeArgument(this TypeSymbol type) + { + return type is { IsValueType: true } + && !type.IsNullableType() + && !type.IsPointerOrFunctionPointer() + && !type.IsRestrictedType(); + } + public static TypeSymbol GetNullableUnderlyingType(this TypeSymbol type) { return type.GetNullableUnderlyingTypeWithAnnotations().Type; diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index c47d182743398..534955161f867 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -222,6 +222,11 @@ Atribut AsyncMethodBuilder je u anonymních metod bez explicitního návratového typu zakázaný. + + '{0}' cannot be made nullable. + '{0}' cannot be made nullable. + + The receiver type '{0}' is not a valid record type and is not a struct type. Typ příjemce {0} není platným typem záznamu a není typem struktury. @@ -257,6 +262,11 @@ Rozšiřující metoda, kde jako cíl je nastavený příjemce, se nedá použít jako cíl operátoru &. + + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + + InterpolatedStringHandlerArgumentAttribute arguments cannot refer to the parameter the attribute is used on. Argumenty InterpolatedStringHandlerArgumentAttribute nemůžou odkazovat na parametr, na kterém se atribut používá. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 97a2811376d3b..bee442a062f93 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -222,6 +222,11 @@ Das AsyncMethodBuilder-Attribut ist für anonyme Methoden ohne expliziten Rückgabetyp unzulässig. + + '{0}' cannot be made nullable. + '{0}' cannot be made nullable. + + The receiver type '{0}' is not a valid record type and is not a struct type. Der Empfängertyp "{0}" ist kein gültiger Datensatztyp und kein Strukturtyp. @@ -257,6 +262,11 @@ Eine Erweiterungsmethode mit einem Empfänger kann nicht als Ziel eines &-Operators verwendet werden. + + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + + InterpolatedStringHandlerArgumentAttribute arguments cannot refer to the parameter the attribute is used on. InterpolatedStringHandlerArgumentAttribute-Argumente können nicht auf den Parameter verweisen, für den das Attribut verwendet wird. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 2c560d8d9200e..151ffc81008c4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -222,6 +222,11 @@ El atributo AsyncMethodBuilder no se permite en métodos anónimos sin un tipo de valor devuelto explícito. + + '{0}' cannot be made nullable. + '{0}' cannot be made nullable. + + The receiver type '{0}' is not a valid record type and is not a struct type. El tipo de destinatario '{0}' no es un tipo de registro válido y no es un tipo de registro. @@ -257,6 +262,11 @@ No se puede usar un método de extensión con un receptor como destino de un operador "&". + + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + + InterpolatedStringHandlerArgumentAttribute arguments cannot refer to the parameter the attribute is used on. Los argumentos de InterpolatedStringHandlerArgumentAttribute no pueden hacer referencia al parámetro en el que se usa el atributo. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 98df771b0f62d..aa2cca41f87e4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -222,6 +222,11 @@ L'attribut AsyncMethodBuilder n'est pas autorisé pour les méthodes anonymes sans type de retour explicite. + + '{0}' cannot be made nullable. + '{0}' cannot be made nullable. + + The receiver type '{0}' is not a valid record type and is not a struct type. Le type de récepteur '{0}' n’est pas un type d’enregistrement valide et n’est pas un type struct. @@ -257,6 +262,11 @@ Impossible d'utiliser une méthode d'extension avec un récepteur en tant que cible d'un opérateur '&'. + + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + + InterpolatedStringHandlerArgumentAttribute arguments cannot refer to the parameter the attribute is used on. Les arguments de l'attribut InterpolatedStringHandlerArgumentAttribute ne peuvent pas faire référence au paramètre sur lequel l'attribut est utilisé. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 670f9ebd2ade1..2490a830c0e6b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -222,6 +222,11 @@ L'attributo AsyncMethodBuilder non è consentito in metodi anonimi senza un tipo restituito esplicito. + + '{0}' cannot be made nullable. + '{0}' cannot be made nullable. + + The receiver type '{0}' is not a valid record type and is not a struct type. Il tipo di ricevitore '{0}' non è un tipo di record valido e non è un tipo struct. @@ -257,6 +262,11 @@ Non è possibile usare un metodo di estensione con un ricevitore come destinazione di un operatore '&'. + + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + + InterpolatedStringHandlerArgumentAttribute arguments cannot refer to the parameter the attribute is used on. Gli argomenti di InterpolatedStringHandlerArgumentAttribute non possono fare riferimento al parametro in cui viene usato l'attributo. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 67e77e00cdece..d267d8119b3d4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -222,6 +222,11 @@ AsyncMethodBuilder 属性は、明示的な戻り値の型のない匿名メソッドでは許可されていません。 + + '{0}' cannot be made nullable. + '{0}' cannot be made nullable. + + The receiver type '{0}' is not a valid record type and is not a struct type. レシーバーの種類 '{0}' は有効なレコード型でも構造体型でもありません。 @@ -257,6 +262,11 @@ レシーバーが '&' 演算子の対象となっている拡張メソッドを使用することはできません。 + + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + + InterpolatedStringHandlerArgumentAttribute arguments cannot refer to the parameter the attribute is used on. InterpolatedStringHandlerArgumentAttribute 引数は、属性が使用されているパラメーターを参照できません。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 1370ca584ea46..2041531983122 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -222,6 +222,11 @@ AsyncMethodBuilder 특성은 명시적 반환 형식이 없는 익명 메서드에서 허용되지 않습니다. + + '{0}' cannot be made nullable. + '{0}' cannot be made nullable. + + The receiver type '{0}' is not a valid record type and is not a struct type. 수신기 형식 '{0}'은(는) 올바른 레코드 형식이 아니며 구조체 형식이 아닙니다. @@ -257,6 +262,11 @@ '&' 연산자의 대상으로 수신기가 있는 확장 메서드는 사용할 수 없습니다. + + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + + InterpolatedStringHandlerArgumentAttribute arguments cannot refer to the parameter the attribute is used on. InterpolatedStringHandlerArgumentAttribute 인수는 특성이 사용되는 매개 변수를 참조할 수 없습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 14c1f0421acbc..a6fd6b61619c0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -222,6 +222,11 @@ Atrybut AsyncMethodBuilder jest niedozwolony w metodach anonimowych bez jawnego zwracanego typu. + + '{0}' cannot be made nullable. + '{0}' cannot be made nullable. + + The receiver type '{0}' is not a valid record type and is not a struct type. Typ odbiorcy "{0}" nie jest prawidłowym typem rekordu i nie jest typem struktury. @@ -257,6 +262,11 @@ Nie można użyć metody rozszerzenia z odbiornikiem jako elementem docelowym operatora „&”. + + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + + InterpolatedStringHandlerArgumentAttribute arguments cannot refer to the parameter the attribute is used on. Argumenty atrybutu InterpolatedStringHandlerArgumentAttribute nie mogą odwoływać się do parametru, na podstawie którego jest używany atrybut. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 8ad9edeb9b875..04f9b792aefb9 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -222,6 +222,11 @@ O atributo AsyncMethodBuilder não é permitido em métodos anônimos sem um tipo de retorno explícito. + + '{0}' cannot be made nullable. + '{0}' cannot be made nullable. + + The receiver type '{0}' is not a valid record type and is not a struct type. O tipo de receptor '{0}' não é um tipo de registro válido e não é um tipo struct. @@ -257,6 +262,11 @@ Não é possível usar um método de extensão com um receptor como destino de um operador '&'. + + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + + InterpolatedStringHandlerArgumentAttribute arguments cannot refer to the parameter the attribute is used on. Argumentos InterpolatedStringHandlerArgumentAttribute não podem fazer referência ao parâmetro no qual o atributo é usado. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 21a7ed321ee8e..ae4f788696228 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -222,6 +222,11 @@ Атрибут AsyncMethodBuilder запрещен для анонимных методов без явного типа возвращаемого значения. + + '{0}' cannot be made nullable. + '{0}' cannot be made nullable. + + The receiver type '{0}' is not a valid record type and is not a struct type. Тип получателя "{0}" не является допустимым типом записи и не является типом структуры. @@ -257,6 +262,11 @@ Невозможно использовать метод расширения с приемником в качестве целевого объекта оператора "&". + + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + + InterpolatedStringHandlerArgumentAttribute arguments cannot refer to the parameter the attribute is used on. Аргументы InterpolatedStringHandlerArgumentAttribute не могут ссылаться на параметр, в котором используется атрибут. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 16403d22aa1e5..2ec632f162270 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -222,6 +222,11 @@ Açık dönüş türü olmadan, anonim yöntemlerde AsyncMethodBuilder özniteliğine izin verilmez. + + '{0}' cannot be made nullable. + '{0}' cannot be made nullable. + + The receiver type '{0}' is not a valid record type and is not a struct type. '{0}' alıcı türü geçerli bir kayıt türü değil ve bir yapı türü değil. @@ -257,6 +262,11 @@ '&' operatörünün hedefi olarak alıcı içeren bir genişletme metodu kullanılamaz. + + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + + InterpolatedStringHandlerArgumentAttribute arguments cannot refer to the parameter the attribute is used on. InterpolatedStringHandlerArgumentAttribute bağımsız değişkenleri özniteliğin kullanıldığı parametreye başvuramaz. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 7b122b800dadb..ec6f6946c8308 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -222,6 +222,11 @@ 没有显式返回类型的匿名方法不允许使用 AsyncMethodBuilder 属性。 + + '{0}' cannot be made nullable. + '{0}' cannot be made nullable. + + The receiver type '{0}' is not a valid record type and is not a struct type. 接收方类型 '{0}' 非有效记录类型,且非结构类型。 @@ -257,6 +262,11 @@ 不可将具有接收器的扩展方法用作 "&" 运算符的目标。 + + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + + InterpolatedStringHandlerArgumentAttribute arguments cannot refer to the parameter the attribute is used on. InterpolatedStringHandlerArgumentAttribute 参数不能引用在其上使用该属性的参数。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 67000287b6586..d9f12c5da3099 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -222,6 +222,11 @@ 沒有明確傳回型別的匿名方法上不允許 AsyncMethodBuilder 屬性。 + + '{0}' cannot be made nullable. + '{0}' cannot be made nullable. + + The receiver type '{0}' is not a valid record type and is not a struct type. 接收器類型 '{0}' 不是有效的記錄類型,而且不是結構類型。 @@ -257,6 +262,11 @@ 無法使用具有接收器的擴充方法作為 '&' 運算子的目標。 + + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + + InterpolatedStringHandlerArgumentAttribute arguments cannot refer to the parameter the attribute is used on. InterpolatedStringHandlerArgumentAttribute 引數無法參考屬性所使用的參數。 diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index 96c9ee0369c5d..105541efd79e1 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -9511,32 +9511,23 @@ public void WRN_InvalidSearchPathDir() } [WorkItem(650083, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/650083")] - [ConditionalFact(typeof(WindowsDesktopOnly), Reason = "https://github.com/dotnet/roslyn/issues/55730")] - public void ReservedDeviceNameAsFileName() + [InlineData("a.cs /t:library /appconfig:.\\aux.config")] + [InlineData("a.cs /out:com1.dll")] + [InlineData("a.cs /doc:..\\lpt2.xml")] + [InlineData("a.cs /pdb:..\\prn.pdb")] + [Theory] + public void ReservedDeviceNameAsFileName(string commandLine) { - var parsedArgs = DefaultParse(new[] { "com9.cs", "/t:library " }, WorkingDirectory); - Assert.Equal(0, parsedArgs.Errors.Length); - - parsedArgs = DefaultParse(new[] { "a.cs", "/t:library ", "/appconfig:.\\aux.config" }, WorkingDirectory); - Assert.Equal(1, parsedArgs.Errors.Length); - Assert.Equal((int)ErrorCode.FTL_InvalidInputFileName, parsedArgs.Errors.First().Code); - - - parsedArgs = DefaultParse(new[] { "a.cs", "/out:com1.dll " }, WorkingDirectory); - Assert.Equal(1, parsedArgs.Errors.Length); - Assert.Equal((int)ErrorCode.FTL_InvalidInputFileName, parsedArgs.Errors.First().Code); - - parsedArgs = DefaultParse(new[] { "a.cs", "/doc:..\\lpt2.xml: " }, WorkingDirectory); - Assert.Equal(1, parsedArgs.Errors.Length); - Assert.Equal((int)ErrorCode.FTL_InvalidInputFileName, parsedArgs.Errors.First().Code); - - parsedArgs = DefaultParse(new[] { "a.cs", "/debug+", "/pdb:.\\prn.pdb" }, WorkingDirectory); - Assert.Equal(1, parsedArgs.Errors.Length); - Assert.Equal((int)ErrorCode.FTL_InvalidInputFileName, parsedArgs.Errors.First().Code); - - parsedArgs = DefaultParse(new[] { "a.cs", "@con.rsp" }, WorkingDirectory); - Assert.Equal(1, parsedArgs.Errors.Length); - Assert.Equal((int)ErrorCode.ERR_OpenResponseFile, parsedArgs.Errors.First().Code); + var parsedArgs = DefaultParse(commandLine.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries), WorkingDirectory); + if (ExecutionConditionUtil.OperatingSystemRestrictsFileNames) + { + Assert.Equal(1, parsedArgs.Errors.Length); + Assert.Equal((int)ErrorCode.FTL_InvalidInputFileName, parsedArgs.Errors.First().Code); + } + else + { + Assert.Equal(0, parsedArgs.Errors.Length); + } } [Fact] @@ -11723,7 +11714,12 @@ public void MicrosoftDiaSymReaderNativeAltLoadPath() var arguments = "/nologo /t:library /debug:full Source.cs"; // env variable not set (deterministic) -- DSRN is required: - var result = ProcessUtilities.Run(cscCopy, arguments + " /deterministic", workingDirectory: dir.Path); + var result = ProcessUtilities.Run( + cscCopy, + arguments + " /deterministic", + workingDirectory: dir.Path, + additionalEnvironmentVars: new[] { KeyValuePairUtil.Create("MICROSOFT_DIASYMREADER_NATIVE_ALT_LOAD_PATH", "") }); + AssertEx.AssertEqualToleratingWhitespaceDifferences( "error CS0041: Unexpected error writing debug information -- 'Unable to load DLL 'Microsoft.DiaSymReader.Native.amd64.dll': " + "The specified module could not be found. (Exception from HRESULT: 0x8007007E)'", result.Output.Trim()); diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.cs b/src/Compilers/CSharp/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.cs index 507f72f53e62e..ba775a35557ac 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.cs @@ -2894,5 +2894,280 @@ public class Class1 : TestBaseClass var c2 = CreateCompilation(new[] { source1, source2 }, new[] { libRef }, assemblyName: "WantsIVTAccess", options: TestOptions.SigningReleaseDll); c2.VerifyEmitDiagnostics(); } + + [Fact] + [WorkItem(57742, "https://github.com/dotnet/roslyn/issues/57742")] + public void Issue57742_01() + { + string lib_cs = @" +using System.Runtime.CompilerServices; + +[ assembly: InternalsVisibleTo(""Issue57742_01, PublicKey=00240000048000009400000006020000002400005253413100040000010001002b986f6b5ea5717d35c72d38561f413e267029efa9b5f107b9331d83df657381325b3a67b75812f63a9436ceccb49494de8f574f8e639d4d26c0fcf8b0e9a1a196b80b6f6ed053628d10d027e032df2ed1d60835e5f47d32c9ef6da10d0366a319573362c821b5f8fa5abc5bb22241de6f666a85d82d6ba8c3090d01636bd2bb"") ] +internal class PublicKeyConstants +{ + public const string PublicKey = ""Something""; +} +"; + var lib = CreateCompilation(lib_cs, assemblyName: "Issue57742_01_Lib"); + + string source1 = @" +[assembly: TestAttribute(""something"" + PublicKeyConstants.PublicKey)] + +class TestAttribute : System.Attribute +{ + public TestAttribute(string x) {} +} +"; + + var comp = CreateCompilation(source1, new[] { lib.ToMetadataReference() }, assemblyName: "Issue57742_01"); + var expected = new[] + { + // (2,40): error CS0281: Friend access was granted by 'Issue57742_01_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('') does not match that specified by the InternalsVisibleTo attribute in the granting assembly + // [assembly: TestAttribute("something" + PublicKeyConstants.PublicKey)] + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis, "PublicKeyConstants").WithArguments("Issue57742_01_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "").WithLocation(2, 40) + }; + + comp.VerifyDiagnostics(expected); + + comp = CreateCompilation(source1, new[] { lib.EmitToImageReference() }, assemblyName: "Issue57742_01"); + comp.VerifyDiagnostics(expected); + } + + [Fact] + [WorkItem(57742, "https://github.com/dotnet/roslyn/issues/57742")] + public void Issue57742_02() + { + string lib_cs = @" +using System.Runtime.CompilerServices; + +[ assembly: InternalsVisibleTo(""Issue57742_02, PublicKey=00240000048000009400000006020000002400005253413100040000010001002b986f6b5ea5717d35c72d38561f413e267029efa9b5f107b9331d83df657381325b3a67b75812f63a9436ceccb49494de8f574f8e639d4d26c0fcf8b0e9a1a196b80b6f6ed053628d10d027e032df2ed1d60835e5f47d32c9ef6da10d0366a319573362c821b5f8fa5abc5bb22241de6f666a85d82d6ba8c3090d01636bd2bb"") ] +internal class PublicKeyConstants +{ + public const string PublicKey = ""Something""; +} +"; + var lib = CreateCompilation(lib_cs, assemblyName: "Issue57742_02_Lib"); + + string source1 = @" +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo(""something"" + PublicKeyConstants.PublicKey)] +"; + + var comp = CreateCompilation(source1, new[] { lib.ToMetadataReference() }, assemblyName: "Issue57742_02"); + var expected = new[] + { + // (4,45): error CS0281: Friend access was granted by 'Issue57742_02_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + // [assembly: InternalsVisibleTo("something" + PublicKeyConstants.PublicKey)] + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis, "PublicKeyConstants").WithArguments("Issue57742_02_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "").WithLocation(4, 45) + }; + + comp.VerifyDiagnostics(expected); + + comp = CreateCompilation(source1, new[] { lib.EmitToImageReference() }, assemblyName: "Issue57742_02"); + comp.VerifyDiagnostics(expected); + } + + [Fact] + [WorkItem(57742, "https://github.com/dotnet/roslyn/issues/57742")] + public void Issue57742_03() + { + string lib_cs = @" +using System.Runtime.CompilerServices; + +[ assembly: InternalsVisibleTo(""Issue57742_03, PublicKey=00240000048000009400000006020000002400005253413100040000010001002b986f6b5ea5717d35c72d38561f413e267029efa9b5f107b9331d83df657381325b3a67b75812f63a9436ceccb49494de8f574f8e639d4d26c0fcf8b0e9a1a196b80b6f6ed053628d10d027e032df2ed1d60835e5f47d32c9ef6da10d0366a319573362c821b5f8fa5abc5bb22241de6f666a85d82d6ba8c3090d01636bd2bb"") ] +internal class PublicKeyConstants +{ + public const string PublicKey = ""Something""; +} +"; + var lib = CreateCompilation(lib_cs, assemblyName: "Issue57742_03_Lib"); + + string source1 = @" +using System.Reflection; + +[assembly: TestAttribute(""something"" + PublicKeyConstants.PublicKey)] +[assembly: AssemblyKeyFile(""something"" + PublicKeyConstants.PublicKey)] + +class TestAttribute : System.Attribute +{ + public TestAttribute(string x) {} +} +"; + + CompilationReference compilationReference = lib.ToMetadataReference(); + var comp = CreateCompilation(source1, new[] { compilationReference }, assemblyName: "Issue57742_03"); + var expected = new[] + { + // error CS7027: Error signing output with public key from file 'somethingSomething' -- Assembly signing not supported. + Diagnostic(ErrorCode.ERR_PublicKeyFileFailure).WithArguments("somethingSomething", "Assembly signing not supported.").WithLocation(1, 1), + // error CS0281: Friend access was granted by 'Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('Issue57742_03, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis).WithArguments("Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "Issue57742_03, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(1, 1), + // (4,40): error CS0281: Friend access was granted by 'Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + // [assembly: TestAttribute("something" + PublicKeyConstants.PublicKey)] + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis, "PublicKeyConstants").WithArguments("Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "").WithLocation(4, 40), + // (5,42): error CS0281: Friend access was granted by 'Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + // [assembly: AssemblyKeyFile("something" + PublicKeyConstants.PublicKey)] + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis, "PublicKeyConstants").WithArguments("Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "").WithLocation(5, 42) + }; + + comp.VerifyDiagnostics(expected); + + MetadataReference imageReference = lib.EmitToImageReference(); + comp = CreateCompilation(source1, new[] { imageReference }, assemblyName: "Issue57742_03"); + comp.VerifyDiagnostics(expected); + + string source2 = @" +using System.Reflection; + +[assembly: TestAttribute(""something"" + PublicKeyConstants.PublicKey)] +[assembly: AssemblyKeyName(""something"" + PublicKeyConstants.PublicKey)] + +class TestAttribute : System.Attribute +{ + public TestAttribute(string x) {} +} +"; + + var comp2 = CreateCompilation(source2, new[] { compilationReference }, assemblyName: "Issue57742_03"); + var expected2 = new[] + { + // error CS7028: Error signing output with public key from container 'somethingSomething' -- Assembly signing not supported. + Diagnostic(ErrorCode.ERR_PublicKeyContainerFailure).WithArguments("somethingSomething", "Assembly signing not supported.").WithLocation(1, 1), + // error CS0281: Friend access was granted by 'Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('Issue57742_03, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis).WithArguments("Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "Issue57742_03, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(1, 1), + // (4,40): error CS0281: Friend access was granted by 'Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + // [assembly: TestAttribute("something" + PublicKeyConstants.PublicKey)] + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis, "PublicKeyConstants").WithArguments("Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "").WithLocation(4, 40), + // (5,42): error CS0281: Friend access was granted by 'Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + // [assembly: AssemblyKeyName("something" + PublicKeyConstants.PublicKey)] + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis, "PublicKeyConstants").WithArguments("Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "").WithLocation(5, 42) + }; + + comp2.VerifyDiagnostics(expected2); + + comp2 = CreateCompilation(source2, new[] { imageReference }, assemblyName: "Issue57742_03"); + comp2.VerifyDiagnostics(expected2); + + string source3 = @" +using System.Reflection; + +[assembly: AssemblyKeyFile(""something"" + PublicKeyConstants.PublicKey)] +"; + + var comp3 = CreateCompilation(source3, new[] { compilationReference }, assemblyName: "Issue57742_03"); + var expected3 = new[] + { + // error CS7027: Error signing output with public key from file 'somethingSomething' -- Assembly signing not supported. + Diagnostic(ErrorCode.ERR_PublicKeyFileFailure).WithArguments("somethingSomething", "Assembly signing not supported.").WithLocation(1, 1), + // error CS0281: Friend access was granted by 'Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('Issue57742_03, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis).WithArguments("Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "Issue57742_03, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(1, 1), + // (4,42): error CS0281: Friend access was granted by 'Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + // [assembly: AssemblyKeyFile("something" + PublicKeyConstants.PublicKey)] + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis, "PublicKeyConstants").WithArguments("Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "").WithLocation(4, 42) + }; + + comp3.VerifyDiagnostics(expected3); + + comp3 = CreateCompilation(source3, new[] { imageReference }, assemblyName: "Issue57742_03"); + comp3.VerifyDiagnostics(expected3); + + string source4 = @" +using System.Reflection; + +[assembly: AssemblyKeyName(""something"" + PublicKeyConstants.PublicKey)] +"; + + var comp4 = CreateCompilation(source4, new[] { compilationReference }, assemblyName: "Issue57742_03"); + var expected4 = new[] + { + // error CS7028: Error signing output with public key from container 'somethingSomething' -- Assembly signing not supported. + Diagnostic(ErrorCode.ERR_PublicKeyContainerFailure).WithArguments("somethingSomething", "Assembly signing not supported.").WithLocation(1, 1), + // error CS0281: Friend access was granted by 'Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('Issue57742_03, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis).WithArguments("Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "Issue57742_03, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(1, 1), + // (4,42): error CS0281: Friend access was granted by 'Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + // [assembly: AssemblyKeyName("something" + PublicKeyConstants.PublicKey)] + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis, "PublicKeyConstants").WithArguments("Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "").WithLocation(4, 42) + }; + + comp4.VerifyDiagnostics(expected4); + + comp4 = CreateCompilation(source4, new[] { imageReference }, assemblyName: "Issue57742_03"); + comp4.VerifyDiagnostics(expected4); + } + + [Fact] + [WorkItem(57742, "https://github.com/dotnet/roslyn/issues/57742")] + public void Issue57742_04() + { + string lib_cs = @" +using System.Runtime.CompilerServices; + +[ assembly: InternalsVisibleTo(""Issue57742_04, PublicKey=00240000048000009400000006020000002400005253413100040000010001002b986f6b5ea5717d35c72d38561f413e267029efa9b5f107b9331d83df657381325b3a67b75812f63a9436ceccb49494de8f574f8e639d4d26c0fcf8b0e9a1a196b80b6f6ed053628d10d027e032df2ed1d60835e5f47d32c9ef6da10d0366a319573362c821b5f8fa5abc5bb22241de6f666a85d82d6ba8c3090d01636bd2bb"") ] +internal class PublicKeyConstants +{ + public const string PublicKey = ""Something""; +} +"; + var lib = CreateCompilation(lib_cs, assemblyName: "Issue57742_04_Lib"); + + string source1 = @" +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo(""something"" + PublicKeyConstants.PublicKey)] +[assembly: AssemblyKeyFile(""something"" + PublicKeyConstants.PublicKey)] +"; + + CompilationReference compilationReference = lib.ToMetadataReference(); + var comp = CreateCompilation(source1, new[] { compilationReference }, assemblyName: "Issue57742_04"); + var expected = new[] + { + // error CS7027: Error signing output with public key from file 'somethingSomething' -- Assembly signing not supported. + Diagnostic(ErrorCode.ERR_PublicKeyFileFailure).WithArguments("somethingSomething", "Assembly signing not supported.").WithLocation(1, 1), + // error CS0281: Friend access was granted by 'Issue57742_04_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('Issue57742_04, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis).WithArguments("Issue57742_04_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "Issue57742_04, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(1, 1), + // (5,45): error CS0281: Friend access was granted by 'Issue57742_04_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + // [assembly: InternalsVisibleTo("something" + PublicKeyConstants.PublicKey)] + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis, "PublicKeyConstants").WithArguments("Issue57742_04_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "").WithLocation(5, 45), + // (6,42): error CS0281: Friend access was granted by 'Issue57742_04_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + // [assembly: AssemblyKeyFile("something" + PublicKeyConstants.PublicKey)] + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis, "PublicKeyConstants").WithArguments("Issue57742_04_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "").WithLocation(6, 42) + }; + + comp.VerifyDiagnostics(expected); + + MetadataReference imageReference = lib.EmitToImageReference(); + comp = CreateCompilation(source1, new[] { imageReference }, assemblyName: "Issue57742_04"); + comp.VerifyDiagnostics(expected); + + string source2 = @" +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo(""something"" + PublicKeyConstants.PublicKey)] +[assembly: AssemblyKeyName(""something"" + PublicKeyConstants.PublicKey)] +"; + + var comp2 = CreateCompilation(source2, new[] { compilationReference }, assemblyName: "Issue57742_04"); + var expected2 = new[] + { + // error CS7028: Error signing output with public key from container 'somethingSomething' -- Assembly signing not supported. + Diagnostic(ErrorCode.ERR_PublicKeyContainerFailure).WithArguments("somethingSomething", "Assembly signing not supported.").WithLocation(1, 1), + // error CS0281: Friend access was granted by 'Issue57742_04_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('Issue57742_04, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis).WithArguments("Issue57742_04_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "Issue57742_04, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(1, 1), + // (5,45): error CS0281: Friend access was granted by 'Issue57742_04_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + // [assembly: InternalsVisibleTo("something" + PublicKeyConstants.PublicKey)] + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis, "PublicKeyConstants").WithArguments("Issue57742_04_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "").WithLocation(5, 45), + // (6,42): error CS0281: Friend access was granted by 'Issue57742_04_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. + // [assembly: AssemblyKeyName("something" + PublicKeyConstants.PublicKey)] + Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis, "PublicKeyConstants").WithArguments("Issue57742_04_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "").WithLocation(6, 42) + }; + + comp2.VerifyDiagnostics(expected2); + + comp2 = CreateCompilation(source2, new[] { imageReference }, assemblyName: "Issue57742_04"); + comp2.VerifyDiagnostics(expected2); + } } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenFunctionPointersTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenFunctionPointersTests.cs index 900bbe013a54a..9fd93e28050f4 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenFunctionPointersTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenFunctionPointersTests.cs @@ -8111,6 +8111,49 @@ static void M8(T t) where T : unmanaged {} ); } + [Fact, WorkItem(57025, "https://github.com/dotnet/roslyn/issues/57025")] + public void UnmanagedCallersOnlyRequiresNonRef_Errors() + { + var comp = CreateCompilation(new[] { @" +using System.Runtime.InteropServices; +class C +{ + [UnmanagedCallersOnly] + static ref int M1() => throw null; + + [UnmanagedCallersOnly] + static ref readonly int M2() => throw null; + + [UnmanagedCallersOnly] + static void M3(ref int o) => throw null; + + [UnmanagedCallersOnly] + static void M4(in int o) => throw null; + + [UnmanagedCallersOnly] + static void M5(out int o) => throw null; +} +", UnmanagedCallersOnlyAttribute }); + + comp.VerifyDiagnostics( + // (6,12): error CS8977: Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + // static ref int M1() => throw null; + Diagnostic(ErrorCode.ERR_CannotUseRefInUnmanagedCallersOnly, "ref int").WithLocation(6, 12), + // (9,12): error CS8977: Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + // static ref readonly int M2() => throw null; + Diagnostic(ErrorCode.ERR_CannotUseRefInUnmanagedCallersOnly, "ref readonly int").WithLocation(9, 12), + // (12,20): error CS8977: Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + // static void M3(ref int o) => throw null; + Diagnostic(ErrorCode.ERR_CannotUseRefInUnmanagedCallersOnly, "ref int o").WithLocation(12, 20), + // (15,20): error CS8977: Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + // static void M4(in int o) => throw null; + Diagnostic(ErrorCode.ERR_CannotUseRefInUnmanagedCallersOnly, "in int o").WithLocation(15, 20), + // (18,20): error CS8977: Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + // static void M5(out int o) => throw null; + Diagnostic(ErrorCode.ERR_CannotUseRefInUnmanagedCallersOnly, "out int o").WithLocation(18, 20) + ); + } + [Fact] public void UnmanagedCallersOnlyRequiresUnmanagedTypes_Valid() { @@ -9471,7 +9514,13 @@ static class CExt Diagnostic(ErrorCode.ERR_UnmanagedCallersOnlyMethodsCannotBeCalledDirectly, "ls").WithArguments("CExt.Deconstruct(S, out int, out int)").WithLocation(11, 32), // (12,18): error CS8901: 'CExt.Deconstruct(S, out int, out int)' is attributed with 'UnmanagedCallersOnly' and cannot be called directly. Obtain a function pointer to this method. // _ = s is (int _, int _); - Diagnostic(ErrorCode.ERR_UnmanagedCallersOnlyMethodsCannotBeCalledDirectly, "(int _, int _)").WithArguments("CExt.Deconstruct(S, out int, out int)").WithLocation(12, 18) + Diagnostic(ErrorCode.ERR_UnmanagedCallersOnlyMethodsCannotBeCalledDirectly, "(int _, int _)").WithArguments("CExt.Deconstruct(S, out int, out int)").WithLocation(12, 18), + // (18,46): error CS8977: Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + // public static void Deconstruct(this S s, out int i1, out int i2) => throw null; + Diagnostic(ErrorCode.ERR_CannotUseRefInUnmanagedCallersOnly, "out int i1").WithLocation(18, 46), + // (18,58): error CS8977: Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + // public static void Deconstruct(this S s, out int i1, out int i2) => throw null; + Diagnostic(ErrorCode.ERR_CannotUseRefInUnmanagedCallersOnly, "out int i2").WithLocation(18, 58) ); } @@ -9574,7 +9623,10 @@ static class CExt comp.VerifyDiagnostics( // (10,29): error CS8901: 'CExt.GetPinnableReference(S)' is attributed with 'UnmanagedCallersOnly' and cannot be called directly. Obtain a function pointer to this method. // fixed (int* i = s) - Diagnostic(ErrorCode.ERR_UnmanagedCallersOnlyMethodsCannotBeCalledDirectly, "s").WithArguments("CExt.GetPinnableReference(S)").WithLocation(10, 29) + Diagnostic(ErrorCode.ERR_UnmanagedCallersOnlyMethodsCannotBeCalledDirectly, "s").WithArguments("CExt.GetPinnableReference(S)").WithLocation(10, 29), + // (20,19): error CS8977: Cannot use 'ref', 'in', or 'out' in the signature of a method attributed with 'UnmanagedCallersOnly'. + // public static ref int GetPinnableReference(this S s) => throw null; + Diagnostic(ErrorCode.ERR_CannotUseRefInUnmanagedCallersOnly, "ref int").WithLocation(20, 19) ); } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenShortCircuitOperatorTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenShortCircuitOperatorTests.cs index 2b69bd5d8ef77..e2fedf4e12ac4 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenShortCircuitOperatorTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenShortCircuitOperatorTests.cs @@ -5491,15 +5491,15 @@ T M() var compilation = CreateCompilation(source); compilation.VerifyDiagnostics( - // (15,33): error CS0023: Operator '?' cannot be applied to operand of type 'T' - // Func a = () => c?.M(); - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "T").WithLocation(15, 33), - // (18,41): error CS0023: Operator '?' cannot be applied to operand of type 'T' - // static public object F2(C c) => c?.M(); - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "T").WithLocation(18, 41), - // (20,44): error CS0023: Operator '?' cannot be applied to operand of type 'T' - // static public object P1 => (new C())?.M(); - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "T").WithLocation(20, 44) + // (15,34): error CS8977: 'T' cannot be made nullable. + // Func a = () => c?.M(); + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".M()").WithArguments("T").WithLocation(15, 34), + // (18,42): error CS8977: 'T' cannot be made nullable. + // static public object F2(C c) => c?.M(); + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".M()").WithArguments("T").WithLocation(18, 42), + // (20,45): error CS8977: 'T' cannot be made nullable. + // static public object P1 => (new C())?.M(); + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".M()").WithArguments("T").WithLocation(20, 45) ); } @@ -5620,15 +5620,15 @@ static public void F1(C c) var compilation = CreateCompilation(source, options: TestOptions.DebugExe.WithAllowUnsafe(true)); compilation.VerifyDiagnostics( - // (16,40): error CS0023: Operator '?' cannot be applied to operand of type 'void*' - // Func a = o => c?.M(); - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "void*").WithLocation(16, 40), - // (19,38): error CS0023: Operator '?' cannot be applied to operand of type 'void*' - // static public object F2(C c) => c?.M(); - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "void*").WithLocation(19, 38), - // (21,41): error CS0023: Operator '?' cannot be applied to operand of type 'void*' - // static public object P1 => (new C())?.M(); - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "void*").WithLocation(21, 41) + // (16,41): error CS8977: 'void*' cannot be made nullable. + // Func a = o => c?.M(); + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".M()").WithArguments("void*").WithLocation(16, 41), + // (19,39): error CS8977: 'void*' cannot be made nullable. + // static public object F2(C c) => c?.M(); + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".M()").WithArguments("void*").WithLocation(19, 39), + // (21,42): error CS8977: 'void*' cannot be made nullable. + // static public object P1 => (new C())?.M(); + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".M()").WithArguments("void*").WithLocation(21, 42) ); } @@ -5757,9 +5757,9 @@ public T M() var compilation = CreateCompilation(source); compilation.VerifyDiagnostics( - // (15,17): error CS0023: Operator '?' cannot be applied to operand of type 'T' - // for (; x?.M();) - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "T").WithLocation(15, 17) + // (15,18): error CS8977: 'T' cannot be made nullable. + // for (; x?.M();) + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".M()").WithArguments("T").WithLocation(15, 18) ); } @@ -7515,5 +7515,37 @@ public MyObject2(MyObjectType obj) CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: expectedOutput); CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: expectedOutput); } + + [Fact] + [WorkItem(57629, "https://github.com/dotnet/roslyn/issues/57629")] + public void Issue57629() + { + var source = @" +namespace OperatorQuestionmarkProblem +{ + public class OuterClass + { + public class InnerClass + { + public TValue SomeInfo() { throw null; } + + public InnerClass Next { get; set; } + + void Test() + { + Next?.SomeInfo(); + _ = Next?.SomeInfo(); + } + } + } +} +"; + var compilation = CreateCompilation(source); + compilation.VerifyEmitDiagnostics( + // (15,26): error CS8977: 'TValue' cannot be made nullable. + // _ = Next?.SomeInfo(); + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".SomeInfo()").WithArguments("TValue").WithLocation(15, 26) + ); + } } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs index 9f1c730bfa387..6ca4c246a22ea 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs @@ -363,12 +363,10 @@ Slice 1 5"); verifier.VerifyIL("C.Main", @" { - // Code size 57 (0x39) - .maxstack 3 + // Code size 53 (0x35) + .maxstack 4 .locals init (int[] V_0, //array - S V_1, - int V_2, - int V_3) + S V_1) IL_0000: ldc.i4.2 IL_0001: newarr ""int"" IL_0006: stloc.0 @@ -380,26 +378,22 @@ .locals init (int[] V_0, //array IL_0010: newobj ""S..ctor(int[])"" IL_0015: stloc.1 IL_0016: ldloca.s V_1 - IL_0018: call ""int S.Length.get"" - IL_001d: ldc.i4.1 - IL_001e: stloc.2 - IL_001f: ldloc.2 - IL_0020: sub - IL_0021: stloc.3 - IL_0022: ldloca.s V_1 - IL_0024: ldloc.2 - IL_0025: ldloc.3 - IL_0026: call ""ref int S.Slice(int, int)"" - IL_002b: dup - IL_002c: ldind.i4 - IL_002d: ldc.i4.5 - IL_002e: add - IL_002f: stind.i4 - IL_0030: ldloc.0 - IL_0031: ldc.i4.1 - IL_0032: ldelem.i4 - IL_0033: call ""void System.Console.WriteLine(int)"" - IL_0038: ret + IL_0018: ldc.i4.1 + IL_0019: ldloca.s V_1 + IL_001b: call ""int S.Length.get"" + IL_0020: ldc.i4.1 + IL_0021: sub + IL_0022: call ""ref int S.Slice(int, int)"" + IL_0027: dup + IL_0028: ldind.i4 + IL_0029: ldc.i4.5 + IL_002a: add + IL_002b: stind.i4 + IL_002c: ldloc.0 + IL_002d: ldc.i4.1 + IL_002e: ldelem.i4 + IL_002f: call ""void System.Console.WriteLine(int)"" + IL_0034: ret }"); } @@ -783,52 +777,46 @@ public static void Main() abcd"); verifier.VerifyIL("C.Main", @" { - // Code size 86 (0x56) + // Code size 80 (0x50) .maxstack 4 - .locals init (int V_0, + .locals init (string V_0, System.ReadOnlySpan V_1, - System.ReadOnlySpan V_2, - int V_3) + int V_2, + System.ReadOnlySpan V_3) IL_0000: ldstr ""abcd"" IL_0005: dup - IL_0006: dup - IL_0007: callvirt ""int string.Length.get"" - IL_000c: ldc.i4.0 - IL_000d: sub - IL_000e: stloc.0 - IL_000f: ldc.i4.0 - IL_0010: ldloc.0 - IL_0011: callvirt ""string string.Substring(int, int)"" - IL_0016: call ""void System.Console.WriteLine(string)"" - IL_001b: call ""System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(string)"" - IL_0020: stloc.2 - IL_0021: ldloca.s V_2 - IL_0023: call ""int System.ReadOnlySpan.Length.get"" - IL_0028: ldc.i4.0 - IL_0029: sub - IL_002a: stloc.3 - IL_002b: ldloca.s V_2 - IL_002d: ldc.i4.0 - IL_002e: ldloc.3 - IL_002f: call ""System.ReadOnlySpan System.ReadOnlySpan.Slice(int, int)"" - IL_0034: stloc.1 - IL_0035: ldc.i4.0 - IL_0036: stloc.0 - IL_0037: br.s IL_004b - IL_0039: ldloca.s V_1 - IL_003b: ldloc.0 - IL_003c: call ""ref readonly char System.ReadOnlySpan.this[int].get"" - IL_0041: ldind.u2 - IL_0042: call ""void System.Console.Write(char)"" - IL_0047: ldloc.0 - IL_0048: ldc.i4.1 - IL_0049: add - IL_004a: stloc.0 - IL_004b: ldloc.0 - IL_004c: ldloca.s V_1 - IL_004e: call ""int System.ReadOnlySpan.Length.get"" - IL_0053: blt.s IL_0039 - IL_0055: ret + IL_0006: stloc.0 + IL_0007: ldloc.0 + IL_0008: ldc.i4.0 + IL_0009: ldloc.0 + IL_000a: callvirt ""int string.Length.get"" + IL_000f: callvirt ""string string.Substring(int, int)"" + IL_0014: call ""void System.Console.WriteLine(string)"" + IL_0019: call ""System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(string)"" + IL_001e: stloc.3 + IL_001f: ldloca.s V_3 + IL_0021: ldc.i4.0 + IL_0022: ldloca.s V_3 + IL_0024: call ""int System.ReadOnlySpan.Length.get"" + IL_0029: call ""System.ReadOnlySpan System.ReadOnlySpan.Slice(int, int)"" + IL_002e: stloc.1 + IL_002f: ldc.i4.0 + IL_0030: stloc.2 + IL_0031: br.s IL_0045 + IL_0033: ldloca.s V_1 + IL_0035: ldloc.2 + IL_0036: call ""ref readonly char System.ReadOnlySpan.this[int].get"" + IL_003b: ldind.u2 + IL_003c: call ""void System.Console.Write(char)"" + IL_0041: ldloc.2 + IL_0042: ldc.i4.1 + IL_0043: add + IL_0044: stloc.2 + IL_0045: ldloc.2 + IL_0046: ldloca.s V_1 + IL_0048: call ""int System.ReadOnlySpan.Length.get"" + IL_004d: blt.s IL_0033 + IL_004f: ret }"); var (model, elementAccesses) = GetModelAndAccesses(comp); @@ -1056,8 +1044,8 @@ static void Main() g"); verifier.VerifyIL(@"C.Main", @" { - // Code size 129 (0x81) - .maxstack 3 + // Code size 130 (0x82) + .maxstack 4 .locals init (System.ReadOnlySpan V_0, //s System.Index V_1, //index System.ReadOnlySpan& V_2, @@ -1093,29 +1081,29 @@ .locals init (System.ReadOnlySpan V_0, //s IL_0046: stloc.3 IL_0047: ldloca.s V_3 IL_0049: call ""int System.ReadOnlySpan.Length.get"" - IL_004e: dup - IL_004f: ldc.i4.2 - IL_0050: sub - IL_0051: stloc.s V_4 - IL_0053: ldloc.s V_4 - IL_0055: sub - IL_0056: stloc.s V_5 - IL_0058: ldloca.s V_3 + IL_004e: stloc.s V_4 + IL_0050: ldloc.s V_4 + IL_0052: ldc.i4.2 + IL_0053: sub + IL_0054: stloc.s V_5 + IL_0056: ldloca.s V_3 + IL_0058: ldloc.s V_5 IL_005a: ldloc.s V_4 IL_005c: ldloc.s V_5 - IL_005e: call ""System.ReadOnlySpan System.ReadOnlySpan.Slice(int, int)"" - IL_0063: stloc.0 - IL_0064: ldloca.s V_0 - IL_0066: ldc.i4.0 - IL_0067: call ""ref readonly char System.ReadOnlySpan.this[int].get"" - IL_006c: ldind.u2 - IL_006d: call ""void System.Console.WriteLine(char)"" - IL_0072: ldloca.s V_0 - IL_0074: ldc.i4.1 - IL_0075: call ""ref readonly char System.ReadOnlySpan.this[int].get"" - IL_007a: ldind.u2 - IL_007b: call ""void System.Console.WriteLine(char)"" - IL_0080: ret + IL_005e: sub + IL_005f: call ""System.ReadOnlySpan System.ReadOnlySpan.Slice(int, int)"" + IL_0064: stloc.0 + IL_0065: ldloca.s V_0 + IL_0067: ldc.i4.0 + IL_0068: call ""ref readonly char System.ReadOnlySpan.this[int].get"" + IL_006d: ldind.u2 + IL_006e: call ""void System.Console.WriteLine(char)"" + IL_0073: ldloca.s V_0 + IL_0075: ldc.i4.1 + IL_0076: call ""ref readonly char System.ReadOnlySpan.this[int].get"" + IL_007b: ldind.u2 + IL_007c: call ""void System.Console.WriteLine(char)"" + IL_0081: ret } "); } @@ -1144,8 +1132,8 @@ static void Main() 6"); verifier.VerifyIL("C.Main", @" { - // Code size 141 (0x8d) - .maxstack 3 + // Code size 142 (0x8e) + .maxstack 4 .locals init (System.Span V_0, //s System.Index V_1, //index System.Span& V_2, @@ -1185,29 +1173,29 @@ .locals init (System.Span V_0, //s IL_0052: stloc.3 IL_0053: ldloca.s V_3 IL_0055: call ""int System.Span.Length.get"" - IL_005a: dup - IL_005b: ldc.i4.2 - IL_005c: sub - IL_005d: stloc.s V_4 - IL_005f: ldloc.s V_4 - IL_0061: sub - IL_0062: stloc.s V_5 - IL_0064: ldloca.s V_3 + IL_005a: stloc.s V_4 + IL_005c: ldloc.s V_4 + IL_005e: ldc.i4.2 + IL_005f: sub + IL_0060: stloc.s V_5 + IL_0062: ldloca.s V_3 + IL_0064: ldloc.s V_5 IL_0066: ldloc.s V_4 IL_0068: ldloc.s V_5 - IL_006a: call ""System.Span System.Span.Slice(int, int)"" - IL_006f: stloc.0 - IL_0070: ldloca.s V_0 - IL_0072: ldc.i4.0 - IL_0073: call ""ref int System.Span.this[int].get"" - IL_0078: ldind.i4 - IL_0079: call ""void System.Console.WriteLine(int)"" - IL_007e: ldloca.s V_0 - IL_0080: ldc.i4.1 - IL_0081: call ""ref int System.Span.this[int].get"" - IL_0086: ldind.i4 - IL_0087: call ""void System.Console.WriteLine(int)"" - IL_008c: ret + IL_006a: sub + IL_006b: call ""System.Span System.Span.Slice(int, int)"" + IL_0070: stloc.0 + IL_0071: ldloca.s V_0 + IL_0073: ldc.i4.0 + IL_0074: call ""ref int System.Span.this[int].get"" + IL_0079: ldind.i4 + IL_007a: call ""void System.Console.WriteLine(int)"" + IL_007f: ldloca.s V_0 + IL_0081: ldc.i4.1 + IL_0082: call ""ref int System.Span.this[int].get"" + IL_0087: ldind.i4 + IL_0088: call ""void System.Console.WriteLine(int)"" + IL_008d: ret } "); } @@ -1320,72 +1308,67 @@ static void Main() 4"); verifier.VerifyIL("C.Main", @" { - // Code size 95 (0x5f) - .maxstack 3 + // Code size 87 (0x57) + .maxstack 4 .locals init (C V_0, //c int[] V_1, int V_2, - int V_3, - int V_4) + C V_3) IL_0000: newobj ""C..ctor()"" IL_0005: stloc.0 IL_0006: ldloc.0 - IL_0007: dup - IL_0008: callvirt ""int C." + propertyName + @".get"" - IL_000d: ldc.i4.1 - IL_000e: stloc.3 - IL_000f: ldloc.3 - IL_0010: sub - IL_0011: stloc.s V_4 - IL_0013: ldloc.3 - IL_0014: ldloc.s V_4 - IL_0016: callvirt ""int[] C.Slice(int, int)"" - IL_001b: stloc.1 - IL_001c: ldc.i4.0 - IL_001d: stloc.2 - IL_001e: br.s IL_002c - IL_0020: ldloc.1 - IL_0021: ldloc.2 - IL_0022: ldelem.i4 - IL_0023: call ""void System.Console.WriteLine(int)"" + IL_0007: stloc.3 + IL_0008: ldloc.3 + IL_0009: ldc.i4.1 + IL_000a: ldloc.3 + IL_000b: callvirt ""int C." + propertyName + @".get"" + IL_0010: ldc.i4.1 + IL_0011: sub + IL_0012: callvirt ""int[] C.Slice(int, int)"" + IL_0017: stloc.1 + IL_0018: ldc.i4.0 + IL_0019: stloc.2 + IL_001a: br.s IL_0028 + IL_001c: ldloc.1 + IL_001d: ldloc.2 + IL_001e: ldelem.i4 + IL_001f: call ""void System.Console.WriteLine(int)"" + IL_0024: ldloc.2 + IL_0025: ldc.i4.1 + IL_0026: add + IL_0027: stloc.2 IL_0028: ldloc.2 - IL_0029: ldc.i4.1 - IL_002a: add - IL_002b: stloc.2 - IL_002c: ldloc.2 - IL_002d: ldloc.1 - IL_002e: ldlen - IL_002f: conv.i4 - IL_0030: blt.s IL_0020 - IL_0032: ldloc.0 - IL_0033: dup - IL_0034: callvirt ""int C." + propertyName + @".get"" - IL_0039: ldc.i4.2 - IL_003a: sub - IL_003b: ldc.i4.0 - IL_003c: sub - IL_003d: stloc.s V_4 - IL_003f: ldc.i4.0 - IL_0040: ldloc.s V_4 - IL_0042: callvirt ""int[] C.Slice(int, int)"" - IL_0047: stloc.1 - IL_0048: ldc.i4.0 - IL_0049: stloc.2 - IL_004a: br.s IL_0058 - IL_004c: ldloc.1 - IL_004d: ldloc.2 - IL_004e: ldelem.i4 - IL_004f: call ""void System.Console.WriteLine(int)"" - IL_0054: ldloc.2 - IL_0055: ldc.i4.1 - IL_0056: add - IL_0057: stloc.2 - IL_0058: ldloc.2 - IL_0059: ldloc.1 - IL_005a: ldlen - IL_005b: conv.i4 - IL_005c: blt.s IL_004c - IL_005e: ret + IL_0029: ldloc.1 + IL_002a: ldlen + IL_002b: conv.i4 + IL_002c: blt.s IL_001c + IL_002e: ldloc.0 + IL_002f: stloc.3 + IL_0030: ldloc.3 + IL_0031: ldc.i4.0 + IL_0032: ldloc.3 + IL_0033: callvirt ""int C." + propertyName + @".get"" + IL_0038: ldc.i4.2 + IL_0039: sub + IL_003a: callvirt ""int[] C.Slice(int, int)"" + IL_003f: stloc.1 + IL_0040: ldc.i4.0 + IL_0041: stloc.2 + IL_0042: br.s IL_0050 + IL_0044: ldloc.1 + IL_0045: ldloc.2 + IL_0046: ldelem.i4 + IL_0047: call ""void System.Console.WriteLine(int)"" + IL_004c: ldloc.2 + IL_004d: ldc.i4.1 + IL_004e: add + IL_004f: stloc.2 + IL_0050: ldloc.2 + IL_0051: ldloc.1 + IL_0052: ldlen + IL_0053: conv.i4 + IL_0054: blt.s IL_0044 + IL_0056: ret } "); } @@ -1654,22 +1637,14 @@ public static void Main() }", expectedOutput: "bc"); verifier.VerifyIL("C.Main", @" { - // Code size 24 (0x18) + // Code size 18 (0x12) .maxstack 3 - .locals init (int V_0, - int V_1) IL_0000: ldstr ""abcdef"" IL_0005: ldc.i4.1 - IL_0006: stloc.0 - IL_0007: ldc.i4.3 - IL_0008: ldloc.0 - IL_0009: sub - IL_000a: stloc.1 - IL_000b: ldloc.0 - IL_000c: ldloc.1 - IL_000d: callvirt ""string string.Substring(int, int)"" - IL_0012: call ""void System.Console.WriteLine(string)"" - IL_0017: ret + IL_0006: ldc.i4.2 + IL_0007: callvirt ""string string.Substring(int, int)"" + IL_000c: call ""void System.Console.WriteLine(string)"" + IL_0011: ret }"); } @@ -3146,15 +3121,135 @@ public static void Main() { int i2 = Get()[^GetIdx()]; Console.WriteLine(i2); + Console.WriteLine(); + i2 = Get()[GetIdx()]; + Console.WriteLine(i2); + Console.WriteLine(); + i2 = Get()[(Index)GetIdx()]; + Console.WriteLine(i2); } } ", TestOptions.ReleaseExe); CompileAndVerify(comp, expectedOutput: @"Get -Length GetIdx +Length Indexer get 3 + +Get +GetIdx +Indexer get +2 + +Get +GetIdx +Indexer get +2 +"); + } + + [Fact] + [WorkItem(57349, "https://github.com/dotnet/roslyn/issues/57349")] + public void OrderOfEvaluation_03() + { + var comp = CreateCompilationWithIndexAndRangeAndSpan(@" +using System; + +class CollectionX +{ + + public int Length + { + get + { + Console.Write("" Length""); + return 4; + } + } + + public int Slice(int x, int y) + { + Console.Write("" Slice {0}, {1}"", x, y); + return 111; + } +} + +static class SideEffect +{ + static CollectionX Get() + { + Console.WriteLine(); + Console.Write(""Get""); + return new CollectionX(); + } + static Range GetIdx() + { + Console.Write("" GetIdx""); + return 1..3; + } + static int GetIdx1(int x) + { + Console.Write("" GetIdx1""); + return x; + } + static int GetIdx2(int x) + { + Console.Write("" GetIdx2""); + return x; + } + static Index GetIdx3(Index x) + { + Console.Write("" GetIdx3""); + return x; + } + static Index GetIdx4(Index x) + { + Console.Write("" GetIdx4""); + return x; + } + + public static void Main() + { + _ = Get()[GetIdx()]; + _ = Get()[GetIdx1(1)..GetIdx2(3)]; + _ = Get()[^GetIdx1(3)..GetIdx2(3)]; + _ = Get()[GetIdx1(1)..^GetIdx2(1)]; + _ = Get()[^GetIdx1(3)..^GetIdx2(1)]; + _ = Get()[GetIdx3(1)..GetIdx4(3)]; + _ = Get()[^GetIdx1(3)..]; + _ = Get()[GetIdx1(1)..]; + _ = Get()[..GetIdx2(3)]; + _ = Get()[..^GetIdx2(1)]; + _ = Get()[..]; + _ = Get()[GetIdx3(1)..]; + _ = Get()[..GetIdx4(3)]; + _ = Get()[^GetIdx1(3)..GetIdx4(3)]; + _ = Get()[GetIdx1(1)..GetIdx4(3)]; + _ = Get()[GetIdx3(1)..GetIdx2(3)]; + _ = Get()[GetIdx3(1)..^GetIdx2(1)]; + } +} +", TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: +@" +Get GetIdx Length Slice 1, 2 +Get GetIdx1 GetIdx2 Slice 1, 2 +Get GetIdx1 GetIdx2 Length Slice 1, 2 +Get GetIdx1 GetIdx2 Length Slice 1, 2 +Get GetIdx1 GetIdx2 Length Slice 1, 2 +Get GetIdx3 GetIdx4 Length Slice 1, 2 +Get GetIdx1 Length Slice 1, 3 +Get GetIdx1 Length Slice 1, 3 +Get GetIdx2 Slice 0, 3 +Get GetIdx2 Length Slice 0, 3 +Get Length Slice 0, 4 +Get GetIdx3 Length Slice 1, 3 +Get GetIdx4 Length Slice 0, 3 +Get GetIdx1 GetIdx4 Length Slice 1, 2 +Get GetIdx1 GetIdx4 Length Slice 1, 2 +Get GetIdx3 GetIdx2 Length Slice 1, 2 +Get GetIdx3 GetIdx2 Length Slice 1, 2 "); } } diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs index aab4d02456396..d80e69b386486 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs @@ -7,6 +7,7 @@ using System; using System.IO; using System.Linq; +using System.Reflection; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; @@ -12789,5 +12790,41 @@ public void SingleFileWithNoMethodBody() "); } + + [Fact] + public void CompilerInfo_WindowsPdb() + { + var compilerAssembly = typeof(Compilation).Assembly; + var fileVersion = Version.Parse(compilerAssembly.GetCustomAttribute().Version); + var versionString = compilerAssembly.GetCustomAttribute().InformationalVersion; + + var source = "class C { void F() {} }"; + + var c = CreateCompilation( + new[] { Parse(source, "a.cs") }, + options: TestOptions.DebugDll); + + c.VerifyPdb($@" + + + + + + + + + + + + + + + + + + + +", options: PdbValidationOptions.IncludeModuleDebugInfo, format: DebugInformationFormat.Pdb); + } } } diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/FunctionPointerOperations.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/FunctionPointerOperations.cs index 067b8d11b3b5d..f31377e822c22 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/FunctionPointerOperations.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/FunctionPointerOperations.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Test.Utilities; using Xunit; @@ -187,6 +188,67 @@ static void M2() VerifyOperationTreeAndDiagnosticsForTest(comp, expectedOperationTree, expectedDiagnostics); } + [Fact] + public void FunctionPointerInvocationSignatureTest() + { + var comp = CreateFunctionPointerCompilation(@" +unsafe class C +{ + public string Prop { get; } + void M(delegate* ptr) + { + /**/ptr(Prop)/**/; + } +}"); + var (actualOperation, syntaxNode) = GetOperationAndSyntaxForTest(comp); + + var fktPointerOp = (IFunctionPointerInvocationOperation)actualOperation; + var signature = fktPointerOp.GetFunctionPointerSignature(); + + Assert.NotNull(syntaxNode); + Assert.Equal(1, signature.Parameters.Length); + Assert.Equal(SpecialType.System_String, signature.Parameters[0].Type.SpecialType); + Assert.Equal(SpecialType.System_Void, signature.ReturnType.SpecialType); + } + + [Fact] + public void FunctionPointerUnsafe() + { + var comp = CreateFunctionPointerCompilation(@" +using System; +static unsafe class C +{ + static int Getter(int i) => i; + static void Print(delegate** p) + { + for (int i = 0; i < 3; i++) + Console.Write(/**/p[i](i)/**/); + } + + static void Main() + { + delegate** p = stackalloc delegate*[] { &Getter, &Getter, &Getter }; + Print(p); + } +} +"); + var expectedOperationTree = @" +IFunctionPointerInvocationOperation (OperationKind.FunctionPointerInvocation, Type: System.Int32) (Syntax: 'p[i](i)') + Target: + IOperation: (OperationKind.None, Type: delegate*) (Syntax: 'p[i]') + Children(2): + IParameterReferenceOperation: p (OperationKind.ParameterReference, Type: delegate**) (Syntax: 'p') + ILocalReferenceOperation: i (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: ) (OperationKind.Argument, Type: null) (Syntax: 'i') + ILocalReferenceOperation: i (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + "; + + VerifyOperationTreeAndDiagnosticsForTest(comp, expectedOperationTree, expectedDiagnostics: new DiagnosticDescription[0]); + } + [Fact] public void FunctionPointerInvocation() { @@ -201,12 +263,16 @@ void M(delegate* ptr) }"); var expectedOperationTree = @" -IOperation: (OperationKind.None, Type: System.Void) (Syntax: 'ptr(Prop)') - Children(2): - IParameterReferenceOperation: ptr (OperationKind.ParameterReference, Type: delegate*) (Syntax: 'ptr') - IPropertyReferenceOperation: System.String C.Prop { get; } (OperationKind.PropertyReference, Type: System.String) (Syntax: 'Prop') - Instance Receiver: - IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'Prop') +IFunctionPointerInvocationOperation (OperationKind.FunctionPointerInvocation, Type: System.Void) (Syntax: 'ptr(Prop)') + Target: + IParameterReferenceOperation: ptr (OperationKind.ParameterReference, Type: delegate*) (Syntax: 'ptr') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: ) (OperationKind.Argument, Type: null) (Syntax: 'Prop') + IPropertyReferenceOperation: System.String C.Prop { get; } (OperationKind.PropertyReference, Type: System.String) (Syntax: 'Prop') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'Prop') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) "; VerifyOperationTreeAndDiagnosticsForTest(comp, expectedOperationTree, expectedDiagnostics: new DiagnosticDescription[0]); @@ -326,34 +392,42 @@ void M(delegate* ptr) IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null, IsInvalid) (Syntax: 'string s = ptr(Prop)') Declarators: IVariableDeclaratorOperation (Symbol: System.String s) (OperationKind.VariableDeclarator, Type: null, IsInvalid) (Syntax: 's = ptr(Prop)') - Initializer: + Initializer: IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null, IsInvalid) (Syntax: '= ptr(Prop)') IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, IsInvalid, IsImplicit) (Syntax: 'ptr(Prop)') Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - IOperation: (OperationKind.None, Type: System.Int32, IsInvalid) (Syntax: 'ptr(Prop)') - Children(2): - IParameterReferenceOperation: ptr (OperationKind.ParameterReference, Type: delegate*, IsInvalid) (Syntax: 'ptr') - IPropertyReferenceOperation: System.String C.Prop { get; } (OperationKind.PropertyReference, Type: System.String, IsInvalid) (Syntax: 'Prop') - Instance Receiver: - IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsInvalid, IsImplicit) (Syntax: 'Prop') - Initializer: + Operand: + IFunctionPointerInvocationOperation (OperationKind.FunctionPointerInvocation, Type: System.Int32, IsInvalid) (Syntax: 'ptr(Prop)') + Target: + IParameterReferenceOperation: ptr (OperationKind.ParameterReference, Type: delegate*, IsInvalid) (Syntax: 'ptr') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: ) (OperationKind.Argument, Type: null, IsInvalid) (Syntax: 'Prop') + IPropertyReferenceOperation: System.String C.Prop { get; } (OperationKind.PropertyReference, Type: System.String, IsInvalid) (Syntax: 'Prop') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsInvalid, IsImplicit) (Syntax: 'Prop') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: null IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsInvalid) (Syntax: 's = ptr(Prop);') - Expression: + Expression: ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.String, IsInvalid) (Syntax: 's = ptr(Prop)') - Left: + Left: ILocalReferenceOperation: s (OperationKind.LocalReference, Type: System.String) (Syntax: 's') - Right: + Right: IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, IsInvalid, IsImplicit) (Syntax: 'ptr(Prop)') Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - IOperation: (OperationKind.None, Type: System.Int32, IsInvalid) (Syntax: 'ptr(Prop)') - Children(2): - IParameterReferenceOperation: ptr (OperationKind.ParameterReference, Type: delegate*, IsInvalid) (Syntax: 'ptr') - IPropertyReferenceOperation: System.String C.Prop { get; } (OperationKind.PropertyReference, Type: System.String, IsInvalid) (Syntax: 'Prop') - Instance Receiver: - IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsInvalid, IsImplicit) (Syntax: 'Prop') + Operand: + IFunctionPointerInvocationOperation (OperationKind.FunctionPointerInvocation, Type: System.Int32, IsInvalid) (Syntax: 'ptr(Prop)') + Target: + IParameterReferenceOperation: ptr (OperationKind.ParameterReference, Type: delegate*, IsInvalid) (Syntax: 'ptr') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: ) (OperationKind.Argument, Type: null, IsInvalid) (Syntax: 'Prop') + IPropertyReferenceOperation: System.String C.Prop { get; } (OperationKind.PropertyReference, Type: System.String, IsInvalid) (Syntax: 'Prop') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsInvalid, IsImplicit) (Syntax: 'Prop') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) "; var expectedDiagnostics = new DiagnosticDescription[] { @@ -463,6 +537,7 @@ static void Test(delegate* ptr, bool b, string s1, string s2) Statements (0) Next (Regular) Block[B1] Entering: {R1} + .locals {R1} { CaptureIds: [0] [1] @@ -470,37 +545,47 @@ static void Test(delegate* ptr, bool b, string s1, string s2) Predecessors: [B0] Statements (1) IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'ptr') - Value: + Value: IParameterReferenceOperation: ptr (OperationKind.ParameterReference, Type: delegate*) (Syntax: 'ptr') + Jump if False (Regular) to Block[B3] IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b') + Next (Regular) Block[B2] Block[B2] - Block Predecessors: [B1] Statements (1) IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 's1') - Value: + Value: IParameterReferenceOperation: s1 (OperationKind.ParameterReference, Type: System.String) (Syntax: 's1') + Next (Regular) Block[B4] Block[B3] - Block Predecessors: [B1] Statements (1) IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 's2') - Value: + Value: IParameterReferenceOperation: s2 (OperationKind.ParameterReference, Type: System.String) (Syntax: 's2') + Next (Regular) Block[B4] Block[B4] - Block Predecessors: [B2] [B3] Statements (1) IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'ptr(b ? s1 : s2);') - Expression: - IOperation: (OperationKind.None, Type: System.Void) (Syntax: 'ptr(b ? s1 : s2)') - Children(2): - IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: delegate*, IsImplicit) (Syntax: 'ptr') - IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'b ? s1 : s2') + Expression: + IFunctionPointerInvocationOperation (OperationKind.FunctionPointerInvocation, Type: System.Void) (Syntax: 'ptr(b ? s1 : s2)') + Target: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: delegate*, IsImplicit) (Syntax: 'ptr') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: ) (OperationKind.Argument, Type: null) (Syntax: 'b ? s1 : s2') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'b ? s1 : s2') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B5] Leaving: {R1} } + Block[B5] - Exit Predecessors: [B4] Statements (0) diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IAddressOfOperation.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IAddressOfOperation.cs index 0521f233c737c..1202f89f40f70 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IAddressOfOperation.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IAddressOfOperation.cs @@ -125,7 +125,7 @@ unsafe void M(bool x, S2* p1, S2* p2, int* p3) Reference: IFieldReferenceOperation: System.Int32 S2.i (OperationKind.FieldReference, Type: System.Int32) (Syntax: '(x ? p1 : p2)->i') Instance Receiver: - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '(x ? p1 : p2)') + IOperation: (OperationKind.None, Type: S2, IsImplicit) (Syntax: '(x ? p1 : p2)') Children(1): IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: S2*, IsImplicit) (Syntax: 'x ? p1 : p2') diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IArgument.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IArgument.cs index 5e54b64042ffd..8d675f4aab51e 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IArgument.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IArgument.cs @@ -3544,7 +3544,7 @@ public void DirectlyBindArgument_Attribute() [assembly: /**/System.CLSCompliant(isCompliant: true)/**/] "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null) (Syntax: 'System.CLSC ... iant: true)') +IOperation: (OperationKind.None, Type: System.CLSCompliantAttribute) (Syntax: 'System.CLSC ... iant: true)') Children(1): ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True) (Syntax: 'true') "; diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IDynamicIndexerAccessExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IDynamicIndexerAccessExpression.cs index 177a1d26fd053..ed0381a8b77bc 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IDynamicIndexerAccessExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IDynamicIndexerAccessExpression.cs @@ -460,7 +460,7 @@ void M(dynamic d, dynamic p) Expression: IInvalidOperation (OperationKind.Invalid, Type: C, IsInvalid, IsImplicit) (Syntax: 'C') Children(1): - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'C') + IOperation: (OperationKind.None, Type: C, IsInvalid) (Syntax: 'C') Arguments(1): IParameterReferenceOperation: d (OperationKind.ParameterReference, Type: dynamic) (Syntax: 'd') ArgumentNames(0) diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFieldReferenceExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFieldReferenceExpression.cs index 49dd0f3383748..e2ba4e27fec38 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFieldReferenceExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFieldReferenceExpression.cs @@ -34,7 +34,7 @@ void M() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null) (Syntax: 'Conditional(field)') +IOperation: (OperationKind.None, Type: System.Diagnostics.ConditionalAttribute) (Syntax: 'Conditional(field)') Children(1): IFieldReferenceOperation: System.String C.field (Static) (OperationKind.FieldReference, Type: System.String, Constant: ""field"") (Syntax: 'field') Instance Receiver: @@ -275,7 +275,7 @@ void M() string expectedOperationTree = @" ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 's.field[3] = 1') Left: - IOperation: (OperationKind.None, Type: null) (Syntax: 's.field[3]') + IOperation: (OperationKind.None, Type: System.Int32) (Syntax: 's.field[3]') Children(2): IFieldReferenceOperation: System.Int32* S1.field (OperationKind.FieldReference, Type: System.Int32*) (Syntax: 's.field') Instance Receiver: @@ -313,7 +313,7 @@ void M() string expectedOperationTree = @" ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 's.field[3] = 1') Left: - IOperation: (OperationKind.None, Type: null) (Syntax: 's.field[3]') + IOperation: (OperationKind.None, Type: System.Int32) (Syntax: 's.field[3]') Children(2): IFieldReferenceOperation: System.Int32* S1.field (OperationKind.FieldReference, Type: System.Int32*) (Syntax: 's.field') Instance Receiver: diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFixedStatement.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFixedStatement.cs index d354529d6b3e2..373410386ff3f 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFixedStatement.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFixedStatement.cs @@ -46,7 +46,7 @@ void M1() IVariableDeclaratorOperation (Symbol: System.Int32* p) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'p = &i') Initializer: IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= &i') - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '&i') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: '&i') Children(1): IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*) (Syntax: '&i') Reference: @@ -71,7 +71,7 @@ void M1() ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""P is "", IsImplicit) (Syntax: 'P is ') IInterpolationOperation (OperationKind.Interpolation, Type: null) (Syntax: '{*p}') Expression: - IOperation: (OperationKind.None, Type: null) (Syntax: '*p') + IOperation: (OperationKind.None, Type: System.Int32) (Syntax: '*p') Children(1): ILocalReferenceOperation: p (OperationKind.LocalReference, Type: System.Int32*) (Syntax: 'p') Alignment: @@ -123,7 +123,7 @@ void M1() IVariableDeclaratorOperation (Symbol: System.Int32* p1) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'p1 = &i1') Initializer: IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= &i1') - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '&i1') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: '&i1') Children(1): IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*) (Syntax: '&i1') Reference: @@ -133,7 +133,7 @@ void M1() IVariableDeclaratorOperation (Symbol: System.Int32* p2) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'p2 = &i2') Initializer: IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= &i2') - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '&i2') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: '&i2') Children(1): IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*) (Syntax: '&i2') Reference: @@ -152,11 +152,11 @@ void M1() Right: IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.Int32) (Syntax: '*p1 + *p2') Left: - IOperation: (OperationKind.None, Type: null) (Syntax: '*p1') + IOperation: (OperationKind.None, Type: System.Int32) (Syntax: '*p1') Children(1): ILocalReferenceOperation: p1 (OperationKind.LocalReference, Type: System.Int32*) (Syntax: 'p1') Right: - IOperation: (OperationKind.None, Type: null) (Syntax: '*p2') + IOperation: (OperationKind.None, Type: System.Int32) (Syntax: '*p2') Children(1): ILocalReferenceOperation: p2 (OperationKind.LocalReference, Type: System.Int32*) (Syntax: 'p2') "; @@ -204,7 +204,7 @@ void M1() IVariableDeclaratorOperation (Symbol: System.Int32* p1) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'p1 = &i1') Initializer: IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= &i1') - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '&i1') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: '&i1') Children(1): IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*) (Syntax: '&i1') Reference: @@ -223,7 +223,7 @@ void M1() IVariableDeclaratorOperation (Symbol: System.Int32* p2) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'p2 = &i2') Initializer: IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= &i2') - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '&i2') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: '&i2') Children(1): IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*) (Syntax: '&i2') Reference: @@ -242,11 +242,11 @@ void M1() Right: IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.Int32) (Syntax: '*p1 + *p2') Left: - IOperation: (OperationKind.None, Type: null) (Syntax: '*p1') + IOperation: (OperationKind.None, Type: System.Int32) (Syntax: '*p1') Children(1): ILocalReferenceOperation: p1 (OperationKind.LocalReference, Type: System.Int32*) (Syntax: 'p1') Right: - IOperation: (OperationKind.None, Type: null) (Syntax: '*p2') + IOperation: (OperationKind.None, Type: System.Int32) (Syntax: '*p2') Children(1): ILocalReferenceOperation: p2 (OperationKind.LocalReference, Type: System.Int32*) (Syntax: 'p2') "; @@ -301,7 +301,7 @@ void M1() Left: ILocalReferenceOperation: i3 (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i3') Right: - IOperation: (OperationKind.None, Type: null) (Syntax: '*p1') + IOperation: (OperationKind.None, Type: System.Int32) (Syntax: '*p1') Children(1): ILocalReferenceOperation: p1 (OperationKind.LocalReference, Type: System.Int32*) (Syntax: 'p1') "; @@ -349,7 +349,7 @@ void M1() IVariableDeclaratorOperation (Symbol: System.Int32* p1) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'p1 = &i1') Initializer: IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= &i1') - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '&i1') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: '&i1') Children(1): IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*) (Syntax: '&i1') Reference: @@ -426,7 +426,7 @@ unsafe void M(bool b) Left: ILocalReferenceOperation: p (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32*, IsImplicit) (Syntax: 'p = &i') Right: - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '&i') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: '&i') Children(1): IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*) (Syntax: '&i') Reference: @@ -513,7 +513,7 @@ unsafe void M(bool b) Left: ILocalReferenceOperation: p (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32*, IsImplicit) (Syntax: 'p = &i') Right: - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '&i') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: '&i') Children(1): IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*) (Syntax: '&i') Reference: @@ -676,7 +676,7 @@ unsafe void M(bool b) ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""P is "", IsImplicit) (Syntax: 'P is ') IInterpolationOperation (OperationKind.Interpolation, Type: null) (Syntax: '{*p}') Expression: - IOperation: (OperationKind.None, Type: null) (Syntax: '*p') + IOperation: (OperationKind.None, Type: System.Int32) (Syntax: '*p') Children(1): ILocalReferenceOperation: p (OperationKind.LocalReference, Type: System.Int32*) (Syntax: 'p') Alignment: @@ -734,7 +734,7 @@ void M(object x) Left: ILocalReferenceOperation: p (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32*, IsImplicit) (Syntax: 'p = &i') Right: - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '&i') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: '&i') Children(1): IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*) (Syntax: '&i') Reference: @@ -824,7 +824,7 @@ void M(bool x, P input) Left: ILocalReferenceOperation: p1 (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32*, IsImplicit) (Syntax: 'p1 = &i') Right: - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '&i') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: '&i') Children(1): IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*) (Syntax: '&i') Reference: @@ -881,7 +881,7 @@ void M(bool x, P input) Left: ILocalReferenceOperation: p2 (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32*, IsImplicit) (Syntax: 'p2 = &(input ?? this).j') Right: - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '&(input ?? this).j') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: '&(input ?? this).j') Children(1): IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*) (Syntax: '&(input ?? this).j') Reference: diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IForEachLoopStatement.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IForEachLoopStatement.cs index b5131145b7ce3..4e1ca16a6c64e 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IForEachLoopStatement.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IForEachLoopStatement.cs @@ -1608,7 +1608,7 @@ static class Extensions Expression: IInvalidOperation (OperationKind.Invalid, Type: System.Void) (Syntax: 'System.Cons ... Line(value)') Children(2): - IOperation: (OperationKind.None, Type: null) (Syntax: 'System.Console') + IOperation: (OperationKind.None, Type: System.Console) (Syntax: 'System.Console') ILocalReferenceOperation: value (OperationKind.LocalReference, Type: var) (Syntax: 'value') NextVariables(0)"; VerifyOperationTreeForTest(source, expectedOperationTree, parseOptions: TestOptions.Regular9); @@ -1822,7 +1822,7 @@ static class Extensions Expression: IInvalidOperation (OperationKind.Invalid, Type: System.Void) (Syntax: 'System.Cons ... Line(value)') Children(2): - IOperation: (OperationKind.None, Type: null) (Syntax: 'System.Console') + IOperation: (OperationKind.None, Type: System.Console) (Syntax: 'System.Console') ILocalReferenceOperation: value (OperationKind.LocalReference, Type: var) (Syntax: 'value') NextVariables(0)"; var comp = CreateCompilationWithTasksExtensions(new[] { source, s_IAsyncEnumerable }, parseOptions: TestOptions.Regular9); @@ -4563,7 +4563,7 @@ static class Extensions Expression: IInvalidOperation (OperationKind.Invalid, Type: System.Void) (Syntax: 'System.Cons ... Line(value)') Children(2): - IOperation: (OperationKind.None, Type: null) (Syntax: 'System.Console') + IOperation: (OperationKind.None, Type: System.Console) (Syntax: 'System.Console') ILocalReferenceOperation: value (OperationKind.LocalReference, Type: var) (Syntax: 'value') Next (Regular) Block[B2] Leaving: {R1} @@ -5089,7 +5089,7 @@ static class Extensions Expression: IInvalidOperation (OperationKind.Invalid, Type: System.Void) (Syntax: 'System.Cons ... Line(value)') Children(2): - IOperation: (OperationKind.None, Type: null) (Syntax: 'System.Console') + IOperation: (OperationKind.None, Type: System.Console) (Syntax: 'System.Console') ILocalReferenceOperation: value (OperationKind.LocalReference, Type: var) (Syntax: 'value') Next (Regular) Block[B2] Leaving: {R1} diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFromEndIndexOperation_IRangeOperation.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFromEndIndexOperation_IRangeOperation.cs index c2e97de085956..bce08b4f9f464 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFromEndIndexOperation_IRangeOperation.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFromEndIndexOperation_IRangeOperation.cs @@ -39,7 +39,7 @@ public void M() Left: IDiscardOperation (Symbol: System.Int32 _) (OperationKind.Discard, Type: System.Int32) (Syntax: '_') Right: - IOperation: (OperationKind.None, Type: null) (Syntax: 'this[^0]') + IOperation: (OperationKind.None, Type: System.Int32) (Syntax: 'this[^0]') Children(2): IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') IUnaryOperation (UnaryOperatorKind.Hat) (OperationKind.Unary, Type: System.Index) (Syntax: '^0') @@ -51,7 +51,7 @@ public void M() Left: IDiscardOperation (Symbol: System.Int32 _) (OperationKind.Discard, Type: System.Int32) (Syntax: '_') Right: - IOperation: (OperationKind.None, Type: null) (Syntax: 'this[0..]') + IOperation: (OperationKind.None, Type: System.Int32) (Syntax: 'this[0..]') Children(2): IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') IRangeOperation (OperationKind.Range, Type: System.Range) (Syntax: '0..') @@ -79,7 +79,7 @@ public void M() Left: IDiscardOperation (Symbol: System.Int32 _) (OperationKind.Discard, Type: System.Int32) (Syntax: '_') Right: - IOperation: (OperationKind.None, Type: null) (Syntax: 'this[^0]') + IOperation: (OperationKind.None, Type: System.Int32) (Syntax: 'this[^0]') Children(2): IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') IUnaryOperation (UnaryOperatorKind.Hat) (OperationKind.Unary, Type: System.Index) (Syntax: '^0') @@ -91,7 +91,7 @@ public void M() Left: IDiscardOperation (Symbol: System.Int32 _) (OperationKind.Discard, Type: System.Int32) (Syntax: '_') Right: - IOperation: (OperationKind.None, Type: null) (Syntax: 'this[0..]') + IOperation: (OperationKind.None, Type: System.Int32) (Syntax: 'this[0..]') Children(2): IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') IRangeOperation (OperationKind.Range, Type: System.Range) (Syntax: '0..') diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IInterpolatedStringOperation.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IInterpolatedStringOperation.cs index feb1d20c304d9..57188b980ac25 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IInterpolatedStringOperation.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IInterpolatedStringOperation.cs @@ -447,7 +447,7 @@ public void M(int x) Expression: IInvalidOperation (OperationKind.Invalid, Type: Class, IsInvalid, IsImplicit) (Syntax: 'Class') Children(1): - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'Class') + IOperation: (OperationKind.None, Type: Class, IsInvalid) (Syntax: 'Class') Alignment: null FormatString: @@ -2270,382 +2270,2514 @@ public void InterpolatedStringHandlerConversionFlow_01() public class C { - public void M(bool b, [InterpolatedStringHandlerArgument("""", ""b"")]CustomHandler c) {} - - public void M1(C c) + public void M1(C c, CustomHandler custom) /**/{ - c.M(true, $""literal{c,1:format}""); + custom = $""literal{c,1:format}""; }/**/ } - -public partial struct CustomHandler -{ - public CustomHandler(int literalLength, int formattedCount, C c, bool b, out bool success) : this() - { - success = true; - } -} "; + string handlerType = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false, includeTrailingOutConstructorParameter: false); + var expectedDiagnostics = DiagnosticDescription.None; - // Proper CFG support is tracked by https://github.com/dotnet/roslyn/issues/54718 string expectedFlowGraph = @" Block[B0] - Entry Statements (0) Next (Regular) Block[B1] -Block[B1] - Block - Predecessors: [B0] - Statements (1) - IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'c.M(true, $ ... :format}"");') - Expression: - IInvocationOperation ( void C.M(System.Boolean b, CustomHandler c)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'c.M(true, $ ... 1:format}"")') + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (5) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'custom') + Value: + IParameterReferenceOperation: custom (OperationKind.ParameterReference, Type: CustomHandler) (Syntax: 'custom') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') Instance Receiver: - IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') - Arguments(2): - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null) (Syntax: 'true') - ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True) (Syntax: 'true') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null) (Syntax: '$""literal{c,1:format}""') - IOperation: (OperationKind.None, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') - Children(2): - IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, C c, System.Boolean b, out System.Boolean success)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') - Arguments(5): - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') - ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{c,1:format}""') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') - ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'true') - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: 'true') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: success) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Initializer: - null - IInterpolatedStringOperation (OperationKind.InterpolatedString, Type: System.String) (Syntax: '$""literal{c,1:format}""') - Parts(2): - IInterpolatedStringAppendOperation (OperationKind.InterpolatedStringAppendLiteral, Type: null, IsImplicit) (Syntax: 'literal') - AppendCall: - IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') - Instance Receiver: - IInstanceReferenceOperation (ReferenceKind: InterpolatedStringHandler) (OperationKind.InstanceReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') - Arguments(1): - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') - ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IInterpolatedStringAppendOperation (OperationKind.InterpolatedStringAppendFormatted, Type: null, IsImplicit) (Syntax: '{c,1:format}') - AppendCall: - IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{c,1:format}') - Instance Receiver: - IInstanceReferenceOperation (ReferenceKind: InterpolatedStringHandler) (OperationKind.InstanceReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') - Arguments(3): - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'c') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) - (ImplicitReference) - Operand: - IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') - ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: ':format') - ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"") (Syntax: ':format') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{c,1:format}') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'c') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Next (Regular) Block[B2] + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: ':format') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"") (Syntax: ':format') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'custom = $"" ... 1:format}"";') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: CustomHandler) (Syntax: 'custom = $"" ... ,1:format}""') + Left: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: 'custom') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Next (Regular) Block[B2] + Leaving: {R1} +} Block[B2] - Exit Predecessors: [B1] Statements (0) "; - VerifyFlowGraphAndDiagnosticsForTest(new[] { code, GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false), InterpolatedStringHandlerArgumentAttribute }, - expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, handlerType }, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); } [Fact] - public void DynamicConstruction_01() + public void InterpolatedStringHandlerConversionFlow_02() { var code = @" using System; using System.Runtime.CompilerServices; -dynamic d = 1; -/**/M($""literal{d,1:format}"")/**/; -void M(CustomHandler c) {} +public class C +{ + public void M1(C c, CustomHandler custom) + /**/{ + custom = $""literal{c,1:format}""; + }/**/ +} +"; -[InterpolatedStringHandler] -public struct CustomHandler + string handlerType = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: true, includeTrailingOutConstructorParameter: false); + + var expectedDiagnostics = DiagnosticDescription.None; + + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} { - public CustomHandler(int literalLength, int formattedCount) - { - } + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'custom') + Value: + IParameterReferenceOperation: custom (OperationKind.ParameterReference, Type: CustomHandler) (Syntax: 'custom') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Jump if False (Regular) to Block[B3] + IInvocationOperation ( System.Boolean CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Boolean, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B2] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IInvocationOperation ( System.Boolean CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Boolean, IsImplicit) (Syntax: '{c,1:format}') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'c') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: ':format') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"") (Syntax: ':format') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B3] + Block[B3] - Block + Predecessors: [B1] [B2] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'custom = $"" ... 1:format}"";') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: CustomHandler) (Syntax: 'custom = $"" ... ,1:format}""') + Left: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: 'custom') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Next (Regular) Block[B4] + Leaving: {R1} +} +Block[B4] - Exit + Predecessors: [B3] + Statements (0) +"; - public void AppendLiteral(dynamic d) - { - Console.WriteLine(""AppendLiteral""); - } + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, handlerType }, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } - public void AppendFormatted(dynamic d, int alignment, string format) - { - Console.WriteLine(""AppendFormatted""); - } + [Fact] + public void InterpolatedStringHandlerConversionFlow_03() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public void M1(C c, CustomHandler custom) + /**/{ + custom = $""literal{c,1:format}""; + }/**/ } "; + string handlerType = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false, includeTrailingOutConstructorParameter: true); + var expectedDiagnostics = DiagnosticDescription.None; - string expectedOperationTree = @" -IInvocationOperation (void M(CustomHandler c)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'M($""literal ... 1:format}"")') - Instance Receiver: - null - Arguments(1): - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null) (Syntax: '$""literal{d,1:format}""') - IInterpolatedStringHandlerCreationOperation (HandlerAppendCallsReturnBool: False, HandlerCreationHasSuccessParameter: False) (OperationKind.InterpolatedStringHandlerCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{d,1:format}""') - Creation: - IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{d,1:format}""') - Arguments(2): - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{d,1:format}""') - ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{d,1:format}""') + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'custom') + Value: + IParameterReferenceOperation: custom (OperationKind.ParameterReference, Type: CustomHandler) (Syntax: 'custom') + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, out System.Boolean success)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: success) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 2 (IsInitialization) (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Jump if False (Regular) to Block[B4] + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Leaving: {R2} + Next (Regular) Block[B3] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B2] + Statements (2) + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{d,1:format}""') - ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{d,1:format}""') + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{c,1:format}') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'c') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Initializer: - null - Content: - IInterpolatedStringOperation (OperationKind.InterpolatedString, Type: System.String) (Syntax: '$""literal{d,1:format}""') - Parts(2): - IInterpolatedStringAppendOperation (OperationKind.InterpolatedStringAppendLiteral, Type: null, IsImplicit) (Syntax: 'literal') - AppendCall: - IInvocationOperation ( void CustomHandler.AppendLiteral(dynamic d)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') - Instance Receiver: - IInstanceReferenceOperation (ReferenceKind: InterpolatedStringHandler) (OperationKind.InstanceReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{d,1:format}""') - Arguments(1): - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: d) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: dynamic, IsImplicit) (Syntax: 'literal') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) - Operand: - ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IInterpolatedStringAppendOperation (OperationKind.InterpolatedStringAppendFormatted, Type: null, IsImplicit) (Syntax: '{d,1:format}') - AppendCall: - IDynamicInvocationOperation (OperationKind.DynamicInvocation, Type: dynamic, IsImplicit) (Syntax: '{d,1:format}') - Expression: - IDynamicMemberReferenceOperation (Member Name: ""AppendFormatted"", Containing Type: null) (OperationKind.DynamicMemberReference, Type: null, IsImplicit) (Syntax: '{d,1:format}') - Type Arguments(0) - Instance Receiver: - IInstanceReferenceOperation (ReferenceKind: InterpolatedStringHandler) (OperationKind.InstanceReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{d,1:format}""') - Arguments(3): - ILocalReferenceOperation: d (OperationKind.LocalReference, Type: dynamic) (Syntax: 'd') - ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') - ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"") (Syntax: ':format') - ArgumentNames(3): - ""null"" - ""alignment"" - ""format"" - ArgumentRefKinds(0) - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: ':format') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"") (Syntax: ':format') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B2] [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'custom = $"" ... 1:format}"";') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: CustomHandler) (Syntax: 'custom = $"" ... ,1:format}""') + Left: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: 'custom') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) "; - VerifyOperationTreeAndDiagnosticsForTest(new[] { code, InterpolatedStringHandlerAttribute }, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, handlerType }, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); } [Fact] - public void DynamicConstruction_02() + public void InterpolatedStringHandlerConversionFlow_04() { var code = @" +using System; using System.Runtime.CompilerServices; -dynamic d = 1; -/**/M(d, $""{1}literal"")/**/; -void M(dynamic d, [InterpolatedStringHandlerArgument(""d"")]CustomHandler c) {} - -public partial struct CustomHandler +public class C { - public CustomHandler(int literalLength, int formattedCount, dynamic d) : this() {} + public void M1(C c, CustomHandler custom) + /**/{ + custom = $""literal{c,1:format}""; + }/**/ } "; - var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + string handlerType = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: true, includeTrailingOutConstructorParameter: true); - var expectedDiagnostics = new[] { - // (4,16): error CS8953: An interpolated string handler construction cannot use dynamic. Manually construct an instance of 'CustomHandler'. - // M(d, /**/$"{1}literal"/**/); - Diagnostic(ErrorCode.ERR_InterpolatedStringHandlerCreationCannotUseDynamic, @"$""{1}literal""").WithArguments("CustomHandler").WithLocation(4, 16) - }; + var expectedDiagnostics = DiagnosticDescription.None; - string expectedOperationTree = @" -IInvocationOperation (void M(dynamic d, CustomHandler c)) (OperationKind.Invocation, Type: System.Void, IsInvalid) (Syntax: 'M(d, $""{1}literal"")') - Instance Receiver: - null - Arguments(2): - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: d) (OperationKind.Argument, Type: null) (Syntax: 'd') - ILocalReferenceOperation: d (OperationKind.LocalReference, Type: dynamic) (Syntax: 'd') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsInvalid) (Syntax: '$""{1}literal""') - IInterpolatedStringHandlerCreationOperation (HandlerAppendCallsReturnBool: False, HandlerCreationHasSuccessParameter: False) (OperationKind.InterpolatedStringHandlerCreation, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') - Creation: - IDynamicObjectCreationOperation (OperationKind.DynamicObjectCreation, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') - Arguments(3): - ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') - ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') - IInterpolatedStringHandlerArgumentPlaceholderOperation (ArgumentIndex: 0) (OperationKind.InterpolatedStringHandlerArgumentPlaceholder, Type: null, IsImplicit) (Syntax: 'd') - ArgumentNames(0) - ArgumentRefKinds(3): - None - None - None - Initializer: - null - Content: - IInterpolatedStringOperation (OperationKind.InterpolatedString, Type: System.String, IsInvalid) (Syntax: '$""{1}literal""') - Parts(2): - IInterpolatedStringAppendOperation (OperationKind.InterpolatedStringAppendFormatted, Type: null, IsInvalid, IsImplicit) (Syntax: '{1}') - AppendCall: - IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsInvalid, IsImplicit) (Syntax: '{1}') - Instance Receiver: - IInstanceReferenceOperation (ReferenceKind: InterpolatedStringHandler) (OperationKind.InstanceReference, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') - Arguments(3): - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '1') - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsInvalid, IsImplicit) (Syntax: '1') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '{1}') - ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsInvalid, IsImplicit) (Syntax: '{1}') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: format) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '{1}') - IDefaultValueOperation (OperationKind.DefaultValue, Type: System.String, Constant: null, IsInvalid, IsImplicit) (Syntax: '{1}') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IInterpolatedStringAppendOperation (OperationKind.InterpolatedStringAppendLiteral, Type: null, IsInvalid, IsImplicit) (Syntax: 'literal') - AppendCall: - IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsInvalid, IsImplicit) (Syntax: 'literal') - Instance Receiver: - IInstanceReferenceOperation (ReferenceKind: InterpolatedStringHandler) (OperationKind.InstanceReference, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') - Arguments(1): - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: 'literal') - ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"", IsInvalid) (Syntax: 'literal') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'custom') + Value: + IParameterReferenceOperation: custom (OperationKind.ParameterReference, Type: CustomHandler) (Syntax: 'custom') + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, out System.Boolean success)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: success) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 2 (IsInitialization) (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Jump if False (Regular) to Block[B5] + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Leaving: {R2} + Next (Regular) Block[B3] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B2] + Statements (0) + Jump if False (Regular) to Block[B5] + IInvocationOperation ( System.Boolean CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Boolean, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IInvocationOperation ( System.Boolean CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Boolean, IsImplicit) (Syntax: '{c,1:format}') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'c') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: ':format') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"") (Syntax: ':format') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B5] + Block[B5] - Block + Predecessors: [B2] [B3] [B4] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'custom = $"" ... 1:format}"";') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: CustomHandler) (Syntax: 'custom = $"" ... ,1:format}""') + Left: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: 'custom') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Next (Regular) Block[B6] + Leaving: {R1} +} +Block[B6] - Exit + Predecessors: [B5] + Statements (0) "; - VerifyOperationTreeAndDiagnosticsForTest(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute }, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, handlerType }, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); } - [Fact, WorkItem(54703, "https://github.com/dotnet/roslyn/issues/54703")] - public void InterpolationEscapeConstantValue_WithDefaultHandler() + [Fact] + public void InterpolatedStringHandlerConversionFlow_05() { var code = @" -int i = 1; -System.Console.WriteLine(/**/$""{{ {i} }}""/**/);"; +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public void M1(C c, CustomHandler custom) + /**/{ + custom = $""literal""; + }/**/ +} +"; + + string handlerType = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: true, includeTrailingOutConstructorParameter: false); var expectedDiagnostics = DiagnosticDescription.None; - string expectedOperationTree = @" -IInterpolatedStringOperation (OperationKind.InterpolatedString, Type: System.String) (Syntax: '$""{{ {i} }}""') - Parts(3): - IInterpolatedStringTextOperation (OperationKind.InterpolatedStringText, Type: null) (Syntax: '{{ ') - Text: - ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""{ "", IsImplicit) (Syntax: '{{ ') - IInterpolationOperation (OperationKind.Interpolation, Type: null) (Syntax: '{i}') - Expression: - ILocalReferenceOperation: i (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i') - Alignment: - null - FormatString: - null - IInterpolatedStringTextOperation (OperationKind.InterpolatedStringText, Type: null) (Syntax: ' }}') - Text: - ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "" }"", IsImplicit) (Syntax: ' }}') + // Note that the blocks for the blocks for the AppendLiteral and the result have been merged in this result, even though the AppendLiteral call returns bool. + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (4) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'custom') + Value: + IParameterReferenceOperation: custom (OperationKind.ParameterReference, Type: CustomHandler) (Syntax: 'custom') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal""') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '$""literal""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + IInvocationOperation ( System.Boolean CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Boolean, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'custom = $""literal"";') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: CustomHandler) (Syntax: 'custom = $""literal""') + Left: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: 'custom') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal""') + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0) "; - VerifyOperationTreeAndDiagnosticsForTest(new[] { code, GetInterpolatedStringHandlerDefinition(includeSpanOverloads: false, useDefaultParameters: false, useBoolReturns: false) }, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, handlerType }, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); } - [Fact, WorkItem(54703, "https://github.com/dotnet/roslyn/issues/54703")] - public void InterpolationEscapeConstantValue_WithoutDefaultHandler() + [Fact] + public void InterpolatedStringHandlerConversionFlow_06() { var code = @" -int i = 1; -System.Console.WriteLine(/**/$""{{ {i} }}""/**/);"; +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public void M1(C c, CustomHandler custom) + /**/{ + custom = $""literal""; + }/**/ +} +"; + + string handlerType = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: true, includeTrailingOutConstructorParameter: true); var expectedDiagnostics = DiagnosticDescription.None; - // The difference between this test and the previous one is the constant value of the ILiteralOperations. When handlers are involved, the - // constant value is { and }. When they are not involved, the constant values are {{ and }} - string expectedOperationTree = @" -IInterpolatedStringOperation (OperationKind.InterpolatedString, Type: System.String) (Syntax: '$""{{ {i} }}""') - Parts(3): - IInterpolatedStringTextOperation (OperationKind.InterpolatedStringText, Type: null) (Syntax: '{{ ') - Text: - ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""{{ "", IsImplicit) (Syntax: '{{ ') - IInterpolationOperation (OperationKind.Interpolation, Type: null) (Syntax: '{i}') - Expression: - ILocalReferenceOperation: i (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i') - Alignment: - null - FormatString: - null - IInterpolatedStringTextOperation (OperationKind.InterpolatedStringText, Type: null) (Syntax: ' }}') - Text: - ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "" }}"", IsImplicit) (Syntax: ' }}') + // Note that the blocks for the blocks for the AppendLiteral and the result have been merged in this result, even though the AppendLiteral call returns bool. + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'custom') + Value: + IParameterReferenceOperation: custom (OperationKind.ParameterReference, Type: CustomHandler) (Syntax: 'custom') + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, out System.Boolean success)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '$""literal""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: success) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal""') + IFlowCaptureReferenceOperation: 2 (IsInitialization) (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Jump if False (Regular) to Block[B4] + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal""') + Leaving: {R2} + Next (Regular) Block[B3] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B2] + Statements (1) + IInvocationOperation ( System.Boolean CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Boolean, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B2] [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'custom = $""literal"";') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: CustomHandler) (Syntax: 'custom = $""literal""') + Left: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: 'custom') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal""') + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) "; - VerifyOperationTreeAndDiagnosticsForTest(new[] { code }, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, handlerType }, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); } [Fact] - public void InterpolatedStringsAddedUnderObjectAddition_DefiniteAssignment() + public void InterpolatedStringHandlerConversionFlow_07() { var code = @" -#pragma warning disable CS0219 // Unused local -object o1; -object o2; -object o3; -_ = /**/$""{o1 = null}"" + $""{o2 = null}"" + $""{o3 = null}"" + 1/**/; -"; +using System; +using System.Runtime.CompilerServices; - var comp = CreateCompilation(new[] { code, GetInterpolatedStringHandlerDefinition(includeSpanOverloads: false, useDefaultParameters: false, useBoolReturns: true) }); +public class C +{ + public void M(bool b, [InterpolatedStringHandlerArgument("""", ""b"")]CustomHandler c) {} + + public void M1(C c) + /**/{ + c.M(true, $""literal{c,1:format}""); + }/**/ +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, C c, bool b, out bool success) : this() + { + success = true; + } +} +"; var expectedDiagnostics = DiagnosticDescription.None; - string expectedOperationTree = @" -IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String) (Syntax: '$""{o1 = nul ... null}"" + 1') - Left: - IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String) (Syntax: '$""{o1 = nul ... o3 = null}""') - Left: - IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String) (Syntax: '$""{o1 = nul ... o2 = null}""') - Left: - IInterpolatedStringOperation (OperationKind.InterpolatedString, Type: System.String) (Syntax: '$""{o1 = null}""') + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] [2] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c') + Value: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'true') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True) (Syntax: 'true') + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [3] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, C c, System.Boolean b, out System.Boolean success)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(5): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'true') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: success) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 3 (IsInitialization) (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Jump if False (Regular) to Block[B4] + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Leaving: {R2} + Next (Regular) Block[B3] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B2] + Statements (2) + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{c,1:format}') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'c') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: ':format') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"") (Syntax: ':format') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B2] [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'c.M(true, $ ... :format}"");') + Expression: + IInvocationOperation ( void C.M(System.Boolean b, CustomHandler c)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'c.M(true, $ ... 1:format}"")') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null) (Syntax: 'true') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false), InterpolatedStringHandlerArgumentAttribute }, + expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void InterpolatedStringHandlerConversionFlow_08() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public void M(bool b, [InterpolatedStringHandlerArgument(""b"")]CustomHandler c) {} + + public void M1(C c) + /**/{ + c.M(true, $""literal{c,1:format}""); + }/**/ +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, bool b, out bool success) : this() + { + success = true; + } +} +"; + + var expectedDiagnostics = DiagnosticDescription.None; + + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] [2] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c') + Value: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'true') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True) (Syntax: 'true') + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [3] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, System.Boolean b, out System.Boolean success)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(4): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'true') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: success) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 3 (IsInitialization) (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Jump if False (Regular) to Block[B4] + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Leaving: {R2} + Next (Regular) Block[B3] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B2] + Statements (2) + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{c,1:format}') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'c') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: ':format') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"") (Syntax: ':format') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B2] [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'c.M(true, $ ... :format}"");') + Expression: + IInvocationOperation ( void C.M(System.Boolean b, CustomHandler c)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'c.M(true, $ ... 1:format}"")') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null) (Syntax: 'true') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false), InterpolatedStringHandlerArgumentAttribute }, + expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void InterpolatedStringHandlerConversionFlow_09() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public void M(bool b, [InterpolatedStringHandlerArgument("""")]CustomHandler c) {} + + public void M1(C c) + /**/{ + c.M(true, $""literal{c,1:format}""); + }/**/ +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, C c, out bool success) : this() + { + success = true; + } +} +"; + + var expectedDiagnostics = DiagnosticDescription.None; + + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] [2] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c') + Value: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'true') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True) (Syntax: 'true') + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [3] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, C c, out System.Boolean success)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(4): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: success) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 3 (IsInitialization) (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Jump if False (Regular) to Block[B4] + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Leaving: {R2} + Next (Regular) Block[B3] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B2] + Statements (2) + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{c,1:format}') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'c') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: ':format') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"") (Syntax: ':format') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B2] [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'c.M(true, $ ... :format}"");') + Expression: + IInvocationOperation ( void C.M(System.Boolean b, CustomHandler c)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'c.M(true, $ ... 1:format}"")') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null) (Syntax: 'true') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false), InterpolatedStringHandlerArgumentAttribute }, + expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void InterpolatedStringHandlerConversionFlow_10() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public void M(bool b, [InterpolatedStringHandlerArgument("""", ""b"")]CustomHandler c, int d) {} + + public void M1(C c) + /**/{ + c.M(true, $""literal{c,1:format}"", 1); + }/**/ +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, C c, bool b, out bool success) : this() + { + success = true; + } +} +"; + + var expectedDiagnostics = DiagnosticDescription.None; + + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] [2] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c') + Value: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'true') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True) (Syntax: 'true') + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [3] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, C c, System.Boolean b, out System.Boolean success)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(5): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'true') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: success) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 3 (IsInitialization) (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Jump if False (Regular) to Block[B4] + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Leaving: {R2} + Next (Regular) Block[B3] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B2] + Statements (2) + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{c,1:format}') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'c') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: ':format') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"") (Syntax: ':format') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B2] [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'c.M(true, $ ... rmat}"", 1);') + Expression: + IInvocationOperation ( void C.M(System.Boolean b, CustomHandler c, System.Int32 d)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'c.M(true, $ ... ormat}"", 1)') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null) (Syntax: 'true') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: d) (OperationKind.Argument, Type: null) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false), InterpolatedStringHandlerArgumentAttribute }, + expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void InterpolatedStringHandlerConversionFlow_11() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public void M(bool b, [InterpolatedStringHandlerArgument("""", ""b"")]CustomHandler c1, int d, [InterpolatedStringHandlerArgument("""", ""d"")]CustomHandler c2) {} + + public void M1(C c) + /**/{ + c.M(true, $""literal{c,1:format}"", 1, $""literal{c,2:format}""); + }/**/ +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, C c, bool b, out bool success) : this() + { + success = true; + } + + public CustomHandler(int literalLength, int formattedCount, C c, int i, out bool success) : this() + { + success = true; + } +} +"; + + var expectedDiagnostics = DiagnosticDescription.None; + + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] [2] [4] [5] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c') + Value: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'true') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True) (Syntax: 'true') + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [3] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, C c, System.Boolean b, out System.Boolean success)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(5): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'true') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: success) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 3 (IsInitialization) (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Jump if False (Regular) to Block[B4] + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Leaving: {R2} + Next (Regular) Block[B3] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B2] + Statements (2) + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{c,1:format}') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'c') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: ':format') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"") (Syntax: ':format') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B2] [B3] + Statements (1) + IFlowCaptureOperation: 4 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '1') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + Next (Regular) Block[B5] + Entering: {R3} + .locals {R3} + { + CaptureIds: [6] + Block[B5] - Block + Predecessors: [B4] + Statements (1) + IFlowCaptureOperation: 5 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{c,2:format}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, C c, System.Int32 i, out System.Boolean success)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,2:format}""') + Arguments(5): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,2:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{c,2:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,2:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,2:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,2:format}""') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: i) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + IFlowCaptureReferenceOperation: 4 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: success) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,2:format}""') + IFlowCaptureReferenceOperation: 6 (IsInitialization) (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,2:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Jump if False (Regular) to Block[B7] + IFlowCaptureReferenceOperation: 6 (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,2:format}""') + Leaving: {R3} + Next (Regular) Block[B6] + Leaving: {R3} + } + Block[B6] - Block + Predecessors: [B5] + Statements (2) + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 5 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,2:format}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{c,2:format}') + Instance Receiver: + IFlowCaptureReferenceOperation: 5 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,2:format}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'c') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '2') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: ':format') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"") (Syntax: ':format') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B7] + Block[B7] - Block + Predecessors: [B5] [B6] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'c.M(true, $ ... :format}"");') + Expression: + IInvocationOperation ( void C.M(System.Boolean b, CustomHandler c1, System.Int32 d, CustomHandler c2)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'c.M(true, $ ... 2:format}"")') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') + Arguments(4): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null) (Syntax: 'true') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c1) (OperationKind.Argument, Type: null) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: d) (OperationKind.Argument, Type: null) (Syntax: '1') + IFlowCaptureReferenceOperation: 4 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c2) (OperationKind.Argument, Type: null) (Syntax: '$""literal{c,2:format}""') + IFlowCaptureReferenceOperation: 5 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,2:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B8] + Leaving: {R1} +} +Block[B8] - Exit + Predecessors: [B7] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false), InterpolatedStringHandlerArgumentAttribute }, + expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void InterpolatedStringHandlerConversionFlow_12() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public void M(bool b, [InterpolatedStringHandlerArgument("""", ""b"")]CustomHandler c) {} + + public void M1(C c) + /**/{ + c.M(c: $""literal{c,1:format}"", b: true); + }/**/ +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, C c, bool b, out bool success) : this() + { + success = true; + } +} +"; + + var expectedDiagnostics = new DiagnosticDescription[] { + // (11,43): error CS8950: Parameter 'b' is an argument to the interpolated string handler conversion on parameter 'c', but the corresponding argument is specified after the interpolated string expression. Reorder the arguments to move 'b' before 'c'. + // c.M(c: $"literal{c,1:format}", b: true); + Diagnostic(ErrorCode.ERR_InterpolatedStringHandlerArgumentLocatedAfterInterpolatedString, "true").WithArguments("b", "c").WithLocation(11, 43) + }; + + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c') + Value: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, C c, System.Boolean b, out System.Boolean success)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(5): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c: $""litera ... ,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c: $""litera ... ,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c: $""litera ... ,1:format}""') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: 'b: true') + IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid, IsImplicit) (Syntax: 'true') + Children(0) + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: success) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c: $""litera ... ,1:format}""') + IFlowCaptureReferenceOperation: 2 (IsInitialization) (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Jump if False (Regular) to Block[B4] + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Leaving: {R2} + Next (Regular) Block[B3] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B2] + Statements (2) + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{c,1:format}') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'c') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: ':format') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"") (Syntax: ':format') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B2] [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsInvalid) (Syntax: 'c.M(c: $""li ... , b: true);') + Expression: + IInvocationOperation ( void C.M(System.Boolean b, CustomHandler c)) (OperationKind.Invocation, Type: System.Void, IsInvalid) (Syntax: 'c.M(c: $""li ... "", b: true)') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null) (Syntax: 'c: $""litera ... ,1:format}""') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null, IsInvalid) (Syntax: 'b: true') + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True, IsInvalid) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) + +"; + + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false), InterpolatedStringHandlerArgumentAttribute }, + expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void InterpolatedStringHandlerConversionFlow_13() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + + +public class C +{ + + public void M1(C c) + /**/{ + _ = new C1 { C2 = { [true, $""literal""] = { A = 1, B = 2 } } }; + }/**/ +} + +class C1 +{ + public C2 C2 { get => null; set { } } +} + +public class C2 +{ + public C3 this[bool b, [InterpolatedStringHandlerArgument("""", ""b"")] CustomHandler c] + { + get => new C3(); + set { } + } +} + +public class C3 +{ + public int A + { + get => 0; + set { } + } + public int B + { + get => 0; + set { } + } +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, C2 c, bool b) : this(literalLength, formattedCount) + { + Console.WriteLine(""CustomHandler ctor""); + } +} +"; + + var expectedDiagnostics = new[] { + // (11,36): error CS8976: Interpolated string handler conversions that reference the instance being indexed cannot be used in indexer member initializers. + // _ = new C1 { C2 = { [true, $"literal"] = { A = 1, B = 2 } } }; + Diagnostic(ErrorCode.ERR_InterpolatedStringsReferencingInstanceCannotBeInObjectInitializers, @"$""literal""").WithLocation(11, 36) + }; + + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: 'new C1 { C2 ... B = 2 } } }') + Value: + IObjectCreationOperation (Constructor: C1..ctor()) (OperationKind.ObjectCreation, Type: C1, IsInvalid) (Syntax: 'new C1 { C2 ... B = 2 } } }') + Arguments(0) + Initializer: + null + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [1] [2] + Block[B2] - Block + Predecessors: [B1] + Statements (5) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'true') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True) (Syntax: 'true') + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: '$""literal""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, C2 c, System.Boolean b)) (OperationKind.ObjectCreation, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""literal""') + Arguments(4): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '$""literal""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsInvalid, IsImplicit) (Syntax: '$""literal""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '$""literal""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsInvalid, IsImplicit) (Syntax: '$""literal""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '$""literal""') + IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid, IsImplicit) (Syntax: '$""literal""') + Children(0) + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'true') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsInvalid, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""literal""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"", IsInvalid) (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'A = 1') + Left: + IPropertyReferenceOperation: System.Int32 C3.A { get; set; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IPropertyReferenceOperation: C3 C2.this[System.Boolean b, CustomHandler c] { get; set; } (OperationKind.PropertyReference, Type: C3, IsInvalid) (Syntax: '[true, $""literal""]') + Instance Receiver: + IPropertyReferenceOperation: C2 C1.C2 { get; set; } (OperationKind.PropertyReference, Type: C2) (Syntax: 'C2') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C1, IsInvalid, IsImplicit) (Syntax: 'new C1 { C2 ... B = 2 } } }') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null) (Syntax: 'true') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsInvalid) (Syntax: '$""literal""') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""literal""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'B = 2') + Left: + IPropertyReferenceOperation: System.Int32 C3.B { get; set; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'B') + Instance Receiver: + IPropertyReferenceOperation: C3 C2.this[System.Boolean b, CustomHandler c] { get; set; } (OperationKind.PropertyReference, Type: C3, IsInvalid) (Syntax: '[true, $""literal""]') + Instance Receiver: + IPropertyReferenceOperation: C2 C1.C2 { get; set; } (OperationKind.PropertyReference, Type: C2) (Syntax: 'C2') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C1, IsInvalid, IsImplicit) (Syntax: 'new C1 { C2 ... B = 2 } } }') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null) (Syntax: 'true') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsInvalid) (Syntax: '$""literal""') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""literal""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + Next (Regular) Block[B3] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B2] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsInvalid) (Syntax: '_ = new C1 ... = 2 } } };') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C1, IsInvalid) (Syntax: '_ = new C1 ... B = 2 } } }') + Left: + IDiscardOperation (Symbol: C1 _) (OperationKind.Discard, Type: C1) (Syntax: '_') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C1, IsInvalid, IsImplicit) (Syntax: 'new C1 { C2 ... B = 2 } } }') + Next (Regular) Block[B4] + Leaving: {R1} +} +Block[B4] - Exit + Predecessors: [B3] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false), InterpolatedStringHandlerArgumentAttribute }, + expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void InterpolatedStringHandlerConversionFlow_14() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public C(bool b, [InterpolatedStringHandlerArgument(""b"")]CustomHandler c1) {} + + public void M1(C c) + /**/{ + _ = new C(true, $""literal{c,1:format}""); + }/**/ +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, bool b, out bool success) : this() + { + success = true; + } +} +"; + + var expectedDiagnostics = DiagnosticDescription.None; + + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'true') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True) (Syntax: 'true') + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, System.Boolean b, out System.Boolean success)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(4): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'true') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: success) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 2 (IsInitialization) (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Jump if False (Regular) to Block[B4] + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Leaving: {R2} + Next (Regular) Block[B3] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B2] + Statements (2) + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{c,1:format}') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'c') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'c') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: ':format') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"") (Syntax: ':format') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B2] [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '_ = new C(t ... :format}"");') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C) (Syntax: '_ = new C(t ... 1:format}"")') + Left: + IDiscardOperation (Symbol: C _) (OperationKind.Discard, Type: C) (Syntax: '_') + Right: + IObjectCreationOperation (Constructor: C..ctor(System.Boolean b, CustomHandler c1)) (OperationKind.ObjectCreation, Type: C) (Syntax: 'new C(true, ... 1:format}"")') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null) (Syntax: 'true') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c1) (OperationKind.Argument, Type: null) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false), InterpolatedStringHandlerArgumentAttribute }, + expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void InterpolatedStringHandlerConversionFlow_15() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public C(bool b, [InterpolatedStringHandlerArgument("""", ""b"")]CustomHandler c1) {} + + public void M1(C c) + /**/{ + _ = new C(true, $""literal{c,1:format}""); + }/**/ +} +"; + + var expectedDiagnostics = new[] { + // (7,23): error CS8944: 'C.C(bool, CustomHandler)' is not an instance method, the receiver cannot be an interpolated string handler argument. + // public C(bool b, [InterpolatedStringHandlerArgument("", "b")]CustomHandler c1) {} + Diagnostic(ErrorCode.ERR_NotInstanceInvalidInterpolatedStringHandlerArgumentName, @"InterpolatedStringHandlerArgument("""", ""b"")").WithArguments("C.C(bool, CustomHandler)").WithLocation(7, 23), + // (11,25): error CS8949: The InterpolatedStringHandlerArgumentAttribute applied to parameter 'CustomHandler' is malformed and cannot be interpreted. Construct an instance of 'CustomHandler' manually. + // _ = new C(true, $"literal{c,1:format}"); + Diagnostic(ErrorCode.ERR_InterpolatedStringHandlerArgumentAttributeMalformed, @"$""literal{c,1:format}""").WithArguments("CustomHandler", "CustomHandler").WithLocation(11, 25) + }; + + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (5) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'true') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True) (Syntax: 'true') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount)) (OperationKind.ObjectCreation, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsInvalid, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '$""literal{c,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsInvalid, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"", IsInvalid) (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsInvalid, IsImplicit) (Syntax: '{c,1:format}') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""literal{c,1:format}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: 'c') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsInvalid, IsImplicit) (Syntax: 'c') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C, IsInvalid) (Syntax: 'c') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: ':format') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"", IsInvalid) (Syntax: ':format') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsInvalid) (Syntax: '_ = new C(t ... :format}"");') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C, IsInvalid) (Syntax: '_ = new C(t ... 1:format}"")') + Left: + IDiscardOperation (Symbol: C _) (OperationKind.Discard, Type: C) (Syntax: '_') + Right: + IObjectCreationOperation (Constructor: C..ctor(System.Boolean b, CustomHandler c1)) (OperationKind.ObjectCreation, Type: C, IsInvalid) (Syntax: 'new C(true, ... 1:format}"")') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: b) (OperationKind.Argument, Type: null) (Syntax: 'true') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'true') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c1) (OperationKind.Argument, Type: null, IsInvalid) (Syntax: '$""literal{c,1:format}""') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""literal{c,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false), InterpolatedStringHandlerArgumentAttribute }, + expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void InterpolatedStringHandlerConversionFlow_16() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public void M1(C c, CustomHandler custom) + /**/{ + custom = $""""; + }/**/ +} +"; + + string handlerType = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false, includeTrailingOutConstructorParameter: true); + + var expectedDiagnostics = DiagnosticDescription.None; + + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'custom') + Value: + IParameterReferenceOperation: custom (OperationKind.ParameterReference, Type: CustomHandler) (Syntax: 'custom') + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, out System.Boolean success)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '$""""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '$""""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: success) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""""') + IFlowCaptureReferenceOperation: 2 (IsInitialization) (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Jump if False (Regular) to Block[B3] + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""""') + Leaving: {R2} + Next (Regular) Block[B3] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B2*2] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'custom = $"""";') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: CustomHandler) (Syntax: 'custom = $""""') + Left: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: 'custom') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""""') + Next (Regular) Block[B4] + Leaving: {R1} +} +Block[B4] - Exit + Predecessors: [B3] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, handlerType }, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void DynamicConstruction_01() + { + var code = @" +using System; +using System.Runtime.CompilerServices; +dynamic d = 1; +/**/M($""literal{d,1:format}"")/**/; + +void M(CustomHandler c) {} + +[InterpolatedStringHandler] +public struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount) + { + } + + public void AppendLiteral(dynamic d) + { + Console.WriteLine(""AppendLiteral""); + } + + public void AppendFormatted(dynamic d, int alignment, string format) + { + Console.WriteLine(""AppendFormatted""); + } +} +"; + + var expectedDiagnostics = DiagnosticDescription.None; + + string expectedOperationTree = @" +IInvocationOperation (void M(CustomHandler c)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'M($""literal ... 1:format}"")') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null) (Syntax: '$""literal{d,1:format}""') + IInterpolatedStringHandlerCreationOperation (HandlerAppendCallsReturnBool: False, HandlerCreationHasSuccessParameter: False) (OperationKind.InterpolatedStringHandlerCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{d,1:format}""') + Creation: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{d,1:format}""') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{d,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{d,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{d,1:format}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{d,1:format}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Content: + IInterpolatedStringOperation (OperationKind.InterpolatedString, Type: System.String) (Syntax: '$""literal{d,1:format}""') + Parts(2): + IInterpolatedStringAppendOperation (OperationKind.InterpolatedStringAppendLiteral, Type: null, IsImplicit) (Syntax: 'literal') + AppendCall: + IInvocationOperation ( void CustomHandler.AppendLiteral(dynamic d)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: InterpolatedStringHandler) (OperationKind.InstanceReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{d,1:format}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: d) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: dynamic, IsImplicit) (Syntax: 'literal') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInterpolatedStringAppendOperation (OperationKind.InterpolatedStringAppendFormatted, Type: null, IsImplicit) (Syntax: '{d,1:format}') + AppendCall: + IDynamicInvocationOperation (OperationKind.DynamicInvocation, Type: dynamic, IsImplicit) (Syntax: '{d,1:format}') + Expression: + IDynamicMemberReferenceOperation (Member Name: ""AppendFormatted"", Containing Type: null) (OperationKind.DynamicMemberReference, Type: null, IsImplicit) (Syntax: '{d,1:format}') + Type Arguments(0) + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: InterpolatedStringHandler) (OperationKind.InstanceReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{d,1:format}""') + Arguments(3): + ILocalReferenceOperation: d (OperationKind.LocalReference, Type: dynamic) (Syntax: 'd') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""format"") (Syntax: ':format') + ArgumentNames(3): + ""null"" + ""alignment"" + ""format"" + ArgumentRefKinds(0) + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) +"; + + VerifyOperationTreeAndDiagnosticsForTest(new[] { code, InterpolatedStringHandlerAttribute }, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void DynamicConstruction_02() + { + var code = @" +using System.Runtime.CompilerServices; +dynamic d = 1; +/**/M(d, $""{1}literal"")/**/; + +void M(dynamic d, [InterpolatedStringHandlerArgument(""d"")]CustomHandler c) {} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, dynamic d) : this() {} +} +"; + + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var expectedDiagnostics = new[] { + // (4,16): error CS8953: An interpolated string handler construction cannot use dynamic. Manually construct an instance of 'CustomHandler'. + // M(d, /**/$"{1}literal"/**/); + Diagnostic(ErrorCode.ERR_InterpolatedStringHandlerCreationCannotUseDynamic, @"$""{1}literal""").WithArguments("CustomHandler").WithLocation(4, 16) + }; + + string expectedOperationTree = @" +IInvocationOperation (void M(dynamic d, CustomHandler c)) (OperationKind.Invocation, Type: System.Void, IsInvalid) (Syntax: 'M(d, $""{1}literal"")') + Instance Receiver: + null + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: d) (OperationKind.Argument, Type: null) (Syntax: 'd') + ILocalReferenceOperation: d (OperationKind.LocalReference, Type: dynamic) (Syntax: 'd') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsInvalid) (Syntax: '$""{1}literal""') + IInterpolatedStringHandlerCreationOperation (HandlerAppendCallsReturnBool: False, HandlerCreationHasSuccessParameter: False) (OperationKind.InterpolatedStringHandlerCreation, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') + Creation: + IDynamicObjectCreationOperation (OperationKind.DynamicObjectCreation, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') + Arguments(3): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') + IInterpolatedStringHandlerArgumentPlaceholderOperation (ArgumentIndex: 0) (OperationKind.InterpolatedStringHandlerArgumentPlaceholder, Type: null, IsImplicit) (Syntax: 'd') + ArgumentNames(0) + ArgumentRefKinds(3): + None + None + None + Initializer: + null + Content: + IInterpolatedStringOperation (OperationKind.InterpolatedString, Type: System.String, IsInvalid) (Syntax: '$""{1}literal""') + Parts(2): + IInterpolatedStringAppendOperation (OperationKind.InterpolatedStringAppendFormatted, Type: null, IsInvalid, IsImplicit) (Syntax: '{1}') + AppendCall: + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsInvalid, IsImplicit) (Syntax: '{1}') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: InterpolatedStringHandler) (OperationKind.InstanceReference, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '1') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsInvalid, IsImplicit) (Syntax: '1') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '{1}') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsInvalid, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: format) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '{1}') + IDefaultValueOperation (OperationKind.DefaultValue, Type: System.String, Constant: null, IsInvalid, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInterpolatedStringAppendOperation (OperationKind.InterpolatedStringAppendLiteral, Type: null, IsInvalid, IsImplicit) (Syntax: 'literal') + AppendCall: + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsInvalid, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: InterpolatedStringHandler) (OperationKind.InstanceReference, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"", IsInvalid) (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) +"; + + VerifyOperationTreeAndDiagnosticsForTest(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute }, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void DynamicConstruction_Flow() + { + var code = @" +using System.Runtime.CompilerServices; + +class C +{ + void M() + /**/{ + dynamic d = 1; + M1(d, $""{1}literal""); + + void M1(dynamic d, [InterpolatedStringHandlerArgument(""d"")]CustomHandler c) {} + }/**/ + +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, dynamic d) : this() {} +} +"; + + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var expectedDiagnostics = new DiagnosticDescription[] { + // (9,15): error CS8953: An interpolated string handler construction cannot use dynamic. Manually construct an instance of 'CustomHandler'. + // M1(d, $"{1}literal"); + Diagnostic(ErrorCode.ERR_InterpolatedStringHandlerCreationCannotUseDynamic, @"$""{1}literal""").WithArguments("CustomHandler").WithLocation(9, 15) + }; + + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [dynamic d] + Methods: [void M1(dynamic d, CustomHandler c)] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: dynamic, IsImplicit) (Syntax: 'd = 1') + Left: + ILocalReferenceOperation: d (IsDeclaration: True) (OperationKind.LocalReference, Type: dynamic, IsImplicit) (Syntax: 'd = 1') + Right: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: dynamic, IsImplicit) (Syntax: '1') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [0] [1] + Block[B2] - Block + Predecessors: [B1] + Statements (5) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'd') + Value: + ILocalReferenceOperation: d (OperationKind.LocalReference, Type: dynamic) (Syntax: 'd') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') + Value: + IDynamicObjectCreationOperation (OperationKind.DynamicObjectCreation, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') + Arguments(3): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: dynamic, IsImplicit) (Syntax: 'd') + ArgumentNames(0) + ArgumentRefKinds(3): + None + None + None + Initializer: + null + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsInvalid, IsImplicit) (Syntax: '{1}') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '1') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsInvalid, IsImplicit) (Syntax: '1') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '{1}') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsInvalid, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: format) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: '{1}') + IDefaultValueOperation (OperationKind.DefaultValue, Type: System.String, Constant: null, IsInvalid, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsInvalid, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsInvalid, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"", IsInvalid) (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsInvalid) (Syntax: 'M1(d, $""{1}literal"");') + Expression: + IInvocationOperation (void M1(dynamic d, CustomHandler c)) (OperationKind.Invocation, Type: System.Void, IsInvalid) (Syntax: 'M1(d, $""{1}literal"")') + Instance Receiver: + null + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: d) (OperationKind.Argument, Type: null) (Syntax: 'd') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: dynamic, IsImplicit) (Syntax: 'd') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsInvalid) (Syntax: '$""{1}literal""') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsInvalid, IsImplicit) (Syntax: '$""{1}literal""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B3] + Leaving: {R2} {R1} + } + + { void M1(dynamic d, CustomHandler c) + + Block[B0#0R1] - Entry + Statements (0) + Next (Regular) Block[B1#0R1] + Block[B1#0R1] - Exit + Predecessors: [B0#0R1] + Statements (0) + } +} +Block[B3] - Exit + Predecessors: [B2] + Statements (0) + +"; + + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute }, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact, WorkItem(54703, "https://github.com/dotnet/roslyn/issues/54703")] + public void InterpolationEscapeConstantValue_WithDefaultHandler() + { + var code = @" +int i = 1; +System.Console.WriteLine(/**/$""{{ {i} }}""/**/);"; + + var expectedDiagnostics = DiagnosticDescription.None; + + string expectedOperationTree = @" +IInterpolatedStringOperation (OperationKind.InterpolatedString, Type: System.String) (Syntax: '$""{{ {i} }}""') + Parts(3): + IInterpolatedStringTextOperation (OperationKind.InterpolatedStringText, Type: null) (Syntax: '{{ ') + Text: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""{ "", IsImplicit) (Syntax: '{{ ') + IInterpolationOperation (OperationKind.Interpolation, Type: null) (Syntax: '{i}') + Expression: + ILocalReferenceOperation: i (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i') + Alignment: + null + FormatString: + null + IInterpolatedStringTextOperation (OperationKind.InterpolatedStringText, Type: null) (Syntax: ' }}') + Text: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "" }"", IsImplicit) (Syntax: ' }}') +"; + + VerifyOperationTreeAndDiagnosticsForTest(new[] { code, GetInterpolatedStringHandlerDefinition(includeSpanOverloads: false, useDefaultParameters: false, useBoolReturns: false) }, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact, WorkItem(54703, "https://github.com/dotnet/roslyn/issues/54703")] + public void InterpolationEscapeConstantValue_WithoutDefaultHandler() + { + var code = @" +int i = 1; +System.Console.WriteLine(/**/$""{{ {i} }}""/**/);"; + + var expectedDiagnostics = DiagnosticDescription.None; + + // The difference between this test and the previous one is the constant value of the ILiteralOperations. When handlers are involved, the + // constant value is { and }. When they are not involved, the constant values are {{ and }} + string expectedOperationTree = @" +IInterpolatedStringOperation (OperationKind.InterpolatedString, Type: System.String) (Syntax: '$""{{ {i} }}""') + Parts(3): + IInterpolatedStringTextOperation (OperationKind.InterpolatedStringText, Type: null) (Syntax: '{{ ') + Text: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""{{ "", IsImplicit) (Syntax: '{{ ') + IInterpolationOperation (OperationKind.Interpolation, Type: null) (Syntax: '{i}') + Expression: + ILocalReferenceOperation: i (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i') + Alignment: + null + FormatString: + null + IInterpolatedStringTextOperation (OperationKind.InterpolatedStringText, Type: null) (Syntax: ' }}') + Text: + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "" }}"", IsImplicit) (Syntax: ' }}') +"; + + VerifyOperationTreeAndDiagnosticsForTest(new[] { code }, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void InterpolatedStringsAddedUnderObjectAddition_DefiniteAssignment() + { + var code = @" +#pragma warning disable CS0219 // Unused local +object o1; +object o2; +object o3; +_ = /**/$""{o1 = null}"" + $""{o2 = null}"" + $""{o3 = null}"" + 1/**/; +"; + + var comp = CreateCompilation(new[] { code, GetInterpolatedStringHandlerDefinition(includeSpanOverloads: false, useDefaultParameters: false, useBoolReturns: true) }); + + var expectedDiagnostics = DiagnosticDescription.None; + + string expectedOperationTree = @" +IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String) (Syntax: '$""{o1 = nul ... null}"" + 1') + Left: + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String) (Syntax: '$""{o1 = nul ... o3 = null}""') + Left: + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String) (Syntax: '$""{o1 = nul ... o2 = null}""') + Left: + IInterpolatedStringOperation (OperationKind.InterpolatedString, Type: System.String) (Syntax: '$""{o1 = null}""') Parts(1): IInterpolationOperation (OperationKind.Interpolation, Type: null) (Syntax: '{o1 = null}') Expression: @@ -2989,63 +5121,696 @@ public void ParenthesizedInterpolatedStringsAdded_CustomHandler() OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) "; - VerifyOperationTreeAndDiagnosticsForTest(new[] { code, handler }, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + VerifyOperationTreeAndDiagnosticsForTest(new[] { code, handler }, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void ExplicitCastToCustomHandler() + { + var code = @" +int i1 = 1; + +CustomHandler s = /**/(CustomHandler)$""{i1,1:d}""/**/; +"; + + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "struct", useBoolReturns: false); + + var expectedDiagnostics = DiagnosticDescription.None; + var expectedOperationTree = @" +IInterpolatedStringHandlerCreationOperation (HandlerAppendCallsReturnBool: False, HandlerCreationHasSuccessParameter: False) (OperationKind.InterpolatedStringHandlerCreation, Type: CustomHandler) (Syntax: '(CustomHand ... $""{i1,1:d}""') + Creation: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""{i1,1:d}""') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""{i1,1:d}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '$""{i1,1:d}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""{i1,1:d}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""{i1,1:d}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Content: + IInterpolatedStringOperation (OperationKind.InterpolatedString, Type: System.String) (Syntax: '$""{i1,1:d}""') + Parts(1): + IInterpolatedStringAppendOperation (OperationKind.InterpolatedStringAppendFormatted, Type: null, IsImplicit) (Syntax: '{i1,1:d}') + AppendCall: + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{i1,1:d}') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: InterpolatedStringHandler) (OperationKind.InstanceReference, Type: CustomHandler, IsImplicit) (Syntax: '$""{i1,1:d}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'i1') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'i1') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILocalReferenceOperation: i1 (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: ':d') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""d"") (Syntax: ':d') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) +"; + + VerifyOperationTreeAndDiagnosticsForTest(new[] { code, handler }, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void HandlerInAppendFormatCall() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + + +class C +{ + public static void M(int i, [InterpolatedStringHandlerArgument(""i"")]CustomHandler handler) + { + Console.WriteLine(handler.ToString()); + } + + public static void M() + /**/{ + C.M(1, $""{$""Inner string""}{2}""); + }/**/ +} + +public partial class CustomHandler +{ + private int I = 0; + + public CustomHandler(int literalLength, int formattedCount, int i) : this(literalLength, formattedCount) + { + Console.WriteLine(""int constructor""); + I = i; + } + + public CustomHandler(int literalLength, int formattedCount, CustomHandler c) : this(literalLength, formattedCount) + { + Console.WriteLine(""CustomHandler constructor""); + _builder.AppendLine(""c.I:"" + c.I.ToString()); + } + + public void AppendFormatted([InterpolatedStringHandlerArgument("""")]CustomHandler c) + { + _builder.AppendLine(""CustomHandler AppendFormatted""); + _builder.Append(c.ToString()); + } +} +"; + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial class", useBoolReturns: false); + + var expectedDiagnostics = DiagnosticDescription.None; + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '1') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""{$""Inner string""}{2}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, System.Int32 i)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""{$""Inner string""}{2}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""{$""Inner string""}{2}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '$""{$""Inner string""}{2}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""{$""Inner string""}{2}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2, IsImplicit) (Syntax: '$""{$""Inner string""}{2}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: i) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (3) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""Inner string""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, CustomHandler c)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""Inner string""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""Inner string""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 12, IsImplicit) (Syntax: '$""Inner string""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""Inner string""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '$""Inner string""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""Inner string""') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""{$""Inner string""}{2}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'Inner string') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""Inner string""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'Inner string') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""Inner string"") (Syntax: 'Inner string') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(CustomHandler c)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{$""Inner string""}') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""{$""Inner string""}{2}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""Inner string""') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""Inner string""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B3] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B2] + Statements (2) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{2}') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""{$""Inner string""}{2}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '2') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: '2') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{2}') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '{2}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{2}') + IDefaultValueOperation (OperationKind.DefaultValue, Type: System.String, Constant: null, IsImplicit) (Syntax: '{2}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'C.M(1, $""{$ ... ing""}{2}"");') + Expression: + IInvocationOperation (void C.M(System.Int32 i, CustomHandler handler)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'C.M(1, $""{$ ... ring""}{2}"")') + Instance Receiver: + null + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: i) (OperationKind.Argument, Type: null) (Syntax: '1') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: handler) (OperationKind.Argument, Type: null) (Syntax: '$""{$""Inner string""}{2}""') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""{$""Inner string""}{2}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B4] + Leaving: {R1} +} +Block[B4] - Exit + Predecessors: [B3] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute }, expectedFlowGraph, expectedDiagnostics); } [Fact] - public void ExplicitCastToCustomHandler() + public void InvalidConstructor() { var code = @" -int i1 = 1; +using System.Runtime.CompilerServices; -CustomHandler s = /**/(CustomHandler)$""{i1,1:d}""/**/; +public class C +{ + public static void M1(int i) + /**/{ + C.M(in i, $""literal{1}""); + }/**/ + + public static void M(in int i, [InterpolatedStringHandlerArgumentAttribute(""i"")] CustomHandler c) { } +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, ref int i) : this() { } +} "; - var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "struct", useBoolReturns: false); + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var expectedDiagnostics = new[] { + // (8,16): error CS1620: Argument 3 must be passed with the 'ref' keyword + // C.M(in i, $"literal{1}"); + Diagnostic(ErrorCode.ERR_BadArgRef, "i").WithArguments("3", "ref").WithLocation(8, 16) + }; + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (5) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: 'i') + Value: + IParameterReferenceOperation: i (OperationKind.ParameterReference, Type: System.Int32, IsInvalid) (Syntax: 'i') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + Value: + IInvalidOperation (OperationKind.Invalid, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Children(3): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{1}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{1}""') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'i') + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{1}') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: '1') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{1}') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{1}') + IDefaultValueOperation (OperationKind.DefaultValue, Type: System.String, Constant: null, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsInvalid) (Syntax: 'C.M(in i, $ ... teral{1}"");') + Expression: + IInvocationOperation (void C.M(in System.Int32 i, CustomHandler c)) (OperationKind.Invocation, Type: System.Void, IsInvalid) (Syntax: 'C.M(in i, $""literal{1}"")') + Instance Receiver: + null + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: i) (OperationKind.Argument, Type: null, IsInvalid) (Syntax: 'in i') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'i') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null) (Syntax: '$""literal{1}""') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, handler, InterpolatedStringHandlerArgumentAttribute }, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void HandlerConstructorWithDefaultArgument() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + + +class C +{ + public static void M1() + /**/{ + C.M($""Literal""); + }/**/ + public static void M(CustomHandler c) => Console.WriteLine(c.ToString()); +} + +[InterpolatedStringHandler] +partial struct CustomHandler +{ + private string _s = null; + public CustomHandler(int literalLength, int formattedCount, out bool isValid, int i = 1) { _s = i.ToString(); isValid = false; } + public void AppendLiteral(string s) => _s += s; + public override string ToString() => _s; +} +"; var expectedDiagnostics = DiagnosticDescription.None; - var expectedOperationTree = @" -IInterpolatedStringHandlerCreationOperation (HandlerAppendCallsReturnBool: False, HandlerCreationHasSuccessParameter: False) (OperationKind.InterpolatedStringHandlerCreation, Type: CustomHandler) (Syntax: '(CustomHand ... $""{i1,1:d}""') - Creation: - IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""{i1,1:d}""') - Arguments(2): - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""{i1,1:d}""') - ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '$""{i1,1:d}""') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""{i1,1:d}""') - ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""{i1,1:d}""') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Initializer: - null - Content: - IInterpolatedStringOperation (OperationKind.InterpolatedString, Type: System.String) (Syntax: '$""{i1,1:d}""') - Parts(1): - IInterpolatedStringAppendOperation (OperationKind.InterpolatedStringAppendFormatted, Type: null, IsImplicit) (Syntax: '{i1,1:d}') - AppendCall: - IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{i1,1:d}') - Instance Receiver: - IInstanceReferenceOperation (ReferenceKind: InterpolatedStringHandler) (OperationKind.InstanceReference, Type: CustomHandler, IsImplicit) (Syntax: '$""{i1,1:d}""') - Arguments(3): - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'i1') - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'i1') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Operand: - ILocalReferenceOperation: i1 (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i1') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') - ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: ':d') - ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""d"") (Syntax: ':d') - InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + CaptureIds: [0] + .locals {R2} + { + CaptureIds: [1] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""Literal""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, out System.Boolean isValid, [System.Int32 i = 1])) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""Literal""') + Arguments(4): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""Literal""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""Literal""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""Literal""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '$""Literal""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: isValid) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""Literal""') + IFlowCaptureReferenceOperation: 1 (IsInitialization) (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""Literal""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: i) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""Literal""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""Literal""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Jump if False (Regular) to Block[B3] + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: null, IsImplicit) (Syntax: '$""Literal""') + Leaving: {R2} + Next (Regular) Block[B2] + Leaving: {R2} + } + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String s)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'Literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""Literal""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: s) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'Literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""Literal"") (Syntax: 'Literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B3] + Block[B3] - Block + Predecessors: [B1] [B2] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'C.M($""Literal"");') + Expression: + IInvocationOperation (void C.M(CustomHandler c)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'C.M($""Literal"")') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null) (Syntax: '$""Literal""') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""Literal""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B4] + Leaving: {R1} +} +Block[B4] - Exit + Predecessors: [B3] + Statements (0) "; - VerifyOperationTreeAndDiagnosticsForTest(new[] { code, handler }, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, InterpolatedStringHandlerArgumentAttribute, InterpolatedStringHandlerAttribute }, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void InterpolatedStringHandlerArgumentsAttribute_ConversionFromArgumentType() + { + var code = @" +using System; +using System.Globalization; +using System.Runtime.CompilerServices; + +public class C +{ + public static void M1(int i) + /**/{ + C.M(i, $""literal{1}""); + }/**/ + + public static implicit operator C(int i) => throw null; + public static implicit operator C(double d) + { + Console.WriteLine(d.ToString(""G"", CultureInfo.InvariantCulture)); + return new C(); + } + public override string ToString() => ""C""; + + public static void M(double d, [InterpolatedStringHandlerArgument(""d"")] CustomHandler handler) => Console.WriteLine(handler.ToString()); +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, C c) : this(literalLength, formattedCount) { } +} +"; + + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: true); + + var expectedDiagnostics = DiagnosticDescription.None; + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'i') + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Double, IsImplicit) (Syntax: 'i') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (ImplicitNumeric) + Operand: + IParameterReferenceOperation: i (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'i') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, C c)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'i') + IConversionOperation (TryCast: False, Unchecked) (OperatorMethod: C C.op_Implicit(System.Double d)) (OperationKind.Conversion, Type: C, IsImplicit) (Syntax: 'i') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: True) (MethodSymbol: C C.op_Implicit(System.Double d)) + (ImplicitUserDefined) + Operand: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Double, IsImplicit) (Syntax: 'i') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + Jump if False (Regular) to Block[B3] + IInvocationOperation ( System.Boolean CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Boolean, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B2] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IInvocationOperation ( System.Boolean CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Boolean, IsImplicit) (Syntax: '{1}') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: '1') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{1}') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{1}') + IDefaultValueOperation (OperationKind.DefaultValue, Type: System.String, Constant: null, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B3] + Block[B3] - Block + Predecessors: [B1] [B2] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'C.M(i, $""literal{1}"");') + Expression: + IInvocationOperation (void C.M(System.Double d, CustomHandler handler)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'C.M(i, $""literal{1}"")') + Instance Receiver: + null + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: d) (OperationKind.Argument, Type: null) (Syntax: 'i') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Double, IsImplicit) (Syntax: 'i') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: handler) (OperationKind.Argument, Type: null) (Syntax: '$""literal{1}""') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B4] + Leaving: {R1} +} +Block[B4] - Exit + Predecessors: [B3] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, InterpolatedStringHandlerArgumentAttribute, handler }, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void DiscardsUsedAsParameters() + { + var code = @" +using System; +using System.Runtime.CompilerServices; + +public class C +{ + public static void M1() + /**/{ + C.M(out _, $""literal{1}""); + }/**/ + + public static void M(out int i, [InterpolatedStringHandlerArgument(""i"")]CustomHandler c) + { + i = 0; + } +} + +public partial struct CustomHandler +{ + public CustomHandler(int literalLength, int formattedCount, out int i) : this(literalLength, formattedCount) + { + i = 1; + } +} +"; + + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial struct", useBoolReturns: false); + + var expectedDiagnostics = DiagnosticDescription.None; + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (4) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + Value: + IObjectCreationOperation (Constructor: CustomHandler..ctor(System.Int32 literalLength, System.Int32 formattedCount, out System.Int32 i)) (OperationKind.ObjectCreation, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literalLength) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 7, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: formattedCount) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '$""literal{1}""') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: i) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'out _') + IDiscardOperation (Symbol: System.Int32 _) (OperationKind.Discard, Type: System.Int32) (Syntax: '_') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Initializer: + null + IInvocationOperation ( void CustomHandler.AppendLiteral(System.String literal)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'literal') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: literal) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'literal') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""literal"") (Syntax: 'literal') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IInvocationOperation ( void CustomHandler.AppendFormatted(System.Object o, [System.Int32 alignment = 0], [System.String format = null])) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: '{1}') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: o) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '1') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: '1') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: alignment) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{1}') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: format) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '{1}') + IDefaultValueOperation (OperationKind.DefaultValue, Type: System.String, Constant: null, IsImplicit) (Syntax: '{1}') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'C.M(out _, ... teral{1}"");') + Expression: + IInvocationOperation (void C.M(out System.Int32 i, CustomHandler c)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'C.M(out _, ... iteral{1}"")') + Instance Receiver: + null + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: i) (OperationKind.Argument, Type: null) (Syntax: 'out _') + IDiscardOperation (Symbol: System.Int32 _) (OperationKind.Discard, Type: System.Int32) (Syntax: '_') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: c) (OperationKind.Argument, Type: null) (Syntax: '$""literal{1}""') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: CustomHandler, IsImplicit) (Syntax: '$""literal{1}""') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"; + VerifyFlowGraphAndDiagnosticsForTest(new[] { code, InterpolatedStringHandlerArgumentAttribute, handler }, expectedFlowGraph, expectedDiagnostics); } } } diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IInvocationOperation.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IInvocationOperation.cs index 7c9b02b8fa03a..602074ec9fc82 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IInvocationOperation.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IInvocationOperation.cs @@ -86,7 +86,7 @@ public static void M2() string expectedOperationTree = @" IInvalidOperation (OperationKind.Invalid, Type: System.Void, IsInvalid) (Syntax: 'C.M1()') Children(1): - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'C') + IOperation: (OperationKind.None, Type: C, IsInvalid) (Syntax: 'C') "; var expectedDiagnostics = new DiagnosticDescription[] { // file.cs(8,19): error CS0120: An object reference is required for the non-static field, method, or property 'C.M1()' diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_INoneOperation.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_INoneOperation.cs index 488b6dd3a83aa..0b3a4be6ec148 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_INoneOperation.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_INoneOperation.cs @@ -318,7 +318,7 @@ public static void Main(bool a, int b, int c, int* i, int j) Left: IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, IsImplicit) (Syntax: 'j') Right: - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'i[1, (a ? b : c)]') + IOperation: (OperationKind.None, Type: System.Int32, IsInvalid) (Syntax: 'i[1, (a ? b : c)]') Children(2): IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32*, IsInvalid, IsImplicit) (Syntax: 'i') IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid, IsImplicit) (Syntax: 'i[1, (a ? b : c)]') diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IParameterReferenceExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IParameterReferenceExpression.cs index d9d84be826ad6..8598001ee5210 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IParameterReferenceExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IParameterReferenceExpression.cs @@ -480,7 +480,7 @@ public unsafe int M(int *x) } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null) (Syntax: '*x') +IOperation: (OperationKind.None, Type: System.Int32) (Syntax: '*x') Children(1): IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32*) (Syntax: 'x') "; @@ -515,7 +515,7 @@ public unsafe void M(int[] array) IVariableDeclaratorOperation (Symbol: System.Int32* p) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'p = array') Initializer: IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= array') - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: 'array') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: 'array') Children(1): IParameterReferenceOperation: array (OperationKind.ParameterReference, Type: System.Int32[]) (Syntax: 'array') "; @@ -542,7 +542,7 @@ public System.Type M(System.TypedReference x) } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null) (Syntax: '__reftype(x)') +IOperation: (OperationKind.None, Type: System.Type) (Syntax: '__reftype(x)') Children(1): IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.TypedReference) (Syntax: 'x') "; @@ -565,7 +565,7 @@ public void M(System.Type x) } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null) (Syntax: '__makeref(x)') +IOperation: (OperationKind.None, Type: System.TypedReference) (Syntax: '__makeref(x)') Children(1): IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Type) (Syntax: 'x') "; @@ -588,7 +588,7 @@ public void M(System.TypedReference x) } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null) (Syntax: '__refvalue(x, int)') +IOperation: (OperationKind.None, Type: System.Int32) (Syntax: '__refvalue(x, int)') Children(1): IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.TypedReference) (Syntax: 'x') "; @@ -730,7 +730,7 @@ public unsafe void M(int x) } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null) (Syntax: 'stackalloc int[x]') +IOperation: (OperationKind.None, Type: System.Int32*) (Syntax: 'stackalloc int[x]') Children(1): IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'x') "; diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IPointerIndirectionReferenceOperation.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IPointerIndirectionReferenceOperation.cs index 62efe250cfe61..03e609aba93d5 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IPointerIndirectionReferenceOperation.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IPointerIndirectionReferenceOperation.cs @@ -46,7 +46,7 @@ struct S { } Left: IParameterReferenceOperation: s (OperationKind.ParameterReference, Type: C.S) (Syntax: 's') Right: - IOperation: (OperationKind.None, Type: null) (Syntax: '*sp') + IOperation: (OperationKind.None, Type: C.S) (Syntax: '*sp') Children(1): IParameterReferenceOperation: sp (OperationKind.ParameterReference, Type: C.S*) (Syntax: 'sp') @@ -90,7 +90,7 @@ struct S { public int x; } Left: IFieldReferenceOperation: System.Int32 C.S.x (OperationKind.FieldReference, Type: System.Int32) (Syntax: 'sp->x') Instance Receiver: - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: 'sp') + IOperation: (OperationKind.None, Type: C.S, IsImplicit) (Syntax: 'sp') Children(1): IParameterReferenceOperation: sp (OperationKind.ParameterReference, Type: C.S*) (Syntax: 'sp') Right: @@ -104,7 +104,7 @@ struct S { public int x; } Right: IFieldReferenceOperation: System.Int32 C.S.x (OperationKind.FieldReference, Type: System.Int32) (Syntax: 'sp->x') Instance Receiver: - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: 'sp') + IOperation: (OperationKind.None, Type: C.S, IsImplicit) (Syntax: 'sp') Children(1): IParameterReferenceOperation: sp (OperationKind.ParameterReference, Type: C.S*) (Syntax: 'sp') diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IPropertyReferenceExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IPropertyReferenceExpression.cs index 065bc15ae248f..8a2a833c06756 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IPropertyReferenceExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IPropertyReferenceExpression.cs @@ -269,7 +269,7 @@ public static void M() Instance Receiver: IInvalidOperation (OperationKind.Invalid, Type: C, IsInvalid, IsImplicit) (Syntax: 'C') Children(1): - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'C') + IOperation: (OperationKind.None, Type: C, IsInvalid) (Syntax: 'C') Arguments(1): IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: i) (OperationKind.Argument, Type: null) (Syntax: '1') ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') @@ -309,7 +309,7 @@ public static void M() Instance Receiver: IInvalidOperation (OperationKind.Invalid, Type: C, IsInvalid, IsImplicit) (Syntax: 'C') Children(1): - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'C') + IOperation: (OperationKind.None, Type: C, IsInvalid) (Syntax: 'C') Arguments(1): IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: i) (OperationKind.Argument, Type: null) (Syntax: '1') ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUnaryOperatorExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUnaryOperatorExpression.cs index e8b7c78e00869..80d24a5965ec5 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUnaryOperatorExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUnaryOperatorExpression.cs @@ -3326,7 +3326,7 @@ unsafe void Method() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null) (Syntax: '*p2') +IOperation: (OperationKind.None, Type: System.Int32) (Syntax: '*p2') Children(1): ILocalReferenceOperation: p2 (OperationKind.LocalReference, Type: System.Int32*) (Syntax: 'p2') "; diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IVariableDeclaration.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IVariableDeclaration.cs index 97c64b0a83c19..06d72bea0319d 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IVariableDeclaration.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IVariableDeclaration.cs @@ -1422,7 +1422,7 @@ static void Main(string[] args) IVariableDeclaratorOperation (Symbol: System.Int32* p) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'p = &reference.i1') Initializer: IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= &reference.i1') - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '&reference.i1') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: '&reference.i1') Children(1): IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*) (Syntax: '&reference.i1') Reference: @@ -1468,7 +1468,7 @@ static void Main(string[] args) IVariableDeclaratorOperation (Symbol: System.Int32* p1) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'p1 = &reference.i1') Initializer: IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= &reference.i1') - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '&reference.i1') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: '&reference.i1') Children(1): IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*) (Syntax: '&reference.i1') Reference: @@ -1478,7 +1478,7 @@ static void Main(string[] args) IVariableDeclaratorOperation (Symbol: System.Int32* p2) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'p2 = &reference.i2') Initializer: IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= &reference.i2') - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '&reference.i2') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: '&reference.i2') Children(1): IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*) (Syntax: '&reference.i2') Reference: @@ -1728,7 +1728,7 @@ static void Main(string[] args) IVariableDeclaratorOperation (Symbol: System.Int32* p1) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'p1 = &reference.i1') Initializer: IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= &reference.i1') - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '&reference.i1') + IOperation: (OperationKind.None, Type: System.Int32*, IsImplicit) (Syntax: '&reference.i1') Children(1): IAddressOfOperation (OperationKind.AddressOf, Type: System.Int32*) (Syntax: '&reference.i1') Reference: diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_InvalidExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_InvalidExpression.cs index 893cfb2bc2595..4e10cee0ee03b 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_InvalidExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_InvalidExpression.cs @@ -35,7 +35,7 @@ static void Main(string[] args) Children(1): IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: 'Console.WriteLine2') Children(1): - IOperation: (OperationKind.None, Type: null) (Syntax: 'Console') + IOperation: (OperationKind.None, Type: System.Console) (Syntax: 'Console') "; var expectedDiagnostics = new DiagnosticDescription[] { // CS0117: 'Console' does not contain a definition for 'WriteLine2' @@ -428,7 +428,7 @@ static void Main(string[] args) Operand: IInvalidOperation (OperationKind.Invalid, Type: Program, IsInvalid, IsImplicit) (Syntax: 'Program') Children(1): - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'Program') + IOperation: (OperationKind.None, Type: Program, IsInvalid) (Syntax: 'Program') "; var expectedDiagnostics = new DiagnosticDescription[] { // CS0119: 'Program' is a type, which is not valid in the given context @@ -510,7 +510,7 @@ static void Main(string[] args) Dimension Sizes(1): IInvalidOperation (OperationKind.Invalid, Type: Program, IsInvalid, IsImplicit) (Syntax: 'Program') Children(1): - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'Program') + IOperation: (OperationKind.None, Type: Program, IsInvalid) (Syntax: 'Program') Initializer: IArrayInitializerOperation (1 elements) (OperationKind.ArrayInitializer, Type: null, IsInvalid) (Syntax: '{ { 1 } }') Element Values(1): @@ -591,7 +591,7 @@ void M() string expectedOperationTree = @" IInvalidOperation (OperationKind.Invalid, Type: System.String, IsInvalid) (Syntax: 'string.Form ... format: """")') Children(3): - IOperation: (OperationKind.None, Type: null) (Syntax: 'string') + IOperation: (OperationKind.None, Type: System.String) (Syntax: 'string') ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: """") (Syntax: '""""') ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: """") (Syntax: '""""') "; diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_InvalidStatement.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_InvalidStatement.cs index 7ad9585a3b98c..cc02edab1f6e3 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_InvalidStatement.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_InvalidStatement.cs @@ -90,7 +90,7 @@ static void Main(string[] args) Switch expression: IInvalidOperation (OperationKind.Invalid, Type: Program, IsInvalid, IsImplicit) (Syntax: 'Program') Children(1): - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'Program') + IOperation: (OperationKind.None, Type: Program, IsInvalid) (Syntax: 'Program') Sections: ISwitchCaseOperation (1 case clauses, 1 statements) (OperationKind.SwitchCase, Type: null, IsInvalid) (Syntax: 'case 1: ... break;') Clauses: diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_StackAllocArrayCreationAndInitializer.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_StackAllocArrayCreationAndInitializer.cs index cb5326df6ba22..8c9c8085db9a0 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_StackAllocArrayCreationAndInitializer.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_StackAllocArrayCreationAndInitializer.cs @@ -26,7 +26,7 @@ public void F() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc int[1]') +IOperation: (OperationKind.None, Type: System.Int32*, IsInvalid) (Syntax: 'stackalloc int[1]') Children(1): ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') "; @@ -54,7 +54,7 @@ public void F() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc M[1]') +IOperation: (OperationKind.None, Type: M*, IsInvalid) (Syntax: 'stackalloc M[1]') Children(1): ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') "; @@ -83,7 +83,7 @@ public void F() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc M[dimension]') +IOperation: (OperationKind.None, Type: M*, IsInvalid) (Syntax: 'stackalloc M[dimension]') Children(1): ILocalReferenceOperation: dimension (OperationKind.LocalReference, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: 'dimension') "; @@ -111,7 +111,7 @@ public void F(int dimension) } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc M[dimension]') +IOperation: (OperationKind.None, Type: M*, IsInvalid) (Syntax: 'stackalloc M[dimension]') Children(1): IParameterReferenceOperation: dimension (OperationKind.ParameterReference, Type: System.Int32, IsInvalid) (Syntax: 'dimension') "; @@ -139,7 +139,7 @@ public void F(char dimension) } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc M[dimension]') +IOperation: (OperationKind.None, Type: M*, IsInvalid) (Syntax: 'stackalloc M[dimension]') Children(1): IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'dimension') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -170,7 +170,7 @@ public void F(object dimension) } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc ... )dimension]') +IOperation: (OperationKind.None, Type: M*, IsInvalid) (Syntax: 'stackalloc ... )dimension]') Children(1): IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int32, IsInvalid) (Syntax: '(int)dimension') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -199,7 +199,7 @@ public void F() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc int[] { 42 }') +IOperation: (OperationKind.None, Type: System.Int32*, IsInvalid) (Syntax: 'stackalloc int[] { 42 }') Children(2): ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid, IsImplicit) (Syntax: 'stackalloc int[] { 42 }') ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 42, IsInvalid) (Syntax: '42') @@ -226,7 +226,7 @@ public void F() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc int[1] { 42 }') +IOperation: (OperationKind.None, Type: System.Int32*, IsInvalid) (Syntax: 'stackalloc int[1] { 42 }') Children(2): ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 42, IsInvalid) (Syntax: '42') @@ -253,7 +253,7 @@ public void F() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc int[2] { 42 }') +IOperation: (OperationKind.None, Type: System.Int32*, IsInvalid) (Syntax: 'stackalloc int[2] { 42 }') Children(2): ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2, IsInvalid) (Syntax: '2') ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 42, IsInvalid) (Syntax: '42') @@ -280,7 +280,7 @@ public void F(int dimension) } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc ... ion] { 42 }') +IOperation: (OperationKind.None, Type: System.Int32*, IsInvalid) (Syntax: 'stackalloc ... ion] { 42 }') Children(2): IParameterReferenceOperation: dimension (OperationKind.ParameterReference, Type: System.Int32, IsInvalid) (Syntax: 'dimension') ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 42) (Syntax: '42') @@ -309,7 +309,7 @@ public void F() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc ... { new M() }') +IOperation: (OperationKind.None, Type: M*, IsInvalid) (Syntax: 'stackalloc ... { new M() }') Children(2): ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid, IsImplicit) (Syntax: 'stackalloc ... { new M() }') IObjectCreationOperation (Constructor: M..ctor()) (OperationKind.ObjectCreation, Type: M, IsInvalid) (Syntax: 'new M()') @@ -341,7 +341,7 @@ public void F() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc[] { new M() }') +IOperation: (OperationKind.None, Type: M*, IsInvalid) (Syntax: 'stackalloc[] { new M() }') Children(2): ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid, IsImplicit) (Syntax: 'stackalloc[] { new M() }') IObjectCreationOperation (Constructor: M..ctor()) (OperationKind.ObjectCreation, Type: M, IsInvalid) (Syntax: 'new M()') @@ -371,7 +371,7 @@ public void F(int dimension) } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc[]/**/') +IOperation: (OperationKind.None, Type: ?*, IsInvalid) (Syntax: 'stackalloc[]/**/') Children(1): ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsInvalid, IsImplicit) (Syntax: 'stackalloc[]/**/') "; @@ -403,7 +403,7 @@ public void F(int dimension) } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc[2]/**/') +IOperation: (OperationKind.None, Type: ?*, IsInvalid) (Syntax: 'stackalloc[2]/**/') Children(1): ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsInvalid, IsImplicit) (Syntax: 'stackalloc[2]/**/') "; @@ -439,7 +439,7 @@ public void F() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc[ ... , default }') +IOperation: (OperationKind.None, Type: System.Int32*, IsInvalid) (Syntax: 'stackalloc[ ... , default }') Children(4): ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 3, IsInvalid, IsImplicit) (Syntax: 'stackalloc[ ... , default }') ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2, IsInvalid) (Syntax: '2') @@ -471,7 +471,7 @@ public void F() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc int[]') +IOperation: (OperationKind.None, Type: System.Int32*, IsInvalid) (Syntax: 'stackalloc int[]') Children(1): IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: '') Children(0) @@ -498,7 +498,7 @@ public void F() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc int[] { 1 }') +IOperation: (OperationKind.None, Type: System.Int32*, IsInvalid) (Syntax: 'stackalloc int[] { 1 }') Children(2): ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid, IsImplicit) (Syntax: 'stackalloc int[] { 1 }') ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') @@ -525,7 +525,7 @@ public void F(object b) } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc int[b]') +IOperation: (OperationKind.None, Type: System.Int32*, IsInvalid) (Syntax: 'stackalloc int[b]') Children(1): IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'b') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -559,7 +559,7 @@ public void F() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc int[M()]') +IOperation: (OperationKind.None, Type: System.Int32*, IsInvalid) (Syntax: 'stackalloc int[M()]') Children(1): IInvocationOperation ( System.Int32 C.M()) (OperationKind.Invocation, Type: System.Int32, IsInvalid) (Syntax: 'M()') Instance Receiver: @@ -590,7 +590,7 @@ public void F() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc int[(int)M()]') +IOperation: (OperationKind.None, Type: System.Int32*, IsInvalid) (Syntax: 'stackalloc int[(int)M()]') Children(1): IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int32, IsInvalid) (Syntax: '(int)M()') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -624,7 +624,7 @@ public static void F() } "; string expectedOperationTree = @" - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc int[M()]') + IOperation: (OperationKind.None, Type: System.Int32*, IsInvalid) (Syntax: 'stackalloc int[M()]') Children(1): IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'M()') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -656,7 +656,7 @@ public void F() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc int[(int)M()]') +IOperation: (OperationKind.None, Type: System.Int32*, IsInvalid) (Syntax: 'stackalloc int[(int)M()]') Children(1): IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int32, IsInvalid) (Syntax: '(int)M()') Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -688,7 +688,7 @@ public void F() } "; string expectedOperationTree = @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'stackalloc int[0.0]') +IOperation: (OperationKind.None, Type: System.Int32*, IsInvalid) (Syntax: 'stackalloc int[0.0]') Children(1): IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int32, Constant: 0, IsInvalid, IsImplicit) (Syntax: '0.0') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: True, IsReference: False, IsUserDefined: False) (MethodSymbol: null) diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs index 89ffcef4a8677..15fbac74ae714 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs @@ -8768,5 +8768,114 @@ public CustomHandler(int literalLength, int formattedCount" + (validityParameter } #endregion + + [Fact] + public void TestDataFlowsArrayInit_01() + { + var analysis = CompileAndAnalyzeDataFlowExpression(@" +class C { + public void F(int x) + { + int a = 1, y = 2; + int[] b = /**/{ a + x + 3 } /**/; + int c = a + 4 + y; + } +}"); + Assert.Equal("x, a", GetSymbolNamesJoined(analysis.DataFlowsIn)); + } + + [Fact] + public void TestDataFlowsArrayInit_02() + { + var analysis = CompileAndAnalyzeDataFlowExpression(@" +class C { + public void F(int x) + { + int a = 1, y = 2; + int[,] b = /**/{ { a + x + 3 } }/**/; + int c = a + 4 + y; + } +}"); + Assert.Equal("x, a", GetSymbolNamesJoined(analysis.DataFlowsIn)); + } + + [Fact] + public void TestDataFlowsArrayInit_03() + { + var analysis = CompileAndAnalyzeDataFlowExpression(@" +class C { + public void F(int x) + { + int a = 1, y = 2; + int[,] b = {/**/{ a + x + 3 } /**/}; + int c = a + 4 + y; + } +}"); + Assert.Equal("x, a", GetSymbolNamesJoined(analysis.DataFlowsIn)); + } + + [Fact] + [WorkItem(57572, "https://github.com/dotnet/roslyn/issues/57572")] + public void TestDataFlowsArrayInit_04() + { + var analysis = CompileAndAnalyzeDataFlowExpression(@" +class C { + public void F(int x) + { + int a = 1, y = 2; + int[] b = new int[] /**/{ a + x + 3 } /**/; + int c = a + 4 + y; + } +}"); + Assert.Equal("x, a", GetSymbolNamesJoined(analysis.DataFlowsIn)); + } + + [Fact] + [WorkItem(57572, "https://github.com/dotnet/roslyn/issues/57572")] + public void TestDataFlowsArrayInit_05() + { + var analysis = CompileAndAnalyzeDataFlowExpression(@" +class C { + public void F(int x) + { + int a = 1, y = 2; + int[,] b = new int[,] /**/{ {a + x + 3} } /**/; + int c = a + 4 + y; + } +}"); + Assert.Equal("x, a", GetSymbolNamesJoined(analysis.DataFlowsIn)); + } + + [Fact] + [WorkItem(57572, "https://github.com/dotnet/roslyn/issues/57572")] + public void TestDataFlowsArrayInit_06() + { + var analysis = CompileAndAnalyzeDataFlowExpression(@" +class C { + public void F(int x) + { + int a = 1, y = 2; + int[,] b = new int[,] {/**/{ a + x + 3 } /**/}; + int c = a + 4 + y; + } +}"); + Assert.Equal("x, a", GetSymbolNamesJoined(analysis.DataFlowsIn)); + } + + [Fact] + public void TestDataFlowsObjectInit() + { + var analysis = CompileAndAnalyzeDataFlowExpression(@" +class C { + public int Data; + public void F(int x) + { + int a = 1, y = 2; + var b = new object() /**/{ Data = a + x + 3 } /**/; + int c = a + 4 + y; + } +}"); + Assert.Equal("x, a", GetSymbolNamesJoined(analysis.DataFlowsIn)); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs index 7c2db9f5893ba..ba844c625f93a 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs @@ -908,8 +908,8 @@ static void Main() ITupleOperation (OperationKind.Tuple, Type: (System.Int32, System.String), IsInvalid) (Syntax: '(int, string)') NaturalType: (System.Int32, System.String) Elements(2): - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'int') - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'string') + IOperation: (OperationKind.None, Type: System.Int32, IsInvalid) (Syntax: 'int') + IOperation: (OperationKind.None, Type: System.String, IsInvalid) (Syntax: 'string') Arguments(0) "; var expectedDiagnostics = new DiagnosticDescription[] { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs index fc21e2c84c9c1..f41b2be42c353 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs @@ -4371,6 +4371,58 @@ static void Main() CompileAndVerify(source, expectedOutput: expectedOutput); } + [WorkItem(57627, "https://github.com/dotnet/roslyn/issues/57627")] + [Fact] + public void OverloadResolution_48() + { + var source = +@"using System; +using System.Threading.Tasks; + +delegate void MyAction(); +delegate T MyFunc(); + +class A +{ + public static void F(object o) { Console.WriteLine(""F(object o)""); } + public static void F(object o, string format, params object[] args) { Console.WriteLine(""F(object o, string format, params object[] args)""); } + + public static void F(T t) { Console.WriteLine(""F(T t)""); } + public static void F(T t, string format, params object[] args) { Console.WriteLine(""F(T t, string format, params object[] args)""); } + + public static void F(MyAction a) { Console.WriteLine(""F(MyAction a)""); } + public static void F(MyAction a, string format, params object[] args) { Console.WriteLine(""F(MyAction a, string format, params object[] args)""); } + + public static void F(MyFunc f) { Console.WriteLine(""F(MyFunc f)""); } + public static void F(MyFunc f, string format, params object[] args) { Console.WriteLine(""F(MyFunc f, string format, params object[] args)""); } +} + +class B +{ + static async Task Main() + { + A.F(() => { }); + A.F(() => { }, """"); + A.F(() => { }, ""{0}"", 1); + A.F(async () => await Task.FromResult(null)); + A.F(async () => await Task.FromResult(null), """"); + A.F(async () => await Task.FromResult(null), ""{0}"", 1); + } +}"; + + string expectedOutput = +@"F(MyAction a) +F(MyAction a, string format, params object[] args) +F(MyAction a, string format, params object[] args) +F(MyFunc f) +F(MyFunc f, string format, params object[] args) +F(MyFunc f, string format, params object[] args) +"; + CompileAndVerify(source, parseOptions: TestOptions.Regular9, expectedOutput: expectedOutput); + CompileAndVerify(source, parseOptions: TestOptions.Regular10, expectedOutput: expectedOutput); + CompileAndVerify(source, expectedOutput: expectedOutput); + } + [Fact] public void BestCommonType_01() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/FunctionPointerTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/FunctionPointerTests.cs index 717b9416f6a7a..402c0666733f0 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/FunctionPointerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/FunctionPointerTests.cs @@ -2877,12 +2877,12 @@ void M(delegate* ptr, C c) // (7,12): error CS0023: Operator '?' cannot be applied to operand of type 'delegate*' // ptr?.ToString(); Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "delegate*").WithLocation(7, 12), - // (8,16): error CS0023: Operator '?' cannot be applied to operand of type 'delegate*' + // (8,17): error CS8977: 'delegate*' cannot be made nullable. // ptr = c?.GetPtr(); - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "delegate*").WithLocation(8, 16), - // (9,11): error CS0023: Operator '?' cannot be applied to operand of type 'delegate*' + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".GetPtr()").WithArguments("delegate*").WithLocation(8, 17), + // (9,12): error CS8977: 'delegate*' cannot be made nullable. // (c?.GetPtr())(); - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "delegate*").WithLocation(9, 11) + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".GetPtr()").WithArguments("delegate*").WithLocation(9, 12) ); var tree = comp.SyntaxTrees[0]; @@ -2920,14 +2920,14 @@ void M(delegate* ptr, C c) VerifyOperationTreeForNode(comp, model, invocations[1], expectedOperationTree: @" IConditionalAccessOperation (OperationKind.ConditionalAccess, Type: ?, IsInvalid) (Syntax: 'c?.GetPtr()') - Operation: - IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid, IsImplicit) (Syntax: 'c') + Operation: + IInvalidOperation (OperationKind.Invalid, Type: ?, IsImplicit) (Syntax: 'c') Children(1): - IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C, IsInvalid) (Syntax: 'c') - WhenNotNull: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + WhenNotNull: IInvocationOperation ( delegate* C.GetPtr()) (OperationKind.Invocation, Type: delegate*, IsInvalid) (Syntax: '.GetPtr()') - Instance Receiver: - IConditionalAccessInstanceOperation (OperationKind.ConditionalAccessInstance, Type: C, IsInvalid, IsImplicit) (Syntax: 'c') + Instance Receiver: + IConditionalAccessInstanceOperation (OperationKind.ConditionalAccessInstance, Type: C, IsImplicit) (Syntax: 'c') Arguments(0) "); @@ -2941,14 +2941,14 @@ void M(delegate* ptr, C c) IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: '(c?.GetPtr())()') Children(1): IConditionalAccessOperation (OperationKind.ConditionalAccess, Type: ?, IsInvalid) (Syntax: 'c?.GetPtr()') - Operation: - IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid, IsImplicit) (Syntax: 'c') + Operation: + IInvalidOperation (OperationKind.Invalid, Type: ?, IsImplicit) (Syntax: 'c') Children(1): - IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C, IsInvalid) (Syntax: 'c') - WhenNotNull: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + WhenNotNull: IInvocationOperation ( delegate* C.GetPtr()) (OperationKind.Invocation, Type: delegate*, IsInvalid) (Syntax: '.GetPtr()') - Instance Receiver: - IConditionalAccessInstanceOperation (OperationKind.ConditionalAccessInstance, Type: C, IsInvalid, IsImplicit) (Syntax: 'c') + Instance Receiver: + IConditionalAccessInstanceOperation (OperationKind.ConditionalAccessInstance, Type: C, IsImplicit) (Syntax: 'c') Arguments(0) "); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 09d8ebf1b08e0..1ea482e8cf0ed 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -77503,9 +77503,9 @@ static T F(Func? f) }"; var comp = CreateCompilation(new[] { source }, options: WithNullableEnable()); comp.VerifyDiagnostics( - // (6,17): error CS0023: Operator '?' cannot be applied to operand of type 'T' + // (6,18): error CS8977: 'T' cannot be made nullable. // return f?.Invoke(); - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "T").WithLocation(6, 17) + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".Invoke()").WithArguments("T").WithLocation(6, 18) ); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs index d4676cf594cf0..a65f2332c223d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs @@ -11214,5 +11214,119 @@ static unsafe void M(dynamic d, int* p) Diagnostic(ErrorCode.ERR_BadBinaryOps, "d += p").WithArguments("+=", "dynamic", "int*").WithLocation(5, 9) ); } + + [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] + public void LiftedUnaryOperator_InvalidTypeArgument01() + { + var code = @" +S1? s1 = default; +var s2 = +s1; + +struct S1 +{ + public static S2 operator+(S1 s1) => throw null; +} + +ref struct S2 {} +"; + + var comp = CreateCompilation(code); + comp.VerifyDiagnostics( + // (3,10): error CS0023: Operator '+' cannot be applied to operand of type 'S1?' + // var s2 = +s1; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "+s1").WithArguments("+", "S1?").WithLocation(3, 10) + ); + } + + [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] + public void LiftedUnaryOperator_InvalidTypeArgument02() + { + var code = @" +S1? s1 = default; +var s2 = +s1; + +unsafe struct S1 +{ + public static unsafe int* operator+(S1 s1) => throw null; +} +"; + + var comp = CreateCompilation(code, options: TestOptions.UnsafeReleaseExe); + comp.VerifyDiagnostics( + // (3,10): error CS0023: Operator '+' cannot be applied to operand of type 'S1?' + // var s2 = +s1; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "+s1").WithArguments("+", "S1?").WithLocation(3, 10) + ); + } + + [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] + public void LiftedBinaryOperator_InvalidTypeArgument01() + { + var code = @" +var x = new S1(); +int? y = 1; +(x + y)?.M(); + +public readonly ref struct S1 +{ + public static S1 operator+ (S1 x, int y) => throw null; + public void M() {} +} +"; + + var comp = CreateCompilation(code); + comp.VerifyDiagnostics( + // (4,2): error CS0019: Operator '+' cannot be applied to operands of type 'S1' and 'int?' + // (x + y)?.M(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "x + y").WithArguments("+", "S1", "int?").WithLocation(4, 2) + ); + } + + [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] + public void LiftedBinaryOperator_InvalidTypeArgument02() + { + var code = @" +var x = new S1(); +int? y = 1; +(y + x)?.M(); + +public readonly ref struct S1 +{ + public static S1 operator+ (int y, S1 x) => throw null; + public void M() {} +} +"; + + var comp = CreateCompilation(code); + comp.VerifyDiagnostics( + // (4,2): error CS0019: Operator '+' cannot be applied to operands of type 'int?' and 'S1' + // (y + x)?.M(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "y + x").WithArguments("+", "int?", "S1").WithLocation(4, 2) + ); + } + + [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] + public void LiftedBinaryOperator_InvalidTypeArgument03() + { + var code = @" +var x = new S1(); +int? y = 1; +(y > x).ToString(); + +public readonly ref struct S1 +{ + public static bool operator >(int y, S1 x) => throw null; + public static bool operator <(int y, S1 x) => throw null; + public void M() {} +} +"; + + var comp = CreateCompilation(code); + comp.VerifyDiagnostics( + // (4,2): error CS0019: Operator '>' cannot be applied to operands of type 'int?' and 'S1' + // (y > x).ToString(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "y > x").WithArguments(">", "int?", "S1").WithLocation(4, 2) + ); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs index 83a4312ec5bfd..fb9867f8baa30 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs @@ -3520,7 +3520,7 @@ class C VerifyOperationTree(compilation, initializerOperation.Parent.Parent, @" IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsImplicit) (Syntax: ':base(TakeO ... && x1 >= 5)') Expression: - IOperation: (OperationKind.None, Type: null, IsImplicit) (Syntax: '(TakeOutPar ... && x1 >= 5)') + IOperation: (OperationKind.None, Type: System.Void, IsImplicit) (Syntax: '(TakeOutPar ... && x1 >= 5)') Children(1): IInvocationOperation ( C..ctor(System.Boolean b)) (OperationKind.Invocation, Type: System.Void) (Syntax: ':base(TakeO ... && x1 >= 5)') Instance Receiver: diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs index 6566129e0fbb1..10e4e7f56ef71 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs @@ -656,5 +656,99 @@ class C var comp = CreateCompilation(sourceBuilder.ToString()); comp.VerifyDiagnostics(); } + + [ConditionalFact(typeof(IsRelease))] + public void DefiniteAssignment_ManySwitchCasesAndLabels() + { + const int nLabels = 1500; + + // #nullable enable + // class Program + // { + // static int GetIndex() => 0; + // static void Main() + // { + // int index = 0; + // int tmp1; + // int tmp2; // unused + // goto L1498; + // L0: + // if (index < 64) goto LSwitch; + // L1: + // tmp1 = GetIndex(); + // if (index != tmp1) + // { + // if (index < 64) goto LSwitch; + // goto L0; + // } + // // repeat for L2:, ..., L1498: + // // ... + // L1499: + // tmp1 = GetIndex(); + // return; + // LSwitch: + // int tmp3 = index + 1; + // switch (GetIndex()) + // { + // case 0: + // index++; + // goto L0; + // // repeat for case 1:, ..., case 1499: + // // ... + // default: + // break; + // } + // } + // } + + var builder = new StringBuilder(); + builder.AppendLine("#nullable enable"); + builder.AppendLine("class Program"); + builder.AppendLine("{"); + builder.AppendLine(" static int GetIndex() => 0;"); + builder.AppendLine(" static void Main()"); + builder.AppendLine(" {"); + builder.AppendLine(" int index = 0;"); + builder.AppendLine(" int tmp1;"); + builder.AppendLine(" int tmp2; // unused"); + builder.AppendLine($" goto L{nLabels - 2};"); + builder.AppendLine("L0:"); + builder.AppendLine(" if (index < 64) goto LSwitch;"); + for (int i = 0; i < nLabels - 2; i++) + { + builder.AppendLine($"L{i + 1}:"); + builder.AppendLine(" tmp1 = GetIndex();"); + builder.AppendLine(" if (index != tmp1)"); + builder.AppendLine(" {"); + builder.AppendLine(" if (index < 64) goto LSwitch;"); + builder.AppendLine($" goto L{i};"); + builder.AppendLine(" }"); + } + builder.AppendLine($"L{nLabels - 1}:"); + builder.AppendLine(" tmp1 = GetIndex();"); + builder.AppendLine(" return;"); + builder.AppendLine("LSwitch:"); + builder.AppendLine(" int tmp3 = index + 1;"); + builder.AppendLine(" switch (GetIndex())"); + builder.AppendLine(" {"); + for (int i = 0; i < nLabels; i++) + { + builder.AppendLine($" case {i}:"); + builder.AppendLine(" index++;"); + builder.AppendLine($" goto L{i};"); + } + builder.AppendLine(" default:"); + builder.AppendLine(" break;"); + builder.AppendLine(" }"); + builder.AppendLine(" }"); + builder.AppendLine("}"); + + var source = builder.ToString(); + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (9,13): warning CS0168: The variable 'tmp2' is declared but never used + // int tmp2; // unused + Diagnostic(ErrorCode.WRN_UnreferencedVar, "tmp2").WithArguments("tmp2").WithLocation(9, 13)); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index 8c09d3324bf2d..b3718b18db2e6 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -3576,12 +3576,12 @@ void M() S Test() => default; }", options: TestOptions.ReleaseDll).VerifyDiagnostics( - // (8,22): error CS0023: Operator '?' cannot be applied to operand of type 'S' + // (8,23): error CS8977: 'S' cannot be made nullable. // _ = ((C)null)?.Test(); - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "S").WithLocation(8, 22), - // (10,26): error CS0023: Operator '?' cannot be applied to operand of type 'S' + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".Test()").WithArguments("S").WithLocation(8, 23), + // (10,27): error CS8977: 'S' cannot be made nullable. // var a = ((C)null)?.Test(); - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "S").WithLocation(10, 26) + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".Test()").WithArguments("S").WithLocation(10, 27) ); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs index 78ee679ca5466..30684e46ad110 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs @@ -7355,7 +7355,7 @@ public static void Main () compilation.VerifyOperationTree(node, expectedOperationTree: @" -IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'i[1,2]') +IOperation: (OperationKind.None, Type: System.Int32, IsInvalid) (Syntax: 'i[1,2]') Children(2): ILocalReferenceOperation: i (OperationKind.LocalReference, Type: System.Int32*, IsInvalid) (Syntax: 'i') IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid, IsImplicit) (Syntax: 'i[1,2]') @@ -23761,9 +23761,9 @@ static void Main(string[] args) // (14,18): error CS8026: Feature 'null propagation operator' is not available in C# 5. Please use language version 6 or greater. // var x1 = p.P1 ?.ToString; Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion5, "p.P1 ?.ToString").WithArguments("null propagating operator", "6").WithLocation(14, 18), - // (14,23): error CS0023: Operator '?' cannot be applied to operand of type 'method group' + // (14,24): error CS8977: 'method group' cannot be made nullable. // var x1 = p.P1 ?.ToString; - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "method group").WithLocation(14, 23) + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".ToString").WithArguments("method group").WithLocation(14, 24) ); } @@ -23788,10 +23788,10 @@ static void Main(string[] args) } "; CreateCompilationWithMscorlib45(text).VerifyDiagnostics( - // (14,23): error CS0023: Operator '?' cannot be applied to operand of type 'method group' - // var x1 = p.P1 ?.ToString; - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "method group").WithLocation(14, 23) - ); + // (14,24): error CS8977: 'method group' cannot be made nullable. + // var x1 = p.P1 ?.ToString; + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".ToString").WithArguments("method group").WithLocation(14, 24) + ); } @@ -23886,10 +23886,10 @@ unsafe static void Main() "; CreateCompilationWithMscorlib45(text, options: TestOptions.UnsafeReleaseDll).VerifyDiagnostics( - // (9,23): error CS0023: Operator '?' cannot be applied to operand of type 'void*' - // var p = intPtr?.ToPointer(); - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "void*").WithLocation(9, 23) - ); + // (9,24): error CS8977: 'void*' cannot be made nullable. + // var p = intPtr?.ToPointer(); + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".ToPointer()").WithArguments("void*").WithLocation(9, 24) + ); } [Fact] @@ -24287,15 +24287,15 @@ public ref struct S2 } "; CreateCompilationWithMscorlib45(text, options: TestOptions.ReleaseDll).VerifyDiagnostics( - // (10,18): error CS0023: Operator '?' cannot be applied to operand of type 'S2' + // (10,19): error CS8977: 'S2' cannot be made nullable. // var x = o?.F(); - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "S2").WithLocation(10, 18), - // (12,18): error CS0023: Operator '?' cannot be applied to operand of type 'S2' + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".F()").WithArguments("S2").WithLocation(10, 19), + // (12,19): error CS8977: 'S2' cannot be made nullable. // var y = o?.F() ?? default; - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "S2").WithLocation(12, 18), - // (14,18): error CS0023: Operator '?' cannot be applied to operand of type 'S1' + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".F()").WithArguments("S2").WithLocation(12, 19), + // (14,19): error CS8977: 'S1' cannot be made nullable. // var z = o?.F().field ?? default; - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "S1").WithLocation(14, 18) + Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".F().field").WithArguments("S1").WithLocation(14, 19) ); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/UserDefinedConversionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/UserDefinedConversionTests.cs index b7cb6641e57b9..b97f670c35d9b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/UserDefinedConversionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/UserDefinedConversionTests.cs @@ -1647,5 +1647,266 @@ private static C M2() // return (C) M1(); Diagnostic(ErrorCode.ERR_NoExplicitConv, "(C) M1()").WithArguments("void", "C").WithLocation(9, 16)); } + + [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] + public void LiftedConversion_InvalidTypeArgument01() + { + var code = @" +int? i = null; +C c = i; + +class C +{ + public static implicit operator C(long l) => throw null; +} + +namespace System +{ + public class Object {} + public class String {} + public class Exception {} + public class ValueType : Object {} + public struct Void {} + public struct Int32 {} + public struct Nullable where T : struct {} + public ref struct Int64 {} +} +"; + + var comp = CreateEmptyCompilation(code); + comp.VerifyDiagnostics( + // (3,7): error CS0266: Cannot implicitly convert type 'int?' to 'C'. An explicit conversion exists (are you missing a cast?) + // C c = i; + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "i").WithArguments("int?", "C").WithLocation(3, 7) + ); + } + + [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] + public void LiftedConversion_InvalidTypeArgument02() + { + var code = @" +C c = null; +M(c); + +void M(long? i) => throw null; + +class C +{ + public static implicit operator int(C c) => throw null; +} + +namespace System +{ + public class Object {} + public class String {} + public class Exception {} + public class ValueType : Object {} + public struct Void {} + public ref struct Int32 {} + public struct Nullable where T : struct { public Nullable(T value) {} } + public struct Int64 {} + public class Attribute {} +} +"; + + var comp = CreateEmptyCompilation(code); + var verifier = CompileAndVerify(comp, verify: Verification.Skipped); + + // Note that no int? is being created. + verifier.VerifyIL("", @" +{ + // Code size 18 (0x12) + .maxstack 1 + IL_0000: ldnull + IL_0001: call ""int C.op_Implicit(C)"" + IL_0006: conv.i8 + IL_0007: newobj ""long?..ctor(long)"" + IL_000c: call ""void Program.<
$>g__M|0_0(long?)"" + IL_0011: ret +} +"); + } + + [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] + public void LiftedConversion_InvalidTypeArgument03() + { + var code = @" +int? i = null; +C c = (C)i; + +class C +{ + public static explicit operator C(long l) => throw null; +} + +namespace System +{ + public class Object {} + public class String {} + public class Exception {} + public class ValueType : Object {} + public struct Void {} + public struct Int32 {} + public struct Nullable where T : struct { public T Value { get => throw null; } } + public ref struct Int64 {} + public class Attribute {} +} +"; + + var comp = CreateEmptyCompilation(code); + var verifier = CompileAndVerify(comp, verify: Verification.Skipped); + + verifier.VerifyIL("", @" +{ + // Code size 23 (0x17) + .maxstack 1 + .locals init (int? V_0) //i + IL_0000: ldloca.s V_0 + IL_0002: initobj ""int?"" + IL_0008: ldloca.s V_0 + IL_000a: call ""int int?.Value.get"" + IL_000f: conv.i8 + IL_0010: call ""C C.op_Explicit(long)"" + IL_0015: pop + IL_0016: ret +} +"); + } + + [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] + public void LiftedConversion_InvalidTypeArgument04() + { + var code = @" +C c = null; +M((long?)c); + +void M(long? i) => throw null; + +class C +{ + public static explicit operator int(C c) => throw null; +} + +namespace System +{ + public class Object {} + public class String {} + public class Exception {} + public class ValueType : Object {} + public struct Void {} + public ref struct Int32 {} + public struct Nullable where T : struct { public Nullable(T value) {} } + public struct Int64 {} + public class Attribute {} +} +"; + + var comp = CreateEmptyCompilation(code); + var verifier = CompileAndVerify(comp, verify: Verification.Skipped); + + // Note that no int? is being created. + verifier.VerifyIL("", @" +{ + // Code size 18 (0x12) + .maxstack 1 + IL_0000: ldnull + IL_0001: call ""int C.op_Explicit(C)"" + IL_0006: conv.i8 + IL_0007: newobj ""long?..ctor(long)"" + IL_000c: call ""void Program.<
$>g__M|0_0(long?)"" + IL_0011: ret +} +"); + } + + [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] + public void LiftedConversion_InvalidTypeArgument05() + { + var code = @" +unsafe +{ + S? s = null; + void* f = s; + System.Console.WriteLine((int)f); +} + +public struct S +{ + public static unsafe implicit operator void*(S v) => throw null; +} +"; + + var comp = CreateCompilation(code, options: TestOptions.UnsafeReleaseExe); + var verifier = CompileAndVerify(comp, expectedOutput: "0", verify: Verification.Skipped); + + verifier.VerifyIL("", @" +{ + // Code size 42 (0x2a) + .maxstack 1 + .locals init (S? V_0) + IL_0000: ldloca.s V_0 + IL_0002: initobj ""S?"" + IL_0008: ldloc.0 + IL_0009: stloc.0 + IL_000a: ldloca.s V_0 + IL_000c: call ""bool S?.HasValue.get"" + IL_0011: brtrue.s IL_0017 + IL_0013: ldc.i4.0 + IL_0014: conv.u + IL_0015: br.s IL_0023 + IL_0017: ldloca.s V_0 + IL_0019: call ""S S?.GetValueOrDefault()"" + IL_001e: call ""void* S.op_Implicit(S)"" + IL_0023: conv.i4 + IL_0024: call ""void System.Console.WriteLine(int)"" + IL_0029: ret +} +"); + } + + [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] + public void LiftedConversion_InvalidTypeArgument06() + { + var code = @" +unsafe +{ + S? s = null; + void* f = (void*)s; + System.Console.WriteLine((int)f); +} + +public struct S +{ + public static unsafe explicit operator void*(S v) => throw null; +} +"; + + var comp = CreateCompilation(code, options: TestOptions.UnsafeReleaseExe); + var verifier = CompileAndVerify(comp, expectedOutput: "0", verify: Verification.Skipped); + + verifier.VerifyIL("", @" +{ + // Code size 42 (0x2a) + .maxstack 1 + .locals init (S? V_0) + IL_0000: ldloca.s V_0 + IL_0002: initobj ""S?"" + IL_0008: ldloc.0 + IL_0009: stloc.0 + IL_000a: ldloca.s V_0 + IL_000c: call ""bool S?.HasValue.get"" + IL_0011: brtrue.s IL_0017 + IL_0013: ldc.i4.0 + IL_0014: conv.u + IL_0015: br.s IL_0023 + IL_0017: ldloca.s V_0 + IL_0019: call ""S S?.GetValueOrDefault()"" + IL_001e: call ""void* S.op_Explicit(S)"" + IL_0023: conv.i4 + IL_0024: call ""void System.Console.WriteLine(int)"" + IL_0029: ret +} +"); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/AnonymousTypesSemanticsTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/AnonymousTypesSemanticsTests.cs index 4a54c2baa58f9..da807ecd20a9b 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/AnonymousTypesSemanticsTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/AnonymousTypesSemanticsTests.cs @@ -1277,7 +1277,7 @@ public static void Test1(int x) Right: IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: 'ClassA.BB') Children(1): - IOperation: (OperationKind.None, Type: null) (Syntax: 'ClassA') + IOperation: (OperationKind.None, Type: ClassA) (Syntax: 'ClassA') ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: ?, IsInvalid, IsImplicit) (Syntax: 'ClassA.CCC') Left: IPropertyReferenceOperation: ? .CCC { get; } (OperationKind.PropertyReference, Type: ?, IsInvalid, IsImplicit) (Syntax: 'ClassA.CCC') @@ -1286,7 +1286,7 @@ public static void Test1(int x) Right: IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: 'ClassA.CCC') Children(1): - IOperation: (OperationKind.None, Type: null) (Syntax: 'ClassA') + IOperation: (OperationKind.None, Type: ClassA) (Syntax: 'ClassA') Initializer: null "; diff --git a/src/Compilers/CSharp/csc/csc.csproj b/src/Compilers/CSharp/csc/csc.csproj index cc6e6cabf8098..55c001cdc824d 100644 --- a/src/Compilers/CSharp/csc/csc.csproj +++ b/src/Compilers/CSharp/csc/csc.csproj @@ -8,7 +8,7 @@ false true Microsoft.CodeAnalysis.CSharp.CommandLine.Program - netcoreapp3.1;net472 + net6.0;net472 true false true @@ -19,7 +19,7 @@ - + diff --git a/src/Compilers/Core/MSBuildTask/Microsoft.Build.Tasks.CodeAnalysis.csproj b/src/Compilers/Core/MSBuildTask/Microsoft.Build.Tasks.CodeAnalysis.csproj index cc743d0903206..c392b05bbc11c 100644 --- a/src/Compilers/Core/MSBuildTask/Microsoft.Build.Tasks.CodeAnalysis.csproj +++ b/src/Compilers/Core/MSBuildTask/Microsoft.Build.Tasks.CodeAnalysis.csproj @@ -6,7 +6,7 @@ Library Microsoft.CodeAnalysis.BuildTasks en-US - netcoreapp3.1;net472 + net6.0;net472 true diff --git a/src/Compilers/Core/MSBuildTask/RCWForCurrentContext.cs b/src/Compilers/Core/MSBuildTask/RCWForCurrentContext.cs index 09300c5978caa..21b4bddc5d219 100644 --- a/src/Compilers/Core/MSBuildTask/RCWForCurrentContext.cs +++ b/src/Compilers/Core/MSBuildTask/RCWForCurrentContext.cs @@ -2,6 +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. +#pragma warning disable CA1416 // Validate platform compatibility (Windows only APIs) + using System; using System.Runtime.InteropServices; using System.Diagnostics; diff --git a/src/Compilers/Core/MSBuildTask/Utilities.cs b/src/Compilers/Core/MSBuildTask/Utilities.cs index 14c50fe1bb9b1..1f7449505c993 100644 --- a/src/Compilers/Core/MSBuildTask/Utilities.cs +++ b/src/Compilers/Core/MSBuildTask/Utilities.cs @@ -152,6 +152,7 @@ internal static Exception GetLocalizedArgumentException(string errorString, internal static string? TryGetAssemblyPath(Assembly assembly) { +#if NETFRAMEWORK if (assembly.GlobalAssemblyCache) { return null; @@ -164,6 +165,9 @@ internal static Exception GetLocalizedArgumentException(string errorString, } return null; +#else + return assembly.Location; +#endif } /// diff --git a/src/Compilers/Core/Portable/CommandLine/CommandLineParser.cs b/src/Compilers/Core/Portable/CommandLine/CommandLineParser.cs index 9c6219b762728..e79db74edf510 100644 --- a/src/Compilers/Core/Portable/CommandLine/CommandLineParser.cs +++ b/src/Compilers/Core/Portable/CommandLine/CommandLineParser.cs @@ -275,8 +275,14 @@ internal static void ParseAndNormalizeFile( { try { - // Check some ancient reserved device names, such as COM1,..9, LPT1..9, PRN, CON, or AUX etc., and bail out earlier - // Win32 API - GetFullFileName - will resolve them, say 'COM1', as "\\.\COM1" + // Windows 10 and earlier placed restrictions on file names that originally appeared as device + // names. For example COM1, PRN, CON, AUX, etc ... Files could not be created with those names even + // with extensions like .txt. When those restricted names are passed to GetFullPath the + // runtime will escape them with \\.\. For example GetFullPath("aux.txt") will return "\\.\aux.txt". + // The compiler detects these illegal names and bails out early + // + // Windows 11 removed this restriction though and hence the names are now legal. Cannot find documentation + // to support this but experimentally it can be validated. resolvedPath = Path.GetFullPath(resolvedPath); // preserve possible invalid path info for diagnostic purpose invalidPath = resolvedPath; diff --git a/src/Compilers/Core/Portable/Generated/FlowAnalysis.Generated.cs b/src/Compilers/Core/Portable/Generated/FlowAnalysis.Generated.cs index 76d35134f4f2d..ba879b058a5d1 100644 --- a/src/Compilers/Core/Portable/Generated/FlowAnalysis.Generated.cs +++ b/src/Compilers/Core/Portable/Generated/FlowAnalysis.Generated.cs @@ -53,6 +53,10 @@ public interface IFlowCaptureReferenceOperation : IOperation /// An id used to match references to the same intermediate result. /// CaptureId Id { get; } + /// + /// True if this reference to the capture initializes the capture. Used when the capture is being initialized by being passed as an out parameter. + /// + bool IsInitialization { get; } } /// /// Represents result of checking whether the is null. diff --git a/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs b/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs index ce91e27e45569..0ba4d948a9149 100644 --- a/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs +++ b/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs @@ -267,5 +267,7 @@ public enum OperationKind InterpolatedStringAppendInvalid = 0x76, /// Indicates an . InterpolatedStringHandlerArgumentPlaceholder = 0x77, + /// Indicates an . + FunctionPointerInvocation = 0x78, } } diff --git a/src/Compilers/Core/Portable/Generated/Operations.Generated.cs b/src/Compilers/Core/Portable/Generated/Operations.Generated.cs index 7ea82d84937f2..c5dc68ac9c9be 100644 --- a/src/Compilers/Core/Portable/Generated/Operations.Generated.cs +++ b/src/Compilers/Core/Portable/Generated/Operations.Generated.cs @@ -3429,6 +3429,28 @@ public interface IInterpolatedStringHandlerArgumentPlaceholderOperation : IOpera /// InterpolatedStringArgumentPlaceholderKind PlaceholderKind { get; } } + /// + /// Represents an invocation of a function pointer. + /// + /// + /// This node is associated with the following operation kinds: + /// + /// + /// + /// This interface is reserved for implementation by its associated APIs. We reserve the right to + /// change it in the future. + /// + public interface IFunctionPointerInvocationOperation : IOperation + { + /// + /// Invoked pointer. + /// + IOperation Target { get; } + /// + /// Arguments of the invocation. Arguments are in evaluation order. + /// + ImmutableArray Arguments { get; } + } #endregion #region Implementations @@ -6855,14 +6877,16 @@ protected override (bool hasNext, int nextSlot, int nextIndex) MoveNext(int prev } internal sealed partial class FlowCaptureReferenceOperation : Operation, IFlowCaptureReferenceOperation { - internal FlowCaptureReferenceOperation(CaptureId id, SemanticModel? semanticModel, SyntaxNode syntax, ITypeSymbol? type, ConstantValue? constantValue, bool isImplicit) + internal FlowCaptureReferenceOperation(CaptureId id, bool isInitialization, SemanticModel? semanticModel, SyntaxNode syntax, ITypeSymbol? type, ConstantValue? constantValue, bool isImplicit) : base(semanticModel, syntax, isImplicit) { Id = id; + IsInitialization = isInitialization; OperationConstantValue = constantValue; Type = type; } public CaptureId Id { get; } + public bool IsInitialization { get; } protected override IOperation GetCurrent(int slot, int index) => throw ExceptionUtilities.UnexpectedValue((slot, index)); protected override (bool hasNext, int nextSlot, int nextIndex) MoveNext(int previousSlot, int previousIndex) => (false, int.MinValue, int.MinValue); public override ITypeSymbol? Type { get; } @@ -7854,6 +7878,51 @@ internal InterpolatedStringHandlerArgumentPlaceholderOperation(int argumentIndex public override void Accept(OperationVisitor visitor) => visitor.VisitInterpolatedStringHandlerArgumentPlaceholder(this); public override TResult? Accept(OperationVisitor visitor, TArgument argument) where TResult : default => visitor.VisitInterpolatedStringHandlerArgumentPlaceholder(this, argument); } + internal sealed partial class FunctionPointerInvocationOperation : Operation, IFunctionPointerInvocationOperation + { + internal FunctionPointerInvocationOperation(IOperation target, ImmutableArray arguments, SemanticModel? semanticModel, SyntaxNode syntax, ITypeSymbol? type, bool isImplicit) + : base(semanticModel, syntax, isImplicit) + { + Target = SetParentOperation(target, this); + Arguments = SetParentOperation(arguments, this); + Type = type; + } + public IOperation Target { get; } + public ImmutableArray Arguments { get; } + protected override IOperation GetCurrent(int slot, int index) + => slot switch + { + 0 when Target != null + => Target, + 1 when index < Arguments.Length + => Arguments[index], + _ => throw ExceptionUtilities.UnexpectedValue((slot, index)), + }; + protected override (bool hasNext, int nextSlot, int nextIndex) MoveNext(int previousSlot, int previousIndex) + { + switch (previousSlot) + { + case -1: + if (Target != null) return (true, 0, 0); + else goto case 0; + case 0: + if (!Arguments.IsEmpty) return (true, 1, 0); + else goto case 1; + case 1 when previousIndex + 1 < Arguments.Length: + return (true, 1, previousIndex + 1); + case 1: + case 2: + return (false, 2, 0); + default: + throw ExceptionUtilities.UnexpectedValue((previousSlot, previousIndex)); + } + } + public override ITypeSymbol? Type { get; } + internal override ConstantValue? OperationConstantValue => null; + public override OperationKind Kind => OperationKind.FunctionPointerInvocation; + public override void Accept(OperationVisitor visitor) => visitor.VisitFunctionPointerInvocation(this); + public override TResult? Accept(OperationVisitor visitor, TArgument argument) where TResult : default => visitor.VisitFunctionPointerInvocation(this, argument); + } #endregion #region Cloner internal sealed partial class OperationCloner : OperationVisitor @@ -8310,7 +8379,7 @@ public override IOperation VisitDiscardOperation(IDiscardOperation operation, ob public override IOperation VisitFlowCaptureReference(IFlowCaptureReferenceOperation operation, object? argument) { var internalOperation = (FlowCaptureReferenceOperation)operation; - return new FlowCaptureReferenceOperation(internalOperation.Id, internalOperation.OwningSemanticModel, internalOperation.Syntax, internalOperation.Type, internalOperation.OperationConstantValue, internalOperation.IsImplicit); + return new FlowCaptureReferenceOperation(internalOperation.Id, internalOperation.IsInitialization, internalOperation.OwningSemanticModel, internalOperation.Syntax, internalOperation.Type, internalOperation.OperationConstantValue, internalOperation.IsImplicit); } public override IOperation VisitCoalesceAssignment(ICoalesceAssignmentOperation operation, object? argument) { @@ -8432,6 +8501,11 @@ public override IOperation VisitInterpolatedStringHandlerArgumentPlaceholder(IIn var internalOperation = (InterpolatedStringHandlerArgumentPlaceholderOperation)operation; return new InterpolatedStringHandlerArgumentPlaceholderOperation(internalOperation.ArgumentIndex, internalOperation.PlaceholderKind, internalOperation.OwningSemanticModel, internalOperation.Syntax, internalOperation.IsImplicit); } + public override IOperation VisitFunctionPointerInvocation(IFunctionPointerInvocationOperation operation, object? argument) + { + var internalOperation = (FunctionPointerInvocationOperation)operation; + return new FunctionPointerInvocationOperation(Visit(internalOperation.Target), VisitArray(internalOperation.Arguments), internalOperation.OwningSemanticModel, internalOperation.Syntax, internalOperation.Type, internalOperation.IsImplicit); + } } #endregion @@ -8565,6 +8639,7 @@ internal virtual void VisitNoneOperation(IOperation operation) { /* no-op */ } public virtual void VisitInterpolatedStringAddition(IInterpolatedStringAdditionOperation operation) => DefaultVisit(operation); public virtual void VisitInterpolatedStringAppend(IInterpolatedStringAppendOperation operation) => DefaultVisit(operation); public virtual void VisitInterpolatedStringHandlerArgumentPlaceholder(IInterpolatedStringHandlerArgumentPlaceholderOperation operation) => DefaultVisit(operation); + public virtual void VisitFunctionPointerInvocation(IFunctionPointerInvocationOperation operation) => DefaultVisit(operation); } public abstract partial class OperationVisitor { @@ -8695,6 +8770,7 @@ public abstract partial class OperationVisitor public virtual TResult? VisitInterpolatedStringAddition(IInterpolatedStringAdditionOperation operation, TArgument argument) => DefaultVisit(operation, argument); public virtual TResult? VisitInterpolatedStringAppend(IInterpolatedStringAppendOperation operation, TArgument argument) => DefaultVisit(operation, argument); public virtual TResult? VisitInterpolatedStringHandlerArgumentPlaceholder(IInterpolatedStringHandlerArgumentPlaceholderOperation operation, TArgument argument) => DefaultVisit(operation, argument); + public virtual TResult? VisitFunctionPointerInvocation(IFunctionPointerInvocationOperation operation, TArgument argument) => DefaultVisit(operation, argument); } #endregion } diff --git a/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs b/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs index bda79fdeed995..e7c14894ee163 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs @@ -21,7 +21,10 @@ namespace Microsoft.CodeAnalysis.ErrorReporting internal static class FatalError { private static Action? s_fatalHandler; - private static Action? s_nonFatalHandler; + +#if !NET20 + private static Action? s_nonFatalHandler; +#endif #pragma warning disable IDE0052 // Remove unread private members - We want to hold onto last exception to make investigation easier private static Exception? s_reportedException; @@ -50,12 +53,14 @@ public static Action? Handler } } +#if !NET20 + /// /// Set by the host to a fail fast trigger, /// if the host desires to NOT crash the process on a non fatal exception. /// [DisallowNull] - public static Action? NonFatalHandler + public static Action? NonFatalHandler { get { @@ -72,6 +77,8 @@ public static Action? NonFatalHandler } } +#endif + // Same as setting the Handler property except that it avoids the assert. This is useful in // test code which needs to verify the handler is called in specific cases and will continually // overwrite this value. @@ -128,6 +135,8 @@ public static bool ReportAndPropagateUnlessCanceled(Exception exception, Cancell return ReportAndPropagate(exception); } +#if !NET20 + /// /// Use in an exception filter to report a non-fatal error (by calling ) and catch /// the exception, unless the operation was cancelled. @@ -175,6 +184,8 @@ public static bool ReportAndCatchUnlessCanceled(Exception exception, Cancellatio return ReportAndCatch(exception); } +#endif + /// /// Use in an exception filter to report a fatal error without catching the exception. /// The error is reported by calling . @@ -187,6 +198,8 @@ public static bool ReportAndPropagate(Exception exception) return false; } +#if !NET20 + /// /// Report a non-fatal error. /// Calls and doesn't pass the exception through (the method returns true). @@ -200,10 +213,19 @@ public static bool ReportAndPropagate(Exception exception) [DebuggerHidden] public static bool ReportAndCatch(Exception exception) { - Report(exception, s_nonFatalHandler); + Report(exception, static (exception) => s_nonFatalHandler?.Invoke(exception, false)); + return true; + } + + [DebuggerHidden] + public static bool ReportWithDumpAndCatch(Exception exception) + { + Report(exception, static (exception) => s_nonFatalHandler?.Invoke(exception, true)); return true; } +#endif + private static readonly object s_reportedMarker = new(); private static void Report(Exception exception, Action? handler) diff --git a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.InterpolatedStringContext.cs b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.InterpolatedStringContext.cs new file mode 100644 index 0000000000000..046cfe4371883 --- /dev/null +++ b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.InterpolatedStringContext.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.CodeAnalysis.FlowAnalysis +{ + internal partial class ControlFlowGraphBuilder + { + private class InterpolatedStringHandlerArgumentsContext + { + public readonly ImmutableArray ApplicableCreationOperations; + public readonly int StartingStackDepth; + public readonly bool HasReceiver; + + public InterpolatedStringHandlerArgumentsContext(ImmutableArray applicableCreationOperations, int startingStackDepth, bool hasReceiver) + { + ApplicableCreationOperations = applicableCreationOperations; + HasReceiver = hasReceiver; + StartingStackDepth = startingStackDepth; + } + } + + private class InterpolatedStringHandlerCreationContext + { + public readonly IInterpolatedStringHandlerCreationOperation ApplicableCreationOperation; + public readonly int MaximumStackDepth; + public readonly int HandlerPlaceholder; + public readonly int OutPlaceholder; + + public InterpolatedStringHandlerCreationContext(IInterpolatedStringHandlerCreationOperation applicableCreationOperation, int maximumStackDepth, int handlerPlaceholder, int outParameterPlaceholder) + { + ApplicableCreationOperation = applicableCreationOperation; + MaximumStackDepth = maximumStackDepth; + OutPlaceholder = outParameterPlaceholder; + HandlerPlaceholder = handlerPlaceholder; + } + } + + [Conditional("DEBUG")] + [MemberNotNull("_currentInterpolatedStringHandlerCreationContext")] + private void AssertContainingContextIsForThisCreation(IOperation placeholderOperation, bool assertArgumentContext) + { + Debug.Assert(_currentInterpolatedStringHandlerCreationContext != null); + Debug.Assert(placeholderOperation is IInstanceReferenceOperation { ReferenceKind: InstanceReferenceKind.InterpolatedStringHandler } or IInterpolatedStringHandlerArgumentPlaceholderOperation); + + IOperation? operation = placeholderOperation.Parent; + while (operation is not (null or IInterpolatedStringHandlerCreationOperation)) + { + operation = operation.Parent; + } + + Debug.Assert(operation != null); + Debug.Assert(_currentInterpolatedStringHandlerCreationContext.ApplicableCreationOperation == operation); + + if (assertArgumentContext) + { + Debug.Assert(_currentInterpolatedStringHandlerArgumentContext != null); + Debug.Assert(_currentInterpolatedStringHandlerArgumentContext.ApplicableCreationOperations.Contains((IInterpolatedStringHandlerCreationOperation)operation)); + } + } + } +} diff --git a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs index a897097fda241..81dbbcba4fb54 100644 --- a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs +++ b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs @@ -41,6 +41,8 @@ internal sealed partial class ControlFlowGraphBuilder : OperationVisitor _evalStack; private int _startSpillingAt; private ConditionalAccessOperationTracker _currentConditionalAccessTracker; + private InterpolatedStringHandlerArgumentsContext? _currentInterpolatedStringHandlerArgumentContext; + private InterpolatedStringHandlerCreationContext? _currentInterpolatedStringHandlerCreationContext; private IOperation? _currentSwitchOperationExpression; private IOperation? _forToLoopBinaryOperatorLeftOperand; private IOperation? _forToLoopBinaryOperatorRightOperand; @@ -2174,12 +2176,35 @@ private ImmutableArray VisitArray(ImmutableArray originalArray, Func VisitArguments(ImmutableArray arguments) + private ImmutableArray VisitArguments(ImmutableArray arguments, bool instancePushed) { - return VisitArray(arguments, UnwrapArgument, RewriteArgumentFromArray); + VisitAndPushArguments(arguments, instancePushed); + + var visitedArguments = PopArray(arguments, RewriteArgumentFromArray); + return visitedArguments; } - private static IOperation UnwrapArgument(IArgumentOperation argument) + private void VisitAndPushArguments(ImmutableArray arguments, bool instancePushed) + { + var previousInterpolatedStringHandlerContext = _currentInterpolatedStringHandlerArgumentContext; + + if (arguments.SelectAsArray(predicate: arg => arg.Value is IInterpolatedStringHandlerCreationOperation, + selector: arg => (IInterpolatedStringHandlerCreationOperation)arg.Value) + is { IsDefaultOrEmpty: false } interpolatedStrings) + { + _currentInterpolatedStringHandlerArgumentContext = new InterpolatedStringHandlerArgumentsContext( + interpolatedStrings, + _evalStack.Count - (instancePushed ? 1 : 0), + hasReceiver: instancePushed); + } + + VisitAndPushArray(arguments, UnwrapArgument); + _currentInterpolatedStringHandlerArgumentContext = previousInterpolatedStringHandlerContext; + } + + private static readonly Func UnwrapArgument = UnwrapArgumentDoNotCaptureDirectly; + + private static IOperation UnwrapArgumentDoNotCaptureDirectly(IArgumentOperation argument) { return argument.Value; } @@ -4149,7 +4174,18 @@ private void AddDisposingFinally(IOperation resource, bool requiresRuntimeConver if (method != null) { - var args = disposeMethod is object ? VisitArguments(disposeArguments) : ImmutableArray.Empty; + ImmutableArray args; + if (disposeMethod is not null) + { + PushOperand(value); + args = VisitArguments(disposeArguments, instancePushed: true); + value = PopOperand(); + } + else + { + args = ImmutableArray.Empty; + } + var invocation = new InvocationOperation(method, value, isVirtual: disposeMethod?.IsVirtual ?? true, args, semanticModel: null, value.Syntax, method.ReturnType, isImplicit: true); @@ -4531,9 +4567,11 @@ IOperation getCurrent(IOperation enumeratorRef) { if (info?.CurrentProperty != null) { + var instance = info.CurrentProperty.IsStatic ? null : enumeratorRef; + var visitedArguments = makeArguments(info.CurrentArguments, ref instance); return new PropertyReferenceOperation(info.CurrentProperty, - makeArguments(info.CurrentArguments), - info.CurrentProperty.IsStatic ? null : enumeratorRef, + visitedArguments, + instance, semanticModel: null, operation.LoopControlVariable.Syntax, info.CurrentProperty.Type, isImplicit: true); @@ -4594,17 +4632,26 @@ InvocationOperation makeInvocationDroppingInstanceForStaticMethods(IMethodSymbol InvocationOperation makeInvocation(SyntaxNode syntax, IMethodSymbol method, IOperation? instanceOpt, ImmutableArray arguments) { Debug.Assert(method.IsStatic == (instanceOpt == null)); + var visitedArguments = makeArguments(arguments, ref instanceOpt); return new InvocationOperation(method, instanceOpt, - isVirtual: method.IsVirtual || method.IsAbstract || method.IsOverride, - makeArguments(arguments), semanticModel: null, syntax, - method.ReturnType, isImplicit: true); + isVirtual: method.IsVirtual || method.IsAbstract || method.IsOverride, + visitedArguments, semanticModel: null, syntax, + method.ReturnType, isImplicit: true); } - ImmutableArray makeArguments(ImmutableArray arguments) + ImmutableArray makeArguments(ImmutableArray arguments, ref IOperation? instance) { - if (arguments != null) + if (!arguments.IsDefaultOrEmpty) { - return VisitArguments(arguments); + bool hasInstance = instance != null; + if (hasInstance) + { + PushOperand(instance!); + } + arguments = VisitArguments(arguments, hasInstance); + instance = hasInstance ? PopOperand() : null; + + return arguments; } return ImmutableArray.Empty; @@ -5809,15 +5856,37 @@ public override IOperation VisitInvocation(IInvocationOperation operation, int? operation.Type, IsImplicit(operation)); } + public override IOperation? VisitFunctionPointerInvocation(IFunctionPointerInvocationOperation operation, int? argument) + { + EvalStackFrame frame = PushStackFrame(); + var target = operation.Target; + var (visitedPointer, visitedArguments) = handlePointerAndArguments(target, operation.Arguments); + PopStackFrame(frame); + return new FunctionPointerInvocationOperation(visitedPointer, visitedArguments, semanticModel: null, operation.Syntax, + operation.Type, IsImplicit(operation)); + + (IOperation visitedInstance, ImmutableArray visitedArguments) handlePointerAndArguments( + IOperation targetPointer, ImmutableArray arguments) + { + PushOperand(VisitRequired(targetPointer)); + + ImmutableArray visitedArguments = VisitArguments(arguments, instancePushed: false); + IOperation visitedInstance = PopOperand(); + + return (visitedInstance, visitedArguments); + } + } + private (IOperation? visitedInstance, ImmutableArray visitedArguments) VisitInstanceWithArguments(IOperation? instance, ImmutableArray arguments) { - if (instance != null) + bool hasInstance = instance != null; + if (hasInstance) { - PushOperand(VisitRequired(instance)); + PushOperand(VisitRequired(instance!)); } - ImmutableArray visitedArguments = VisitArguments(arguments); - IOperation? visitedInstance = instance == null ? null : PopOperand(); + ImmutableArray visitedArguments = VisitArguments(arguments, instancePushed: hasInstance); + IOperation? visitedInstance = hasInstance ? PopOperand() : null; return (visitedInstance, visitedArguments); } @@ -5834,7 +5903,7 @@ public override IOperation VisitObjectCreation(IObjectCreationOperation operatio { EvalStackFrame frame = PushStackFrame(); EvalStackFrame argumentsFrame = PushStackFrame(); - ImmutableArray visitedArgs = VisitArguments(operation.Arguments); + ImmutableArray visitedArgs = VisitArguments(operation.Arguments, instancePushed: false); PopStackFrame(argumentsFrame); // Initializer is removed from the tree and turned into a series of statements that assign to the created instance IOperation initializedInstance = new ObjectCreationOperation(operation.Constructor, initializer: null, visitedArgs, semanticModel: null, @@ -6011,7 +6080,10 @@ bool tryPushTarget(IOperation instance) if (memberReference.Kind == OperationKind.PropertyReference) { // We assume all arguments have side effects and spill them. We only avoid recapturing things that have already been captured once. - VisitAndPushArray(((IPropertyReferenceOperation)memberReference).Arguments, UnwrapArgument); + // We do not pass an instance here, as the instance is not yet available. For arguments that need the instance (such as interpolated + // string handlers), they will handle the missing instance by substituting an IInvalidOperation + + VisitAndPushArguments(((IPropertyReferenceOperation)memberReference).Arguments, instancePushed: false); SpillEvalStack(); } @@ -6290,24 +6362,28 @@ IArrayInitializerOperation popAndAssembleArrayInitializerValues(IArrayInitialize public override IOperation VisitInstanceReference(IInstanceReferenceOperation operation, int? captureIdForResult) { - if (operation.ReferenceKind == InstanceReferenceKind.ImplicitReceiver) + switch (operation.ReferenceKind) { - // When we're in an object or collection initializer, we need to replace the instance reference with a reference to the object being initialized - Debug.Assert(operation.IsImplicit); + case InstanceReferenceKind.ImplicitReceiver: + // When we're in an object or collection initializer, we need to replace the instance reference with a reference to the object being initialized + Debug.Assert(operation.IsImplicit); - if (_currentImplicitInstance.ImplicitInstance != null) - { - return OperationCloner.CloneOperation(_currentImplicitInstance.ImplicitInstance); - } - else - { - Debug.Fail("This code path should not be reachable."); - return MakeInvalidOperation(operation.Syntax, operation.Type, ImmutableArray.Empty); - } - } - else - { - return new InstanceReferenceOperation(operation.ReferenceKind, semanticModel: null, operation.Syntax, operation.Type, IsImplicit(operation)); + if (_currentImplicitInstance.ImplicitInstance != null) + { + return OperationCloner.CloneOperation(_currentImplicitInstance.ImplicitInstance); + } + else + { + Debug.Fail("This code path should not be reachable."); + return MakeInvalidOperation(operation.Syntax, operation.Type, ImmutableArray.Empty); + } + + case InstanceReferenceKind.InterpolatedStringHandler: + AssertContainingContextIsForThisCreation(operation, assertArgumentContext: false); + return new FlowCaptureReferenceOperation(_currentInterpolatedStringHandlerCreationContext.HandlerPlaceholder, operation.Syntax, operation.Type, operation.GetConstantValue()); + + default: + return new InstanceReferenceOperation(operation.ReferenceKind, semanticModel: null, operation.Syntax, operation.Type, IsImplicit(operation)); } } @@ -6475,12 +6551,191 @@ private IOperation VisitNoneOperationExpression(IOperation operation) public override IOperation? VisitInterpolatedStringHandlerCreation(IInterpolatedStringHandlerCreationOperation operation, int? captureIdForResult) { - return VisitNoneOperation(operation, captureIdForResult); + // We turn the interpolated string into a call to create the handler type, a series of append calls (potentially with branches, depending on the + // handler semantics), and then evaluate to the handler flow capture temp. + + SpillEvalStack(); + int maxStackDepth = _evalStack.Count - 2; + +#if DEBUG + Debug.Assert(_evalStack[maxStackDepth + 1].frameOpt != null); + if (_currentInterpolatedStringHandlerArgumentContext?.ApplicableCreationOperations.Contains(operation) == true) + { + for (int i = _currentInterpolatedStringHandlerArgumentContext.StartingStackDepth; + i < maxStackDepth; + i++) + { + Debug.Assert(_evalStack[i].frameOpt == null); + Debug.Assert(_evalStack[i].operationOpt != null); + } + } +#endif + + RegionBuilder resultRegion = CurrentRegionRequired; + var handlerCaptureId = captureIdForResult ?? GetNextCaptureId(resultRegion); + + var constructorRegion = new RegionBuilder(ControlFlowRegionKind.LocalLifetime); + EnterRegion(constructorRegion); + + BasicBlockBuilder? resultBlock = null; + if (operation.HandlerCreationHasSuccessParameter || operation.HandlerAppendCallsReturnBool) + { + resultBlock = new BasicBlockBuilder(BasicBlockKind.Block); + } + + // Any placeholders for arguments should have already been created, except for the out parameter if it exists. + int outParameterFlowCapture = -1; + IInterpolatedStringHandlerArgumentPlaceholderOperation? outParameterPlaceholder = null; + + if (operation.HandlerCreationHasSuccessParameter) + { + // Only successful constructor binds will have a trailing parameter + Debug.Assert(operation.HandlerCreation is IObjectCreationOperation); + outParameterFlowCapture = GetNextCaptureId(constructorRegion); + var arguments = ((IObjectCreationOperation)operation.HandlerCreation).Arguments; + IArgumentOperation? outParameterArgument = null; + + for (int i = arguments.Length - 1; i > 1; i--) + { + if (arguments[i] is { Value: IInterpolatedStringHandlerArgumentPlaceholderOperation { PlaceholderKind: InterpolatedStringArgumentPlaceholderKind.TrailingValidityArgument } } arg) + { + outParameterArgument = arg; + break; + } + } + + Debug.Assert(outParameterArgument is { Parameter: { RefKind: RefKind.Out, Type.SpecialType: SpecialType.System_Boolean } }); + outParameterPlaceholder = (IInterpolatedStringHandlerArgumentPlaceholderOperation)outParameterArgument.Value; + } + + var previousHandlerContext = _currentInterpolatedStringHandlerCreationContext; + _currentInterpolatedStringHandlerCreationContext = new InterpolatedStringHandlerCreationContext(operation, maxStackDepth, handlerCaptureId, outParameterFlowCapture); + + VisitAndCapture(operation.HandlerCreation, handlerCaptureId); + + if (operation.HandlerCreationHasSuccessParameter) + { + // Branch on the success parameter to the next block + Debug.Assert(resultBlock != null); + Debug.Assert(outParameterPlaceholder != null); + Debug.Assert(outParameterFlowCapture != -1); + + // if (!outParameterFlowCapture) goto resultBlock; + // else goto next block; + ConditionalBranch(new FlowCaptureReferenceOperation(outParameterFlowCapture, outParameterPlaceholder.Syntax, outParameterPlaceholder.Type, constantValue: null), jumpIfTrue: false, resultBlock); + _currentBasicBlock = null; + } + + LeaveRegionsUpTo(resultRegion); + + var appendCalls = ArrayBuilder.GetInstance(); + collectAppendCalls(operation, appendCalls); + + int appendCallsLength = appendCalls.Count; + for (var i = 0; i < appendCallsLength; i++) + { + EnterRegion(new RegionBuilder(ControlFlowRegionKind.LocalLifetime)); + var appendCall = appendCalls[i]; + IOperation visitedAppendCall = VisitRequired(appendCall.AppendCall); + if (operation.HandlerAppendCallsReturnBool) + { + Debug.Assert(resultBlock != null); + + if (i == appendCallsLength - 1) + { + // No matter the result, we're going to the result block next. So just visit the statement, and if the current block can be + // combined with the result block, the compaction machinery will take care of it + AddStatement(visitedAppendCall); + } + else + { + // if (!appendCall()) goto result else goto next block + ConditionalBranch(visitedAppendCall, jumpIfTrue: false, resultBlock); + _currentBasicBlock = null; + } + } + else + { + AddStatement(visitedAppendCall); + } + + LeaveRegionsUpTo(resultRegion); + } + + if (resultBlock != null) + { + AppendNewBlock(resultBlock, linkToPrevious: true); + } + + _currentInterpolatedStringHandlerCreationContext = previousHandlerContext; + appendCalls.Free(); + return new FlowCaptureReferenceOperation(handlerCaptureId, operation.Syntax, operation.Type, operation.GetConstantValue()); + + static void collectAppendCalls(IInterpolatedStringHandlerCreationOperation creation, ArrayBuilder appendCalls) + { + if (creation.Content is IInterpolatedStringOperation interpolatedString) + { + // Simple case + appendStringCalls(interpolatedString, appendCalls); + return; + } + + var stack = ArrayBuilder.GetInstance(); + pushLeftNodes((IInterpolatedStringAdditionOperation)creation.Content, stack); + + while (stack.TryPop(out IInterpolatedStringAdditionOperation? currentAddition)) + { + switch (currentAddition.Left) + { + case IInterpolatedStringOperation interpolatedString1: + appendStringCalls(interpolatedString1, appendCalls); + break; + case IInterpolatedStringAdditionOperation: + break; + default: + throw ExceptionUtilities.UnexpectedValue(currentAddition.Left.Kind); + } + + switch (currentAddition.Right) + { + case IInterpolatedStringOperation interpolatedString1: + appendStringCalls(interpolatedString1, appendCalls); + break; + case IInterpolatedStringAdditionOperation additionOperation: + pushLeftNodes(additionOperation, stack); + break; + default: + throw ExceptionUtilities.UnexpectedValue(currentAddition.Left.Kind); + } + } + + stack.Free(); + return; + + static void appendStringCalls(IInterpolatedStringOperation interpolatedString, ArrayBuilder appendCalls) + { + foreach (var part in interpolatedString.Parts) + { + appendCalls.Add((IInterpolatedStringAppendOperation)part); + } + } + + static void pushLeftNodes(IInterpolatedStringAdditionOperation addition, ArrayBuilder stack) + { + IInterpolatedStringAdditionOperation? current = addition; + do + { + stack.Push(current); + current = current.Left as IInterpolatedStringAdditionOperation; + } + while (current != null); + } + } } public override IOperation? VisitInterpolatedStringAddition(IInterpolatedStringAdditionOperation operation, int? captureIdForResult) { - return VisitNoneOperation(operation, captureIdForResult); + throw ExceptionUtilities.Unreachable; } public override IOperation? VisitInterpolatedStringAppend(IInterpolatedStringAppendOperation operation, int? captureIdForResult) @@ -6490,7 +6745,63 @@ private IOperation VisitNoneOperationExpression(IOperation operation) public override IOperation? VisitInterpolatedStringHandlerArgumentPlaceholder(IInterpolatedStringHandlerArgumentPlaceholderOperation operation, int? captureIdForResult) { - return VisitNoneOperation(operation, captureIdForResult); + switch (operation.PlaceholderKind) + { + case InterpolatedStringArgumentPlaceholderKind.TrailingValidityArgument: + AssertContainingContextIsForThisCreation(operation, assertArgumentContext: false); + + return new FlowCaptureReferenceOperation(_currentInterpolatedStringHandlerCreationContext.OutPlaceholder, operation.Syntax, operation.Type, operation.GetConstantValue(), isInitialization: true); + + case InterpolatedStringArgumentPlaceholderKind.CallsiteReceiver: + AssertContainingContextIsForThisCreation(operation, assertArgumentContext: true); + Debug.Assert(_currentInterpolatedStringHandlerArgumentContext != null); + if (_currentInterpolatedStringHandlerArgumentContext.HasReceiver && tryGetArgumentOrReceiver(-1) is IOperation receiverCapture) + { + Debug.Assert(receiverCapture is IFlowCaptureReferenceOperation); + return OperationCloner.CloneOperation(receiverCapture); + } + else + { + return new InvalidOperation(ImmutableArray.Empty, semanticModel: null, operation.Syntax, operation.Type, operation.GetConstantValue(), isImplicit: true); + } + + case InterpolatedStringArgumentPlaceholderKind.CallsiteArgument: + AssertContainingContextIsForThisCreation(operation, assertArgumentContext: true); + Debug.Assert(_currentInterpolatedStringHandlerArgumentContext != null); + if (tryGetArgumentOrReceiver(operation.ArgumentIndex) is IOperation argumentCapture) + { + Debug.Assert(argumentCapture is IFlowCaptureReferenceOperation or IDiscardOperation); + return OperationCloner.CloneOperation(argumentCapture); + } + else + { + return new InvalidOperation(ImmutableArray.Empty, semanticModel: null, operation.Syntax, operation.Type, operation.GetConstantValue(), isImplicit: true); + } + + default: + throw ExceptionUtilities.UnexpectedValue(operation.PlaceholderKind); + } + + IOperation? tryGetArgumentOrReceiver(int argumentIndex) + { + + if (_currentInterpolatedStringHandlerArgumentContext.HasReceiver) + { + argumentIndex++; + } + + int targetStackDepth = _currentInterpolatedStringHandlerArgumentContext.StartingStackDepth + argumentIndex; + + Debug.Assert(_evalStack.Count > _currentInterpolatedStringHandlerCreationContext.MaximumStackDepth); + + if (targetStackDepth > _currentInterpolatedStringHandlerCreationContext.MaximumStackDepth + || targetStackDepth >= _evalStack.Count) + { + return null; + } + + return _evalStack[targetStackDepth].operationOpt; + } } public override IOperation VisitInterpolatedString(IInterpolatedStringOperation operation, int? captureIdForResult) @@ -6541,9 +6852,6 @@ public override IOperation VisitInterpolatedString(IInterpolatedStringOperation var rewrittenInterpolationText = VisitRequired(interpolatedStringText.Text, argument: null); rewrittenElement = new InterpolatedStringTextOperation(rewrittenInterpolationText, semanticModel: null, element.Syntax, IsImplicit(element)); break; - case IInterpolatedStringAppendOperation interpolatedStringAppend: - rewrittenElement = new InterpolatedStringAppendOperation(VisitRequired(interpolatedStringAppend.AppendCall), interpolatedStringAppend.Kind, semanticModel: null, interpolatedStringAppend.Syntax, IsImplicit(interpolatedStringAppend)); - break; default: throw ExceptionUtilities.UnexpectedValue(element.Kind); @@ -6816,14 +7124,9 @@ public override IOperation VisitRaiseEvent(IRaiseEventOperation operation, int? StartVisitingStatement(operation); EvalStackFrame frame = PushStackFrame(); - var instance = operation.EventReference.Event.IsStatic ? null : operation.EventReference.Instance; - if (instance != null) - { - PushOperand(VisitRequired(instance)); - } - ImmutableArray visitedArguments = VisitArguments(operation.Arguments); - IOperation? visitedInstance = instance == null ? null : PopOperand(); + (IOperation? visitedInstance, ImmutableArray visitedArguments) = + VisitInstanceWithArguments(operation.EventReference.Event.IsStatic ? null : operation.EventReference.Instance, operation.Arguments); var visitedEventReference = new EventReferenceOperation(operation.EventReference.Event, visitedInstance, semanticModel: null, operation.EventReference.Syntax, operation.EventReference.Type, IsImplicit(operation.EventReference)); diff --git a/src/Compilers/Core/Portable/Operations/OperationExtensions.cs b/src/Compilers/Core/Portable/Operations/OperationExtensions.cs index d6828dfa9fd9e..766431c4a3d17 100644 --- a/src/Compilers/Core/Portable/Operations/OperationExtensions.cs +++ b/src/Compilers/Core/Portable/Operations/OperationExtensions.cs @@ -14,6 +14,14 @@ namespace Microsoft.CodeAnalysis.Operations { public static partial class OperationExtensions { + /// + /// Helper function to simplify the access to the function pointer signature of an FunctionPointerInvocationOperation + /// + public static IMethodSymbol GetFunctionPointerSignature(this IFunctionPointerInvocationOperation functionPointer) + { + return ((IFunctionPointerTypeSymbol)functionPointer.Target.Type!).Signature; + } + /// /// This will check whether context around the operation has any error such as syntax or semantic error /// diff --git a/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml b/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml index efa8f38c837cb..1ba3ffefd7696 100644 --- a/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml +++ b/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml @@ -2562,6 +2562,11 @@ An id used to match references to the same intermediate result. + + + True if this reference to the capture initializes the capture. Used when the capture is being initialized by being passed as an out parameter. + + @@ -3162,4 +3167,21 @@ + + + + Represents an invocation of a function pointer. + + + + + Invoked pointer. + + + + + Arguments of the invocation. Arguments are in evaluation order. + + + diff --git a/src/Compilers/Core/Portable/Operations/OperationNodes.cs b/src/Compilers/Core/Portable/Operations/OperationNodes.cs index dab2d1375d057..0538014a37cb0 100644 --- a/src/Compilers/Core/Portable/Operations/OperationNodes.cs +++ b/src/Compilers/Core/Portable/Operations/OperationNodes.cs @@ -487,8 +487,8 @@ protected override (bool hasNext, int nextSlot, int nextIndex) MoveNext(int prev internal sealed partial class FlowCaptureReferenceOperation { - public FlowCaptureReferenceOperation(int id, SyntaxNode syntax, ITypeSymbol? type, ConstantValue? constantValue) : - this(new CaptureId(id), semanticModel: null, syntax: syntax, type: type, constantValue: constantValue, isImplicit: true) + public FlowCaptureReferenceOperation(int id, SyntaxNode syntax, ITypeSymbol? type, ConstantValue? constantValue, bool isInitialization = false) : + this(new CaptureId(id), isInitialization, semanticModel: null, syntax: syntax, type: type, constantValue: constantValue, isImplicit: true) { } } diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 76388d9984967..1252f8cd19dc2 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -5,6 +5,7 @@ Microsoft.CodeAnalysis.Compilation.EmitDifference(Microsoft.CodeAnalysis.Emit.Em Microsoft.CodeAnalysis.Emit.EmitDifferenceResult.ChangedTypes.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.Emit.EmitDifferenceResult.UpdatedMethods.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.Emit.SemanticEditKind.Replace = 4 -> Microsoft.CodeAnalysis.Emit.SemanticEditKind +Microsoft.CodeAnalysis.FlowAnalysis.IFlowCaptureReferenceOperation.IsInitialization.get -> bool Microsoft.CodeAnalysis.GeneratorAttribute.GeneratorAttribute(string! firstLanguage, params string![]! additionalLanguages) -> void Microsoft.CodeAnalysis.GeneratorAttribute.Languages.get -> string![]! Microsoft.CodeAnalysis.GeneratorDriver.ReplaceAdditionalText(Microsoft.CodeAnalysis.AdditionalText! oldText, Microsoft.CodeAnalysis.AdditionalText! newText) -> Microsoft.CodeAnalysis.GeneratorDriver! @@ -73,6 +74,10 @@ Microsoft.CodeAnalysis.LineMapping.LineMapping() -> void Microsoft.CodeAnalysis.LineMapping.LineMapping(Microsoft.CodeAnalysis.Text.LinePositionSpan span, int? characterOffset, Microsoft.CodeAnalysis.FileLinePositionSpan mappedSpan) -> void Microsoft.CodeAnalysis.LineMapping.MappedSpan.get -> Microsoft.CodeAnalysis.FileLinePositionSpan Microsoft.CodeAnalysis.LineMapping.Span.get -> Microsoft.CodeAnalysis.Text.LinePositionSpan +Microsoft.CodeAnalysis.OperationKind.FunctionPointerInvocation = 120 -> Microsoft.CodeAnalysis.OperationKind +Microsoft.CodeAnalysis.Operations.IFunctionPointerInvocationOperation +Microsoft.CodeAnalysis.Operations.IFunctionPointerInvocationOperation.Arguments.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.Operations.IFunctionPointerInvocationOperation.Target.get -> Microsoft.CodeAnalysis.IOperation! Microsoft.CodeAnalysis.OperationKind.InterpolatedStringAddition = 115 -> Microsoft.CodeAnalysis.OperationKind Microsoft.CodeAnalysis.OperationKind.InterpolatedStringAppendFormatted = 117 -> Microsoft.CodeAnalysis.OperationKind Microsoft.CodeAnalysis.OperationKind.InterpolatedStringAppendInvalid = 118 -> Microsoft.CodeAnalysis.OperationKind @@ -149,9 +154,12 @@ static Microsoft.CodeAnalysis.IncrementalValueProviderExtensions.SelectMany(this Microsoft.CodeAnalysis.IncrementalValuesProvider source, System.Func! predicate) -> Microsoft.CodeAnalysis.IncrementalValuesProvider static Microsoft.CodeAnalysis.IncrementalValueProviderExtensions.WithComparer(this Microsoft.CodeAnalysis.IncrementalValueProvider source, System.Collections.Generic.IEqualityComparer! comparer) -> Microsoft.CodeAnalysis.IncrementalValueProvider static Microsoft.CodeAnalysis.IncrementalValueProviderExtensions.WithComparer(this Microsoft.CodeAnalysis.IncrementalValuesProvider source, System.Collections.Generic.IEqualityComparer! comparer) -> Microsoft.CodeAnalysis.IncrementalValuesProvider +static Microsoft.CodeAnalysis.Operations.OperationExtensions.GetFunctionPointerSignature(this Microsoft.CodeAnalysis.Operations.IFunctionPointerInvocationOperation! functionPointer) -> Microsoft.CodeAnalysis.IMethodSymbol! virtual Microsoft.CodeAnalysis.Diagnostics.AnalyzerReference.GetGenerators(string! language) -> System.Collections.Immutable.ImmutableArray virtual Microsoft.CodeAnalysis.Diagnostics.AnalyzerReference.GetGeneratorsForAllLanguages() -> System.Collections.Immutable.ImmutableArray abstract Microsoft.CodeAnalysis.Compilation.GetUsedAssemblyReferences(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Collections.Immutable.ImmutableArray +virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFunctionPointerInvocation(Microsoft.CodeAnalysis.Operations.IFunctionPointerInvocationOperation! operation) -> void +virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFunctionPointerInvocation(Microsoft.CodeAnalysis.Operations.IFunctionPointerInvocationOperation! operation, TArgument argument) -> TResult? virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitInterpolatedStringAddition(Microsoft.CodeAnalysis.Operations.IInterpolatedStringAdditionOperation! operation) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitInterpolatedStringAppend(Microsoft.CodeAnalysis.Operations.IInterpolatedStringAppendOperation! operation) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitInterpolatedStringHandlerArgumentPlaceholder(Microsoft.CodeAnalysis.Operations.IInterpolatedStringHandlerArgumentPlaceholderOperation! operation) -> void diff --git a/src/Compilers/Core/Rebuild/CompilationOptionsReader.cs b/src/Compilers/Core/Rebuild/CompilationOptionsReader.cs index 493188491583c..d9453a94b0e75 100644 --- a/src/Compilers/Core/Rebuild/CompilationOptionsReader.cs +++ b/src/Compilers/Core/Rebuild/CompilationOptionsReader.cs @@ -196,10 +196,10 @@ public IEnumerable GetEmbeddedSourceTextInfo() private EmbeddedSourceTextInfo? ResolveEmbeddedSource(DocumentHandle document, SourceTextInfo sourceTextInfo) { - byte[] bytes = (from handle in PdbReader.GetCustomDebugInformation(document) - let cdi = PdbReader.GetCustomDebugInformation(handle) - where PdbReader.GetGuid(cdi.Kind) == EmbeddedSourceGuid - select PdbReader.GetBlobBytes(cdi.Value)).SingleOrDefault(); + var bytes = (from handle in PdbReader.GetCustomDebugInformation(document) + let cdi = PdbReader.GetCustomDebugInformation(handle) + where PdbReader.GetGuid(cdi.Kind) == EmbeddedSourceGuid + select PdbReader.GetBlobBytes(cdi.Value)).SingleOrDefault(); if (bytes is null) { diff --git a/src/Compilers/Core/Rebuild/Microsoft.CodeAnalysis.Rebuild.csproj b/src/Compilers/Core/Rebuild/Microsoft.CodeAnalysis.Rebuild.csproj index 5c29391f49c6d..0f5d4be204a2c 100644 --- a/src/Compilers/Core/Rebuild/Microsoft.CodeAnalysis.Rebuild.csproj +++ b/src/Compilers/Core/Rebuild/Microsoft.CodeAnalysis.Rebuild.csproj @@ -5,7 +5,7 @@ Library Microsoft.CodeAnalysis.Rebuild - netcoreapp3.1;netstandard2.0 + net6.0;netstandard2.0 AnyCPU enable true diff --git a/src/Compilers/Server/VBCSCompiler/VBCSCompiler.csproj b/src/Compilers/Server/VBCSCompiler/VBCSCompiler.csproj index 3fde7d77d3d8c..01385271dcc11 100644 --- a/src/Compilers/Server/VBCSCompiler/VBCSCompiler.csproj +++ b/src/Compilers/Server/VBCSCompiler/VBCSCompiler.csproj @@ -5,7 +5,7 @@ Exe true - netcoreapp3.1;net472 + net6.0;net472 false true false @@ -17,8 +17,8 @@ - - + + diff --git a/src/Compilers/Shared/BuildServerConnection.cs b/src/Compilers/Shared/BuildServerConnection.cs index 1fe609061ee59..692156a5775bb 100644 --- a/src/Compilers/Shared/BuildServerConnection.cs +++ b/src/Compilers/Shared/BuildServerConnection.cs @@ -510,9 +510,11 @@ internal static string GetPipeName(string clientDirectory) bool isAdmin = false; if (PlatformInformation.IsWindows) { +#pragma warning disable CA1416 // Validate platform compatibility var currentIdentity = WindowsIdentity.GetCurrent(); var principal = new WindowsPrincipal(currentIdentity); isAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator); +#pragma warning restore CA1416 } var userName = Environment.UserName; diff --git a/src/Compilers/Shared/NamedPipeUtil.cs b/src/Compilers/Shared/NamedPipeUtil.cs index 81d200dd9a017..eae5e677e8c72 100644 --- a/src/Compilers/Shared/NamedPipeUtil.cs +++ b/src/Compilers/Shared/NamedPipeUtil.cs @@ -55,22 +55,24 @@ internal static bool CheckClientElevationMatches(NamedPipeServerStream pipeStrea { if (PlatformInformation.IsWindows) { - var serverIdentity = getIdentity(impersonating: false); +#pragma warning disable CA1416 // Validate platform compatibility + var serverIdentity = getIdentity(); (string name, bool admin) clientIdentity = default; - pipeStream.RunAsClient(() => { clientIdentity = getIdentity(impersonating: true); }); + pipeStream.RunAsClient(() => { clientIdentity = getIdentity(); }); return StringComparer.OrdinalIgnoreCase.Equals(serverIdentity.name, clientIdentity.name) && serverIdentity.admin == clientIdentity.admin; - (string name, bool admin) getIdentity(bool impersonating) + (string name, bool admin) getIdentity() { - var currentIdentity = WindowsIdentity.GetCurrent(impersonating); + var currentIdentity = WindowsIdentity.GetCurrent(); var currentPrincipal = new WindowsPrincipal(currentIdentity); var elevatedToAdmin = currentPrincipal.IsInRole(WindowsBuiltInRole.Administrator); return (currentIdentity.Name, elevatedToAdmin); } +#pragma warning restore CA1416 // Validate platform compatibility } return true; diff --git a/src/Compilers/Test/Core/Assert/ConditionalFactAttribute.cs b/src/Compilers/Test/Core/Assert/ConditionalFactAttribute.cs index 283c3bd4cefe8..be4191362ff79 100644 --- a/src/Compilers/Test/Core/Assert/ConditionalFactAttribute.cs +++ b/src/Compilers/Test/Core/Assert/ConditionalFactAttribute.cs @@ -169,6 +169,19 @@ public static class ExecutionConditionUtil public static bool IsCoreClrUnix => IsCoreClr && IsUnix; public static bool IsMonoOrCoreClr => IsMono || IsCoreClr; public static bool RuntimeSupportsCovariantReturnsOfClasses => Type.GetType("System.Runtime.CompilerServices.RuntimeFeature")?.GetField("CovariantReturnsOfClasses") != null; + + private static readonly Lazy s_operatingSystemRestrictsFileNames = new Lazy(() => + { + var tempDir = Path.GetTempPath(); + var path = Path.GetFullPath(Path.Combine(tempDir, "aux.txt")); + return path.StartsWith(@"\\.\", StringComparison.Ordinal); + }); + + /// + /// Is this a version of Windows that has ancient restrictions on file names. For example + /// prevents file names that are aux, com1, etc ... + /// + public static bool OperatingSystemRestrictsFileNames => s_operatingSystemRestrictsFileNames.Value; } public enum ExecutionArchitecture diff --git a/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs b/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs index 0b07f1cee5109..7d10ec350ea4e 100644 --- a/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs +++ b/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs @@ -11,6 +11,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.FlowAnalysis; using Microsoft.CodeAnalysis.Operations; @@ -807,6 +808,14 @@ void assertCaptureReferences( foreach (IFlowCaptureReferenceOperation reference in operation.DescendantsAndSelf().OfType()) { CaptureId id = reference.Id; + + if (reference.IsInitialization) + { + AssertTrueWithGraph(state.Add(id), $"Multiple initialization of [{id}]", finalGraph); + AssertTrueWithGraph(block.EnclosingRegion.CaptureIds.Contains(id), $"Flow capture initialization [{id}] should come from the containing region.", finalGraph); + continue; + } + referencedIds.Add(id); if (isLongLivedCaptureReference(reference, block.EnclosingRegion)) @@ -819,14 +828,34 @@ void assertCaptureReferences( // Except for a few specific scenarios, any references to captures should either be long-lived capture references, // or they should come from the enclosing region. - AssertTrueWithGraph(block.EnclosingRegion.CaptureIds.Contains(id) || longLivedIds.Contains(id) || - ((isFirstOperandOfDynamicOrUserDefinedLogicalOperator(reference) || - isIncrementedNullableForToLoopControlVariable(reference) || - isConditionalAccessReceiver(reference) || - isCoalesceAssignmentTarget(reference) || - isObjectInitializerInitializedObjectTarget(reference)) && - block.EnclosingRegion.EnclosingRegion.CaptureIds.Contains(id)), - $"Operation [{operationIndex}] in [{getBlockId(block)}] uses capture [{id.Value}] from another region. Should the regions be merged?", finalGraph); + if (block.EnclosingRegion.CaptureIds.Contains(id) || longLivedIds.Contains(id)) + { + continue; + } + + if (block.EnclosingRegion.EnclosingRegion.CaptureIds.Contains(id)) + { + AssertTrueWithGraph( + isFirstOperandOfDynamicOrUserDefinedLogicalOperator(reference) + || isIncrementedNullableForToLoopControlVariable(reference) + || isConditionalAccessReceiver(reference) + || isCoalesceAssignmentTarget(reference) + || isObjectInitializerInitializedObjectTarget(reference) + || isInterpolatedStringArgumentCapture(reference) + || isInterpolatedStringHandlerCapture(reference), + $"Operation [{operationIndex}] in [{getBlockId(block)}] uses capture [{id.Value}] from another region. Should the regions be merged?", finalGraph); + } + else if (block.EnclosingRegion.EnclosingRegion?.EnclosingRegion.CaptureIds.Contains(id) ?? false) + { + AssertTrueWithGraph( + isInterpolatedStringArgumentCapture(reference) + || isInterpolatedStringHandlerCapture(reference), + $"Operation [{operationIndex}] in [{getBlockId(block)}] uses capture [{id.Value}] from another region. Should the regions be merged?", finalGraph); + } + else + { + AssertTrueWithGraph(false, $"Operation [{operationIndex}] in [{getBlockId(block)}] uses capture [{id.Value}] from another region. Should the regions be merged?", finalGraph); + } } } @@ -892,6 +921,59 @@ bool isObjectInitializerInitializedObjectTarget(IFlowCaptureReferenceOperation r } && left == referenceSyntax; } + bool isInterpolatedStringArgumentCapture(IFlowCaptureReferenceOperation reference) + { + if (reference.Language != LanguageNames.CSharp) + { + return false; + } + + IOperation containingArgument = reference; + do + { + containingArgument = containingArgument.Parent; + } + while (containingArgument is not (null or IArgumentOperation)); + +#pragma warning disable IDE0055 // Fix formatting + return containingArgument is + { + Parent: IObjectCreationOperation + { + Parent: IFlowCaptureOperation, + Constructor.ContainingType: INamedTypeSymbol ctorContainingType, + Arguments: { Length: >= 3 } arguments, + Syntax: CSharpSyntaxNode syntax + } + } + && applyParenthesizedOrNullSuppressionIfAnyCS(syntax) is CSharp.Syntax.InterpolatedStringExpressionSyntax or CSharp.Syntax.BinaryExpressionSyntax + && ctorContainingType.GetSymbol().IsInterpolatedStringHandlerType + && arguments[0].Value.Type.SpecialType == SpecialType.System_Int32 + && arguments[1].Value.Type.SpecialType == SpecialType.System_Int32; +#pragma warning restore IDE0055 + } + + bool isInterpolatedStringHandlerCapture(IFlowCaptureReferenceOperation reference) + { + if (reference.Language != LanguageNames.CSharp) + { + return false; + } + +#pragma warning disable IDE0055 // Fix formatting + return reference is + { + Parent: IInvocationOperation + { + Instance: { } instance, + TargetMethod: { Name: BoundInterpolatedString.AppendFormattedMethod or BoundInterpolatedString.AppendLiteralMethod, ContainingType: INamedTypeSymbol containingType } + } + } + && ReferenceEquals(instance, reference) + && containingType.GetSymbol().IsInterpolatedStringHandlerType; +#pragma warning restore IDE0055 + } + bool isFirstOperandOfDynamicOrUserDefinedLogicalOperator(IFlowCaptureReferenceOperation reference) { if (reference.Parent is IBinaryOperation binOp) @@ -1839,6 +1921,7 @@ propertyReference.Parent is ISimpleAssignmentOperation simpleAssignment && case OperationKind.None: return !(n is IPlaceholderOperation); + case OperationKind.FunctionPointerInvocation: case OperationKind.Invalid: case OperationKind.YieldReturn: case OperationKind.ExpressionStatement: diff --git a/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs b/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs index 23ce54d25469c..3120529a43060 100644 --- a/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs +++ b/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs @@ -824,6 +824,15 @@ public override void VisitInvocation(IInvocationOperation operation) VisitArguments(operation.Arguments); } + public override void VisitFunctionPointerInvocation(IFunctionPointerInvocationOperation operation) + { + LogString(nameof(IFunctionPointerInvocationOperation)); + LogCommonPropertiesAndNewLine(operation); + + Visit(operation.Target, "Target"); + VisitArguments(operation.Arguments); + } + private void VisitArguments(ImmutableArray arguments) { VisitArray(arguments, "Arguments", logElementCount: true); @@ -953,6 +962,10 @@ public override void VisitFlowCaptureReference(IFlowCaptureReferenceOperation op { LogString(nameof(IFlowCaptureReferenceOperation)); LogString($": {operation.Id.Value}"); + if (operation.IsInitialization) + { + LogString(" (IsInitialization)"); + } LogCommonPropertiesAndNewLine(operation); } diff --git a/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs b/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs index b94b3a2c622d7..7909a9ef47bd6 100644 --- a/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs +++ b/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs @@ -502,6 +502,20 @@ public override void VisitInvocation(IInvocationOperation operation) } } + public override void VisitFunctionPointerInvocation(IFunctionPointerInvocationOperation operation) + { + Assert.Equal(OperationKind.FunctionPointerInvocation, operation.Kind); + Assert.NotNull(operation.Target); + + IEnumerable children = new[] { operation.Target }.Concat(operation.Arguments); + + var signature = operation.GetFunctionPointerSignature(); + Assert.NotNull(signature); + Assert.Same(((IFunctionPointerTypeSymbol)operation.Target.Type).Signature, signature); + + AssertEx.Equal(children, operation.Children); + } + public override void VisitArgument(IArgumentOperation operation) { Assert.Equal(OperationKind.Argument, operation.Kind); diff --git a/src/Compilers/Test/Core/ModuleInitializer.cs b/src/Compilers/Test/Core/ModuleInitializer.cs index ec521237e9d79..6e592c35225c8 100644 --- a/src/Compilers/Test/Core/ModuleInitializer.cs +++ b/src/Compilers/Test/Core/ModuleInitializer.cs @@ -2,9 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - +using System; using System.Diagnostics; +using System.IO; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis; @@ -17,6 +17,10 @@ internal static void Initialize() { Trace.Listeners.Clear(); Trace.Listeners.Add(new ThrowingTraceListener()); + + // Make sure we load DSRN from the directory containing the unit tests and not from a runtime directory on .NET 5+. + Environment.SetEnvironmentVariable("MICROSOFT_DIASYMREADER_NATIVE_ALT_LOAD_PATH", Path.GetDirectoryName(typeof(ModuleInitializer).Assembly.Location)); + Environment.SetEnvironmentVariable("MICROSOFT_DIASYMREADER_NATIVE_USE_ALT_LOAD_PATH_ONLY", "1"); } } } diff --git a/src/Compilers/Test/Core/Traits/Traits.cs b/src/Compilers/Test/Core/Traits/Traits.cs index da6f646df0e58..66a26418fe87d 100644 --- a/src/Compilers/Test/Core/Traits/Traits.cs +++ b/src/Compilers/Test/Core/Traits/Traits.cs @@ -45,6 +45,7 @@ public static class Features public const string CodeActionsAddDebuggerDisplay = "CodeActions.AddDebuggerDisplay"; public const string CodeActionsAddDocCommentNodes = "CodeActions.AddDocCommentParamNodes"; public const string CodeActionsAddExplicitCast = "CodeActions.AddExplicitCast"; + public const string CodeActionsAddInheritdoc = "CodeActions.AddInheritdoc"; public const string CodeActionsAddFileBanner = "CodeActions.AddFileBanner"; public const string CodeActionsAddImport = "CodeActions.AddImport"; public const string CodeActionsAddMissingReference = "CodeActions.AddMissingReference"; diff --git a/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb b/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb index 11940745799d1..82c5396e4a8c2 100644 --- a/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb +++ b/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb @@ -309,10 +309,16 @@ Namespace Microsoft.CodeAnalysis.Operations BoundKind.UnboundLambda, BoundKind.UnstructuredExceptionHandlingStatement - Dim constantValue = TryCast(boundNode, BoundExpression)?.ConstantValueOpt + Dim constantValue As ConstantValue = Nothing + Dim type As TypeSymbol = Nothing + Dim expression = TryCast(boundNode, BoundExpression) + If expression IsNot Nothing Then + constantValue = expression.ConstantValueOpt + type = expression.Type + End If Dim isImplicit As Boolean = boundNode.WasCompilerGenerated Dim children As ImmutableArray(Of IOperation) = GetIOperationChildren(boundNode) - Return New NoneOperation(children, _semanticModel, boundNode.Syntax, type:=Nothing, constantValue, isImplicit) + Return New NoneOperation(children, _semanticModel, boundNode.Syntax, type, constantValue, isImplicit) Case Else ' If you're hitting this because the IOperation test hook has failed, see diff --git a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb index ad45528663b8b..afe53e36bf789 100644 --- a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb +++ b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb @@ -7383,14 +7383,22 @@ End Module parsedArgs.Errors.Verify() parsedArgs = DefaultParse({"/out:com1.exe", "a.vb"}, _baseDirectory) - parsedArgs.Errors.Verify(Diagnostic(ERRID.FTL_InvalidInputFileName).WithArguments("\\.\com1").WithLocation(1, 1)) + If ExecutionConditionUtil.OperatingSystemRestrictsFileNames Then + parsedArgs.Errors.Verify(Diagnostic(ERRID.FTL_InvalidInputFileName).WithArguments("\\.\com1").WithLocation(1, 1)) + Else + parsedArgs.Errors.Verify() + End If parsedArgs = DefaultParse({"/doc:..\lpt2.xml", "a.vb"}, _baseDirectory) - parsedArgs.Errors.Verify(Diagnostic(ERRID.WRN_XMLCannotWriteToXMLDocFile2).WithArguments("..\lpt2.xml", "The system cannot find the path specified").WithLocation(1, 1)) + If ExecutionConditionUtil.OperatingSystemRestrictsFileNames Then + parsedArgs.Errors.Verify(Diagnostic(ERRID.WRN_XMLCannotWriteToXMLDocFile2).WithArguments("..\lpt2.xml", "The system cannot find the path specified").WithLocation(1, 1)) + Else + parsedArgs.Errors.Verify() + End If parsedArgs = DefaultParse({"/SdkPath:..\aux", "com.vb"}, _baseDirectory) parsedArgs.Errors.Verify(Diagnostic(ERRID.WRN_CannotFindStandardLibrary1).WithArguments("System.dll").WithLocation(1, 1), - Diagnostic(ERRID.ERR_LibNotFound).WithArguments("Microsoft.VisualBasic.dll").WithLocation(1, 1)) + Diagnostic(ERRID.ERR_LibNotFound).WithArguments("Microsoft.VisualBasic.dll").WithLocation(1, 1)) End Sub diff --git a/src/Compilers/VisualBasic/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.vb b/src/Compilers/VisualBasic/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.vb index b18da30f4d268..72c039b3114c5 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Attributes/InternalsVisibleToAndStrongNameTests.vb @@ -2410,4 +2410,275 @@ End Class c2.AssertNoDiagnostics() End Sub + + + Public Sub Issue57742_01() + Dim other As VisualBasicCompilation = CreateCompilation( + + +Friend class PublicKeyConstants + public const PublicKey As String = "Something" +End Class +]]> + +) + + Dim source = + + + +class TestAttribute + Inherits System.Attribute + + public Sub New(x As String) + End Sub +End Class +]]> + + + Dim comp As VisualBasicCompilation = CreateCompilation(source, {other.ToMetadataReference()}) + + Dim expected = + +BC36957: Friend access was granted by 'Issue57742_01_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly does not match that specified by the attribute in the granting assembly. + + + comp.AssertTheseDiagnostics(expected) + + comp = CreateCompilation(source, {other.EmitToImageReference()}) + comp.AssertTheseDiagnostics(expected) + End Sub + + + + Public Sub Issue57742_02() + Dim other As VisualBasicCompilation = CreateCompilation( + + +Friend class PublicKeyConstants + public const PublicKey As String = "Something" +End Class +]]> + +) + + Dim source = + + +]]> + + + Dim comp As VisualBasicCompilation = CreateCompilation(source, {other.ToMetadataReference()}) + + Dim expected = + +BC36957: Friend access was granted by 'Issue57742_02_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly does not match that specified by the attribute in the granting assembly. + + + comp.AssertTheseDiagnostics(expected) + + comp = CreateCompilation(source, {other.EmitToImageReference()}) + comp.AssertTheseDiagnostics(expected) + End Sub + + + + Public Sub Issue57742_03() + Dim other As VisualBasicCompilation = CreateCompilation( + + +Friend class PublicKeyConstants + public const PublicKey As String = "Something" +End Class +]]> + +) + + Dim source1 = + + + + +class TestAttribute + Inherits System.Attribute + + public Sub New(x As String) + End Sub +End Class +]]> + + + Dim compilationReference As CompilationReference = other.ToMetadataReference() + Dim comp1 As VisualBasicCompilation = CreateCompilation(source1, {compilationReference}) + + Dim expected1 = + +BC36957: Friend access was granted by 'Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly does not match that specified by the attribute in the granting assembly. +BC36980: Error extracting public key from file 'somethingSomething': Assembly signing not supported. + + + comp1.AssertTheseDiagnostics(expected1) + Dim imageReference As MetadataReference = other.EmitToImageReference() + + comp1 = CreateCompilation(source1, {imageReference}) + comp1.AssertTheseDiagnostics(expected1) + + Dim source2 = + + + + +class TestAttribute + Inherits System.Attribute + + public Sub New(x As String) + End Sub +End Class +]]> + + + Dim comp2 As VisualBasicCompilation = CreateCompilation(source2, {compilationReference}) + + Dim expected2 = + +BC36957: Friend access was granted by 'Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly does not match that specified by the attribute in the granting assembly. +BC36981: Error extracting public key from container 'somethingSomething': Assembly signing not supported. + + + comp2.AssertTheseDiagnostics(expected2) + + comp2 = CreateCompilation(source2, {imageReference}) + comp2.AssertTheseDiagnostics(expected2) + + Dim source3 = + + +]]> + + + Dim comp3 As VisualBasicCompilation = CreateCompilation(source3, {compilationReference}) + + Dim expected3 = + +BC36957: Friend access was granted by 'Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly does not match that specified by the attribute in the granting assembly. +BC36980: Error extracting public key from file 'somethingSomething': Assembly signing not supported. + + + comp3.AssertTheseDiagnostics(expected3) + + comp3 = CreateCompilation(source3, {imageReference}) + comp3.AssertTheseDiagnostics(expected3) + + Dim source4 = + + +]]> + + + Dim comp4 As VisualBasicCompilation = CreateCompilation(source4, {compilationReference}) + + Dim expected4 = + +BC36957: Friend access was granted by 'Issue57742_03_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly does not match that specified by the attribute in the granting assembly. +BC36981: Error extracting public key from container 'somethingSomething': Assembly signing not supported. + + + comp4.AssertTheseDiagnostics(expected4) + + comp4 = CreateCompilation(source4, {imageReference}) + comp4.AssertTheseDiagnostics(expected4) + End Sub + + + + Public Sub Issue57742_04() + Dim other As VisualBasicCompilation = CreateCompilation( + + +Friend class PublicKeyConstants + public const PublicKey As String = "Something" +End Class +]]> + +) + + Dim source1 = + + + +]]> + + + Dim compilationReference As CompilationReference = other.ToMetadataReference() + Dim comp1 As VisualBasicCompilation = CreateCompilation(source1, {compilationReference}) + + Dim expected1 = + +BC36957: Friend access was granted by 'Issue57742_04_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly does not match that specified by the attribute in the granting assembly. +BC36980: Error extracting public key from file 'somethingSomething': Assembly signing not supported. + + + comp1.AssertTheseDiagnostics(expected1) + Dim imageReference As MetadataReference = other.EmitToImageReference() + + comp1 = CreateCompilation(source1, {imageReference}) + comp1.AssertTheseDiagnostics(expected1) + + Dim source2 = + + + +]]> + + + Dim comp2 As VisualBasicCompilation = CreateCompilation(source2, {compilationReference}) + + Dim expected2 = + +BC36957: Friend access was granted by 'Issue57742_04_Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly does not match that specified by the attribute in the granting assembly. +BC36981: Error extracting public key from container 'somethingSomething': Assembly signing not supported. + + + comp2.AssertTheseDiagnostics(expected2) + + comp2 = CreateCompilation(source2, {imageReference}) + comp2.AssertTheseDiagnostics(expected2) + End Sub + End Class diff --git a/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTests.vb b/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTests.vb index ff467108cbbff..032515496a3cf 100644 --- a/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTests.vb @@ -3,6 +3,7 @@ ' See the LICENSE file in the project root for more information. Imports System.IO +Imports System.Reflection Imports System.Reflection.Metadata Imports System.Reflection.Metadata.Ecma335 Imports System.Reflection.PortableExecutable @@ -4685,5 +4686,40 @@ End Class ") End Sub + + + Public Sub CompilerInfo_WindowsPdb() + Dim compilerAssembly = GetType(Compilation).Assembly + Dim fileVersion = Version.Parse(compilerAssembly.GetCustomAttribute(Of AssemblyFileVersionAttribute)().Version).ToString() + Dim versionString = compilerAssembly.GetCustomAttribute(Of AssemblyInformationalVersionAttribute)().InformationalVersion + + Dim source = " +Class C + Sub F + End Sub +End CLass" + + Dim c = CreateCompilation({Parse(source, "a.cs")}, options:=TestOptions.DebugDll) + + c.VerifyPdb(" + + + + + + + + + + + + + + + + + +", options:=PdbValidationOptions.IncludeModuleDebugInfo, format:=DebugInformationFormat.Pdb) + End Sub End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests.vb b/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests.vb index e96622e3e54b5..6a2086a9ee857 100644 --- a/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests.vb +++ b/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests.vb @@ -459,7 +459,7 @@ IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: Children(1): IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'c1.S') Children(1): - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'c1') + IOperation: (OperationKind.None, Type: Program.C1, IsInvalid) (Syntax: 'c1') ]]>.Value Dim expectedDiagnostics = ') Value: - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: '.') + IOperation: (OperationKind.None, Type: ?, IsInvalid) (Syntax: '.') Next (Regular) Block[B9] Leaving: {R4} {R2} diff --git a/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests_IConversionExpression.vb b/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests_IConversionExpression.vb index f624ec0d0a09d..ee22a1eee5a18 100644 --- a/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests_IConversionExpression.vb +++ b/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests_IConversionExpression.vb @@ -1318,7 +1318,7 @@ IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDecla Children(1): IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'c1.M2') Children(1): - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: 'c1') + IOperation: (OperationKind.None, Type: Program.C1, IsInvalid) (Syntax: 'c1') ]]>.Value Dim expectedDiagnostics = .Value Dim expectedDiagnostics = .Value Dim expectedOperationTree = .Value Dim expectedOperationTree = .Value Dim expectedDiagnostics = .Value Dim expectedDiagnostics = .Value Dim expectedOperationTree = .Value diff --git a/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests_InvalidStatement.vb b/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests_InvalidStatement.vb index de181fcb21ec9..adb32e386d744 100644 --- a/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests_InvalidStatement.vb +++ b/src/Compilers/VisualBasic/Test/IOperation/IOperation/IOperationTests_InvalidStatement.vb @@ -63,7 +63,7 @@ End Class]]>.Value Dim expectedOperationTree = , IsInvalid, IsImplicit) (Syntax: 'New With {< ... some-name>}') Right: - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: '') + IOperation: (OperationKind.None, Type: System.Xml.Linq.XElement, IsInvalid) (Syntax: '') ]]>.Value Dim expectedDiagnostics = , IsImplicit) (Syntax: 'New With {< ... -name>.@aa}') Right: - IOperation: (OperationKind.None, Type: null) (Syntax: ' ... e-name>.@aa') + IOperation: (OperationKind.None, Type: System.String) (Syntax: ' ... e-name>.@aa') ]]>.Value Dim expectedDiagnostics = String.Empty @@ -722,7 +722,7 @@ IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: Instance Receiver: IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsInvalid, IsImplicit) (Syntax: 'New With {< ... me>.@}') Right: - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: '.@') + IOperation: (OperationKind.None, Type: System.String, IsInvalid) (Syntax: '.@') ]]>.Value Dim expectedDiagnostics = , IsInvalid, IsImplicit) (Syntax: 'New With {.<_>}') Right: - IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: '.<_>') + IOperation: (OperationKind.None, Type: System.Collections.Generic.IEnumerable(Of System.Xml.Linq.XElement), IsInvalid) (Syntax: '.<_>') IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDeclarationGroup, Type: null) (Syntax: 'Dim ok = Ne ... {.<__>}') IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null) (Syntax: 'ok = New Wi ... {.<__>}') Declarators: @@ -783,7 +783,7 @@ IBlockOperation (4 statements, 2 locals) (OperationKind.Block, Type: null, IsInv Instance Receiver: IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'New With {.<__>}') Right: - IOperation: (OperationKind.None, Type: null) (Syntax: '.<__>') + IOperation: (OperationKind.None, Type: System.Collections.Generic.IEnumerable(Of System.Xml.Linq.XElement)) (Syntax: '.<__>') ILabeledOperation (Label: exit) (OperationKind.Labeled, Type: null, IsImplicit) (Syntax: 'End Sub') Statement: null diff --git a/src/Compilers/VisualBasic/vbc/vbc.csproj b/src/Compilers/VisualBasic/vbc/vbc.csproj index a2d93b25420c9..4ba1ab82cda13 100644 --- a/src/Compilers/VisualBasic/vbc/vbc.csproj +++ b/src/Compilers/VisualBasic/vbc/vbc.csproj @@ -7,7 +7,7 @@ Microsoft.CodeAnalysis.VisualBasic.CommandLine true Microsoft.CodeAnalysis.VisualBasic.CommandLine.Program - net472;netcoreapp3.1 + net472;net6.0 true false true @@ -17,7 +17,7 @@ - + diff --git a/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs b/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs index 067eedae2946a..3462f253cac28 100644 --- a/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs +++ b/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler.cs @@ -24,7 +24,6 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; -using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; @@ -329,6 +328,7 @@ protected override void ModifySelectedNode( CancellationToken cancellationToken) { var root = document.GetRequiredSyntaxRootSynchronously(cancellationToken); + var documentOptions = document.GetOptionsAsync(cancellationToken).WaitAndGetResult(cancellationToken); // Add braces for the selected node if (addBrace) { @@ -350,7 +350,7 @@ or IfStatementSyntax or ElseClauseSyntax) { // Add the braces and get the next caretPosition - var (newRoot, nextCaretPosition) = AddBraceToSelectedNode(document, root, selectedNode, args.TextView.Options, cancellationToken); + var (newRoot, nextCaretPosition) = AddBraceToSelectedNode(document, root, selectedNode, documentOptions, cancellationToken); if (document.Project.Solution.Workspace.TryApplyChanges(document.WithSyntaxRoot(newRoot).Project.Solution)) { args.TextView.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(args.SubjectBuffer.CurrentSnapshot, nextCaretPosition)); @@ -375,7 +375,7 @@ or IfStatementSyntax var insertionPosition = GetBraceInsertionPosition(selectedNode); // 2. Insert the braces and move caret - InsertBraceAndMoveCaret(args.TextView, document, insertionPosition, cancellationToken); + InsertBraceAndMoveCaret(args.TextView, document, documentOptions, insertionPosition, cancellationToken); } } else @@ -385,7 +385,7 @@ or IfStatementSyntax document, root, selectedNode, - args.TextView.Options, + documentOptions, cancellationToken); if (document.Project.Solution.Workspace.TryApplyChanges(document.WithSyntaxRoot(newRoot).Project.Solution)) @@ -399,7 +399,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToSelectedNod Document document, SyntaxNode root, SyntaxNode selectedNode, - IEditorOptions editorOptions, + DocumentOptionSet documentOptions, CancellationToken cancellationToken) { // For these nodes, directly modify the node and replace it. @@ -414,7 +414,7 @@ or EventFieldDeclarationSyntax document, root, selectedNode, - WithBraces(selectedNode, editorOptions), + WithBraces(selectedNode, documentOptions), cancellationToken); // Locate the open brace token, and move the caret after it. var nextCaretPosition = GetOpenBraceSpanEnd(newRoot); @@ -428,7 +428,7 @@ or EventFieldDeclarationSyntax // var c = new Obje$$ct() => var c = new Object(); if (selectedNode is ObjectCreationExpressionSyntax objectCreationExpressionNode) { - var (newNode, oldNode) = ModifyObjectCreationExpressionNode(objectCreationExpressionNode, addOrRemoveInitializer: true, editorOptions); + var (newNode, oldNode) = ModifyObjectCreationExpressionNode(objectCreationExpressionNode, addOrRemoveInitializer: true, documentOptions); var newRoot = ReplaceNodeAndFormat( document, root, @@ -480,7 +480,7 @@ or EventFieldDeclarationSyntax // In this case 'Print("Bar")' is considered as the innerStatement so when we inserted the empty block, we need also insert that if (selectedNode.IsEmbeddedStatementOwner()) { - return AddBraceToEmbeddedStatementOwner(document, root, selectedNode, editorOptions, cancellationToken); + return AddBraceToEmbeddedStatementOwner(document, root, selectedNode, documentOptions, cancellationToken); } throw ExceptionUtilities.UnexpectedValue(selectedNode); @@ -490,7 +490,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) RemoveBraceFromSelect Document document, SyntaxNode root, SyntaxNode selectedNode, - IEditorOptions editorOptions, + DocumentOptionSet documentOptions, CancellationToken cancellationToken) { // Remove the initializer from ObjectCreationExpression @@ -504,7 +504,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) RemoveBraceFromSelect // e.g. var c = new Bar() => var c = new Bar(); if (selectedNode is ObjectCreationExpressionSyntax objectCreationExpressionNode) { - var (newNode, oldNode) = ModifyObjectCreationExpressionNode(objectCreationExpressionNode, addOrRemoveInitializer: false, editorOptions); + var (newNode, oldNode) = ModifyObjectCreationExpressionNode(objectCreationExpressionNode, addOrRemoveInitializer: false, documentOptions); var newRoot = ReplaceNodeAndFormat( document, root, @@ -633,18 +633,19 @@ private static bool IsTokenPartOfExpression(SyntaxToken syntaxToken) return !syntaxToken.GetAncestors().IsEmpty(); } - private static string GetBracePairString(IEditorOptions editorOptions) + private static string GetBracePairString(DocumentOptionSet documentOptions) => string.Concat(SyntaxFacts.GetText(SyntaxKind.OpenBraceToken), - editorOptions.GetNewLineCharacter(), + documentOptions.GetOption(FormattingOptions2.NewLine), SyntaxFacts.GetText(SyntaxKind.CloseBraceToken)); private void InsertBraceAndMoveCaret( ITextView textView, Document document, + DocumentOptionSet documentOptions, int insertionPosition, CancellationToken cancellationToken) { - var bracePair = GetBracePairString(textView.Options); + var bracePair = GetBracePairString(documentOptions); // 1. Insert { }. var newDocument = document.InsertText(insertionPosition, bracePair, cancellationToken); diff --git a/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler_Helpers.cs b/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler_Helpers.cs index c403f44d53663..3e1503a8cba27 100644 --- a/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler_Helpers.cs +++ b/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler_Helpers.cs @@ -10,9 +10,8 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.CSharp.AutomaticCompletion @@ -89,7 +88,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToEmbeddedSta Document document, SyntaxNode root, SyntaxNode embeddedStatementOwner, - IEditorOptions editorOptions, + DocumentOptionSet documentOptions, CancellationToken cancellationToken) { // If there is no inner statement, just add an empty block to it. @@ -112,7 +111,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToEmbeddedSta document, root, embeddedStatementOwner, - WithBraces(embeddedStatementOwner, editorOptions), cancellationToken); + WithBraces(embeddedStatementOwner, documentOptions), cancellationToken); // Locate the open brace token, and move the caret after it. var nextCaretPosition = GetOpenBraceSpanEnd(newRoot); return (newRoot, nextCaretPosition); @@ -143,13 +142,13 @@ WhileStatementSyntax or ForEachStatementSyntax or ForStatementSyntax or LockStat document, root, oldNode: embeddedStatementOwner, - newNode: AddBlockToEmbeddedStatementOwner(embeddedStatementOwner, editorOptions), + newNode: AddBlockToEmbeddedStatementOwner(embeddedStatementOwner, documentOptions), anchorNode: embeddedStatementOwner, nodesToInsert: ImmutableArray.Empty.Add(statement), cancellationToken), - DoStatementSyntax doStatementNode => AddBraceToDoStatement(document, root, doStatementNode, editorOptions, statement, cancellationToken), - IfStatementSyntax ifStatementNode => AddBraceToIfStatement(document, root, ifStatementNode, editorOptions, statement, cancellationToken), - ElseClauseSyntax elseClauseNode => AddBraceToElseClause(document, root, elseClauseNode, editorOptions, statement, cancellationToken), + DoStatementSyntax doStatementNode => AddBraceToDoStatement(document, root, doStatementNode, documentOptions, statement, cancellationToken), + IfStatementSyntax ifStatementNode => AddBraceToIfStatement(document, root, ifStatementNode, documentOptions, statement, cancellationToken), + ElseClauseSyntax elseClauseNode => AddBraceToElseClause(document, root, elseClauseNode, documentOptions, statement, cancellationToken), _ => throw ExceptionUtilities.UnexpectedValue(embeddedStatementOwner), }; } @@ -158,7 +157,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToDoStatement Document document, SyntaxNode root, DoStatementSyntax doStatementNode, - IEditorOptions editorOptions, + DocumentOptionSet documentOptions, StatementSyntax innerStatement, CancellationToken cancellationToken) { @@ -182,7 +181,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToDoStatement document, root, oldNode: doStatementNode, - newNode: AddBlockToEmbeddedStatementOwner(doStatementNode, editorOptions), + newNode: AddBlockToEmbeddedStatementOwner(doStatementNode, documentOptions), anchorNode: doStatementNode, nodesToInsert: ImmutableArray.Empty.Add(innerStatement), cancellationToken); @@ -204,7 +203,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToDoStatement document, root, doStatementNode, - AddBlockToEmbeddedStatementOwner(doStatementNode, editorOptions, innerStatement), + AddBlockToEmbeddedStatementOwner(doStatementNode, documentOptions, innerStatement), cancellationToken); var nextCaretPosition = GetOpenBraceSpanEnd(newRoot); return (newRoot, nextCaretPosition); @@ -214,7 +213,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToIfStatement Document document, SyntaxNode root, IfStatementSyntax ifStatementNode, - IEditorOptions editorOptions, + DocumentOptionSet documentOptions, StatementSyntax innerStatement, CancellationToken cancellationToken) { @@ -234,7 +233,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToIfStatement return ReplaceStatementOwnerAndInsertStatement(document, root, ifStatementNode, - AddBlockToEmbeddedStatementOwner(ifStatementNode, editorOptions), + AddBlockToEmbeddedStatementOwner(ifStatementNode, documentOptions), ifStatementNode, ImmutableArray.Empty.Add(innerStatement), cancellationToken); @@ -257,7 +256,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToIfStatement document, root, ifStatementNode, - AddBlockToEmbeddedStatementOwner(ifStatementNode, editorOptions, innerStatement), + AddBlockToEmbeddedStatementOwner(ifStatementNode, documentOptions, innerStatement), cancellationToken); var nextCaretPosition = GetOpenBraceSpanEnd(newRoot); return (newRoot, nextCaretPosition); @@ -267,7 +266,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToElseClause( Document document, SyntaxNode root, ElseClauseSyntax elseClauseNode, - IEditorOptions editorOptions, + DocumentOptionSet documentOptions, StatementSyntax innerStatement, CancellationToken cancellationToken) { @@ -275,7 +274,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToElseClause( // then treat it as the selected node is the nested if statement if (elseClauseNode.Statement is IfStatementSyntax) { - return AddBraceToEmbeddedStatementOwner(document, root, elseClauseNode.Statement, editorOptions, cancellationToken); + return AddBraceToEmbeddedStatementOwner(document, root, elseClauseNode.Statement, documentOptions, cancellationToken); } // Otherwise, it is just an ending else clause. @@ -298,7 +297,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToElseClause( return ReplaceStatementOwnerAndInsertStatement(document, root, elseClauseNode, - WithBraces(elseClauseNode, editorOptions), + WithBraces(elseClauseNode, documentOptions), elseClauseNode.Parent!, ImmutableArray.Empty.Add(innerStatement), cancellationToken); @@ -327,7 +326,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToElseClause( document, root, elseClauseNode, - AddBlockToEmbeddedStatementOwner(elseClauseNode, editorOptions, innerStatement), + AddBlockToEmbeddedStatementOwner(elseClauseNode, documentOptions, innerStatement), cancellationToken); var nextCaretPosition = formattedNewRoot.GetAnnotatedTokens(s_openBracePositionAnnotation).Single().Span.End; @@ -341,7 +340,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToElseClause( private static (SyntaxNode newNode, SyntaxNode oldNode) ModifyObjectCreationExpressionNode( ObjectCreationExpressionSyntax objectCreationExpressionNode, bool addOrRemoveInitializer, - IEditorOptions editorOptions) + DocumentOptionSet documentOptions) { // 1. Add '()' after the type. // e.g. var c = new Bar => var c = new Bar() @@ -350,7 +349,7 @@ private static (SyntaxNode newNode, SyntaxNode oldNode) ModifyObjectCreationExpr // 2. Add or remove initializer // e.g. var c = new Bar() => var c = new Bar() { } var objectCreationNodeWithCorrectInitializer = addOrRemoveInitializer - ? WithBraces(objectCreationNodeWithArgumentList, editorOptions) + ? WithBraces(objectCreationNodeWithArgumentList, documentOptions) : WithoutBraces(objectCreationNodeWithArgumentList); // 3. Handler the semicolon. @@ -785,32 +784,32 @@ private static bool ShouldRemoveBraceForEventDeclaration(EventDeclarationSyntax #region AddBrace - private static AccessorListSyntax GetAccessorListNode(IEditorOptions editorOptions) - => SyntaxFactory.AccessorList().WithOpenBraceToken(GetOpenBrace(editorOptions)).WithCloseBraceToken(GetCloseBrace(editorOptions)); + private static AccessorListSyntax GetAccessorListNode(DocumentOptionSet documentOptions) + => SyntaxFactory.AccessorList().WithOpenBraceToken(GetOpenBrace(documentOptions)).WithCloseBraceToken(GetCloseBrace(documentOptions)); - private static InitializerExpressionSyntax GetInitializerExpressionNode(IEditorOptions editorOptions) + private static InitializerExpressionSyntax GetInitializerExpressionNode(DocumentOptionSet documentOptions) => SyntaxFactory.InitializerExpression(SyntaxKind.ObjectInitializerExpression) - .WithOpenBraceToken(GetOpenBrace(editorOptions)); + .WithOpenBraceToken(GetOpenBrace(documentOptions)); - private static BlockSyntax GetBlockNode(IEditorOptions editorOptions) - => SyntaxFactory.Block().WithOpenBraceToken(GetOpenBrace(editorOptions)).WithCloseBraceToken(GetCloseBrace(editorOptions)); + private static BlockSyntax GetBlockNode(DocumentOptionSet documentOptions) + => SyntaxFactory.Block().WithOpenBraceToken(GetOpenBrace(documentOptions)).WithCloseBraceToken(GetCloseBrace(documentOptions)); - private static SyntaxToken GetOpenBrace(IEditorOptions editorOptions) + private static SyntaxToken GetOpenBrace(DocumentOptionSet documentOptions) => SyntaxFactory.Token( leading: SyntaxTriviaList.Empty, kind: SyntaxKind.OpenBraceToken, - trailing: SyntaxTriviaList.Create(GetNewLineTrivia(editorOptions))) + trailing: SyntaxTriviaList.Create(GetNewLineTrivia(documentOptions))) .WithAdditionalAnnotations(s_openBracePositionAnnotation); - private static SyntaxToken GetCloseBrace(IEditorOptions editorOptions) + private static SyntaxToken GetCloseBrace(DocumentOptionSet documentOptions) => SyntaxFactory.Token( leading: SyntaxTriviaList.Empty, kind: SyntaxKind.CloseBraceToken, - trailing: SyntaxTriviaList.Create(GetNewLineTrivia(editorOptions))); + trailing: SyntaxTriviaList.Create(GetNewLineTrivia(documentOptions))); - private static SyntaxTrivia GetNewLineTrivia(IEditorOptions editorOptions) + private static SyntaxTrivia GetNewLineTrivia(DocumentOptionSet documentOptions) { - var newLineString = editorOptions.GetNewLineCharacter(); + var newLineString = documentOptions.GetOption(FormattingOptions2.NewLine); return SyntaxFactory.EndOfLine(newLineString); } @@ -818,18 +817,18 @@ private static SyntaxTrivia GetNewLineTrivia(IEditorOptions editorOptions) /// Add braces to the . /// For FieldDeclaration and EventFieldDeclaration, it will change them to PropertyDeclaration and EventDeclaration /// - private static SyntaxNode WithBraces(SyntaxNode node, IEditorOptions editorOptions) + private static SyntaxNode WithBraces(SyntaxNode node, DocumentOptionSet documentOptions) => node switch { - BaseTypeDeclarationSyntax baseTypeDeclarationNode => WithBracesForBaseTypeDeclaration(baseTypeDeclarationNode, editorOptions), - ObjectCreationExpressionSyntax objectCreationExpressionNode => GetObjectCreationExpressionWithInitializer(objectCreationExpressionNode, editorOptions), + BaseTypeDeclarationSyntax baseTypeDeclarationNode => WithBracesForBaseTypeDeclaration(baseTypeDeclarationNode, documentOptions), + ObjectCreationExpressionSyntax objectCreationExpressionNode => GetObjectCreationExpressionWithInitializer(objectCreationExpressionNode, documentOptions), FieldDeclarationSyntax fieldDeclarationNode when fieldDeclarationNode.Declaration.Variables.IsSingle() - => ConvertFieldDeclarationToPropertyDeclaration(fieldDeclarationNode, editorOptions), - EventFieldDeclarationSyntax eventFieldDeclarationNode => ConvertEventFieldDeclarationToEventDeclaration(eventFieldDeclarationNode, editorOptions), - BaseMethodDeclarationSyntax baseMethodDeclarationNode => AddBlockToBaseMethodDeclaration(baseMethodDeclarationNode, editorOptions), - LocalFunctionStatementSyntax localFunctionStatementNode => AddBlockToLocalFunctionDeclaration(localFunctionStatementNode, editorOptions), - AccessorDeclarationSyntax accessorDeclarationNode => AddBlockToAccessorDeclaration(accessorDeclarationNode, editorOptions), - _ when node.IsEmbeddedStatementOwner() => AddBlockToEmbeddedStatementOwner(node, editorOptions), + => ConvertFieldDeclarationToPropertyDeclaration(fieldDeclarationNode, documentOptions), + EventFieldDeclarationSyntax eventFieldDeclarationNode => ConvertEventFieldDeclarationToEventDeclaration(eventFieldDeclarationNode, documentOptions), + BaseMethodDeclarationSyntax baseMethodDeclarationNode => AddBlockToBaseMethodDeclaration(baseMethodDeclarationNode, documentOptions), + LocalFunctionStatementSyntax localFunctionStatementNode => AddBlockToLocalFunctionDeclaration(localFunctionStatementNode, documentOptions), + AccessorDeclarationSyntax accessorDeclarationNode => AddBlockToAccessorDeclaration(accessorDeclarationNode, documentOptions), + _ when node.IsEmbeddedStatementOwner() => AddBlockToEmbeddedStatementOwner(node, documentOptions), _ => throw ExceptionUtilities.UnexpectedValue(node), }; @@ -838,8 +837,8 @@ _ when node.IsEmbeddedStatementOwner() => AddBlockToEmbeddedStatementOwner(node, /// private static BaseTypeDeclarationSyntax WithBracesForBaseTypeDeclaration( BaseTypeDeclarationSyntax baseTypeDeclarationNode, - IEditorOptions editorOptions) - => baseTypeDeclarationNode.WithOpenBraceToken(GetOpenBrace(editorOptions)) + DocumentOptionSet documentOptions) + => baseTypeDeclarationNode.WithOpenBraceToken(GetOpenBrace(documentOptions)) .WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken)); /// @@ -847,22 +846,22 @@ private static BaseTypeDeclarationSyntax WithBracesForBaseTypeDeclaration( /// private static ObjectCreationExpressionSyntax GetObjectCreationExpressionWithInitializer( ObjectCreationExpressionSyntax objectCreationExpressionNode, - IEditorOptions editorOptions) - => objectCreationExpressionNode.WithInitializer(GetInitializerExpressionNode(editorOptions)); + DocumentOptionSet documentOptions) + => objectCreationExpressionNode.WithInitializer(GetInitializerExpressionNode(documentOptions)); /// /// Convert to a property declarations. /// private static PropertyDeclarationSyntax ConvertFieldDeclarationToPropertyDeclaration( FieldDeclarationSyntax fieldDeclarationNode, - IEditorOptions editorOptions) + DocumentOptionSet documentOptions) => SyntaxFactory.PropertyDeclaration( fieldDeclarationNode.AttributeLists, fieldDeclarationNode.Modifiers, fieldDeclarationNode.Declaration.Type, explicitInterfaceSpecifier: null, identifier: fieldDeclarationNode.Declaration.Variables[0].Identifier, - accessorList: GetAccessorListNode(editorOptions), + accessorList: GetAccessorListNode(documentOptions), expressionBody: null, initializer: null, semicolonToken: SyntaxFactory.Token(SyntaxKind.None)).WithTriviaFrom(fieldDeclarationNode); @@ -872,7 +871,7 @@ private static PropertyDeclarationSyntax ConvertFieldDeclarationToPropertyDeclar /// private static EventDeclarationSyntax ConvertEventFieldDeclarationToEventDeclaration( EventFieldDeclarationSyntax eventFieldDeclarationNode, - IEditorOptions editorOptions) + DocumentOptionSet documentOptions) => SyntaxFactory.EventDeclaration( eventFieldDeclarationNode.AttributeLists, eventFieldDeclarationNode.Modifiers, @@ -880,7 +879,7 @@ private static EventDeclarationSyntax ConvertEventFieldDeclarationToEventDeclara eventFieldDeclarationNode.Declaration.Type, explicitInterfaceSpecifier: null, identifier: eventFieldDeclarationNode.Declaration.Variables[0].Identifier, - accessorList: GetAccessorListNode(editorOptions), + accessorList: GetAccessorListNode(documentOptions), semicolonToken: SyntaxFactory.Token(SyntaxKind.None)).WithTriviaFrom(eventFieldDeclarationNode); /// @@ -888,8 +887,8 @@ private static EventDeclarationSyntax ConvertEventFieldDeclarationToEventDeclara /// private static BaseMethodDeclarationSyntax AddBlockToBaseMethodDeclaration( BaseMethodDeclarationSyntax baseMethodDeclarationNode, - IEditorOptions editorOptions) - => baseMethodDeclarationNode.WithBody(GetBlockNode(editorOptions)) + DocumentOptionSet documentOptions) + => baseMethodDeclarationNode.WithBody(GetBlockNode(documentOptions)) // When the method declaration with no body is parsed, it has an invisible trailing semicolon. Make sure it is removed. .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)); @@ -898,8 +897,8 @@ private static BaseMethodDeclarationSyntax AddBlockToBaseMethodDeclaration( /// private static LocalFunctionStatementSyntax AddBlockToLocalFunctionDeclaration( LocalFunctionStatementSyntax localFunctionStatementNode, - IEditorOptions editorOptions) - => localFunctionStatementNode.WithBody(GetBlockNode(editorOptions)) + DocumentOptionSet documentOptions) + => localFunctionStatementNode.WithBody(GetBlockNode(documentOptions)) // When the local method declaration with no body is parsed, it has an invisible trailing semicolon. Make sure it is removed. .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)); @@ -908,8 +907,8 @@ private static LocalFunctionStatementSyntax AddBlockToLocalFunctionDeclaration( /// private static AccessorDeclarationSyntax AddBlockToAccessorDeclaration( AccessorDeclarationSyntax accessorDeclarationNode, - IEditorOptions editorOptions) - => accessorDeclarationNode.WithBody(GetBlockNode(editorOptions)) + DocumentOptionSet documentOptions) + => accessorDeclarationNode.WithBody(GetBlockNode(documentOptions)) // When the accessor with no body is parsed, it has an invisible trailing semicolon. Make sure it is removed. .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)); @@ -918,12 +917,12 @@ private static AccessorDeclarationSyntax AddBlockToAccessorDeclaration( /// private static SyntaxNode AddBlockToEmbeddedStatementOwner( SyntaxNode embeddedStatementOwner, - IEditorOptions editorOptions, + DocumentOptionSet documentOptions, StatementSyntax? extraNodeInsertedBetweenBraces = null) { var block = extraNodeInsertedBetweenBraces != null - ? GetBlockNode(editorOptions).WithStatements(new SyntaxList(extraNodeInsertedBetweenBraces)) - : GetBlockNode(editorOptions); + ? GetBlockNode(documentOptions).WithStatements(new SyntaxList(extraNodeInsertedBetweenBraces)) + : GetBlockNode(documentOptions); return embeddedStatementOwner switch { diff --git a/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticLineEnderTests.cs b/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticLineEnderTests.cs index 45d685701f9f2..a862cdb79d297 100644 --- a/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticLineEnderTests.cs +++ b/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticLineEnderTests.cs @@ -202,6 +202,40 @@ void Method() }"); } + [WpfFact] + [WorkItem(57323, "https://github.com/dotnet/roslyn/issues/57323")] + public void EmbededStatementFollowedByStatement() + { + Test(@"class C +{ + void Method() + { + if (true) + { + } + if (true) + { + $$ + } + if (true) + { + } + } +}", @"class C +{ + void Method() + { + if (true) + { + } + if (true$$) + if (true) + { + } + } +}"); + } + [WpfFact] public void Statement() { diff --git a/src/EditorFeatures/CSharpTest/CodeActions/EnableNullable/EnableNullableTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/EnableNullable/EnableNullableTests.cs index 3a879c5d3fa65..ff0f0d28ef2b9 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/EnableNullable/EnableNullableTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/EnableNullable/EnableNullableTests.cs @@ -3,12 +3,14 @@ // See the LICENSE file in the project root for more information. using System; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Testing; +using Roslyn.Utilities; using Xunit; using VerifyCS = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.CSharpCodeRefactoringVerifier< Microsoft.CodeAnalysis.CSharp.CodeRefactorings.EnableNullable.EnableNullableCodeRefactoringProvider>; @@ -523,6 +525,54 @@ public async Task DisabledIfSetInProject(NullableContextOptions nullableContextO }.RunAsync(); } + [Theory] + [InlineData(LanguageVersion.CSharp1)] + [InlineData(LanguageVersion.CSharp2)] + [InlineData(LanguageVersion.CSharp3)] + [InlineData(LanguageVersion.CSharp4)] + [InlineData(LanguageVersion.CSharp5)] + [InlineData(LanguageVersion.CSharp6)] + [InlineData(LanguageVersion.CSharp7)] + [InlineData(LanguageVersion.CSharp7_1)] + [InlineData(LanguageVersion.CSharp7_2)] + [InlineData(LanguageVersion.CSharp7_3)] + public async Task DisabledForUnsupportedLanguageVersion(LanguageVersion languageVersion) + { + var code = @" +#{|#0:nullable|} enable$$ +"; + + var error = languageVersion switch + { + LanguageVersion.CSharp1 => "CS8022", + LanguageVersion.CSharp2 => "CS8023", + LanguageVersion.CSharp3 => "CS8024", + LanguageVersion.CSharp4 => "CS8025", + LanguageVersion.CSharp5 => "CS8026", + LanguageVersion.CSharp6 => "CS8059", + LanguageVersion.CSharp7 => "CS8107", + LanguageVersion.CSharp7_1 => "CS8302", + LanguageVersion.CSharp7_2 => "CS8320", + LanguageVersion.CSharp7_3 => "CS8370", + _ => throw ExceptionUtilities.Unreachable, + }; + + // /0/Test0.cs(2,2): error [error]: Feature 'nullable reference types' is not available in C# [version]. Please use language version 8.0 or greater. + var expected = DiagnosticResult.CompilerError(error).WithLocation(0); + if (CultureInfo.CurrentCulture.TwoLetterISOLanguageName == "en") + { + expected = expected.WithArguments("nullable reference types", "8.0"); + } + + await new VerifyCS.Test + { + TestCode = code, + ExpectedDiagnostics = { expected }, + FixedCode = code, + LanguageVersion = languageVersion, + }.RunAsync(); + } + [Fact] public async Task DisabledOnNullableDisable() { diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs index 6409f7f261e9c..8a28773c4c0cb 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs @@ -1721,9 +1721,11 @@ public void Main() [Trait(Traits.Feature, Traits.Features.Completion)] [InlineData(nameof(DayOfWeek), nameof(DayOfWeek.Friday))] [InlineData(nameof(DateTime), nameof(DateTime.Now))] + [InlineData(nameof(TimeZoneInfo), nameof(TimeZoneInfo.Local))] public async Task TestNullableEnum(string typeName, string memberName) { var markup = $@" +#nullable enable using System; class C {{ @@ -1742,9 +1744,11 @@ public void Main() [Trait(Traits.Feature, Traits.Features.Completion)] [InlineData(nameof(DayOfWeek), nameof(DayOfWeek.Friday))] [InlineData(nameof(DateTime), nameof(DateTime.Now))] + [InlineData(nameof(TimeZoneInfo), nameof(TimeZoneInfo.Local))] public async Task TestTypeAlias(string typeName, string memberName) { var markup = $@" +#nullable enable using AT = System.{typeName}; public class Program diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/AddInheritdoc/AddInheritdocTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/AddInheritdoc/AddInheritdocTests.cs new file mode 100644 index 0000000000000..66a3faa876a1c --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Diagnostics/AddInheritdoc/AddInheritdocTests.cs @@ -0,0 +1,381 @@ +// 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.Tasks; +using Microsoft.CodeAnalysis.CSharp.CodeFixes.AddInheritdoc; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Testing; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.AddInheritdoc +{ + using VerifyCS = CSharpCodeFixVerifier< + EmptyDiagnosticAnalyzer, + AddInheritdocCodeFixProvider>; + + [Trait(Traits.Feature, Traits.Features.CodeActionsAddInheritdoc)] + public class AddInheritdocTests + { + private static async Task TestAsync(string initialMarkup, string expectedMarkup) + { + var test = new VerifyCS.Test + { + TestCode = initialMarkup, + FixedCode = expectedMarkup, + CodeActionValidationMode = CodeActionValidationMode.Full, + }; + await test.RunAsync(); + } + + private static async Task TestMissingAsync(string initialMarkup) + => await VerifyCS.VerifyCodeFixAsync(initialMarkup, initialMarkup); + + [Fact] + public async Task AddMissingInheritdocOnOverridenMethod() + { + await TestAsync( + @" +/// Some doc. +public class BaseClass +{ + /// Some doc. + public virtual void M() { } +} +/// Some doc. +public class Derived: BaseClass +{ + public override void {|CS1591:M|}() { } +}", + @" +/// Some doc. +public class BaseClass +{ + /// Some doc. + public virtual void M() { } +} +/// Some doc. +public class Derived: BaseClass +{ + /// + public override void M() { } +}"); + } + + [Theory] + [InlineData("public void {|CS1591:OtherMethod|}() { }")] + [InlineData("public void {|CS1591:M|}() { }")] + [InlineData("public new void {|CS1591:M|}() { }")] + public async Task DontOfferOnNotOverridenMethod(string methodDefintion) + { + await TestMissingAsync( + $@" +/// Some doc. +public class BaseClass +{{ + /// Some doc. + public virtual void M() {{ }} +}} +/// Some doc. +public class Derived: BaseClass +{{ + {methodDefintion} +}}"); + } + + [Fact] + public async Task AddMissingInheritdocOnImplicitInterfaceMethod() + { + await TestAsync( + @" +/// Some doc. +public interface IInterface +{ + /// Some doc. + void M(); +} +/// Some doc. +public class MyClass: IInterface +{ + public void {|CS1591:M|}() { } +}", + @" +/// Some doc. +public interface IInterface +{ + /// Some doc. + void M(); +} +/// Some doc. +public class MyClass: IInterface +{ + /// + public void M() { } +}"); + } + + [Fact] + public async Task DontOfferOnExplicitInterfaceMethod() + { + await TestMissingAsync( + @" +/// Some doc. +public interface IInterface +{ + /// Some doc. + void M(); +} +/// Some doc. +public class MyClass: IInterface +{ + void IInterface.M() { } +}"); + } + [Fact] + public async Task AddMissingInheritdocOnOverridenProperty() + { + await TestAsync( + @" +/// Some doc. +public class BaseClass +{ + /// Some doc. + public virtual string P { get; set; } +} +/// Some doc. +public class Derived: BaseClass +{ + public override string {|CS1591:P|} { get; set; } +}", + @" +/// Some doc. +public class BaseClass +{ + /// Some doc. + public virtual string P { get; set; } +} +/// Some doc. +public class Derived: BaseClass +{ + /// + public override string P { get; set; } +}"); + } + + [Fact] + public async Task AddMissingInheritdocOnImplicitInterfaceProperty() + { + await TestAsync( + @" +/// Some doc. +public interface IInterface +{ + /// Some doc. + string P { get; } +} +/// Some doc. +public class MyClass: IInterface +{ + public string {|CS1591:P|} { get; } +}", + @" +/// Some doc. +public interface IInterface +{ + /// Some doc. + string P { get; } +} +/// Some doc. +public class MyClass: IInterface +{ + /// + public string P { get; } +}"); + } + + [Fact] + public async Task AddMissingInheritdocOnImplicitInterfaceEvent() + { + await TestAsync( + @" +/// Some doc. +public interface IInterface +{ + /// Some doc. + event System.Action SomeEvent; +} +/// Some doc. +public class MyClass: IInterface +{ + public event System.Action {|CS1591:SomeEvent|}; + + void OnSomething() => SomeEvent?.Invoke(); +}", + @" +/// Some doc. +public interface IInterface +{ + /// Some doc. + event System.Action SomeEvent; +} +/// Some doc. +public class MyClass: IInterface +{ + /// + public event System.Action SomeEvent; + + void OnSomething() => SomeEvent?.Invoke(); +}"); + } + + [Fact] + public async Task AddMissingInheritdocTriviaTest_1() + { + await TestAsync( + @" +/// Some doc. +public class BaseClass +{ + /// Some doc. + public virtual void M() { } +} +/// Some doc. +public class Derived: BaseClass +{ + // Comment + public override void {|CS1591:M|}() { } +}", + @" +/// Some doc. +public class BaseClass +{ + /// Some doc. + public virtual void M() { } +} +/// Some doc. +public class Derived: BaseClass +{ + /// + // Comment + public override void M() { } +}"); + } + + [Fact] + public async Task AddMissingInheritdocTriviaTest_2() + { + await TestAsync( + @" +/// Some doc. +public class BaseClass +{ + /// Some doc. + public virtual void M() { } +} +/// Some doc. +public class Derived: BaseClass +{ + // Comment 1 + /* Comment 2 */ public /* Comment 3 */ override void {|CS1591:M|} /* Comment 4 */ () /* Comment 5 */ { } /* Comment 6 */ +}", + @" +/// Some doc. +public class BaseClass +{ + /// Some doc. + public virtual void M() { } +} +/// Some doc. +public class Derived: BaseClass +{ + /// + // Comment 1 + /* Comment 2 */ public /* Comment 3 */ override void M /* Comment 4 */ () /* Comment 5 */ { } /* Comment 6 */ +}"); + } + + [Fact] + public async Task AddMissingInheritdocMethodWithAttribute() + { + await TestAsync( + @" +/// Some doc. +[System.AttributeUsage(System.AttributeTargets.Method)] +public sealed class DummyAttribute: System.Attribute +{ +} + +/// Some doc. +public class BaseClass +{ + /// Some doc. + public virtual void M() { } +} +/// Some doc. +public class Derived: BaseClass +{ + [Dummy] + public override void {|CS1591:M|}() { } +}", + @" +/// Some doc. +[System.AttributeUsage(System.AttributeTargets.Method)] +public sealed class DummyAttribute: System.Attribute +{ +} + +/// Some doc. +public class BaseClass +{ + /// Some doc. + public virtual void M() { } +} +/// Some doc. +public class Derived: BaseClass +{ + /// + [Dummy] + public override void M() { } +}"); + } + + [Fact] + public async Task AddMissingInheritdocFixAll() + { + await TestAsync( + @" +/// Some doc. +public class BaseClass +{ + /// Some doc. + public virtual void M() { } + /// Some doc. + public virtual string P { get; } +} +/// Some doc. +public class Derived: BaseClass +{ + public override void {|CS1591:M|}() { } + public override string {|CS1591:P|} { get; } +}", + @" +/// Some doc. +public class BaseClass +{ + /// Some doc. + public virtual void M() { } + /// Some doc. + public virtual string P { get; } +} +/// Some doc. +public class Derived: BaseClass +{ + /// + public override void M() { } + /// + public override string P { get; } +}"); + } + } +} diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs index 422c7a34e5536..25fe14be271cc 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs @@ -560,6 +560,29 @@ class C Diagnostic(RudeEditKind.DeleteActiveStatement, "get", FeaturesResources.code)); } + [Fact] + public void Property_ExpressionBody_NonLeaf() + { + var src1 = @" +class C +{ + int P => M(); + int M() { return 1; } +} +"; + var src2 = @" +class C +{ + int P => M(); + int M() { return 2; } +} +"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + [Fact] public void Property_BlockBodyToExpressionBody1() { @@ -664,6 +687,29 @@ public void Indexer_BlockBodyToExpressionBody2() Diagnostic(RudeEditKind.Delete, "int this[int a]", DeletedSymbolDisplay(CSharpFeaturesResources.indexer_setter, "this[int a].set"))); } + [Fact] + public void Indexer_ExpressionBody_NonLeaf() + { + var src1 = @" +class C +{ + int this[int index] => M(); + int M() { return 1; } +} +"; + var src2 = @" +class C +{ + int this[int index] => M(); + int M() { return 2; } +} +"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + [Fact] public void Update_Leaf_Indexers1() { diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index 1c0ad4e46f56e..b0b9c401a3b0b 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -7,8 +7,8 @@ using System; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.EditAndContinue.Contracts; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; -using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -11074,11 +11074,11 @@ public static void F() var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2, flags: new[] { - ActiveStatementFlags.PartiallyExecuted | ActiveStatementFlags.IsLeafFrame, - ActiveStatementFlags.PartiallyExecuted | ActiveStatementFlags.IsNonLeafFrame, - ActiveStatementFlags.IsLeafFrame, - ActiveStatementFlags.IsNonLeafFrame, - ActiveStatementFlags.IsNonLeafFrame | ActiveStatementFlags.IsLeafFrame + ActiveStatementFlags.PartiallyExecuted | ActiveStatementFlags.LeafFrame, + ActiveStatementFlags.PartiallyExecuted | ActiveStatementFlags.NonLeafFrame, + ActiveStatementFlags.LeafFrame, + ActiveStatementFlags.NonLeafFrame, + ActiveStatementFlags.NonLeafFrame | ActiveStatementFlags.LeafFrame }); edits.VerifyRudeDiagnostics(active, @@ -11109,7 +11109,7 @@ public static void F() var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2, flags: new[] { - ActiveStatementFlags.PartiallyExecuted | ActiveStatementFlags.IsLeafFrame + ActiveStatementFlags.PartiallyExecuted | ActiveStatementFlags.LeafFrame }); edits.VerifyRudeDiagnostics(active, @@ -11137,7 +11137,7 @@ public static void F() var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2, flags: new[] { - ActiveStatementFlags.IsNonLeafFrame | ActiveStatementFlags.IsLeafFrame + ActiveStatementFlags.NonLeafFrame | ActiveStatementFlags.LeafFrame }); edits.VerifyRudeDiagnostics(active, diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs index 40b6dc033b4b0..d62eb5443f7e8 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTrackingServiceTests.cs @@ -9,12 +9,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.EditAndContinue.Contracts; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -159,15 +159,15 @@ public async Task TrackingService_GetLatestSpansAsync(bool scheduleInitialTracki spanProvider.GetBaseActiveStatementSpansImpl = (_, documentIds) => ImmutableArray.Create( ImmutableArray.Create( - new ActiveStatementSpan(0, span11, ActiveStatementFlags.IsNonLeafFrame, unmappedDocumentId: null), - new ActiveStatementSpan(1, span12, ActiveStatementFlags.IsLeafFrame, unmappedDocumentId: null)), + new ActiveStatementSpan(0, span11, ActiveStatementFlags.NonLeafFrame, unmappedDocumentId: null), + new ActiveStatementSpan(1, span12, ActiveStatementFlags.LeafFrame, unmappedDocumentId: null)), ImmutableArray.Empty); spanProvider.GetAdjustedActiveStatementSpansImpl = (document, _) => document.Name switch { "1.cs" => ImmutableArray.Create( - new ActiveStatementSpan(0, span21, ActiveStatementFlags.IsNonLeafFrame, unmappedDocumentId: null), - new ActiveStatementSpan(1, span22, ActiveStatementFlags.IsLeafFrame, unmappedDocumentId: null)), + new ActiveStatementSpan(0, span21, ActiveStatementFlags.NonLeafFrame, unmappedDocumentId: null), + new ActiveStatementSpan(1, span22, ActiveStatementFlags.LeafFrame, unmappedDocumentId: null)), "2.cs" => ImmutableArray.Empty, _ => throw ExceptionUtilities.Unreachable }; @@ -198,8 +198,8 @@ public async Task TrackingService_GetLatestSpansAsync(bool scheduleInitialTracki var spans1 = trackingSession.Test_GetTrackingSpans(); AssertEx.Equal(new[] { - $"V0 →←@[10..15): IsNonLeafFrame", - $"V0 →←@[20..25): IsLeafFrame" + $"V0 →←@[10..15): NonLeafFrame", + $"V0 →←@[20..25): LeafFrame" }, spans1[document1.FilePath].Select(s => $"{s.Span}: {s.Flags}")); var spans2 = await trackingSession.GetSpansAsync(solution, document1.Id, document1.FilePath, CancellationToken.None); @@ -212,8 +212,8 @@ public async Task TrackingService_GetLatestSpansAsync(bool scheduleInitialTracki var spans4 = await trackingSession.GetAdjustedTrackingSpansAsync(document1, snapshot1, CancellationToken.None); AssertEx.Equal(new[] { - $"V0 →←@[11..16): IsNonLeafFrame", - $"V0 →←@[21..26): IsLeafFrame" + $"V0 →←@[11..16): NonLeafFrame", + $"V0 →←@[21..26): LeafFrame" }, spans4.Select(s => $"{s.Span}: {s.Flags}")); AssertEx.Empty(await trackingSession.GetAdjustedTrackingSpansAsync(document2, snapshot2, CancellationToken.None)); @@ -225,8 +225,8 @@ public async Task TrackingService_GetLatestSpansAsync(bool scheduleInitialTracki var spans5 = trackingSession.Test_GetTrackingSpans(); AssertEx.Equal(new[] { - $"V0 →←@[11..16): IsNonLeafFrame", - $"V0 →←@[21..26): IsLeafFrame" + $"V0 →←@[11..16): NonLeafFrame", + $"V0 →←@[21..26): LeafFrame" }, spans5[document1.FilePath].Select(s => $"{s.Span}: {s.Flags}")); } @@ -236,8 +236,8 @@ public async Task TrackingService_GetLatestSpansAsync(bool scheduleInitialTracki var spans6 = await trackingSession.GetAdjustedTrackingSpansAsync(document1, snapshot1, CancellationToken.None); AssertEx.Equal(new[] { - $"V0 →←@[11..16): IsNonLeafFrame", - $"V0 →←@[21..26): IsLeafFrame" + $"V0 →←@[11..16): NonLeafFrame", + $"V0 →←@[21..26): LeafFrame" }, spans6.Select(s => $"{s.Span}: {s.Flags}")); } } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs index e2003b9aa3814..2c81eddb94019 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs @@ -14,11 +14,11 @@ using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.EditAndContinue.Contracts; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -307,7 +307,7 @@ public static void Main() KeyValuePairUtil.Create(newDocument.FilePath, ImmutableArray.Create( new ActiveStatement( ordinal: 0, - ActiveStatementFlags.IsLeafFrame, + ActiveStatementFlags.LeafFrame, new SourceFileSpan(newDocument.FilePath, oldStatementSpan), instructionId: default))) }), diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs index 52c058b281df5..82fc0c3d34d77 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs @@ -6,8 +6,8 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.EditAndContinue.Contracts; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; -using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Roslyn.Test.Utilities; using Xunit; diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs index a80f3fe6d4f9e..427e8c1a82ce9 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs @@ -10,11 +10,11 @@ using Microsoft.CodeAnalysis.CSharp.UnitTests; using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.EditAndContinue.Contracts; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Roslyn.Utilities; using Xunit; diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs index b635fe864d72f..a0ff1d20dff26 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs @@ -9,10 +9,10 @@ using System.Linq; using Microsoft.CodeAnalysis.CSharp.UnitTests; using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.EditAndContinue.Contracts; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Roslyn.Test.Utilities; using Xunit; @@ -21,6 +21,48 @@ namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests [UseExportProvider] public class LineEditTests : EditingTestBase { + #region Top-level Code + + [Fact] + [WorkItem(1426286, "https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1426286")] + public void TopLevelCode_LineChange() + { + var src1 = @" +Console.ReadLine(1); +"; + var src2 = @" + +Console.ReadLine(1); +"; + var edits = GetTopEdits(src1, src2); + edits.VerifyLineEdits( + new[] { new SourceLineUpdate(1, 2) }); + } + + [Fact] + [WorkItem(1426286, "https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1426286")] + public void TopLevelCode_LocalFunction_LineChange() + { + var src1 = @" +void F() +{ + Console.ReadLine(1); +} +"; + var src2 = @" +void F() +{ + + Console.ReadLine(1); +} +"; + var edits = GetTopEdits(src1, src2); + edits.VerifyLineEdits( + new[] { new SourceLineUpdate(3, 4) }); + } + + #endregion + #region Methods [Fact] @@ -232,6 +274,32 @@ public void Method_LineChange3() { var src1 = @" class C +{ + static void Bar() + { + Console.ReadLine(2); + } +} +"; + var src2 = @" +class C +{ + static void Bar() + { + + Console.ReadLine(2); + } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyLineEdits( + new[] { new SourceLineUpdate(5, 6) }); + } + + [Fact] + public void Method_LineChange4() + { + var src1 = @" +class C { static int X() => 1; diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index 17c798fa60422..ee4df8c5e2fa4 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -7153,6 +7153,51 @@ static void Main(int X) capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); } + [Fact] + public void LocalFunctions_Update() + { + var src1 = @" +using System; + +class C +{ + void M() + { + N(3); + + void N(int x) + { + if (x > 3) + { + x.ToString(); + } + else + { + N(x - 1); + } + } + } +}"; + var src2 = @" +using System; + +class C +{ + void M() + { + N(3); + + void N(int x) + { + Console.WriteLine(""Hello""); + } + } +}"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.M"), preserveLocalVariables: true)); + } + [Fact] public void LocalFunction_In_Parameter_InsertWhole() { @@ -7727,7 +7772,6 @@ public void LocalFunctions_TypeParameter_Reorder() "Reorder [B]@11 -> @9"); GetTopEdits(edits).VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Move, "B", FeaturesResources.type_parameter), Diagnostic(RudeEditKind.ChangingTypeParameters, "L", FeaturesResources.local_function)); } @@ -7743,7 +7787,6 @@ public void LocalFunctions_TypeParameter_ReorderAndUpdate() "Update [A]@9 -> [C]@11"); GetTopEdits(edits).VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Move, "B", FeaturesResources.type_parameter), Diagnostic(RudeEditKind.ChangingTypeParameters, "L", FeaturesResources.local_function)); } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index dca9939939b67..1b69ecd483209 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -12601,7 +12601,9 @@ public void Property_ExpressionBody_Update() var edits = GetTopEdits(src1, src2); - edits.VerifyEdits("Update [int P => 1;]@10 -> [int P => 2;]@10"); + edits.VerifyEdits( + "Update [int P => 1;]@10 -> [int P => 2;]@10", + "Update [=> 1]@16 -> [=> 2]@16"); edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] { @@ -12633,7 +12635,8 @@ public void Property_ExpressionBodyToBlockBody1() edits.VerifyEdits( "Update [int P => 1;]@10 -> [int P { get { return 2; } }]@10", "Insert [{ get { return 2; } }]@16", - "Insert [get { return 2; }]@18"); + "Insert [get { return 2; }]@18", + "Delete [=> 1]@16"); edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] { @@ -12653,7 +12656,8 @@ public void Property_ExpressionBodyToBlockBody2() "Update [int P => 1;]@10 -> [int P { get { return 2; } set { } }]@10", "Insert [{ get { return 2; } set { } }]@16", "Insert [get { return 2; }]@18", - "Insert [set { }]@36"); + "Insert [set { }]@36", + "Delete [=> 1]@16"); edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] { @@ -12672,6 +12676,7 @@ public void Property_BlockBodyToExpressionBody1() edits.VerifyEdits( "Update [int P { get { return 2; } }]@10 -> [int P => 1;]@10", + "Insert [=> 1]@16", "Delete [{ get { return 2; } }]@16", "Delete [get { return 2; }]@18"); @@ -12691,6 +12696,7 @@ public void Property_BlockBodyToExpressionBody2() edits.VerifyEdits( "Update [int P { get { return 2; } set { } }]@10 -> [int P => 1;]@10", + "Insert [=> 1]@16", "Delete [{ get { return 2; } set { } }]@16", "Delete [get { return 2; }]@18", "Delete [set { }]@36"); @@ -12709,7 +12715,8 @@ public void Property_ExpressionBodyToGetterExpressionBody() edits.VerifyEdits( "Update [int P => 1;]@10 -> [int P { get => 2; }]@10", "Insert [{ get => 2; }]@16", - "Insert [get => 2;]@18"); + "Insert [get => 2;]@18", + "Delete [=> 1]@16"); edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] { @@ -12726,6 +12733,7 @@ public void Property_GetterExpressionBodyToExpressionBody() var edits = GetTopEdits(src1, src2); edits.VerifyEdits( "Update [int P { get => 2; }]@10 -> [int P => 1;]@10", + "Insert [=> 1]@16", "Delete [{ get => 2; }]@16", "Delete [get => 2;]@18"); @@ -13697,7 +13705,8 @@ public void IndexerWithExpressionBody_Update() var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [int this[int a] => 1;]@10 -> [int this[int a] => 2;]@10"); + "Update [int this[int a] => 1;]@10 -> [int this[int a] => 2;]@10", + "Update [=> 1]@26 -> [=> 2]@26"); edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] { @@ -13727,7 +13736,8 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [int this[int a] => new Func(() => a + 1)() + 10;]@35 -> [int this[int a] => new Func(() => 2)() + 11;]@35"); + "Update [int this[int a] => new Func(() => a + 1)() + 10;]@35 -> [int this[int a] => new Func(() => 2)() + 11;]@35", + "Update [=> new Func(() => a + 1)() + 10]@51 -> [=> new Func(() => 2)() + 11]@51"); edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.NotCapturingVariable, "a", "a")); @@ -13755,7 +13765,8 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [int this[int a] => new Func(() => a + 1)();]@35 -> [int this[int a] => new Func(() => 2)();]@35"); + "Update [int this[int a] => new Func(() => a + 1)();]@35 -> [int this[int a] => new Func(() => 2)();]@35", + "Update [=> new Func(() => a + 1)()]@51 -> [=> new Func(() => 2)()]@51"); edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.NotCapturingVariable, "a", "a")); @@ -13783,7 +13794,8 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [int this[int a] => new Func(() => { return a + 1; })();]@35 -> [int this[int a] => new Func(() => { return 2; })();]@35"); + "Update [int this[int a] => new Func(() => { return a + 1; })();]@35 -> [int this[int a] => new Func(() => { return 2; })();]@35", + "Update [=> new Func(() => { return a + 1; })()]@51 -> [=> new Func(() => { return 2; })()]@51"); edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.NotCapturingVariable, "a", "a")); @@ -13811,7 +13823,8 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [int this[int a] => new Func(delegate { return a + 1; })();]@35 -> [int this[int a] => new Func(delegate { return 2; })();]@35"); + "Update [int this[int a] => new Func(delegate { return a + 1; })();]@35 -> [int this[int a] => new Func(delegate { return 2; })();]@35", + "Update [=> new Func(delegate { return a + 1; })()]@51 -> [=> new Func(delegate { return 2; })()]@51"); edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.NotCapturingVariable, "a", "a")); @@ -13828,7 +13841,8 @@ public void Indexer_ExpressionBodyToBlockBody() edits.VerifyEdits( "Update [int this[int a] => 1;]@10 -> [int this[int a] { get { return 1; } }]@10", "Insert [{ get { return 1; } }]@26", - "Insert [get { return 1; }]@28"); + "Insert [get { return 1; }]@28", + "Delete [=> 1]@26"); edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] { @@ -13846,6 +13860,7 @@ public void Indexer_BlockBodyToExpressionBody() edits.VerifyEdits( "Update [int this[int a] { get { return 1; } }]@10 -> [int this[int a] => 1;]@10", + "Insert [=> 1]@26", "Delete [{ get { return 1; } }]@26", "Delete [get { return 1; }]@28"); @@ -13892,6 +13907,7 @@ public void Indexer_GetterExpressionBodyToExpressionBody() edits.VerifyEdits( "Update [int this[int a] { get => 1; }]@10 -> [int this[int a] => 1;]@10", + "Insert [=> 1]@26", "Delete [{ get => 1; }]@26", "Delete [get => 1;]@28"); @@ -13911,7 +13927,8 @@ public void Indexer_ExpressionBodyToGetterExpressionBody() edits.VerifyEdits( "Update [int this[int a] => 1;]@10 -> [int this[int a] { get => 1; }]@10", "Insert [{ get => 1; }]@26", - "Insert [get => 1;]@28"); + "Insert [get => 1;]@28", + "Delete [=> 1]@26"); edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] { @@ -13995,6 +14012,7 @@ public void Indexer_GetterAndSetterBlockBodiesToExpressionBody() edits.VerifyEdits( "Update [int this[int a] { get { return 1; } set { Console.WriteLine(0); } }]@10 -> [int this[int a] => 1;]@10", + "Insert [=> 1]@26", "Delete [{ get { return 1; } set { Console.WriteLine(0); } }]@26", "Delete [get { return 1; }]@28", "Delete [set { Console.WriteLine(0); }]@46"); @@ -14015,7 +14033,8 @@ public void Indexer_ExpressionBodyToGetterAndSetterBlockBodies() "Update [int this[int a] => 1;]@10 -> [int this[int a] { get { return 1; } set { Console.WriteLine(0); } }]@10", "Insert [{ get { return 1; } set { Console.WriteLine(0); } }]@26", "Insert [get { return 1; }]@28", - "Insert [set { Console.WriteLine(0); }]@46"); + "Insert [set { Console.WriteLine(0); }]@46", + "Delete [=> 1]@26"); edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] { @@ -14297,6 +14316,7 @@ public void Indexer_ReadOnlyRef_Parameter_InsertWhole() edits.VerifyEdits( "Insert [int this[in int i] => throw null;]@13", "Insert [[in int i]]@21", + "Insert [=> throw null]@32", "Insert [in int i]@22"); edits.VerifyRudeDiagnostics(); @@ -14328,6 +14348,7 @@ public void Indexer_ReadOnlyRef_ReturnType_Insert() edits.VerifyEdits( "Insert [ref readonly int this[int i] => throw null;]@13", "Insert [[int i]]@34", + "Insert [=> throw null]@42", "Insert [int i]@35"); edits.VerifyRudeDiagnostics(); diff --git a/src/EditorFeatures/CSharpTest/EditorConfigSettings/Updater/SettingsUpdaterTests.cs b/src/EditorFeatures/CSharpTest/EditorConfigSettings/Updater/SettingsUpdaterTests.cs index 97604f9d5d4f5..38b1a39070577 100644 --- a/src/EditorFeatures/CSharpTest/EditorConfigSettings/Updater/SettingsUpdaterTests.cs +++ b/src/EditorFeatures/CSharpTest/EditorConfigSettings/Updater/SettingsUpdaterTests.cs @@ -2,9 +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.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.AddImports; using Microsoft.CodeAnalysis.CodeStyle; @@ -78,7 +76,7 @@ public async Task TestAddNewWhitespaceOptionAsync() { await TestAsync( string.Empty, - "[*.cs]\r\ncsharp_new_line_before_else=true", + "[*.cs]\r\ncsharp_new_line_before_else = true", (CSharpFormattingOptions2.NewLineForElse, true)); } @@ -89,7 +87,7 @@ public async Task TestAddNewBoolCodeStyleOptionWithSeverityAsync() option = option.WithValue(true).WithNotification(NotificationOption2.Suggestion); await TestAsync( string.Empty, - "[*.cs]\r\ncsharp_style_throw_expression=true:suggestion", + "[*.cs]\r\ncsharp_style_throw_expression = true:suggestion", (CSharpCodeStyleOptions.PreferThrowExpression, option)); } @@ -100,7 +98,7 @@ public async Task TestAddNewEnumCodeStyleOptionWithSeverityAsync() option = option.WithValue(AddImportPlacement.InsideNamespace).WithNotification(NotificationOption2.Warning); await TestAsync( string.Empty, - "[*.cs]\r\ncsharp_using_directive_placement=inside_namespace:warning", + "[*.cs]\r\ncsharp_using_directive_placement = inside_namespace:warning", (CSharpCodeStyleOptions.PreferredUsingDirectivePlacement, option)); } @@ -133,7 +131,7 @@ internal async Task TestAddNewAnalyzerOptionOptionAsync( await TestAsync( string.Empty, - $"{expectedHeader}\r\ndotnet_diagnostic.{id}.severity={expectedSeverity}", + $"{expectedHeader}\r\ndotnet_diagnostic.{id}.severity = {expectedSeverity}", (analyzerSetting, severity)); } @@ -141,8 +139,8 @@ await TestAsync( public async Task TestUpdateExistingWhitespaceOptionAsync() { await TestAsync( - "[*.cs]\r\ncsharp_new_line_before_else=true", - "[*.cs]\r\ncsharp_new_line_before_else=false", + "[*.cs]\r\ncsharp_new_line_before_else = true", + "[*.cs]\r\ncsharp_new_line_before_else = false", (CSharpFormattingOptions2.NewLineForElse, false)); } @@ -153,7 +151,7 @@ public async Task TestAddNewWhitespaceOptionToExistingFileAsync() [*.{cs,vb}] # CA1000: Do not declare static members on generic types -dotnet_diagnostic.CA1000.severity=false +dotnet_diagnostic.CA1000.severity = false "; @@ -161,11 +159,11 @@ public async Task TestAddNewWhitespaceOptionToExistingFileAsync() [*.{cs,vb}] # CA1000: Do not declare static members on generic types -dotnet_diagnostic.CA1000.severity=false +dotnet_diagnostic.CA1000.severity = false [*.cs] -csharp_new_line_before_else=true"; +csharp_new_line_before_else = true"; await TestAsync( initialEditorConfig, updatedEditorConfig, @@ -189,7 +187,7 @@ public async Task TestAddNewWhitespaceOptionToWithNonMathcingGroupsAsync() [*.xml] indent_size = 2 [*.cs] -csharp_new_line_before_else=true"; +csharp_new_line_before_else = true"; await TestAsync( initialEditorConfig, updatedEditorConfig, @@ -224,7 +222,7 @@ public async Task TestAddNewWhitespaceOptionWithStarGroup() # CSharp code style settings: [*.cs] -csharp_new_line_before_else=true"; +csharp_new_line_before_else = true"; await TestAsync( initialEditorConfig, @@ -237,7 +235,7 @@ public async Task TestAddMultimpleNewWhitespaceOptions() { await TestAsync( string.Empty, - "[*.cs]\r\ncsharp_new_line_before_else=true\r\ncsharp_new_line_before_catch=true\r\ncsharp_new_line_before_finally=true", + "[*.cs]\r\ncsharp_new_line_before_else = true\r\ncsharp_new_line_before_catch = true\r\ncsharp_new_line_before_finally = true", (CSharpFormattingOptions2.NewLineForElse, true), (CSharpFormattingOptions2.NewLineForCatch, true), (CSharpFormattingOptions2.NewLineForFinally, true)); @@ -268,7 +266,7 @@ public async Task TestAddOptionThatAppliesToBothLanguages() # Dotnet code style settings: [*.{cs,vb}] -dotnet_sort_system_directives_first=true +dotnet_sort_system_directives_first = true # CSharp code style settings: [*.cs]"; @@ -313,7 +311,7 @@ public async Task TestAddOptionWithRelativePathGroupingPresent() # CSharp code style settings: [*.cs] -csharp_new_line_before_else=true"; +csharp_new_line_before_else = true"; await TestAsync( initialEditorConfig, @@ -332,7 +330,7 @@ public async Task TestAnalyzerSettingsUpdaterService() analyzerSetting.ChangeSeverity(DiagnosticSeverity.Error); var updates = await updater.GetChangedEditorConfigAsync(default); var update = Assert.Single(updates); - Assert.Equal($"[*.cs]\r\ndotnet_diagnostic.{id}.severity=error", update.NewText); + Assert.Equal($"[*.cs]\r\ndotnet_diagnostic.{id}.severity = error", update.NewText); } [Fact, Trait(Traits.Feature, Traits.Features.EditorConfigUI)] @@ -351,7 +349,7 @@ public async Task TestCodeStyleSettingUpdaterService() setting.ChangeSeverity(DiagnosticSeverity.Error); var updates = await updater.GetChangedEditorConfigAsync(default); var update = Assert.Single(updates); - Assert.Equal("[*.cs]\r\ncsharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental=false:error", update.NewText); + Assert.Equal("[*.cs]\r\ncsharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:error", update.NewText); value = "false:error"; var editorconfig = workspace.CurrentSolution.Projects.SelectMany(p => p.AnalyzerConfigDocuments.Where(a => a.FilePath == EditorconfigPath)).Single(); var text = await editorconfig.GetTextAsync(); @@ -360,7 +358,7 @@ public async Task TestCodeStyleSettingUpdaterService() setting.ChangeValue(0); updates = await updater.GetChangedEditorConfigAsync(default); update = Assert.Single(updates); - Assert.Equal("[*.cs]\r\ncsharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental=true:error", update.NewText); + Assert.Equal("[*.cs]\r\ncsharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:error", update.NewText); } [Fact, Trait(Traits.Feature, Traits.Features.EditorConfigUI)] @@ -372,7 +370,7 @@ public async Task TestWhitespaceSettingUpdaterService() setting.SetValue(false); var updates = await updater.GetChangedEditorConfigAsync(default); var update = Assert.Single(updates); - Assert.Equal("[*.cs]\r\ncsharp_new_line_before_else=false", update.NewText); + Assert.Equal("[*.cs]\r\ncsharp_new_line_before_else = false", update.NewText); } } } diff --git a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs index af28800b42683..a3408389d024a 100644 --- a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs +++ b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs @@ -23,35 +23,71 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.NavigateTo [Trait(Traits.Feature, Traits.Features.NavigateTo)] public class NavigateToSearcherTests { - private static IReturnsResult SetupSearchProject( + private static void SetupSearchProject( Mock searchService, string pattern, bool isFullyLoaded, INavigateToSearchResult? result) { - var searchServiceSetup = searchService.Setup(ss => ss.SearchProjectAsync( - It.IsAny(), - It.IsAny>(), - pattern, - ImmutableHashSet.Empty, - It.IsAny>(), - isFullyLoaded, - It.IsAny())); - - var searchServiceCallback = searchServiceSetup.Callback( - (Project project, - ImmutableArray priorityDocuments, - string pattern2, - IImmutableSet kinds, - Func onResultFound2, - bool isFullyLoaded2, - CancellationToken cancellationToken) => - { - if (result != null) - onResultFound2(result); - }); - - return searchServiceCallback.ReturnsAsync(isFullyLoaded ? NavigateToSearchLocation.Latest : NavigateToSearchLocation.Cache); + if (isFullyLoaded) + { + // First do a full search + searchService.Setup(ss => ss.SearchProjectAsync( + It.IsAny(), + It.IsAny>(), + pattern, + ImmutableHashSet.Empty, + It.IsAny>(), + It.IsAny())).Callback( + (Project project, + ImmutableArray priorityDocuments, + string pattern, + IImmutableSet kinds, + Func onResultFound, + CancellationToken cancellationToken) => + { + if (result != null) + onResultFound(result); + }).Returns(Task.CompletedTask); + + searchService.Setup(ss => ss.SearchGeneratedDocumentsAsync( + It.IsAny(), + pattern, + ImmutableHashSet.Empty, + It.IsAny>(), + It.IsAny())).Callback( + (Project project, + string pattern, + IImmutableSet kinds, + Func onResultFound, + CancellationToken cancellationToken) => + { + if (result != null) + onResultFound(result); + }).Returns(Task.CompletedTask); + + // Followed by a generated doc search. + } + else + { + searchService.Setup(ss => ss.SearchCachedDocumentsAsync( + It.IsAny(), + It.IsAny>(), + pattern, + ImmutableHashSet.Empty, + It.IsAny>(), + It.IsAny())).Callback( + (Project project, + ImmutableArray priorityDocuments, + string pattern2, + IImmutableSet kinds, + Func onResultFound2, + CancellationToken cancellationToken) => + { + if (result != null) + onResultFound2(result); + }).Returns(Task.CompletedTask); + } } private static ValueTask IsFullyLoadedAsync(bool projectSystem, bool remoteHost) @@ -86,12 +122,11 @@ public async Task NotFullyLoadedOnlyMakesOneSearchProjectCallIfValueReturned() AsynchronousOperationListenerProvider.NullListener, callbackMock.Object, pattern, - searchCurrentDocument: false, kinds: ImmutableHashSet.Empty, CancellationToken.None, hostMock.Object); - await searcher.SearchAsync(CancellationToken.None); + await searcher.SearchAsync(searchCurrentDocument: false, CancellationToken.None); } [Theory] @@ -129,12 +164,11 @@ public async Task NotFullyLoadedMakesTwoSearchProjectCallIfValueNotReturned(bool AsynchronousOperationListenerProvider.NullListener, callbackMock.Object, pattern, - searchCurrentDocument: false, kinds: ImmutableHashSet.Empty, CancellationToken.None, hostMock.Object); - await searcher.SearchAsync(CancellationToken.None); + await searcher.SearchAsync(searchCurrentDocument: false, CancellationToken.None); } [Theory] @@ -169,12 +203,11 @@ public async Task NotFullyLoadedStillReportsAsNotCompleteIfRemoteHostIsStillHydr AsynchronousOperationListenerProvider.NullListener, callbackMock.Object, pattern, - searchCurrentDocument: false, kinds: ImmutableHashSet.Empty, CancellationToken.None, hostMock.Object); - await searcher.SearchAsync(CancellationToken.None); + await searcher.SearchAsync(searchCurrentDocument: false, CancellationToken.None); } [Fact] @@ -209,12 +242,11 @@ public async Task FullyLoadedMakesSingleSearchProjectCallIfValueNotReturned() AsynchronousOperationListenerProvider.NullListener, callbackMock.Object, pattern, - searchCurrentDocument: false, kinds: ImmutableHashSet.Empty, CancellationToken.None, hostMock.Object); - await searcher.SearchAsync(CancellationToken.None); + await searcher.SearchAsync(searchCurrentDocument: false, CancellationToken.None); } private class TestNavigateToSearchResult : INavigateToSearchResult, INavigableItem diff --git a/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs b/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs index 4e29ee0c6ec53..1aad733a1d6ff 100644 --- a/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs +++ b/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs @@ -530,6 +530,70 @@ await RunTestAsync(async path => }); } + [Fact] + public async Task OldPdb_NullResult() + { + var source1 = @" +public class C +{ + public event System.EventHandler [|E|] { add { } remove { } } +}"; + var source2 = @" +public class C +{ + // A change + public event System.EventHandler E { add { } remove { } } +}"; + + await RunTestAsync(async path => + { + MarkupTestFile.GetSpan(source1, out var metadataSource, out var expectedSpan); + + var (project, symbol) = await CompileAndFindSymbolAsync(path, Location.OnDisk, Location.OnDisk, metadataSource, c => c.GetMember("C.E")); + + // Archive off the current PDB so we can restore it later + var pdbFilePath = GetPdbPath(path); + var archivePdbFilePath = pdbFilePath + ".old"; + File.Move(pdbFilePath, archivePdbFilePath); + + CompileTestSource(path, source2, project, Location.OnDisk, Location.OnDisk, buildReferenceAssembly: false, windowsPdb: false); + + // Move the old file back, so the PDB is now old + File.Delete(pdbFilePath); + File.Move(archivePdbFilePath, pdbFilePath); + + await GenerateFileAndVerifyAsync(project, symbol, source1, expectedSpan, expectNullResult: true); + }); + } + + [Theory] + [CombinatorialData] + public async Task SourceFileChecksumIncorrect_NullResult(Location pdbLocation) + { + var source1 = @" +public class C +{ + public event System.EventHandler [|E|] { add { } remove { } } +}"; + var source2 = @" +public class C +{ + // A change + public event System.EventHandler E { add { } remove { } } +}"; + + await RunTestAsync(async path => + { + MarkupTestFile.GetSpan(source1, out var metadataSource, out var expectedSpan); + + var (project, symbol) = await CompileAndFindSymbolAsync(path, pdbLocation, Location.OnDisk, metadataSource, c => c.GetMember("C.E")); + + File.WriteAllText(GetSourceFilePath(path), source2, Encoding.UTF8); + + await GenerateFileAndVerifyAsync(project, symbol, metadataSource, expectedSpan, expectNullResult: true); + }); + } + private static Task TestAsync( Location pdbLocation, Location sourceLocation, @@ -646,11 +710,6 @@ private static async Task GenerateFileAndVerifyAsync( bool buildReferenceAssembly = false, bool windowsPdb = false) { - var assemblyName = "ReferencedAssembly"; - var sourceCodePath = GetSourceFilePath(path); - var dllFilePath = GetDllPath(path); - var pdbFilePath = GetPdbPath(path); - var preprocessorSymbolsAttribute = preprocessorSymbols?.Length > 0 ? $"PreprocessorSymbols=\"{string.Join(";", preprocessorSymbols)}\"" : ""; @@ -669,7 +728,28 @@ private static async Task GenerateFileAndVerifyAsync( var project = workspace.CurrentSolution.Projects.First(); - var languageServices = workspace.Services.GetLanguageServices(LanguageNames.CSharp); + CompileTestSource(path, source, project, pdbLocation, sourceLocation, buildReferenceAssembly, windowsPdb); + + project = project.AddMetadataReference(MetadataReference.CreateFromFile(GetDllPath(path))); + + var mainCompilation = await project.GetRequiredCompilationAsync(CancellationToken.None).ConfigureAwait(false); + + var symbol = symbolMatcher(mainCompilation); + + AssertEx.NotNull(symbol, $"Couldn't find symbol to go-to-def for."); + + return (project, symbol); + } + + private static void CompileTestSource(string path, string source, Project project, Location pdbLocation, Location sourceLocation, bool buildReferenceAssembly, bool windowsPdb) + { + var dllFilePath = GetDllPath(path); + var sourceCodePath = GetSourceFilePath(path); + var pdbFilePath = GetPdbPath(path); + + var assemblyName = "ReferencedAssembly"; + + var languageServices = project.Solution.Workspace.Services.GetLanguageServices(LanguageNames.CSharp); var compilationFactory = languageServices.GetRequiredService(); var options = compilationFactory.GetDefaultCompilationOptions().WithOutputKind(OutputKind.DynamicallyLinkedLibrary); var parseOptions = project.ParseOptions; @@ -683,7 +763,7 @@ private static async Task GenerateFileAndVerifyAsync( if (sourceLocation == Location.OnDisk) { embeddedTexts = null; - File.WriteAllText(sourceCodePath, source); + File.WriteAllText(sourceCodePath, source, Encoding.UTF8); } else { @@ -718,16 +798,6 @@ private static async Task GenerateFileAndVerifyAsync( var result = compilation.Emit(dllStream, pdbStream, options: emitOptions, embeddedTexts: embeddedTexts); Assert.Empty(result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error)); } - - project = project.AddMetadataReference(MetadataReference.CreateFromFile(dllFilePath)); - - var mainCompilation = await project.GetRequiredCompilationAsync(CancellationToken.None).ConfigureAwait(false); - - var symbol = symbolMatcher(mainCompilation); - - AssertEx.NotNull(symbol, $"Couldn't find symbol to go-to-def for."); - - return (project, symbol); } private static string GetDllPath(string path) diff --git a/src/EditorFeatures/CSharpTest/SimplifyPropertyPattern/SimplifyPropertyPatternTests.cs b/src/EditorFeatures/CSharpTest/SimplifyPropertyPattern/SimplifyPropertyPatternTests.cs index 35736edcb445a..5eacd6faad381 100644 --- a/src/EditorFeatures/CSharpTest/SimplifyPropertyPattern/SimplifyPropertyPatternTests.cs +++ b/src/EditorFeatures/CSharpTest/SimplifyPropertyPattern/SimplifyPropertyPatternTests.cs @@ -9,6 +9,7 @@ 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.SimplifyPropertyPattern @@ -698,5 +699,51 @@ void S(Type t) DiagnosticSelector = ds => ds[1], }.RunAsync(); } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsSimplifyPropertyPattern)] + [WorkItem(57674, "https://github.com/dotnet/roslyn/issues/57674")] + public async Task TestTuplePattern() + { + var testCode = @" +record R(int Prop); + +class C +{ + void S(R r) + { + _ = (A: r, r) is (A: { Prop: { } }, _); + } +}"; + await new VerifyCS.Test + { + TestCode = testCode, + FixedCode = testCode, + LanguageVersion = LanguageVersion.CSharp10, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsSimplifyPropertyPattern)] + [WorkItem(57674, "https://github.com/dotnet/roslyn/issues/57674")] + public async Task TestPositionalPattern() + { + var testCode = @" +record R(R Child, int Value); + +class C +{ + void S(R r) + { + _ = r is R(Child: { Child: { } }, _); + } +}"; + await new VerifyCS.Test + { + TestCode = testCode, + FixedCode = testCode, + LanguageVersion = LanguageVersion.CSharp10, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + }.RunAsync(); + } } } diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/CommandHandlers/RenameCommandHandler.cs b/src/EditorFeatures/Core.Wpf/InlineRename/CommandHandlers/RenameCommandHandler.cs index 53e1e0f258a22..ab4214d0f94a3 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/CommandHandlers/RenameCommandHandler.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/CommandHandlers/RenameCommandHandler.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Extensions; +using Microsoft.CodeAnalysis.Telemetry; #if !COCOA using System.Linq; @@ -113,7 +114,8 @@ protected override void Commit(InlineRenameSession activeSession, ITextView text } errorReportingService.ShowGlobalErrorInfo( - string.Format(EditorFeaturesWpfResources.Error_performing_rename_0, ex.Message), + message: string.Format(EditorFeaturesWpfResources.Error_performing_rename_0, ex.Message), + TelemetryFeatureName.InlineRename, ex, new InfoBarUI( WorkspacesResources.Show_Stack_Trace, diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/Dashboard/Dashboard.xaml.cs b/src/EditorFeatures/Core.Wpf/InlineRename/Dashboard/Dashboard.xaml.cs index 3d41d81bac01e..cdbc0d90fd25c 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/Dashboard/Dashboard.xaml.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/Dashboard/Dashboard.xaml.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.Notification; +using Microsoft.CodeAnalysis.Telemetry; using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Text.Editor; @@ -352,7 +353,8 @@ private void Commit() } errorReportingService.ShowGlobalErrorInfo( - string.Format(EditorFeaturesWpfResources.Error_performing_rename_0, ex.Message), + message: string.Format(EditorFeaturesWpfResources.Error_performing_rename_0, ex.Message), + TelemetryFeatureName.InlineRename, ex, new InfoBarUI( WorkspacesResources.Show_Stack_Trace, diff --git a/src/EditorFeatures/Core.Wpf/Microsoft.CodeAnalysis.EditorFeatures.Wpf.csproj b/src/EditorFeatures/Core.Wpf/Microsoft.CodeAnalysis.EditorFeatures.Wpf.csproj index 8bf0802796e61..024f0433090ee 100644 --- a/src/EditorFeatures/Core.Wpf/Microsoft.CodeAnalysis.EditorFeatures.Wpf.csproj +++ b/src/EditorFeatures/Core.Wpf/Microsoft.CodeAnalysis.EditorFeatures.Wpf.csproj @@ -7,7 +7,9 @@ net472 true true - full + + true @@ -48,6 +50,7 @@ + @@ -72,4 +75,4 @@ - \ No newline at end of file + diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs index fbf3a3b77e44c..1cb6846ff49e2 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs @@ -109,11 +109,10 @@ private void StartSearch(INavigateToCallback callback, string searchValue, IImmu _asyncListener, roslynCallback, searchValue, - searchCurrentDocument, kinds, _threadingContext.DisposalToken); - _ = searcher.SearchAsync(_cancellationTokenSource.Token); + _ = searcher.SearchAsync(searchCurrentDocument, _cancellationTokenSource.Token); } } } diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Updater/SettingsUpdateHelper.cs b/src/EditorFeatures/Core/EditorConfigSettings/Updater/SettingsUpdateHelper.cs index 4dbf3a9b1f8a3..8ac09031a04a5 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Updater/SettingsUpdateHelper.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Updater/SettingsUpdateHelper.cs @@ -75,12 +75,12 @@ internal static partial class SettingsUpdateHelper var optionValue = storageLocation.GetEditorConfigStringValue(value, optionSet); if (value is ICodeStyleOption codeStyleOption && !optionValue.Contains(':')) { - var severity = codeStyleOption.Notification switch + var severity = codeStyleOption.Notification.Severity switch { - { Severity: ReportDiagnostic.Hidden } => "silent", - { Severity: ReportDiagnostic.Info } => "suggestion", - { Severity: ReportDiagnostic.Warn } => "warning", - { Severity: ReportDiagnostic.Error } => "error", + ReportDiagnostic.Hidden => "silent", + ReportDiagnostic.Info => "suggestion", + ReportDiagnostic.Warn => "warning", + ReportDiagnostic.Error => "error", _ => string.Empty }; optionValue = $"{optionValue}:{severity}"; @@ -181,7 +181,7 @@ private static (SourceText? newText, TextLine? lastValidHeaderSpanEnd, TextLine? string.Equals(key, optionName, StringComparison.OrdinalIgnoreCase)) { // We found the rule in the file -- replace it with updated option value. - textChange = new TextChange(curLine.Span, $"{untrimmedKey}={optionValue}{comment}"); + textChange = new TextChange(curLine.Span, $"{untrimmedKey}= {optionValue}{comment}"); } } else if (s_headerPattern.IsMatch(curLineText.Trim())) @@ -294,7 +294,7 @@ private static (SourceText? newText, TextLine? lastValidHeaderSpanEnd, TextLine? string optionValue, Language language) { - var newEntry = $"{optionName}={optionValue}"; + var newEntry = $"{optionName} = {optionValue}"; if (lastValidSpecificHeaderSpanEnd.HasValue) { if (lastValidSpecificHeaderSpanEnd.Value.ToString().Trim().Length != 0) diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs index d653d52c0afe2..fa7ba79bc627a 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs @@ -71,12 +71,12 @@ protected void NavigateToPosition(Workspace workspace, DocumentId documentId, in ITextVersion textVersion, CancellationToken cancellationToken) { - // If the item points to a location in this document, then just determine the current location - // of that item and go directly to it. - var navigationSpan = item.TryGetNavigationSpan(textVersion); - if (navigationSpan != null) + if (symbolItem.Location.InDocumentInfo != null) { - return Task.FromResult((document.Id, navigationSpan.Value.Start, 0)); + // If the item points to a location in this document, then just determine the where that span currently + // is (in case recent edits have moved it) and navigate there. + var navigationSpan = item.GetCurrentItemSpan(textVersion, symbolItem.Location.InDocumentInfo.Value.navigationSpan); + return Task.FromResult((document.Id, navigationSpan.Start, 0)); } else { diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs index a795a33658651..e6bf128a28af8 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs @@ -3,13 +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 Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor { @@ -30,13 +30,6 @@ internal abstract class NavigationBarItem : IEquatable /// This can be empty for items whose location is in another document. public ImmutableArray Spans { get; } - /// - /// The span in the owning document corresponding to where to navigate to if the user selects this item in the - /// drop down. - /// - /// This can be for items whose location is in another document. - public TextSpan? NavigationSpan { get; } - internal ITextVersion? TextVersion { get; } public NavigationBarItem( @@ -44,7 +37,6 @@ public NavigationBarItem( string text, Glyph glyph, ImmutableArray spans, - TextSpan? navigationSpan, ImmutableArray childItems = default, int indent = 0, bool bolded = false, @@ -54,22 +46,12 @@ public NavigationBarItem( Text = text; Glyph = glyph; Spans = spans; - NavigationSpan = navigationSpan; ChildItems = childItems.NullToEmpty(); Indent = indent; Bolded = bolded; Grayed = grayed; } - public TextSpan? TryGetNavigationSpan(ITextVersion textVersion) - { - if (this.NavigationSpan == null) - return null; - - return this.TextVersion!.CreateTrackingSpan(this.NavigationSpan.Value.ToSpan(), SpanTrackingMode.EdgeExclusive) - .GetSpan(textVersion).ToTextSpan(); - } - public abstract override bool Equals(object? obj); public abstract override int GetHashCode(); @@ -82,8 +64,18 @@ public bool Equals(NavigationBarItem? other) Grayed == other.Grayed && Indent == other.Indent && ChildItems.SequenceEqual(other.ChildItems) && - Spans.SequenceEqual(other.Spans) && - EqualityComparer.Default.Equals(NavigationSpan, other.NavigationSpan); + Spans.SequenceEqual(other.Spans); + } + } + + internal static class NavigationBarItemExtensions + { + public static TextSpan GetCurrentItemSpan(this NavigationBarItem item, ITextVersion toVersion, TextSpan span) + { + Contract.ThrowIfNull(item.TextVersion, "This should only be called for locations the caller knows to be in the open file"); + return item.TextVersion.CreateTrackingSpan(span.ToSpan(), SpanTrackingMode.EdgeExclusive) + .GetSpan(toVersion) + .ToTextSpan(); } } } diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs index 1da57da3ddeaa..35d38fdca8306 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs @@ -22,7 +22,6 @@ public NavigationBarProjectItem( string language) : base(textVersion: null, text, glyph, spans: ImmutableArray.Empty, - navigationSpan: null, childItems: ImmutableArray.Empty, indent: 0, bolded: false, grayed: false) { diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarSelectedItems.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarSelectedItems.cs index c78c1f06ec403..49a2c4b7bb0a0 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarSelectedItems.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarSelectedItems.cs @@ -8,6 +8,8 @@ namespace Microsoft.CodeAnalysis.Editor { internal class NavigationBarSelectedTypeAndMember : IEquatable { + public static readonly NavigationBarSelectedTypeAndMember Empty = new(typeItem: null, memberItem: null); + public NavigationBarItem? TypeItem { get; } public bool ShowTypeItemGrayed { get; } public NavigationBarItem? MemberItem { get; } diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/SimpleNavigationBarItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/SimpleNavigationBarItem.cs index 898da9a86094e..9fb20324480b9 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/SimpleNavigationBarItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/SimpleNavigationBarItem.cs @@ -11,8 +11,8 @@ namespace Microsoft.CodeAnalysis.Editor { internal sealed class SimpleNavigationBarItem : NavigationBarItem, IEquatable { - public SimpleNavigationBarItem(ITextVersion textVersion, string text, Glyph glyph, ImmutableArray spans, TextSpan? navigationSpan, ImmutableArray childItems, int indent, bool bolded, bool grayed) - : base(textVersion, text, glyph, spans, navigationSpan, childItems, indent, bolded, grayed) + public SimpleNavigationBarItem(ITextVersion textVersion, string text, Glyph glyph, ImmutableArray spans, ImmutableArray childItems, int indent, bool bolded, bool grayed) + : base(textVersion, text, glyph, spans, childItems, indent, bolded, grayed) { } diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs index ad0c44fcd8cc9..38254efe062ef 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs @@ -3,8 +3,10 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.NavigationBar; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; @@ -24,7 +26,6 @@ internal WrappedNavigationBarItem(ITextVersion textVersion, RoslynNavigationBarI underlyingItem.Text, underlyingItem.Glyph, GetSpans(underlyingItem), - GetNavigationSpan(underlyingItem), underlyingItem.ChildItems.SelectAsArray(v => (NavigationBarItem)new WrappedNavigationBarItem(textVersion, v)), underlyingItem.Indent, underlyingItem.Bolded, @@ -35,16 +36,31 @@ internal WrappedNavigationBarItem(ITextVersion textVersion, RoslynNavigationBarI private static ImmutableArray GetSpans(RoslynNavigationBarItem underlyingItem) { - return underlyingItem is RoslynNavigationBarItem.SymbolItem symbolItem && symbolItem.Location.InDocumentInfo != null - ? symbolItem.Location.InDocumentInfo.Value.spans - : ImmutableArray.Empty; - } + using var _ = ArrayBuilder.GetInstance(out var spans); + AddSpans(underlyingItem, spans); + spans.SortAndRemoveDuplicates(Comparer.Default); + return spans.ToImmutable(); - private static TextSpan? GetNavigationSpan(RoslynNavigationBarItem underlyingItem) - { - return underlyingItem is RoslynNavigationBarItem.SymbolItem symbolItem && symbolItem.Location.InDocumentInfo != null - ? symbolItem.Location.InDocumentInfo.Value.navigationSpan - : null; + static void AddSpans(RoslynNavigationBarItem underlyingItem, ArrayBuilder spans) + { + // For a regular symbol we want to select it if the user puts their caret in any of the spans of it in this file. + if (underlyingItem is RoslynNavigationBarItem.SymbolItem { Location.InDocumentInfo.spans: var symbolSpans }) + { + spans.AddRange(symbolSpans); + } + else if (underlyingItem is RoslynNavigationBarItem.ActionlessItem) + { + // An actionless item represents something that exists just to show a child-list, but should otherwise + // not navigate or cause anything to be generated. However, we still want to automatically select it + // whenever the user puts their caret in any of the spans of its child items in this file. + // + // For example, in VB any withevents members will be put in the type-list, and the events those members + // are hooked up to will then be in the member-list. In this case, we want moving into the span of that + // member to select the withevent member in the type-list. + foreach (var child in underlyingItem.ChildItems) + AddSpans(child, spans); + } + } } public override bool Equals(object? obj) diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs index 5c50c657d6738..4c71a20ca40d9 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptNavigationBarItemService.cs @@ -47,17 +47,15 @@ private static ImmutableArray ConvertItems(ImmutableArray TryNavigateToItemAsync( Document document, NavigationBarItem item, ITextView view, ITextVersion textVersion, CancellationToken cancellationToken) { - var navigationSpan = item.TryGetNavigationSpan(textVersion); - if (navigationSpan != null) - { - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + // Spans.First() is safe here as we filtered out any items with no spans above in ConvertItems. + var navigationSpan = item.GetCurrentItemSpan(textVersion, item.Spans.First()); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - var workspace = document.Project.Solution.Workspace; - var navigationService = VSTypeScriptDocumentNavigationServiceWrapper.Create(workspace); - navigationService.TryNavigateToPosition( - workspace, document.Id, navigationSpan.Value.Start, - virtualSpace: 0, options: null, cancellationToken: cancellationToken); - } + var workspace = document.Project.Solution.Workspace; + var navigationService = VSTypeScriptDocumentNavigationServiceWrapper.Create(workspace); + navigationService.TryNavigateToPosition( + workspace, document.Id, navigationSpan.Start, + virtualSpace: 0, options: null, cancellationToken: cancellationToken); return true; } @@ -75,7 +73,6 @@ private static NavigationBarItem ConvertToNavigationBarItem(VSTypescriptNavigati item.Text, VSTypeScriptGlyphHelpers.ConvertTo(item.Glyph), item.Spans, - item.Spans.First(), ConvertItems(item.ChildItems, textVersion), item.Indent, item.Bolded, diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTrackingSpan.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTrackingSpan.cs index 2175827b9963e..951217859ee0b 100644 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTrackingSpan.cs +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTrackingSpan.cs @@ -3,8 +3,8 @@ // See the LICENSE file in the project root for more information. using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.EditAndContinue.Contracts; using Microsoft.CodeAnalysis.Text.Shared.Extensions; -using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Microsoft.VisualStudio.Text; namespace Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue @@ -27,7 +27,7 @@ public ActiveStatementTrackingSpan(ITrackingSpan trackingSpan, int ordinal, Acti /// /// True if at least one of the threads whom this active statement belongs to is in a leaf frame. /// - public bool IsLeaf => (Flags & ActiveStatementFlags.IsLeafFrame) != 0; + public bool IsLeaf => (Flags & ActiveStatementFlags.LeafFrame) != 0; public static ActiveStatementTrackingSpan Create(ITextSnapshot snapshot, ActiveStatementSpan span) => new(snapshot.CreateTrackingSpan(snapshot.GetTextSpan(span.LineSpan).ToSpan(), SpanTrackingMode.EdgeExclusive), span.Ordinal, span.Flags, span.UnmappedDocumentId); diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/Contracts/ContractWrappers.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/Contracts/ContractWrappers.cs new file mode 100644 index 0000000000000..2b6af5acbbdcd --- /dev/null +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/Contracts/ContractWrappers.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.Collections.Immutable; +using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; +using Microsoft.VisualStudio.Debugger.Contracts.HotReload; + +using Contracts = Microsoft.CodeAnalysis.EditAndContinue.Contracts; + +namespace Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue +{ + internal static class ContractWrappers + { + public static Contracts.ManagedActiveStatementDebugInfo ToContract(this ManagedActiveStatementDebugInfo info) + => new(ToContract(info.ActiveInstruction), info.DocumentName, ToContract(info.SourceSpan), (Contracts.ActiveStatementFlags)info.Flags); + + public static Contracts.ManagedInstructionId ToContract(this ManagedInstructionId id) + => new(ToContract(id.Method), id.ILOffset); + + public static Contracts.ManagedMethodId ToContract(this ManagedMethodId id) + => new(id.Module, id.Token, id.Version); + + public static Contracts.SourceSpan ToContract(this SourceSpan id) + => new(id.StartLine, id.StartColumn, id.EndLine, id.EndColumn); + + public static Contracts.ManagedHotReloadAvailability ToContract(this ManagedHotReloadAvailability value) + => new((Contracts.ManagedHotReloadAvailabilityStatus)value.Status, value.LocalizedMessage); + + public static ManagedModuleUpdates FromContract(this Contracts.ManagedModuleUpdates updates) + => new((ManagedModuleUpdateStatus)updates.Status, updates.Updates.SelectAsArray(FromContract)); + + public static ManagedModuleUpdate FromContract(this Contracts.ManagedModuleUpdate update) + => new( + update.Module, + update.ILDelta, + update.MetadataDelta, + update.PdbDelta, + update.SequencePoints.SelectAsArray(FromContract), + update.UpdatedMethods, + update.UpdatedTypes, + update.ActiveStatements.SelectAsArray(FromContract), + update.ExceptionRegions.SelectAsArray(FromContract)); + + public static SequencePointUpdates FromContract(this Contracts.SequencePointUpdates updates) + => new(updates.FileName, updates.LineUpdates.SelectAsArray(FromContract)); + + public static SourceLineUpdate FromContract(this Contracts.SourceLineUpdate update) + => new(update.OldLine, update.NewLine); + + public static ManagedActiveStatementUpdate FromContract(this Contracts.ManagedActiveStatementUpdate update) + => new(FromContract(update.Method), update.ILOffset, FromContract(update.NewSpan)); + + public static ManagedModuleMethodId FromContract(this Contracts.ManagedModuleMethodId update) + => new(update.Token, update.Version); + + public static SourceSpan FromContract(this Contracts.SourceSpan id) + => new(id.StartLine, id.StartColumn, id.EndLine, id.EndColumn); + + public static ManagedExceptionRegionUpdate FromContract(this Contracts.ManagedExceptionRegionUpdate update) + => new(FromContract(update.Method), update.Delta, FromContract(update.NewSpan)); + + public static ImmutableArray FromContract(this ImmutableArray diagnostics) + => diagnostics.SelectAsArray(FromContract); + + public static ManagedHotReloadDiagnostic FromContract(this Contracts.ManagedHotReloadDiagnostic diagnostic) + => new(diagnostic.Id, diagnostic.Message, (ManagedHotReloadDiagnosticSeverity)diagnostic.Severity, diagnostic.FilePath, FromContract(diagnostic.Span)); + } +} diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/Contracts/ManagedHotReloadServiceImpl.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/Contracts/ManagedHotReloadServiceImpl.cs new file mode 100644 index 0000000000000..6d39919ec5ddc --- /dev/null +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/Contracts/ManagedHotReloadServiceImpl.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; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Debugger.Contracts.HotReload; + +using Contracts = Microsoft.CodeAnalysis.EditAndContinue.Contracts; + +namespace Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue +{ + internal sealed class ManagedHotReloadServiceImpl : Contracts.IManagedHotReloadService + { + private readonly IManagedHotReloadService _service; + + public ManagedHotReloadServiceImpl(IManagedHotReloadService service) + => _service = service; + + public async ValueTask> GetActiveStatementsAsync(CancellationToken cancellation) + => (await _service.GetActiveStatementsAsync(cancellation).ConfigureAwait(false)).SelectAsArray(a => a.ToContract()); + + public async ValueTask GetAvailabilityAsync(Guid module, CancellationToken cancellation) + => (await _service.GetAvailabilityAsync(module, cancellation).ConfigureAwait(false)).ToContract(); + + public ValueTask> GetCapabilitiesAsync(CancellationToken cancellation) + => _service.GetCapabilitiesAsync(cancellation); + + public ValueTask PrepareModuleForUpdateAsync(Guid module, CancellationToken cancellation) + => _service.PrepareModuleForUpdateAsync(module, cancellation); + } +} diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/DebuggerContractVersionCheck.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/DebuggerContractVersionCheck.cs new file mode 100644 index 0000000000000..93b8c909d9ae1 --- /dev/null +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/DebuggerContractVersionCheck.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; +using System.Runtime.CompilerServices; +using Microsoft.VisualStudio.Debugger.Contracts.HotReload; + +namespace Microsoft.CodeAnalysis.EditAndContinue +{ + /// + /// Temporaroly needed to allow us to run integration tests on older VS than build that has the required version of Microsoft.VisualStudio.Debugger.Contracts. + /// TODO: Remove (https://github.com/dotnet/roslyn/issues/56742) + /// + internal static class DebuggerContractVersionCheck + { + public static bool IsRequiredDebuggerContractVersionAvailable() + { + try + { + _ = LoadContracts(); + return true; + } + catch + { + return false; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + private static Type LoadContracts() + => typeof(ManagedHotReloadAvailability); + } +} diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueDiagnosticAnalyzer.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueDiagnosticAnalyzer.cs index 716e7cb888a2c..0ec02d5612395 100644 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueDiagnosticAnalyzer.cs +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueDiagnosticAnalyzer.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Roslyn.Utilities; +using Microsoft.VisualStudio.Debugger.Contracts; namespace Microsoft.CodeAnalysis.EditAndContinue { @@ -39,7 +40,18 @@ public bool OpenFileOnly(OptionSet options) public override Task> AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken) => SpecializedTasks.EmptyImmutableArray(); - public override async Task> AnalyzeSemanticsAsync(Document designTimeDocument, CancellationToken cancellationToken) + public override Task> AnalyzeSemanticsAsync(Document document, CancellationToken cancellationToken) + { + if (!DebuggerContractVersionCheck.IsRequiredDebuggerContractVersionAvailable()) + { + return SpecializedTasks.EmptyImmutableArray(); + } + + return AnalyzeSemanticsImplAsync(document, cancellationToken); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task> AnalyzeSemanticsImplAsync(Document designTimeDocument, CancellationToken cancellationToken) { var workspace = designTimeDocument.Project.Solution.Workspace; @@ -49,8 +61,7 @@ public override async Task> AnalyzeSemanticsAsync(Doc } // avoid creating and synchronizing compile-time solution if the Hot Reload/EnC session is not active - if (mefServices.GetExports().SingleOrDefault()?.Value.Service.IsSessionActive != true && - mefServices.GetExports().SingleOrDefault()?.Value.Service.IsSessionActive != true) + if (mefServices.GetExports().SingleOrDefault()?.Value.IsSessionActive != true) { return ImmutableArray.Empty; } diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueHostWorkspaceEventListener.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueHostWorkspaceEventListener.cs index a976f9b1fab57..09c9b0e4b1406 100644 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueHostWorkspaceEventListener.cs +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueHostWorkspaceEventListener.cs @@ -4,6 +4,7 @@ using System; using System.Composition; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.EditAndContinue; @@ -36,6 +37,17 @@ public void StopListening(Workspace workspace) } private void WorkspaceDocumentOpened(object? sender, DocumentEventArgs e) + { + if (!DebuggerContractVersionCheck.IsRequiredDebuggerContractVersionAvailable()) + { + return; + } + + WorkspaceDocumentOpenedImpl(e); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void WorkspaceDocumentOpenedImpl(DocumentEventArgs e) { var proxy = new RemoteEditAndContinueServiceProxy(e.Document.Project.Solution.Workspace); _ = Task.Run(() => proxy.OnSourceFileUpdatedAsync(e.Document, CancellationToken.None)).ReportNonFatalErrorAsync(); diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueLanguageService.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueLanguageService.cs index cec43f699dd79..bab37ec4f33eb 100644 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueLanguageService.cs +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/EditAndContinueLanguageService.cs @@ -18,12 +18,17 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue { - internal sealed class EditAndContinueLanguageService : IEditAndContinueSolutionProvider + [Shared] + [Export(typeof(IManagedHotReloadLanguageService))] + [Export(typeof(IEditAndContinueSolutionProvider))] + [Export(typeof(EditAndContinueLanguageService))] + [ExportMetadata("UIContext", EditAndContinueUIContext.EncCapableProjectExistsInWorkspaceUIContextString)] + internal sealed class EditAndContinueLanguageService : IManagedHotReloadLanguageService, IEditAndContinueSolutionProvider { private static readonly ActiveStatementSpanProvider s_noActiveStatementSpanProvider = (_, _, _) => ValueTaskFactory.FromResult(ImmutableArray.Empty); - private readonly Lazy _debuggerService; + private readonly Lazy _debuggerService; private readonly IDiagnosticAnalyzerService _diagnosticService; private readonly EditAndContinueDiagnosticUpdateSource _diagnosticUpdateSource; @@ -38,12 +43,14 @@ internal sealed class EditAndContinueLanguageService : IEditAndContinueSolutionP public event Action? SolutionCommitted; /// - /// Import lazily so that the host does not need to implement it + /// Import lazily so that the host does not need to implement it /// unless the host implements debugger components. /// + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public EditAndContinueLanguageService( Lazy workspaceProvider, - Lazy debuggerService, + Lazy debuggerService, IDiagnosticAnalyzerService diagnosticService, EditAndContinueDiagnosticUpdateSource diagnosticUpdateSource) { @@ -93,7 +100,7 @@ public async ValueTask StartSessionAsync(CancellationToken cancellationToken) _debuggingSession = await proxy.StartDebuggingSessionAsync( solution, - _debuggerService.Value, + new ManagedHotReloadServiceImpl(_debuggerService.Value), captureMatchingDocuments: openedDocumentIds, captureAllMatchingDocuments: false, reportDiagnostics: true, @@ -187,7 +194,7 @@ public async ValueTask CommitUpdatesAsync(CancellationToken cancellationToken) Contract.ThrowIfTrue(_disabled); await GetDebuggingSession().CommitSolutionUpdateAsync(_diagnosticService, cancellationToken).ConfigureAwait(false); } - catch (Exception e) when (FatalError.ReportAndCatch(e)) + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { } } @@ -205,7 +212,7 @@ public async ValueTask DiscardUpdatesAsync(CancellationToken cancellationToken) { await GetDebuggingSession().DiscardSolutionUpdateAsync(cancellationToken).ConfigureAwait(false); } - catch (Exception e) when (FatalError.ReportAndCatch(e)) + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { } } @@ -271,7 +278,7 @@ public async ValueTask GetEditAndContinueUpdatesAsync(Canc var activeStatementSpanProvider = GetActiveStatementSpanProvider(solution); var (updates, _, _, _) = await GetDebuggingSession().EmitSolutionUpdateAsync(solution, activeStatementSpanProvider, _diagnosticService, _diagnosticUpdateSource, cancellationToken).ConfigureAwait(false); _pendingUpdatedSolution = solution; - return updates; + return updates.FromContract(); } public async ValueTask GetHotReloadUpdatesAsync(CancellationToken cancellationToken) @@ -290,7 +297,7 @@ public async ValueTask GetHotReloadUpdatesAsync(Cancell var diagnostics = await EmitSolutionUpdateResults.GetHotReloadDiagnosticsAsync(solution, diagnosticData, rudeEdits, syntaxError, cancellationToken).ConfigureAwait(false); - return new ManagedHotReloadUpdates(updates, diagnostics); + return new ManagedHotReloadUpdates(updates, diagnostics.FromContract()); } public async ValueTask GetCurrentActiveStatementPositionAsync(ManagedInstructionId instruction, CancellationToken cancellationToken) @@ -303,8 +310,8 @@ public async ValueTask GetHotReloadUpdatesAsync(Cancell var activeStatementSpanProvider = new ActiveStatementSpanProvider((documentId, filePath, cancellationToken) => activeStatementTrackingService.GetSpansAsync(solution, documentId, filePath, cancellationToken)); - var span = await GetDebuggingSession().GetCurrentActiveStatementPositionAsync(solution, activeStatementSpanProvider, instruction, cancellationToken).ConfigureAwait(false); - return span?.ToSourceSpan(); + var span = await GetDebuggingSession().GetCurrentActiveStatementPositionAsync(solution, activeStatementSpanProvider, instruction.ToContract(), cancellationToken).ConfigureAwait(false); + return span?.ToSourceSpan().FromContract(); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { @@ -317,7 +324,7 @@ public async ValueTask GetHotReloadUpdatesAsync(Cancell try { var solution = GetCurrentCompileTimeSolution(); - return await GetDebuggingSession().IsActiveStatementInExceptionRegionAsync(solution, instruction, cancellationToken).ConfigureAwait(false); + return await GetDebuggingSession().IsActiveStatementInExceptionRegionAsync(solution, instruction.ToContract(), cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs deleted file mode 100644 index c2344e52eae49..0000000000000 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs +++ /dev/null @@ -1,97 +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.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Debugging; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; - -namespace Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue -{ - [Shared] - [Export(typeof(ManagedEditAndContinueLanguageService))] - [Export(typeof(IManagedEditAndContinueLanguageService))] - [Export(typeof(IEditAndContinueSolutionProvider))] - [ExportMetadata("UIContext", EditAndContinueUIContext.EncCapableProjectExistsInWorkspaceUIContextString)] - internal sealed class ManagedEditAndContinueLanguageService : IManagedEditAndContinueLanguageService, IEditAndContinueSolutionProvider - { - private readonly EditAndContinueLanguageService _encService; - - /// - /// Import and lazily so that the host does not need to implement them - /// unless it implements debugger components. - /// - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public ManagedEditAndContinueLanguageService( - Lazy workspaceProvider, - IDiagnosticAnalyzerService diagnosticService, - EditAndContinueDiagnosticUpdateSource diagnosticUpdateSource, - Lazy debuggerService) - { - _encService = new EditAndContinueLanguageService(workspaceProvider, debuggerService, diagnosticService, diagnosticUpdateSource); - } - - public EditAndContinueLanguageService Service => _encService; - - /// - /// Called by the debugger when a debugging session starts and managed debugging is being used. - /// - public Task StartDebuggingAsync(DebugSessionFlags flags, CancellationToken cancellationToken) - { - if (flags.HasFlag(DebugSessionFlags.EditAndContinueDisabled)) - { - _encService.Disable(); - return Task.CompletedTask; - } - - return _encService.StartSessionAsync(cancellationToken).AsTask(); - } - - public Task EnterBreakStateAsync(CancellationToken cancellationToken) - => _encService.EnterBreakStateAsync(cancellationToken).AsTask(); - - public Task ExitBreakStateAsync(CancellationToken cancellationToken) - => _encService.ExitBreakStateAsync(cancellationToken).AsTask(); - - public Task CommitUpdatesAsync(CancellationToken cancellationToken) - => _encService.CommitUpdatesAsync(cancellationToken).AsTask(); - - public Task DiscardUpdatesAsync(CancellationToken cancellationToken) - => _encService.DiscardUpdatesAsync(cancellationToken).AsTask(); - - public Task StopDebuggingAsync(CancellationToken cancellationToken) - => _encService.EndSessionAsync(cancellationToken).AsTask(); - - public Task HasChangesAsync(string? sourceFilePath, CancellationToken cancellationToken) - => _encService.HasChangesAsync(sourceFilePath, cancellationToken).AsTask(); - - public Task GetManagedModuleUpdatesAsync(CancellationToken cancellationToken) - => _encService.GetEditAndContinueUpdatesAsync(cancellationToken).AsTask(); - - public Task GetCurrentActiveStatementPositionAsync(ManagedInstructionId instruction, CancellationToken cancellationToken) - => _encService.GetCurrentActiveStatementPositionAsync(instruction, cancellationToken).AsTask(); - - public Task IsActiveStatementInExceptionRegionAsync(ManagedInstructionId instruction, CancellationToken cancellationToken) - => _encService.IsActiveStatementInExceptionRegionAsync(instruction, cancellationToken).AsTask(); - - public event Action SolutionCommitted - { - add - { - _encService.SolutionCommitted += value; - } - remove - { - _encService.SolutionCommitted -= value; - } - } - } -} diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs deleted file mode 100644 index 84ebecb0811bb..0000000000000 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs +++ /dev/null @@ -1,95 +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 System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Debugging; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; -using Microsoft.VisualStudio.Debugger.Contracts.HotReload; - -namespace Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue -{ - [Shared] - [Export(typeof(ManagedHotReloadLanguageService))] - [Export(typeof(IManagedHotReloadLanguageService))] - [Export(typeof(IEditAndContinueSolutionProvider))] - [ExportMetadata("UIContext", EditAndContinueUIContext.EncCapableProjectExistsInWorkspaceUIContextString)] - internal sealed class ManagedHotReloadLanguageService : IManagedHotReloadLanguageService, IEditAndContinueSolutionProvider - { - private sealed class DebuggerService : IManagedEditAndContinueDebuggerService - { - private readonly Lazy _hotReloadService; - - public DebuggerService(Lazy hotReloadService) - { - _hotReloadService = hotReloadService; - } - - public Task> GetActiveStatementsAsync(CancellationToken cancellationToken) - => Task.FromResult(ImmutableArray.Empty); - - public Task GetAvailabilityAsync(Guid module, CancellationToken cancellationToken) - => Task.FromResult(new ManagedEditAndContinueAvailability(ManagedEditAndContinueAvailabilityStatus.Available)); - - public Task> GetCapabilitiesAsync(CancellationToken cancellationToken) - => _hotReloadService.Value.GetCapabilitiesAsync(cancellationToken).AsTask(); - - public Task PrepareModuleForUpdateAsync(Guid module, CancellationToken cancellationToken) - => Task.CompletedTask; - } - - private readonly EditAndContinueLanguageService _encService; - - /// - /// Import and lazily so that the host does not need to implement them - /// unless it implements debugger components. - /// - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public ManagedHotReloadLanguageService( - Lazy workspaceProvider, - IDiagnosticAnalyzerService diagnosticService, - EditAndContinueDiagnosticUpdateSource diagnosticUpdateSource, - Lazy hotReloadService) - { - _encService = new EditAndContinueLanguageService(workspaceProvider, new Lazy(() => new DebuggerService(hotReloadService)), diagnosticService, diagnosticUpdateSource); - } - - public EditAndContinueLanguageService Service => _encService; - - public ValueTask StartSessionAsync(CancellationToken cancellationToken) - => _encService.StartSessionAsync(cancellationToken); - - public ValueTask GetUpdatesAsync(CancellationToken cancellationToken) - => _encService.GetHotReloadUpdatesAsync(cancellationToken); - - public ValueTask CommitUpdatesAsync(CancellationToken cancellationToken) - => _encService.CommitUpdatesAsync(cancellationToken); - - public ValueTask DiscardUpdatesAsync(CancellationToken cancellationToken) - => _encService.DiscardUpdatesAsync(cancellationToken); - - public ValueTask EndSessionAsync(CancellationToken cancellationToken) - => _encService.EndSessionAsync(cancellationToken); - - public event Action SolutionCommitted - { - add - { - _encService.SolutionCommitted += value; - } - remove - { - _encService.SolutionCommitted -= value; - } - } - } -} diff --git a/src/EditorFeatures/Core/Implementation/EditorLayerExtensionManager.cs b/src/EditorFeatures/Core/Implementation/EditorLayerExtensionManager.cs index 0551a3b8bcc82..0a4e0d733d82e 100644 --- a/src/EditorFeatures/Core/Implementation/EditorLayerExtensionManager.cs +++ b/src/EditorFeatures/Core/Implementation/EditorLayerExtensionManager.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Telemetry; using Microsoft.VisualStudio.Text; using Roslyn.Utilities; using static Microsoft.CodeAnalysis.Internal.Log.FunctionId; @@ -74,7 +75,11 @@ public override void HandleException(object provider, Exception exception) { base.HandleException(provider, exception); - _errorReportingService?.ShowGlobalErrorInfo(string.Format(WorkspacesResources._0_encountered_an_error_and_has_been_disabled, provider.GetType().Name), + var providerType = provider.GetType(); + + _errorReportingService?.ShowGlobalErrorInfo( + message: string.Format(WorkspacesResources._0_encountered_an_error_and_has_been_disabled, providerType.Name), + TelemetryFeatureName.GetExtensionName(providerType), exception, new InfoBarUI(WorkspacesResources.Show_Stack_Trace, InfoBarUI.UIKind.HyperLink, () => ShowDetailedErrorInfo(exception), closeAfterAction: false), new InfoBarUI(WorkspacesResources.Enable, InfoBarUI.UIKind.Button, () => @@ -88,7 +93,7 @@ public override void HandleException(object provider, Exception exception) IgnoreProvider(provider); LogEnableAndIgnoreProvider(provider); }), - new InfoBarUI(String.Empty, InfoBarUI.UIKind.Close, () => LogLeaveDisabled(provider))); + new InfoBarUI(string.Empty, InfoBarUI.UIKind.Close, () => LogLeaveDisabled(provider))); } else { diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Symbols/WorkspaceSymbolsHandler.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Symbols/WorkspaceSymbolsHandler.cs index 5739a2959e84b..bff75eab9ea0a 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Symbols/WorkspaceSymbolsHandler.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Symbols/WorkspaceSymbolsHandler.cs @@ -71,11 +71,10 @@ public WorkspaceSymbolsHandler( _asyncListener, new LSPNavigateToCallback(context, progress), request.Query, - searchCurrentDocument: false, s_supportedKinds, _threadingContext.DisposalToken); - await searcher.SearchAsync(cancellationToken).ConfigureAwait(false); + await searcher.SearchAsync(searchCurrentDocument: false, cancellationToken).ConfigureAwait(false); return progress.GetValues(); } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index a74df6136df2c..4a7fcfcbb667e 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -42,7 +42,7 @@ internal partial class NavigationBarController : ForegroundThreadAffinitizedObje /// The last full information we have presented. If we end up wanting to present the same thing again, we can /// just skip doing that as the UI will already know about this. /// - private (ImmutableArray projectItems, NavigationBarProjectItem? selectedProjectItem, NavigationBarModel model, NavigationBarSelectedTypeAndMember selectedInfo) _lastPresentedInfo; + private (ImmutableArray projectItems, NavigationBarProjectItem? selectedProjectItem, NavigationBarModel? model, NavigationBarSelectedTypeAndMember selectedInfo) _lastPresentedInfo; /// /// Source of events that should cause us to update the nav bar model with new information. @@ -56,7 +56,7 @@ internal partial class NavigationBarController : ForegroundThreadAffinitizedObje /// compute the model once for every batch. The bool type parameter isn't used, but is provided as this /// type is generic. /// - private readonly AsyncBatchingWorkQueue _computeModelQueue; + private readonly AsyncBatchingWorkQueue _computeModelQueue; /// /// Queue to batch up work to do to determine the selected item. Used so we can batch up a lot of events and @@ -77,7 +77,7 @@ public NavigationBarController( _uiThreadOperationExecutor = uiThreadOperationExecutor; _asyncListener = asyncListener; - _computeModelQueue = new AsyncBatchingWorkQueue( + _computeModelQueue = new AsyncBatchingWorkQueue( TimeSpan.FromMilliseconds(TaggerConstants.ShortDelay), ComputeModelAndSelectItemAsync, EqualityComparer.Default, @@ -115,6 +115,8 @@ public NavigationBarController( StartModelUpdateAndSelectedItemUpdateTasks(); } + public TestAccessor GetTestAccessor() => new TestAccessor(this); + private void OnEventSourceChanged(object? sender, TaggerEventArgs e) { StartModelUpdateAndSelectedItemUpdateTasks(); @@ -251,5 +253,18 @@ private async Task ProcessItemSelectionAsync(NavigationBarItem item, Cancellatio // Now that the edit has been done, refresh to make sure everything is up-to-date. StartModelUpdateAndSelectedItemUpdateTasks(); } + + public struct TestAccessor + { + private readonly NavigationBarController _navigationBarController; + + public TestAccessor(NavigationBarController navigationBarController) + { + _navigationBarController = navigationBarController; + } + + public Task GetModelAsync() + => _navigationBarController._computeModelQueue.WaitUntilCurrentBatchCompletesAsync(); + } } } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 8bc14cda90313..d506d0309cad1 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -2,9 +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.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -23,7 +20,7 @@ internal partial class NavigationBarController /// /// Starts a new task to compute the model based on the current text. /// - private async ValueTask ComputeModelAndSelectItemAsync(ImmutableArray unused, CancellationToken cancellationToken) + private async ValueTask ComputeModelAndSelectItemAsync(ImmutableArray unused, CancellationToken cancellationToken) { // Jump back to the UI thread to determine what snapshot the user is processing. await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); @@ -36,11 +33,12 @@ private async ValueTask ComputeModelAndSelectItemAsync(Immut var model = await ComputeModelAsync(textSnapshot, cancellationToken).ConfigureAwait(false); // Now, enqueue work to select the right item in this new model. - StartSelectedItemUpdateTask(); + if (model != null) + StartSelectedItemUpdateTask(); return model; - static async Task ComputeModelAsync(ITextSnapshot textSnapshot, CancellationToken cancellationToken) + static async Task ComputeModelAsync(ITextSnapshot textSnapshot, CancellationToken cancellationToken) { // When computing items just get the partial semantics workspace. This will ensure we can get data for this // file, and hopefully have enough loaded to get data for other files in the case of partial types. In the @@ -53,16 +51,14 @@ static async Task ComputeModelAsync(ITextSnapshot textSnapsh return null; var itemService = document.GetLanguageService(); - if (itemService != null) + if (itemService == null) + return null; + + using (Logger.LogBlock(FunctionId.NavigationBar_ComputeModelAsync, cancellationToken)) { - using (Logger.LogBlock(FunctionId.NavigationBar_ComputeModelAsync, cancellationToken)) - { - var items = await itemService.GetItemsAsync(document, textSnapshot.Version, cancellationToken).ConfigureAwait(false); - return new NavigationBarModel(itemService, items); - } + var items = await itemService.GetItemsAsync(document, textSnapshot.Version, cancellationToken).ConfigureAwait(false); + return new NavigationBarModel(itemService, items); } - - return new NavigationBarModel(itemService: null, ImmutableArray.Empty); } } @@ -113,7 +109,7 @@ private async ValueTask SelectItemAsync(CancellationToken cancellationToken) _presenter.PresentItems( projectItems, selectedProjectItem, - model.Types, + model?.Types ?? ImmutableArray.Empty, currentSelectedItem.TypeItem, currentSelectedItem.MemberItem); @@ -121,17 +117,19 @@ private async ValueTask SelectItemAsync(CancellationToken cancellationToken) } internal static NavigationBarSelectedTypeAndMember ComputeSelectedTypeAndMember( - NavigationBarModel model, int caretPosition, CancellationToken cancellationToken) + NavigationBarModel? model, int caretPosition, CancellationToken cancellationToken) { - var (item, gray) = GetMatchingItem(model.Types, caretPosition, model.ItemService, cancellationToken); - if (item == null) + if (model != null) { - // Nothing to show at all - return new NavigationBarSelectedTypeAndMember(null, null); + var (item, gray) = GetMatchingItem(model.Types, caretPosition, model.ItemService, cancellationToken); + if (item != null) + { + var rightItem = GetMatchingItem(item.ChildItems, caretPosition, model.ItemService, cancellationToken); + return new NavigationBarSelectedTypeAndMember(item, gray, rightItem.item, rightItem.gray); + } } - var rightItem = GetMatchingItem(item.ChildItems, caretPosition, model.ItemService, cancellationToken); - return new NavigationBarSelectedTypeAndMember(item, gray, rightItem.item, rightItem.gray); + return NavigationBarSelectedTypeAndMember.Empty; } /// @@ -139,12 +137,12 @@ internal static NavigationBarSelectedTypeAndMember ComputeSelectedTypeAndMember( /// positioned after the cursor. /// /// A tuple of the matching item, and if it should be shown grayed. - private static (NavigationBarItem item, bool gray) GetMatchingItem( + private static (NavigationBarItem? item, bool gray) GetMatchingItem( ImmutableArray items, int point, INavigationBarItemService itemsService, CancellationToken cancellationToken) { - NavigationBarItem exactItem = null; + NavigationBarItem? exactItem = null; var exactItemStart = 0; - NavigationBarItem nextItem = null; + NavigationBarItem? nextItem = null; var nextItemStart = int.MaxValue; foreach (var item in items) diff --git a/src/EditorFeatures/Core/Implementation/Workspaces/EditorErrorReportingService.cs b/src/EditorFeatures/Core/Implementation/Workspaces/EditorErrorReportingService.cs index f1cc787a583db..62fc8fb137562 100644 --- a/src/EditorFeatures/Core/Implementation/Workspaces/EditorErrorReportingService.cs +++ b/src/EditorFeatures/Core/Implementation/Workspaces/EditorErrorReportingService.cs @@ -5,6 +5,7 @@ using System; using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Telemetry; namespace Microsoft.CodeAnalysis.Editor.Implementation.Workspaces { @@ -15,10 +16,10 @@ internal class EditorErrorReportingService : IErrorReportingService public void ShowDetailedErrorInfo(Exception exception) => Logger.Log(FunctionId.Extension_Exception, exception.StackTrace); - public void ShowGlobalErrorInfo(string message, Exception? exception, params InfoBarUI[] items) + public void ShowGlobalErrorInfo(string message, TelemetryFeatureName featureName, Exception? exception, params InfoBarUI[] items) => Logger.Log(FunctionId.Extension_Exception, message); - public void ShowFeatureNotAvailableErrorInfo(string message, Exception? exception) + public void ShowFeatureNotAvailableErrorInfo(string message, TelemetryFeatureName featureName, Exception? exception) { // telemetry has already been reported } diff --git a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj index 427f9e858626b..393a8bbb2f76c 100644 --- a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj +++ b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj @@ -42,6 +42,7 @@ + @@ -63,6 +64,7 @@ + - netcoreapp3.1 + net6.0 true Microsoft.NETCore.Compilers diff --git a/src/NuGet/Microsoft.Net.Compilers.Toolset/CoreClrCompilerArtifacts.targets b/src/NuGet/Microsoft.Net.Compilers.Toolset/CoreClrCompilerArtifacts.targets index 45590df74bb10..4bba65e560005 100644 --- a/src/NuGet/Microsoft.Net.Compilers.Toolset/CoreClrCompilerArtifacts.targets +++ b/src/NuGet/Microsoft.Net.Compilers.Toolset/CoreClrCompilerArtifacts.targets @@ -3,9 +3,9 @@ - - - + + + @@ -13,26 +13,26 @@ - - + + - - - + + + - - - + + + - - - + + + - - + + diff --git a/src/NuGet/Microsoft.Net.Compilers.Toolset/Microsoft.Net.Compilers.Toolset.Package.csproj b/src/NuGet/Microsoft.Net.Compilers.Toolset/Microsoft.Net.Compilers.Toolset.Package.csproj index 67e2edeaa291f..5347eebe10eb3 100644 --- a/src/NuGet/Microsoft.Net.Compilers.Toolset/Microsoft.Net.Compilers.Toolset.Package.csproj +++ b/src/NuGet/Microsoft.Net.Compilers.Toolset/Microsoft.Net.Compilers.Toolset.Package.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1;net472 + net6.0;net472 true Microsoft.Net.Compilers.Toolset @@ -24,7 +24,7 @@ $(NoWarn);NU5100;NU5128 <_DependsOn Condition="'$(TargetFramework)' == 'net472'">InitializeDesktopCompilerArtifacts - <_DependsOn Condition="'$(TargetFramework)' == 'netcoreapp3.1'">InitializeCoreClrCompilerArtifacts + <_DependsOn Condition="'$(TargetFramework)' == 'net6.0'">InitializeCoreClrCompilerArtifacts @@ -38,18 +38,18 @@ Targets="Publish" ReferenceOutputAssembly="false" SkipGetTargetFrameworkProperties="true" - Condition="'$(TargetFramework)' == 'netcoreapp3.1'" - SetTargetFramework="TargetFramework=netcoreapp3.1" /> + Condition="'$(TargetFramework)' == 'net6.0'" + SetTargetFramework="TargetFramework=net6.0" /> <_File Include="@(DesktopCompilerArtifact)" TargetDir="tasks/net472"/> <_File Include="@(DesktopCompilerResourceArtifact)" TargetDir="tasks/net472"/> - <_File Include="@(CoreClrCompilerBuildArtifact)" TargetDir="tasks/netcoreapp3.1"/> - <_File Include="@(CoreClrCompilerToolsArtifact)" TargetDir="tasks/netcoreapp3.1"/> - <_File Include="@(CoreClrCompilerBinArtifact)" TargetDir="tasks/netcoreapp3.1/bincore"/> - <_File Include="@(CoreClrCompilerBinRuntimesArtifact)" TargetDir="tasks/netcoreapp3.1/bincore/runtimes"/> + <_File Include="@(CoreClrCompilerBuildArtifact)" TargetDir="tasks/net6.0"/> + <_File Include="@(CoreClrCompilerToolsArtifact)" TargetDir="tasks/net6.0"/> + <_File Include="@(CoreClrCompilerBinArtifact)" TargetDir="tasks/net6.0/bincore"/> + <_File Include="@(CoreClrCompilerBinRuntimesArtifact)" TargetDir="tasks/net6.0/bincore/runtimes"/> <_File Include="$(MSBuildProjectDirectory)\build\**\*.*" Condition="'$(TargetFramework)' == 'net472'" TargetDir="build" /> <_File Include="$(MSBuildProjectDirectory)\buildMultiTargeting\**\*.*" Condition="'$(TargetFramework)' == 'net472'" TargetDir="buildMultiTargeting" /> @@ -59,5 +59,5 @@ - + diff --git a/src/NuGet/Microsoft.Net.Compilers.Toolset/build/Microsoft.Net.Compilers.Toolset.props b/src/NuGet/Microsoft.Net.Compilers.Toolset/build/Microsoft.Net.Compilers.Toolset.props index 157388e0be7e2..4b771afb964cf 100644 --- a/src/NuGet/Microsoft.Net.Compilers.Toolset/build/Microsoft.Net.Compilers.Toolset.props +++ b/src/NuGet/Microsoft.Net.Compilers.Toolset/build/Microsoft.Net.Compilers.Toolset.props @@ -2,7 +2,7 @@ - <_RoslynTargetDirectoryName Condition="'$(MSBuildRuntimeType)' == 'Core'">netcoreapp3.1 + <_RoslynTargetDirectoryName Condition="'$(MSBuildRuntimeType)' == 'Core'">net6.0 <_RoslynTargetDirectoryName Condition="'$(MSBuildRuntimeType)' != 'Core'">net472 <_RoslynTasksDirectory>$(MSBuildThisFileDirectory)..\tasks\$(_RoslynTargetDirectoryName)\ $(_RoslynTasksDirectory)Microsoft.Build.Tasks.CodeAnalysis.dll diff --git a/src/Test/PdbUtilities/Reader/PdbValidationOptions.cs b/src/Test/PdbUtilities/Reader/PdbValidationOptions.cs index a2b5f1462a12d..4e4879327d580 100644 --- a/src/Test/PdbUtilities/Reader/PdbValidationOptions.cs +++ b/src/Test/PdbUtilities/Reader/PdbValidationOptions.cs @@ -20,7 +20,8 @@ public enum PdbValidationOptions ExcludeScopes = PdbToXmlOptions.ExcludeScopes, ExcludeNamespaces = PdbToXmlOptions.ExcludeNamespaces, ExcludeAsyncInfo = PdbToXmlOptions.ExcludeAsyncInfo, - ExcludeCustomDebugInformation = PdbToXmlOptions.ExcludeCustomDebugInformation + ExcludeCustomDebugInformation = PdbToXmlOptions.ExcludeCustomDebugInformation, + IncludeModuleDebugInfo = PdbToXmlOptions.IncludeModuleDebugInfo } public static class PdbValidationOptionsExtensions @@ -34,7 +35,8 @@ public static PdbToXmlOptions ToPdbToXmlOptions(this PdbValidationOptions option PdbValidationOptions.ExcludeScopes | PdbValidationOptions.ExcludeNamespaces | PdbValidationOptions.ExcludeAsyncInfo | - PdbValidationOptions.ExcludeCustomDebugInformation; + PdbValidationOptions.ExcludeCustomDebugInformation | + PdbValidationOptions.IncludeModuleDebugInfo; return PdbToXmlOptions.ResolveTokens | PdbToXmlOptions.ThrowOnError | PdbToXmlOptions.IncludeEmbeddedSources | (PdbToXmlOptions)(options & mask); } diff --git a/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj b/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj index 8ca3cbb28f026..06b1c0ea0f7dd 100644 --- a/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj +++ b/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj @@ -4,7 +4,7 @@ Exe - net6.0;netcoreapp3.1;net472 + net6.0;net472 false AnyCPU true @@ -12,7 +12,7 @@ true - + - net472 - x86 + net6.0 + diff --git a/src/Tools/BuildActionTelemetryTable/Program.cs b/src/Tools/BuildActionTelemetryTable/Program.cs index a48e954bc07bc..3d53d49fdf2ce 100644 --- a/src/Tools/BuildActionTelemetryTable/Program.cs +++ b/src/Tools/BuildActionTelemetryTable/Program.cs @@ -5,19 +5,284 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; using System.Text; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; -using TelemetryInfo = System.Tuple; +using TelemetryInfo = System.Tuple; namespace BuildActionTelemetryTable { public class Program { - private static readonly string s_executingPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + private static readonly string s_executingPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; + + private static ImmutableHashSet IgnoredCodeActions { get; } = new HashSet() + { + "Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider+PostProcessCodeAction", + "Microsoft.CodeAnalysis.CodeActions.CodeAction+CodeActionWithNestedActions", + "Microsoft.CodeAnalysis.CodeActions.CodeAction+DocumentChangeAction", + "Microsoft.CodeAnalysis.CodeActions.CodeAction+SolutionChangeAction", + "Microsoft.CodeAnalysis.CodeActions.CodeAction+NoChangeAction", + "Microsoft.CodeAnalysis.CodeActions.CustomCodeActions+DocumentChangeAction", + "Microsoft.CodeAnalysis.CodeActions.CustomCodeActions+SolutionChangeAction", + }.ToImmutableHashSet(); + + private static ImmutableDictionary CodeActionDescriptionMap { get; } = new Dictionary() + { + { "Microsoft.CodeAnalysis.CSharp.TypeStyle.UseExplicitTypeCodeFixProvider+MyCodeAction", "Use Explicit Type" }, + { "Microsoft.CodeAnalysis.CSharp.TypeStyle.UseImplicitTypeCodeFixProvider+MyCodeAction", "Use Implicit Type" }, + { "Microsoft.CodeAnalysis.CSharp.UseSimpleUsingStatement.UseSimpleUsingStatementCodeFixProvider+MyCodeAction", "Use Simple Using Statement" }, + { "Microsoft.CodeAnalysis.CSharp.UseIsNullCheck.CSharpUseIsNullCheckForCastAndEqualityOperatorCodeFixProvider+MyCodeAction", "Use 'Is Null' Check" }, + { "Microsoft.CodeAnalysis.CSharp.UseIndexOrRangeOperator.CSharpUseIndexOperatorCodeFixProvider+MyCodeAction", "Use Index Operator" }, + { "Microsoft.CodeAnalysis.CSharp.UseIndexOrRangeOperator.CSharpUseRangeOperatorCodeFixProvider+MyCodeAction", "Use Range Operator" }, + { "Microsoft.CodeAnalysis.CSharp.UseImplicitObjectCreation.CSharpUseImplicitObjectCreationCodeFixProvider+MyCodeAction", "Use Implicit Object Creation" }, + { "Microsoft.CodeAnalysis.CSharp.RemoveUnnecessaryCast.CSharpRemoveUnnecessaryCastCodeFixProvider+MyCodeAction", "Remove Unnecessary Cast" }, + { "Microsoft.CodeAnalysis.CSharp.UseDefaultLiteral.CSharpUseDefaultLiteralCodeFixProvider+MyCodeAction", "Use Default Literal" }, + { "Microsoft.CodeAnalysis.CSharp.UseDeconstruction.CSharpUseDeconstructionCodeFixProvider+MyCodeAction", "Use Deconstruction" }, + { "Microsoft.CodeAnalysis.CSharp.UseCompoundAssignment.CSharpUseCompoundCoalesceAssignmentCodeFixProvider+MyCodeAction", "Use Compound Assignment" }, + { "Microsoft.CodeAnalysis.CSharp.RemoveUnreachableCode.CSharpRemoveUnreachableCodeCodeFixProvider+MyCodeAction", "Remove Unreachable Code" }, + { "Microsoft.CodeAnalysis.CSharp.MisplacedUsingDirectives.MisplacedUsingDirectivesCodeFixProvider+MoveMisplacedUsingsCodeAction", "Misplaced Using Directives" }, + { "Microsoft.CodeAnalysis.CSharp.MakeStructFieldsWritable.CSharpMakeStructFieldsWritableCodeFixProvider+MyCodeAction", "Make Struct Fields Writable" }, + { "Microsoft.CodeAnalysis.CSharp.InvokeDelegateWithConditionalAccess.InvokeDelegateWithConditionalAccessCodeFixProvider+MyCodeAction", "Invoke Delegate With Conditional Access" }, + { "Microsoft.CodeAnalysis.CSharp.InlineDeclaration.CSharpInlineDeclarationCodeFixProvider+MyCodeAction", "Inline Declaration" }, + { "Microsoft.CodeAnalysis.CSharp.ConvertSwitchStatementToExpression.ConvertSwitchStatementToExpressionCodeFixProvider+MyCodeAction", "Convert Switch Statement To Expression" }, + { "Microsoft.CodeAnalysis.CSharp.RemoveConfusingSuppression.CSharpRemoveConfusingSuppressionCodeFixProvider+MyCodeAction", "Remove Confusing Suppressino" }, + { "Microsoft.CodeAnalysis.CSharp.RemoveUnnecessaryDiscardDesignation.CSharpRemoveUnnecessaryDiscardDesignationCodeFixProvider+MyCodeAction", "Remove Unneccessary Discard Designation" }, + { "Microsoft.CodeAnalysis.CSharp.NewLines.EmbeddedStatementPlacement.EmbeddedStatementPlacementCodeFixProvider+MyCodeAction", "New Lines: Embedded Statement Placement" }, + { "Microsoft.CodeAnalysis.CSharp.NewLines.ConstructorInitializerPlacement.ConstructorInitializerPlacementCodeFixProvider+MyCodeAction", "New Lines: Constructor Initializer Placement" }, + { "Microsoft.CodeAnalysis.CSharp.NewLines.ConsecutiveBracePlacement.ConsecutiveBracePlacementCodeFixProvider+MyCodeAction", "New Lines: Consecutive Brace Placement" }, + { "Microsoft.CodeAnalysis.CSharp.UsePatternMatching.CSharpIsAndCastCheckWithoutNameCodeFixProvider+MyCodeAction", "Use Pattern Matching: Is And Cast Check Without Name" }, + { "Microsoft.CodeAnalysis.CSharp.UsePatternMatching.CSharpUseNotPatternCodeFixProvider+MyCodeAction", "Use Pattern Matching: Use Not Pattern" }, + { "Microsoft.CodeAnalysis.CSharp.UsePatternMatching.CSharpAsAndNullCheckCodeFixProvider+MyCodeAction", "Use Pattern Matching: As And Null Check" }, + { "Microsoft.CodeAnalysis.CSharp.UsePatternMatching.CSharpIsAndCastCheckCodeFixProvider+MyCodeAction", "Use Pattern Matching: Is And Cast Check" }, + { "Microsoft.CodeAnalysis.CSharp.UsePatternCombinators.CSharpUsePatternCombinatorsCodeFixProvider+MyCodeAction", "Use Pattern Mathcing: Use Pattern Combinators" }, + { "Microsoft.CodeAnalysis.CSharp.UseLocalFunction.CSharpUseLocalFunctionCodeFixProvider+MyCodeAction", "Use Local Function" }, + { "Microsoft.CodeAnalysis.CSharp.UseExpressionBody.UseExpressionBodyCodeRefactoringProvider+MyCodeAction", "Use Expression Body (Refactoring)" }, + { "Microsoft.CodeAnalysis.CSharp.UseExpressionBody.UseExpressionBodyCodeFixProvider+MyCodeAction", "Use Expression Body (Codefix)" }, + { "Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda.UseExpressionBodyForLambdaCodeStyleProvider+MyCodeAction", "Use Expression Body For Lambda" }, + { "Microsoft.CodeAnalysis.CSharp.UseExplicitTypeForConst.UseExplicitTypeForConstCodeFixProvider+MyCodeAction", "Use Explicit Type For Const" }, + { "Microsoft.CodeAnalysis.CSharp.ReverseForStatement.CSharpReverseForStatementCodeRefactoringProvider+MyCodeAction", "Reverse For Statement" }, + { "Microsoft.CodeAnalysis.CSharp.ReplaceDefaultLiteral.CSharpReplaceDefaultLiteralCodeFixProvider+MyCodeAction", "Replace Default Literal" }, + { "Microsoft.CodeAnalysis.CSharp.RemoveUnusedLocalFunction.CSharpRemoveUnusedLocalFunctionCodeFixProvider+MyCodeAction", "Remove Unused Local Function" }, + { "Microsoft.CodeAnalysis.CSharp.MakeRefStruct.MakeRefStructCodeFixProvider+MyCodeAction", "Make Ref Struct" }, + { "Microsoft.CodeAnalysis.CSharp.MakeLocalFunctionStatic.MakeLocalFunctionStaticCodeFixProvider+MyCodeAction", "Make Local Function Static (CodeFix)" }, + { "Microsoft.CodeAnalysis.CSharp.MakeLocalFunctionStatic.MakeLocalFunctionStaticCodeRefactoringProvider+MyCodeAction", "Make Local Function Static (Refactoring)" }, + { "Microsoft.CodeAnalysis.CSharp.MakeLocalFunctionStatic.PassInCapturedVariablesAsArgumentsCodeFixProvider+MyCodeAction", "Make Local Function Static Pass In Captured Variables As Arguments" }, + { "Microsoft.CodeAnalysis.CSharp.ImplementInterface.AbstractChangeImplementionCodeRefactoringProvider+MyCodeAction", "Implement Interface" }, + { "Microsoft.CodeAnalysis.CSharp.DisambiguateSameVariable.CSharpDisambiguateSameVariableCodeFixProvider+MyCodeAction", "Disambiguate Same Variable" }, + { "Microsoft.CodeAnalysis.CSharp.Diagnostics.AddBraces.CSharpAddBracesCodeFixProvider+MyCodeAction", "Add Braces" }, + { "Microsoft.CodeAnalysis.CSharp.ConvertBetweenRegularAndVerbatimString.AbstractConvertBetweenRegularAndVerbatimStringCodeRefactoringProvider`1+MyCodeAction", "Convert Between Regular And Verbatim String" }, + { "Microsoft.CodeAnalysis.CSharp.CodeRefactorings.UseType.AbstractUseTypeCodeRefactoringProvider+MyCodeAction", "Use Type" }, + { "Microsoft.CodeAnalysis.CSharp.CodeRefactorings.LambdaSimplifier.LambdaSimplifierCodeRefactoringProvider+MyCodeAction", "Lambda Simplifier" }, + { "Microsoft.CodeAnalysis.CSharp.CodeRefactorings.InlineTemporary.CSharpInlineTemporaryCodeRefactoringProvider+MyCodeAction", "Inline Temporary" }, + { "Microsoft.CodeAnalysis.CSharp.CodeRefactorings.EnableNullable.EnableNullableCodeRefactoringProvider+MyCodeAction", "Enable nullable" }, + { "Microsoft.CodeAnalysis.CSharp.CodeRefactorings.ConvertLocalFunctionToMethod.CSharpConvertLocalFunctionToMethodCodeRefactoringProvider+MyCodeAction", "Convert Local Function To Method" }, + { "Microsoft.CodeAnalysis.CSharp.UseInterpolatedVerbatimString.CSharpUseInterpolatedVerbatimStringCodeFixProvider+MyCodeAction", "Use Interpolated Verbatim String" }, + { "Microsoft.CodeAnalysis.CSharp.CodeFixes.RemoveNewModifier.RemoveNewModifierCodeFixProvider+MyCodeAction", "Remove New Modifier" }, + { "Microsoft.CodeAnalysis.CSharp.CodeFixes.RemoveInKeyword.RemoveInKeywordCodeFixProvider+MyCodeAction", "Remove In Keyword" }, + { "Microsoft.CodeAnalysis.CSharp.CodeFixes.DeclareAsNullable.CSharpDeclareAsNullableCodeFixProvider+MyCodeAction", "Declare As Nullable" }, + { "Microsoft.CodeAnalysis.CSharp.CodeFixes.MakeStatementAsynchronous.CSharpMakeStatementAsynchronousCodeFixProvider+MyCodeAction", "Make Statement Asynchronous" }, + { "Microsoft.CodeAnalysis.CSharp.CodeFixes.Iterator.CSharpAddYieldCodeFixProvider+MyCodeAction", "Add Yield" }, + { "Microsoft.CodeAnalysis.CSharp.CodeFixes.Iterator.CSharpChangeToIEnumerableCodeFixProvider+MyCodeAction", "Change To IEnumerable" }, + { "Microsoft.CodeAnalysis.CSharp.CodeFixes.HideBase.HideBaseCodeFixProvider+AddNewKeywordAction", "Hide Basen" }, + { "Microsoft.CodeAnalysis.CSharp.CodeFixes.FixReturnType.CSharpFixReturnTypeCodeFixProvider+MyCodeAction", "Fix Return Type" }, + { "Microsoft.CodeAnalysis.CSharp.CodeFixes.ConditionalExpressionInStringInterpolation.CSharpAddParenthesesAroundConditionalExpressionInInterpolatedStringCodeFixProvider+MyCodeAction", "Add Parentheses Around Conditional Expression In String Interpolation" }, + { "Microsoft.CodeAnalysis.CSharp.AssignOutParameters.AbstractAssignOutParametersCodeFixProvider+MyCodeAction", "Assign Out Parameters" }, + { "Microsoft.CodeAnalysis.Wrapping.WrapItemsAction", "Wrap Items" }, + { "Microsoft.CodeAnalysis.UpgradeProject.ProjectOptionsChangeAction", "Upgrade Project" }, + { "Microsoft.CodeAnalysis.ExtractInterface.ExtractInterfaceCodeAction", "Extract Interface" }, + { "Microsoft.CodeAnalysis.ExtractClass.ExtractClassWithDialogCodeAction", "Extract Class With Dialog" }, + { "Microsoft.CodeAnalysis.CodeFixes.FixMultipleCodeAction", "Fix Multiple" }, + { "Microsoft.CodeAnalysis.CodeFixes.Suppression.TopLevelSuppressionCodeAction", "Top Level Suppression" }, + { "Microsoft.CodeAnalysis.ChangeSignature.ChangeSignatureCodeAction", "Change Signature" }, + { "Microsoft.CodeAnalysis.AddPackage.InstallPackageDirectlyCodeAction", "Install Package Directly" }, + { "Microsoft.CodeAnalysis.AddPackage.InstallPackageParentCodeAction", "Install Package Parent" }, + { "Microsoft.CodeAnalysis.AddPackage.InstallWithPackageManagerCodeAction", "Install With Package Manager" }, + { "Microsoft.CodeAnalysis.AddMissingReference.AddMissingReferenceCodeAction", "Add Missing Reference" }, + { "Microsoft.CodeAnalysis.UpdateLegacySuppressions.UpdateLegacySuppressionsCodeFixProvider+MyCodeAction", "Update Legacy Suppressions" }, + { "Microsoft.CodeAnalysis.UseThrowExpression.UseThrowExpressionCodeFixProvider+MyCodeAction", "Use Throw Expression" }, + { "Microsoft.CodeAnalysis.UseSystemHashCode.UseSystemHashCodeCodeFixProvider+MyCodeAction", "Use System.HashCode" }, + { "Microsoft.CodeAnalysis.UseObjectInitializer.AbstractUseObjectInitializerCodeFixProvider`7+MyCodeAction", "Use Object Initializer" }, + { "Microsoft.CodeAnalysis.UseNullPropagation.AbstractUseNullPropagationCodeFixProvider`10+MyCodeAction", "Use Null Propagation" }, + { "Microsoft.CodeAnalysis.UseExplicitTupleName.UseExplicitTupleNameCodeFixProvider+MyCodeAction", "Use Explicit Tuple Name" }, + { "Microsoft.CodeAnalysis.UseIsNullCheck.AbstractUseIsNullCheckForReferenceEqualsCodeFixProvider`1+MyCodeAction", "Use 'Is Null' Check" }, + { "Microsoft.CodeAnalysis.UseInferredMemberName.AbstractUseInferredMemberNameCodeFixProvider+MyCodeAction", "Use Inferred Member Name" }, + { "Microsoft.CodeAnalysis.UseConditionalExpression.AbstractUseConditionalExpressionForAssignmentCodeFixProvider`6+MyCodeAction", "Use Conditional Expression For Assignment" }, + { "Microsoft.CodeAnalysis.UseConditionalExpression.AbstractUseConditionalExpressionForReturnCodeFixProvider`4+MyCodeAction", "Use Conditional Expression For Return" }, + { "Microsoft.CodeAnalysis.UseCompoundAssignment.AbstractUseCompoundAssignmentCodeFixProvider`3+MyCodeAction", "Use Compound Assignment" }, + { "Microsoft.CodeAnalysis.UseCollectionInitializer.AbstractUseCollectionInitializerCodeFixProvider`8+MyCodeAction", "Use Collection Initializer" }, + { "Microsoft.CodeAnalysis.UseCoalesceExpression.UseCoalesceExpressionCodeFixProvider+MyCodeAction", "Use Coalesce Expression" }, + { "Microsoft.CodeAnalysis.UseCoalesceExpression.UseCoalesceExpressionForNullableCodeFixProvider+MyCodeAction", "Use Coalesce Expression For Nullable" }, + { "Microsoft.CodeAnalysis.SimplifyLinqExpression.AbstractSimplifyLinqExpressionCodeFixProvider`3+MyCodeAction", "Simplify Linq Expression" }, + { "Microsoft.CodeAnalysis.SimplifyInterpolation.AbstractSimplifyInterpolationCodeFixProvider`7+MyCodeAction", "Simplify Interpolation" }, + { "Microsoft.CodeAnalysis.SimplifyBooleanExpression.SimplifyConditionalCodeFixProvider+MyCodeAction", "Simplify Boolean Expression" }, + { "Microsoft.CodeAnalysis.RemoveUnusedParametersAndValues.AbstractRemoveUnusedValuesCodeFixProvider`11+MyCodeAction", "Remove Unused Parameters And Values" }, + { "Microsoft.CodeAnalysis.RemoveUnusedMembers.AbstractRemoveUnusedMembersCodeFixProvider`1+MyCodeAction", "Remove Unused Members" }, + { "Microsoft.CodeAnalysis.RemoveUnnecessaryImports.AbstractRemoveUnnecessaryImportsCodeFixProvider+MyCodeAction", "Remove Unnecessary Imports" }, + { "Microsoft.CodeAnalysis.QualifyMemberAccess.AbstractQualifyMemberAccessCodeFixprovider`2+MyCodeAction", "Qualify Member Access" }, + { "Microsoft.CodeAnalysis.PopulateSwitch.AbstractPopulateSwitchCodeFixProvider`4+MyCodeAction", "Populate Switch" }, + { "Microsoft.CodeAnalysis.OrderModifiers.AbstractOrderModifiersCodeFixProvider+MyCodeAction", "Order Modifiers" }, + { "Microsoft.CodeAnalysis.MakeFieldReadonly.AbstractMakeFieldReadonlyCodeFixProvider`2+MyCodeAction", "Make Field Readonly" }, + { "Microsoft.CodeAnalysis.RemoveUnnecessarySuppressions.RemoveUnnecessaryInlineSuppressionsCodeFixProvider+MyCodeAction", "Remove Unnecessary Inline Suppressions" }, + { "Microsoft.CodeAnalysis.RemoveUnnecessarySuppressions.RemoveUnnecessaryAttributeSuppressionsCodeFixProvider+MyCodeAction", "Remove Unnecessary Attribute Suppressions" }, + { "Microsoft.CodeAnalysis.RemoveRedundantEquality.RemoveRedundantEqualityCodeFixProvider+MyCodeAction", "Remove Redundant Equality" }, + { "Microsoft.CodeAnalysis.NewLines.MultipleBlankLines.MultipleBlankLinesCodeFixProvider+MyCodeAction", "New Lines: Multiple Blank Lines" }, + { "Microsoft.CodeAnalysis.NewLines.ConsecutiveStatementPlacement.ConsecutiveStatementPlacementCodeFixProvider+MyCodeAction", "New Lines: Consecutive Statement Placement" }, + { "Microsoft.CodeAnalysis.FileHeaders.AbstractFileHeaderCodeFixProvider+MyCodeAction", "File Headers" }, + { "Microsoft.CodeAnalysis.ConvertTypeOfToNameOf.AbstractConvertTypeOfToNameOfCodeFixProvider+MyCodeAction", "Convert TypeOf To NameOf" }, + { "Microsoft.CodeAnalysis.ConvertAnonymousTypeToTuple.AbstractConvertAnonymousTypeToTupleCodeFixProvider`3+MyCodeAction", "Convert Anonymous Type To Tuple" }, + { "Microsoft.CodeAnalysis.AddRequiredParentheses.AddRequiredParenthesesCodeFixProvider+MyCodeAction", "Add Required Parentheses" }, + { "Microsoft.CodeAnalysis.AddAccessibilityModifiers.AbstractAddAccessibilityModifiersCodeFixProvider+MyCodeAction", "Add Accessibility Modifiers" }, + { "Microsoft.CodeAnalysis.RemoveUnnecessaryParentheses.AbstractRemoveUnnecessaryParenthesesCodeFixProvider`1+MyCodeAction", "Remove Unnecessary Parentheses" }, + { "Microsoft.CodeAnalysis.UseNamedArguments.AbstractUseNamedArgumentsCodeRefactoringProvider+MyCodeAction", "Use Named Arguments" }, + { "Microsoft.CodeAnalysis.UseAutoProperty.AbstractUseAutoPropertyCodeFixProvider`5+UseAutoPropertyCodeAction", "Use Auto Property" }, + { "Microsoft.CodeAnalysis.UnsealClass.AbstractUnsealClassCodeFixProvider+MyCodeAction", "Unseal Class" }, + { "Microsoft.CodeAnalysis.SplitOrMergeIfStatements.AbstractMergeConsecutiveIfStatementsCodeRefactoringProvider+MyCodeAction", "Merge Consecutive If Statements" }, + { "Microsoft.CodeAnalysis.SplitOrMergeIfStatements.AbstractSplitIntoConsecutiveIfStatementsCodeRefactoringProvider+MyCodeAction", "Split Into Consecutive If Statements" }, + { "Microsoft.CodeAnalysis.SplitOrMergeIfStatements.AbstractMergeNestedIfStatementsCodeRefactoringProvider+MyCodeAction", "Merge Nested If Statements" }, + { "Microsoft.CodeAnalysis.SplitOrMergeIfStatements.AbstractSplitIntoNestedIfStatementsCodeRefactoringProvider+MyCodeAction", "Split Into Nested If Statements" }, + { "Microsoft.CodeAnalysis.SpellCheck.AbstractSpellCheckCodeFixProvider`1+SpellCheckCodeAction", "Spell Check" }, + { "Microsoft.CodeAnalysis.SpellCheck.AbstractSpellCheckCodeFixProvider`1+MyCodeAction", "Spell Check" }, + { "Microsoft.CodeAnalysis.SimplifyTypeNames.AbstractSimplifyTypeNamesCodeFixProvider`1+MyCodeAction", "Simplify Type Names" }, + { "Microsoft.CodeAnalysis.SimplifyThisOrMe.AbstractSimplifyThisOrMeCodeFixProvider`1+MyCodeAction", "Simplify This Or Me" }, + { "Microsoft.CodeAnalysis.ReplacePropertyWithMethods.ReplacePropertyWithMethodsCodeRefactoringProvider+ReplacePropertyWithMethodsCodeAction", "Replace Property With Methods" }, + { "Microsoft.CodeAnalysis.ReplaceMethodWithProperty.ReplaceMethodWithPropertyCodeRefactoringProvider+ReplaceMethodWithPropertyCodeAction", "Replace Method With Property" }, + { "Microsoft.CodeAnalysis.ReplaceDocCommentTextWithTag.AbstractReplaceDocCommentTextWithTagCodeRefactoringProvider+MyCodeAction", "Replace Doc Comment Text With Tag" }, + { "Microsoft.CodeAnalysis.RemoveUnusedVariable.AbstractRemoveUnusedVariableCodeFixProvider`3+MyCodeAction", "Remove Unused Variable" }, + { "Microsoft.CodeAnalysis.RemoveAsyncModifier.AbstractRemoveAsyncModifierCodeFixProvider`2+MyCodeAction", "Remove Async Modifier" }, + { "Microsoft.CodeAnalysis.PreferFrameworkType.PreferFrameworkTypeCodeFixProvider+PreferFrameworkTypeCodeAction", "Prefer Framework Type" }, + { "Microsoft.CodeAnalysis.NameTupleElement.AbstractNameTupleElementCodeRefactoringProvider`2+MyCodeAction", "Name Tuple Element" }, + { "Microsoft.CodeAnalysis.MoveToNamespace.AbstractMoveToNamespaceCodeAction+MoveItemsToNamespaceCodeAction", "Move Items To Namespace" }, + { "Microsoft.CodeAnalysis.MoveToNamespace.AbstractMoveToNamespaceCodeAction+MoveTypeToNamespaceCodeAction", "Move Type To Namespace" }, + { "Microsoft.CodeAnalysis.MoveDeclarationNearReference.AbstractMoveDeclarationNearReferenceCodeRefactoringProvider`1+MyCodeAction", "Move Declaration Near Reference" }, + { "Microsoft.CodeAnalysis.MakeMethodSynchronous.AbstractMakeMethodSynchronousCodeFixProvider+MyCodeAction", "Make Method Synchronous" }, + { "Microsoft.CodeAnalysis.MakeMethodAsynchronous.AbstractMakeMethodAsynchronousCodeFixProvider+MyCodeAction", "Make Method Asynchronous" }, + { "Microsoft.CodeAnalysis.MakeMemberStatic.AbstractMakeMemberStaticCodeFixProvider+MyCodeAction", "Make Member Static" }, + { "Microsoft.CodeAnalysis.MakeTypeAbstract.AbstractMakeTypeAbstractCodeFixProvider`1+MyCodeAction", "Make Type Abstract" }, + { "Microsoft.CodeAnalysis.InvertLogical.AbstractInvertLogicalCodeRefactoringProvider`3+MyCodeAction", "Invert Logical" }, + { "Microsoft.CodeAnalysis.InvertIf.AbstractInvertIfCodeRefactoringProvider`3+MyCodeAction", "Invert If" }, + { "Microsoft.CodeAnalysis.InvertConditional.AbstractInvertConditionalCodeRefactoringProvider`1+MyCodeAction", "Invert Conditional (Refactoring)" }, + { "Microsoft.CodeAnalysis.IntroduceVariable.AbstractIntroduceVariableService`6+IntroduceVariableCodeAction", "Introduce Variable" }, + { "Microsoft.CodeAnalysis.IntroduceVariable.AbstractIntroduceVariableService`6+IntroduceVariableAllOccurrenceCodeAction", "Introduce Variable All Occurrence" }, + { "Microsoft.CodeAnalysis.IntroduceVariable.AbstractIntroduceLocalForExpressionCodeRefactoringProvider`4+MyCodeAction", "Introduce Variable For Expression" }, + { "Microsoft.CodeAnalysis.IntroduceUsingStatement.AbstractIntroduceUsingStatementCodeRefactoringProvider`2+MyCodeAction", "Introduce Using Statement" }, + { "Microsoft.CodeAnalysis.InlineMethod.AbstractInlineMethodRefactoringProvider`4+MySolutionChangeAction", "Inline Method (Refactoring)" }, + { "Microsoft.CodeAnalysis.InitializeParameter.AbstractInitializeParameterCodeRefactoringProvider`4+MyCodeAction", "Initialize Parameter" }, + { "Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceCodeAction", "Implement Interface" }, + { "Microsoft.CodeAnalysis.ImplementInterface.AbstractImplementInterfaceService+ImplementInterfaceWithDisposePatternCodeAction", "Implement Interface With Dispose Pattern" }, + { "Microsoft.CodeAnalysis.ImplementAbstractClass.AbstractImplementAbstractClassCodeFixProvider`1+MyCodeAction", "Implement Abstract Class" }, + { "Microsoft.CodeAnalysis.GenerateType.AbstractGenerateTypeService`6+GenerateTypeCodeAction", "Generate Type" }, + { "Microsoft.CodeAnalysis.GenerateType.AbstractGenerateTypeService`6+GenerateTypeCodeActionWithOption", "Generate Type With Option" }, + { "Microsoft.CodeAnalysis.GenerateType.AbstractGenerateTypeService`6+MyCodeAction", "Generate Type" }, + { "Microsoft.CodeAnalysis.GenerateOverrides.GenerateOverridesCodeRefactoringProvider+GenerateOverridesWithDialogCodeAction", "Generate Overrides With Dialog" }, + { "Microsoft.CodeAnalysis.GenerateMember.GenerateVariable.AbstractGenerateVariableService`3+GenerateVariableCodeAction", "Generate Variable" }, + { "Microsoft.CodeAnalysis.GenerateMember.GenerateVariable.AbstractGenerateVariableService`3+MyCodeAction", "Generate Variable" }, + { "Microsoft.CodeAnalysis.GenerateMember.GenerateVariable.AbstractGenerateVariableService`3+GenerateLocalCodeAction", "Generate Local" }, + { "Microsoft.CodeAnalysis.GenerateMember.GenerateVariable.AbstractGenerateVariableService`3+GenerateParameterCodeAction", "Generate Parameter" }, + { "Microsoft.CodeAnalysis.GenerateMember.GenerateParameterizedMember.AbstractGenerateParameterizedMemberService`4+GenerateParameterizedMemberCodeAction", "Generate Parameterized Member" }, + { "Microsoft.CodeAnalysis.GenerateMember.GenerateEnumMember.AbstractGenerateEnumMemberService`3+GenerateEnumMemberCodeAction", "Generate Enum Member" }, + { "Microsoft.CodeAnalysis.GenerateMember.GenerateDefaultConstructors.AbstractGenerateDefaultConstructorsService`1+GenerateDefaultConstructorCodeAction", "Generate Default Constructors" }, + { "Microsoft.CodeAnalysis.GenerateMember.GenerateDefaultConstructors.AbstractGenerateDefaultConstructorsService`1+CodeActionAll", "Generate Default Constructors All" }, + { "Microsoft.CodeAnalysis.GenerateMember.GenerateConstructor.AbstractGenerateConstructorService`2+MyCodeAction", "Generate Constructor" }, + { "Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers.GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider+GenerateEqualsAndGetHashCodeAction", "Generate Equals And Get Hash Code From Members" }, + { "Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers.GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider+GenerateEqualsAndGetHashCodeWithDialogCodeAction", "Generate Equals And Get Hash Code From Members With Dialog" }, + { "Microsoft.CodeAnalysis.GenerateConstructorFromMembers.AbstractGenerateConstructorFromMembersCodeRefactoringProvider+ConstructorDelegatingCodeAction", "Generate Constructor From Members (Constructor Delegating)" }, + { "Microsoft.CodeAnalysis.GenerateConstructorFromMembers.AbstractGenerateConstructorFromMembersCodeRefactoringProvider+FieldDelegatingCodeAction", "Generate Constructor From Members (Field Delegating)" }, + { "Microsoft.CodeAnalysis.GenerateConstructorFromMembers.AbstractGenerateConstructorFromMembersCodeRefactoringProvider+GenerateConstructorWithDialogCodeAction", "Generate Constructor From Members With Dialog" }, + { "Microsoft.CodeAnalysis.GenerateComparisonOperators.GenerateComparisonOperatorsCodeRefactoringProvider+MyCodeAction", "Generate Comparison Operators" }, + { "Microsoft.CodeAnalysis.Formatting.FormattingCodeFixProvider+MyCodeAction", "Fix Formatting" }, + { "Microsoft.CodeAnalysis.EncapsulateField.AbstractEncapsulateFieldService+MyCodeAction", "Encapsulate Field" }, + { "Microsoft.CodeAnalysis.DiagnosticComments.CodeFixes.AbstractAddDocCommentNodesCodeFixProvider`4+MyCodeAction", "Diagnostic Comments: Add DocComment Nodes" }, + { "Microsoft.CodeAnalysis.DiagnosticComments.CodeFixes.AbstractRemoveDocCommentNodeCodeFixProvider`2+MyCodeAction", "Diagnostic Comments: Remove DocComment Node" }, + { "Microsoft.CodeAnalysis.ConvertTupleToStruct.AbstractConvertTupleToStructCodeRefactoringProvider`10+MyCodeAction", "Convert Tuple To Struct" }, + { "Microsoft.CodeAnalysis.ConvertToInterpolatedString.AbstractConvertConcatenationToInterpolatedStringRefactoringProvider`1+MyCodeAction", "Convert Concatenation To Interpolated String" }, + { "Microsoft.CodeAnalysis.ConvertToInterpolatedString.AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider`5+ConvertToInterpolatedStringCodeAction", "Convert Placeholder To Interpolated String" }, + { "Microsoft.CodeAnalysis.ConvertToInterpolatedString.ConvertRegularStringToInterpolatedStringRefactoringProvider+MyCodeAction", "Convert Regular String To Interpolated String" }, + { "Microsoft.CodeAnalysis.ConvertNumericLiteral.AbstractConvertNumericLiteralCodeRefactoringProvider`1+MyCodeAction", "Convert Numeric Literal" }, + { "Microsoft.CodeAnalysis.ConvertLinq.AbstractConvertLinqQueryToForEachProvider`2+MyCodeAction", "Convert Linq Query To ForEach" }, + { "Microsoft.CodeAnalysis.ConvertLinq.ConvertForEachToLinqQuery.AbstractConvertForEachToLinqQueryProvider`2+ForEachToLinqQueryCodeAction", "Convert ForEach To Linq Query" }, + { "Microsoft.CodeAnalysis.ConvertIfToSwitch.AbstractConvertIfToSwitchCodeRefactoringProvider`4+MyCodeAction", "Convert If To Switch" }, + { "Microsoft.CodeAnalysis.ConvertForToForEach.AbstractConvertForToForEachCodeRefactoringProvider`6+MyCodeAction", "Convert For To ForEach" }, + { "Microsoft.CodeAnalysis.ConvertForEachToFor.AbstractConvertForEachToForCodeRefactoringProvider`2+ForEachToForCodeAction", "Convert ForEach To For" }, + { "Microsoft.CodeAnalysis.ConvertCast.AbstractConvertCastCodeRefactoringProvider`3+MyCodeAction", "Convert Cast" }, + { "Microsoft.CodeAnalysis.ConvertAutoPropertyToFullProperty.AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider`2+ConvertAutoPropertyToFullPropertyCodeAction", "Convert AutoProperty To Full Property" }, + { "Microsoft.CodeAnalysis.ConflictMarkerResolution.AbstractResolveConflictMarkerCodeFixProvider+MyCodeAction", "Resolve Conflict Marker" }, + { "Microsoft.CodeAnalysis.CSharp.ConvertAnonymousTypeToClass.AbstractConvertAnonymousTypeToClassCodeRefactoringProvider`6+MyCodeAction", "Convert Anonymous Type To Class" }, + { "Microsoft.CodeAnalysis.AddMissingImports.AbstractAddMissingImportsRefactoringProvider+AddMissingImportsCodeAction", "Add Missing Imports (Paste)" }, + { "Microsoft.CodeAnalysis.CodeRefactorings.PullMemberUp.AbstractPullMemberUpRefactoringProvider+PullMemberUpWithDialogCodeAction", "Pull Member Up" }, + { "Microsoft.CodeAnalysis.CodeRefactorings.SyncNamespace.AbstractSyncNamespaceCodeRefactoringProvider`3+ChangeNamespaceCodeAction", "Sync Namespace: Change Namespace" }, + { "Microsoft.CodeAnalysis.CodeRefactorings.SyncNamespace.AbstractSyncNamespaceCodeRefactoringProvider`3+MoveFileCodeAction", "Sync Namespace: Move File" }, + { "Microsoft.CodeAnalysis.CodeRefactorings.MoveType.AbstractMoveTypeService`5+MoveTypeCodeAction", "Move Type" }, + { "Microsoft.CodeAnalysis.CodeRefactorings.ExtractMethod.ExtractMethodCodeRefactoringProvider+MyCodeAction", "Extract Method" }, + { "Microsoft.CodeAnalysis.CodeRefactorings.AddAwait.AbstractAddAwaitCodeRefactoringProvider`1+MyCodeAction", "Add Await" }, + { "Microsoft.CodeAnalysis.CodeFixes.NamingStyles.NamingStyleCodeFixProvider+FixNameCodeAction", "Fix Naming Style" }, + { "Microsoft.CodeAnalysis.CodeFixes.MatchFolderAndNamespace.AbstractChangeNamespaceToMatchFolderCodeFixProvider+MyCodeAction", "Change Namespace To Match Folder" }, + { "Microsoft.CodeAnalysis.CodeFixes.FullyQualify.AbstractFullyQualifyCodeFixProvider+MyCodeAction", "Fully Qualify" }, + { "Microsoft.CodeAnalysis.CodeFixes.FullyQualify.AbstractFullyQualifyCodeFixProvider+GroupingCodeAction", "Fully Qualify (Grouping)" }, + { "Microsoft.CodeAnalysis.CodeFixes.Suppression.AbstractSuppressionCodeFixProvider+GlobalSuppressMessageCodeAction", "Suppression.: Global Suppress Message" }, + { "Microsoft.CodeAnalysis.CodeFixes.Suppression.AbstractSuppressionCodeFixProvider+GlobalSuppressMessageFixAllCodeAction", "Suppression: Global Suppress Message (FixAll)" }, + { "Microsoft.CodeAnalysis.CodeFixes.Suppression.AbstractSuppressionCodeFixProvider+LocalSuppressMessageCodeAction", "Suppression: Local Suppress Message" }, + { "Microsoft.CodeAnalysis.CodeFixes.Suppression.AbstractSuppressionCodeFixProvider+PragmaWarningCodeAction", "Suppression: Pragma Warning" }, + { "Microsoft.CodeAnalysis.CodeFixes.Configuration.ConfigureSeverity.ConfigureSeverityLevelCodeFixProvider+TopLevelBulkConfigureSeverityCodeAction", "Configure Severity: TopLevel Bulk Configure Severity" }, + { "Microsoft.CodeAnalysis.CodeFixes.Configuration.ConfigureSeverity.ConfigureSeverityLevelCodeFixProvider+TopLevelConfigureSeverityCodeAction", "Configure Severity: TopLevel Configure Severity" }, + { "Microsoft.CodeAnalysis.CodeFixes.Configuration.ConfigureCodeStyle.ConfigureCodeStyleOptionCodeFixProvider+TopLevelConfigureCodeStyleOptionCodeAction", "Configure CodeStyle Option: TopLevel Configure CodeStyle Option" }, + { "Microsoft.CodeAnalysis.CodeFixes.Async.AbstractConvertToAsyncCodeFixProvider+MyCodeAction", "Convert To Async" }, + { "Microsoft.CodeAnalysis.CodeFixes.AddExplicitCast.AbstractAddExplicitCastCodeFixProvider`1+MyCodeAction", "Add Explicit Cast" }, + { "Microsoft.CodeAnalysis.AliasAmbiguousType.AbstractAliasAmbiguousTypeCodeFixProvider+MyCodeAction", "Alias Ambiguous Type" }, + { "Microsoft.CodeAnalysis.AddParameter.AbstractAddParameterCodeFixProvider`6+MyCodeAction", "Add Parameter" }, + { "Microsoft.CodeAnalysis.AddObsoleteAttribute.AbstractAddObsoleteAttributeCodeFixProvider+MyCodeAction", "Add Obsolete Attribute" }, + { "Microsoft.CodeAnalysis.AddImport.AbstractAddImportFeatureService`1+AssemblyReferenceCodeAction", "AddImport (Assembly Reference)" }, + { "Microsoft.CodeAnalysis.AddImport.AbstractAddImportFeatureService`1+InstallPackageAndAddImportCodeAction", "AddImport (Install Package And Add Import)" }, + { "Microsoft.CodeAnalysis.AddImport.AbstractAddImportFeatureService`1+InstallWithPackageManagerCodeAction", "AddImport (Install With PackageManager)" }, + { "Microsoft.CodeAnalysis.AddImport.AbstractAddImportFeatureService`1+MetadataSymbolReferenceCodeAction", "AddImport (Metadata Symbol Reference)" }, + { "Microsoft.CodeAnalysis.AddImport.AbstractAddImportFeatureService`1+ParentInstallPackageCodeAction", "Add Import (Install Nuget Package)" }, + { "Microsoft.CodeAnalysis.AddImport.AbstractAddImportFeatureService`1+ProjectSymbolReferenceCodeAction", "Add Import (Project Symbol Reference)" }, + { "Microsoft.CodeAnalysis.AddFileBanner.AbstractAddFileBannerCodeRefactoringProvider+MyCodeAction", "Add File Banner" }, + { "Microsoft.CodeAnalysis.AddDebuggerDisplay.AbstractAddDebuggerDisplayCodeRefactoringProvider`2+MyCodeAction", "Add Debugger Display" }, + { "Microsoft.CodeAnalysis.AddConstructorParametersFromMembers.AddConstructorParametersFromMembersCodeRefactoringProvider+AddConstructorParametersCodeAction", "Add Constructor Parameters From Members" }, + { "Microsoft.CodeAnalysis.AddAnonymousTypeMemberName.AbstractAddAnonymousTypeMemberNameCodeFixProvider`3+MyCodeAction", "Add Anonymous Type Member Name" }, + { "Microsoft.CodeAnalysis.CodeFixes.Suppression.AbstractSuppressionCodeFixProvider+GlobalSuppressMessageFixAllCodeAction+GlobalSuppressionSolutionChangeAction", "Suppression: Global Suppress Message (FixAll)" }, + { "Microsoft.CodeAnalysis.CodeFixes.Suppression.AbstractSuppressionCodeFixProvider+RemoveSuppressionCodeAction+AttributeRemoveAction", "Suppression: Remove Suppression (Attribute)" }, + { "Microsoft.CodeAnalysis.CodeFixes.Suppression.AbstractSuppressionCodeFixProvider+RemoveSuppressionCodeAction+PragmaRemoveAction", "Suppression: Remove Suppression (Pragma)" }, + { "Microsoft.CodeAnalysis.VisualBasic.CodeActions.RemoveStatementCodeAction", "Remove Statement" }, + { "Microsoft.CodeAnalysis.VisualBasic.CodeFixes.CorrectNextControlVariable.CorrectNextControlVariableCodeFixProvider+CorrectNextControlVariableCodeAction", "Correct Next Control Variable" }, + { "Microsoft.CodeAnalysis.VisualBasic.CodeFixes.GenerateEndConstruct.GenerateEndConstructCodeFixProvider+MyCodeAction", "Generate End Construct" }, + { "Microsoft.CodeAnalysis.VisualBasic.CodeFixes.GenerateEvent.GenerateEventCodeFixProvider+GenerateEventCodeAction", "Generate Event" }, + { "Microsoft.CodeAnalysis.VisualBasic.CodeFixes.IncorrectExitContinue.IncorrectExitContinueCodeFixProvider+AddKeywordCodeAction", "Incorrect Exit Continue: Add Keyword" }, + { "Microsoft.CodeAnalysis.VisualBasic.CodeFixes.IncorrectExitContinue.IncorrectExitContinueCodeFixProvider+ReplaceKeywordCodeAction", "Incorrect Exit Continue: Replace Keyword" }, + { "Microsoft.CodeAnalysis.VisualBasic.CodeFixes.IncorrectExitContinue.IncorrectExitContinueCodeFixProvider+ReplaceTokenKeywordCodeAction", "Incorrect Exit Continue: Replace Token Keyword" }, + { "Microsoft.CodeAnalysis.VisualBasic.CodeFixes.IncorrectFunctionReturnType.IncorrectFunctionReturnTypeCodeFixProvider+MyCodeAction", "Incorrect Function Return Type" }, + { "Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Iterator.VisualBasicChangeToYieldCodeFixProvider+MyCodeAction", "Change To Yield" }, + { "Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Iterator.VisualBasicConvertToIteratorCodeFixProvider+MyCodeAction", "Convert To Iterator" }, + { "Microsoft.CodeAnalysis.VisualBasic.CodeFixes.MoveToTopOfFile.MoveToTopOfFileCodeFixProvider+MoveToLineCodeAction", "Move To Top Of File" }, + { "Microsoft.CodeAnalysis.VisualBasic.CodeFixes.OverloadBase.OverloadBaseCodeFixProvider+AddKeywordAction", "Overload Base: Add Keyword" }, + { "Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.InlineTemporary.VisualBasicInlineTemporaryCodeRefactoringProvider+MyCodeAction", "Inline Temporary" }, + { "Microsoft.CodeAnalysis.VisualBasic.RemoveSharedFromModuleMembers.VisualBasicRemoveSharedFromModuleMembersCodeFixProvider+MyCodeAction", "Remove Shared From Module Members" }, + { "Microsoft.CodeAnalysis.VisualBasic.RemoveUnnecessaryByVal.VisualBasicRemoveUnnecessaryByValCodeFixProvider+MyCodeAction", "Remove Unnecessary ByVal" }, + { "Microsoft.CodeAnalysis.VisualBasic.RemoveUnnecessaryCast.VisualBasicRemoveUnnecessaryCastCodeFixProvider+MyCodeAction", "Remove Unnecessary Cast" }, + { "Microsoft.CodeAnalysis.VisualBasic.UseIsNotExpression.VisualBasicUseIsNotExpressionCodeFixProvider+MyCodeAction", "Use IsNot Expression" }, + { "Microsoft.CodeAnalysis.CSharp.UseTupleSwap.CSharpUseTupleSwapCodeFixProvider+MyCodeAction", "Use Tuple Swap" }, + { "Microsoft.CodeAnalysis.CSharp.UseIsNullCheck.CSharpUseNullCheckOverTypeCheckCodeFixProvider+MyCodeAction", "Use Null Check Over Type Check" }, + { "Microsoft.CodeAnalysis.CSharp.SimplifyPropertyPattern.CSharpSimplifyPropertyPatternCodeFixProvider+MyCodeAction", "Simplify Property Pattern" }, + { "Microsoft.CodeAnalysis.CSharp.ConvertNamespace.ConvertNamespaceCodeRefactoringProvider+MyCodeAction", "Convert Namespace Refactoring (FileScope/BlockScope)" }, + { "Microsoft.CodeAnalysis.CSharp.ConvertNamespace.ConvertNamespaceCodeFixProvider+MyCodeAction", "Convert Namespace CodeFix (FileScope/BlockScope)" }, + { "Microsoft.CodeAnalysis.CSharp.CodeRefactorings.UseRecursivePatterns.UseRecursivePatternsCodeRefactoringProvider+MyCodeAction", "Use Recursive Patterns" }, + { "Microsoft.CodeAnalysis.MoveStaticMembers.MoveStaticMembersWithDialogCodeAction", "Move Static Members" }, + { "Microsoft.CodeAnalysis.SimplifyInterpolation.AbstractSimplifyInterpolationCodeFixProvider`5+MyCodeAction", "Simplify Interpolation" }, + { "Microsoft.CodeAnalysis.IntroduceVariable.AbstractIntroduceParameterService`4+MyCodeAction", "Introduce Parameter" }, + { "Microsoft.CodeAnalysis.GenerateDefaultConstructors.AbstractGenerateDefaultConstructorsService`1+GenerateDefaultConstructorCodeAction", "Generate Default Constructor" }, + { "Microsoft.CodeAnalysis.GenerateDefaultConstructors.AbstractGenerateDefaultConstructorsService`1+CodeActionAll", "Generate Default Constructur (All)" }, + { "Microsoft.CodeAnalysis.ConvertToInterpolatedString.AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider`6+ConvertToInterpolatedStringCodeAction", "Convert Placeholder To Interpolated String" }, + { "Microsoft.CodeAnalysis.ConvertAnonymousType.AbstractConvertAnonymousTypeToClassCodeRefactoringProvider`6+MyCodeAction", "Convert Anonymous Type To Class" }, + { "Microsoft.CodeAnalysis.ConvertAnonymousType.AbstractConvertAnonymousTypeToTupleCodeRefactoringProvider`3+MyCodeAction", "Convert Anonymous Type To Tuple" }, + { "Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation.VisualBasicSimplifyObjectCreationCodeFixProvider+MyCodeAction", "Simplify Object Creation" }, + { "Microsoft.CodeAnalysis.Editor.Implementation.RenameTracking.RenameTrackingTaggerProvider+RenameTrackingCodeAction", "Rename Tracking" }, + { "Microsoft.CodeAnalysis.CSharp.CodeFixes.AddInheritdoc.AddInheritdocCodeFixProvider+MyCodeAction", "Add Inheritdoc" }, + }.ToImmutableDictionary(); public static void Main(string[] args) { @@ -79,43 +344,67 @@ internal static ImmutableArray GetCodeActionTypes(IEnumerable as internal static ImmutableArray GetTelemetryInfos(ImmutableArray codeActionTypes) { - return codeActionTypes.Select(GetTelemetryInfo) + return codeActionTypes + .Distinct(FullNameTypeComparer.Instance) + .Select(GetTelemetryInfo) .ToImmutableArray(); static TelemetryInfo GetTelemetryInfo(Type type) { - // Generate dev16 telemetry hash - Requires running on 32-bit Full Framework - type = GetTypeForTelemetry(type); - var stringHash = type.FullName.GetHashCode().ToString("X"); - // Generate dev17 telemetry hash var telemetryId = type.GetTelemetryId().ToString(); var fnvHash = telemetryId.Substring(19); - return Tuple.Create(type.FullName, stringHash, fnvHash); - } - - static Type GetTypeForTelemetry(Type type) - { - return type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : type; + return Tuple.Create(type.FullName!, fnvHash); } } internal static string GenerateKustoDatatable(ImmutableArray telemetryInfos) { + var missingDescriptions = new List(); + var table = new StringBuilder(); - table.AppendLine("let actions = datatable(ActionName: string, StringHash: string, FnvHash: string)"); + table.AppendLine("let actions = datatable(Description: string, ActionName: string, FnvHash: string)"); table.AppendLine("["); - foreach (var (actionTypeName, stringHash, fnvHash) in telemetryInfos) + foreach (var (actionTypeName, fnvHash) in telemetryInfos) { - table.AppendLine(@$" ""{actionTypeName}"", ""{stringHash}"", ""{fnvHash}"","); + if (IgnoredCodeActions.Contains(actionTypeName)) + { + continue; + } + + if (!CodeActionDescriptionMap.TryGetValue(actionTypeName, out var description)) + { + description = "**MISSING**"; + missingDescriptions.Add(actionTypeName); + } + + table.AppendLine(@$" ""{description}"", ""{actionTypeName}"", ""{fnvHash}"","); } table.Append("];"); + if (missingDescriptions.Count > 0) + { + Console.WriteLine($"Descriptions were missing for the following type names:{Environment.NewLine}{string.Join(Environment.NewLine, missingDescriptions)}"); + } + return table.ToString(); } + + private class FullNameTypeComparer : IEqualityComparer + { + public static FullNameTypeComparer Instance { get; } = new FullNameTypeComparer(); + + public bool Equals(Type? x, Type? y) + => Equals(x?.FullName, y?.FullName); + + public int GetHashCode([DisallowNull] Type obj) + { + return obj.FullName!.GetHashCode(); + } + } } } diff --git a/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs b/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs index a0beac8371f15..15087e29c0cc7 100644 --- a/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs +++ b/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs @@ -135,9 +135,7 @@ private bool CheckCombined(TextWriter textWriter, IEnumerable pack var list = new List(); foreach (var asset in packageAssets) { - var folder = asset.IsDesktop - ? @"net472" - : @"netcoreapp3.1"; + var folder = asset.IsDesktop ? "net472" : "net6.0"; var fileRelativeName = Path.Combine(folder, asset.FileRelativeName); list.Add(fileRelativeName); } @@ -244,9 +242,9 @@ private bool GetPackageAssets(TextWriter textWriter, List packageA textWriter, isDesktop: false, coreClrAssets, - $@"csc\{Configuration}\netcoreapp3.1\publish", - $@"vbc\{Configuration}\netcoreapp3.1\publish", - $@"VBCSCompiler\{Configuration}\netcoreapp3.1\publish"); + $@"csc\{Configuration}\net6.0\publish", + $@"vbc\{Configuration}\net6.0\publish", + $@"VBCSCompiler\{Configuration}\net6.0\publish"); // The native DLLs ship inside the runtime specific directories but build deploys it at the // root as well. That copy is unnecessary. @@ -262,7 +260,7 @@ private bool GetPackageAssets(TextWriter textWriter, List packageA textWriter, isDesktop: false, coreClrAssets, - $@"Microsoft.Build.Tasks.CodeAnalysis\{Configuration}\netcoreapp3.1\publish"); + $@"Microsoft.Build.Tasks.CodeAnalysis\{Configuration}\net6.0\publish"); packageAssets.AddRange(desktopAssets); packageAssets.AddRange(coreClrAssets); diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs index 01113e6b9465f..94645d0814722 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs @@ -53,24 +53,21 @@ public async Task TryNavigateToItemAsync( Document document, NavigationBarItem item, ITextView view, ITextVersion textVersion, CancellationToken cancellationToken) { // The logic here was ported from FSharp's implementation. The main reason was to avoid shimming INotificationService. - var navigationSpan = item.TryGetNavigationSpan(textVersion); - if (navigationSpan != null) - { - var span = navigationSpan.Value; - var workspace = document.Project.Solution.Workspace; - var navigationService = workspace.Services.GetRequiredService(); + // Spans.First() is safe here as we filtered down to only items that have spans in ConvertItems. + var span = item.GetCurrentItemSpan(textVersion, item.Spans.First()); + var workspace = document.Project.Solution.Workspace; + var navigationService = workspace.Services.GetRequiredService(); - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - if (navigationService.CanNavigateToPosition(workspace, document.Id, span.Start, virtualSpace: 0, cancellationToken)) - { - navigationService.TryNavigateToPosition(workspace, document.Id, span.Start, virtualSpace: 0, options: null, cancellationToken); - } - else - { - var notificationService = workspace.Services.GetRequiredService(); - notificationService.SendNotification(EditorFeaturesResources.The_definition_of_the_object_is_hidden, severity: NotificationSeverity.Error); - } + if (navigationService.CanNavigateToPosition(workspace, document.Id, span.Start, virtualSpace: 0, cancellationToken)) + { + navigationService.TryNavigateToPosition(workspace, document.Id, span.Start, virtualSpace: 0, options: null, cancellationToken); + } + else + { + var notificationService = workspace.Services.GetRequiredService(); + notificationService.SendNotification(EditorFeaturesResources.The_definition_of_the_object_is_hidden, severity: NotificationSeverity.Error); } return true; @@ -89,7 +86,6 @@ private static NavigationBarItem ConvertToNavigationBarItem(FSharpNavigationBarI item.Text, FSharpGlyphHelpers.ConvertTo(item.Glyph), spans, - spans.First(), ConvertItems(item.ChildItems, textVersion), item.Indent, item.Bolded, diff --git a/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs b/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs index f2bc507f09ba2..9853ba2c80c88 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs @@ -30,28 +30,52 @@ public FSharpNavigateToSearchService(IFSharpNavigateToSearchService service) public bool CanFilter => _service.CanFilter; - public async Task SearchDocumentAsync( - Document document, string searchPattern, IImmutableSet kinds, + public async Task SearchDocumentAsync( + Document document, + string searchPattern, + IImmutableSet kinds, Func onResultFound, - bool isFullyLoaded, CancellationToken cancellationToken) + 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); - - return NavigateToSearchLocation.Latest; } - public async Task SearchProjectAsync( - Project project, ImmutableArray priorityDocuments, string searchPattern, - IImmutableSet kinds, Func onResultFound, - bool isFullyLoaded, CancellationToken cancellationToken) + public async Task SearchProjectAsync( + Project project, + ImmutableArray priorityDocuments, + string searchPattern, + IImmutableSet kinds, + Func onResultFound, + CancellationToken cancellationToken) { var results = await _service.SearchProjectAsync(project, priorityDocuments, searchPattern, kinds, cancellationToken).ConfigureAwait(false); foreach (var result in results) await onResultFound(new InternalFSharpNavigateToSearchResult(result)).ConfigureAwait(false); + } - return NavigateToSearchLocation.Latest; + public Task SearchCachedDocumentsAsync( + Project project, + ImmutableArray priorityDocuments, + string searchPattern, + IImmutableSet kinds, + Func onResultFound, + CancellationToken cancellationToken) + { + // we don't support searching cached documents. + return Task.CompletedTask; + } + + public Task SearchGeneratedDocumentsAsync( + Project project, + string searchPattern, + IImmutableSet kinds, + Func onResultFound, + CancellationToken cancellationToken) + { + // we don't support searching generated documents. + return Task.CompletedTask; } } } diff --git a/src/Tools/IdeBenchmarks/IdeBenchmarks.csproj b/src/Tools/IdeBenchmarks/IdeBenchmarks.csproj index b6ab0e38e59d0..a483861471ba9 100644 --- a/src/Tools/IdeBenchmarks/IdeBenchmarks.csproj +++ b/src/Tools/IdeBenchmarks/IdeBenchmarks.csproj @@ -12,6 +12,7 @@ + @@ -28,6 +29,7 @@ + diff --git a/src/Tools/IdeBenchmarks/InheritanceMargin/BenchmarksHelpers.cs b/src/Tools/IdeBenchmarks/InheritanceMargin/BenchmarksHelpers.cs new file mode 100644 index 0000000000000..08338c2b2cd98 --- /dev/null +++ b/src/Tools/IdeBenchmarks/InheritanceMargin/BenchmarksHelpers.cs @@ -0,0 +1,37 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.InheritanceMargin; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace IdeBenchmarks.InheritanceMargin +{ + internal static class BenchmarksHelpers + { + public static async Task> GenerateInheritanceMarginItemsAsync( + Solution solution, + CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var builder); + foreach (var project in solution.Projects) + { + var languageService = project.GetRequiredLanguageService(); + foreach (var document in project.Documents) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var items = await languageService.GetInheritanceMemberItemsAsync(document, root.Span, cancellationToken).ConfigureAwait(false); + builder.AddRange(items); + } + } + + return builder.ToImmutable(); + } + } +} diff --git a/src/Tools/IdeBenchmarks/InheritanceMargin/InheritanceMarginGlyphBenchmarks.cs b/src/Tools/IdeBenchmarks/InheritanceMargin/InheritanceMarginGlyphBenchmarks.cs new file mode 100644 index 0000000000000..65227d2ded917 --- /dev/null +++ b/src/Tools/IdeBenchmarks/InheritanceMargin/InheritanceMarginGlyphBenchmarks.cs @@ -0,0 +1,226 @@ +// 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.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using BenchmarkDotNet.Attributes; +using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin; +using Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; +using Moq; + +namespace IdeBenchmarks.InheritanceMargin +{ + [MemoryDiagnoser] + public class InheritanceMarginGlyphBenchmarks + { + private const int MemberCount = 500; + private const int Iterations = 10; + private const double WidthAndHeightOfGlyph = 18; + + // The test sample file would contains a pair of implemented + implmenting members and the tag for containing interface/class + private const double HeightOfCanvas = WidthAndHeightOfGlyph * (MemberCount * 2 + 2); + + private readonly UseExportProviderAttribute _useExportProviderAttribute = new(); + private readonly IWpfTextView _mockTextView; + + private Application _wpfApp; + private Canvas _canvas; + private ImmutableArray _tags; + private IThreadingContext _threadingContext; + private IStreamingFindUsagesPresenter _streamingFindUsagesPresenter; + private ClassificationTypeMap _classificationTypeMap; + private IClassificationFormatMap _classificationFormatMap; + private IUIThreadOperationExecutor _operationExecutor; + private IAsynchronousOperationListener _listener; + + public InheritanceMarginGlyphBenchmarks() + { + _wpfApp = null!; + _threadingContext = null!; + _streamingFindUsagesPresenter = null!; + _classificationTypeMap = null!; + _classificationFormatMap = null!; + _operationExecutor = null!; + _listener = null!; + _canvas = null!; + var mockTextView = new Mock(); + mockTextView.Setup(textView => textView.ZoomLevel).Returns(100); + _mockTextView = mockTextView.Object; + } + + [GlobalSetup] + public Task SetupAsync() + { + // Note: WPF only allows one application per appDomain, and benchmark.net + // would run all this method for all the Benchmark method within the class. + return SetupWpfApplicaitonAsync(); + } + + [IterationSetup] + public Task IterationSetupAsync() + { + _useExportProviderAttribute.Before(null); + return PrepareGlyphRequiredDataAsync(CancellationToken.None); + } + + [IterationCleanup] + public void IterationCleanup() + { + _useExportProviderAttribute.After(null); + } + + [GlobalCleanup] + public void Cleanup() + { + RunOnUIThread(() => _wpfApp.Shutdown()); + } + + [Benchmark] + public void BenchmarkGlyphRefresh() + { + RunOnUIThread(() => + { + for (var i = 0; i < Iterations; i++) + { + // Add & remove glyphs from the Canvas, which simulates the real refreshing scenanrio when user is scrolling up/down. + for (var j = 0; j < _tags.Length; j++) + { + var tag = _tags[j]; + var glyph = new InheritanceMarginGlyph( + _threadingContext, + _streamingFindUsagesPresenter, + _classificationTypeMap, + _classificationFormatMap, + _operationExecutor, + tag, + _mockTextView, + _listener); + Canvas.SetTop(glyph, j * WidthAndHeightOfGlyph); + _canvas.Children.Add(glyph); + } + _canvas.Measure(new Size(WidthAndHeightOfGlyph, HeightOfCanvas)); + _canvas.Children.Clear(); + } + }); + } + + private async Task PrepareGlyphRequiredDataAsync(CancellationToken cancellationToken) + { + var testFile = CreateTestFile(); + using var workspace = TestWorkspace.CreateCSharp(testFile); + var items = await BenchmarksHelpers.GenerateInheritanceMarginItemsAsync(workspace.CurrentSolution, cancellationToken).ConfigureAwait(false); + + using var _ = Microsoft.CodeAnalysis.PooledObjects.ArrayBuilder.GetInstance(out var builder); + foreach (var grouping in items.GroupBy(i => i.LineNumber)) + { + builder.Add(new InheritanceMarginTag(workspace, grouping.Key, grouping.ToImmutableArray())); + } + + _tags = builder.ToImmutableArray(); + var exportProvider = workspace.ExportProvider; + _threadingContext = exportProvider.GetExportedValue(); + _streamingFindUsagesPresenter = exportProvider.GetExportedValue(); + _classificationTypeMap = exportProvider.GetExportedValue(); + var classificationFormatMapService = exportProvider.GetExportedValue(); + _classificationFormatMap = classificationFormatMapService.GetClassificationFormatMap("tooltip"); + _operationExecutor = exportProvider.GetExportedValue(); + var listenerProvider = exportProvider.GetExportedValue(); + _listener = listenerProvider.GetListener(FeatureAttribute.InheritanceMargin); + } + + private void RunOnUIThread(Action action) + { +#pragma warning disable VSTHRD001 // Only used for Benchmark purpose + _wpfApp.Dispatcher.Invoke(() => +#pragma warning restore VSTHRD001 + { + action?.Invoke(); + }); + } + + private Task SetupWpfApplicaitonAsync() + { + if (Application.Current == null) + { + var tcs = new TaskCompletionSource(); + var mainThread = new Thread(() => + { + _wpfApp = new Application(); + _wpfApp.MainWindow = new Window(); + _wpfApp.Startup += (sender, args) => + { + tcs.SetResult(true); + }; + + _canvas = new Canvas() + { + ClipToBounds = true, + Width = WidthAndHeightOfGlyph, + Height = HeightOfCanvas + }; + + _wpfApp.MainWindow.Content = _canvas; + _wpfApp.Run(); + }); + + mainThread.SetApartmentState(ApartmentState.STA); + mainThread.Start(); + return tcs.Task; + } + + _wpfApp = Application.Current; + return Task.CompletedTask; + } + + private static string CreateTestFile() + { + var builder = new StringBuilder(); + builder.Append(@"using System;"); + builder.Append(Environment.NewLine); + builder.Append(@"namespace TestNs +{ +"); + builder.Append(@" public interface IBar + { +"); + for (var i = 0; i < MemberCount; i++) + { + builder.Append($" int Method{i}();"); + builder.Append(Environment.NewLine); + } + + builder.Append(@" } +"); + + builder.Append(@" public class Bar : IBar + { +"); + for (var i = 0; i < MemberCount; i++) + { + builder.Append($" public int Method{i}() => 1"); + builder.Append(Environment.NewLine); + } + + builder.Append(@" } +"); + + builder.Append('}'); + return builder.ToString(); + } + } +} diff --git a/src/Tools/IdeBenchmarks/InheritanceMargin/InheritanceMarginServiceBenchmarks.cs b/src/Tools/IdeBenchmarks/InheritanceMargin/InheritanceMarginServiceBenchmarks.cs new file mode 100644 index 0000000000000..0ab9507cf4cf9 --- /dev/null +++ b/src/Tools/IdeBenchmarks/InheritanceMargin/InheritanceMarginServiceBenchmarks.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.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Microsoft.Build.Locator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.UnitTests; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.MSBuild; +using Microsoft.CodeAnalysis.Test.Utilities; + +namespace IdeBenchmarks.InheritanceMargin +{ + [MemoryDiagnoser] + public class InheritanceMarginServiceBenchmarks + { + private readonly UseExportProviderAttribute _useExportProviderAttribute = new(); + private Solution _solution; + + public InheritanceMarginServiceBenchmarks() + { + _solution = null!; + } + + [GlobalSetup] + public void Setup() + { + // QueryVisualStudioInstances returns Visual Studio installations on .NET Framework, and .NET Core SDK + // installations on .NET Core. We use the one with the most recent version. + var msBuildInstance = MSBuildLocator.QueryVisualStudioInstances().OrderByDescending(x => x.Version).First(); + MSBuildLocator.RegisterInstance(msBuildInstance); + } + + [IterationSetup] + public void IterationSetup() + { + _useExportProviderAttribute.Before(null); + + var roslynRoot = Environment.GetEnvironmentVariable(Program.RoslynRootPathEnvVariableName); + var solutionPath = Path.Combine(roslynRoot, @"src\Tools\IdeCoreBenchmarks\Assets\Microsoft.CodeAnalysis.sln"); + + if (!File.Exists(solutionPath)) + throw new ArgumentException("Couldn't find solution."); + + Console.WriteLine("Found solution."); + var assemblies = MSBuildMefHostServices.DefaultAssemblies + .AddRange(EditorTestCompositions.EditorFeatures.Assemblies) + .Distinct(); + + var hostService = MefHostServices.Create(assemblies); + var workspace = MSBuildWorkspace.Create(hostService); + _solution = workspace.OpenSolutionAsync(solutionPath).Result; + } + + [IterationCleanup] + public void IterationCleanup() + { + _useExportProviderAttribute.After(null); + } + + [Benchmark] + public async Task BenchmarkInheritanceMarginServiceAsync() + { + var items = await BenchmarksHelpers.GenerateInheritanceMarginItemsAsync( + _solution, + CancellationToken.None).ConfigureAwait(false); + Console.WriteLine($"Total {items.Length} items are generated."); + } + } +} diff --git a/src/Tools/IdeBenchmarks/Program.cs b/src/Tools/IdeBenchmarks/Program.cs index 75df1ab32fbc1..eb23699b6e503 100644 --- a/src/Tools/IdeBenchmarks/Program.cs +++ b/src/Tools/IdeBenchmarks/Program.cs @@ -4,6 +4,9 @@ #nullable disable +using System; +using System.IO; +using System.Runtime.CompilerServices; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Running; @@ -11,6 +14,15 @@ namespace IdeBenchmarks { internal class Program { + + public const string RoslynRootPathEnvVariableName = "ROSLYN_SOURCE_ROOT_PATH"; + + public static string GetRoslynRootLocation([CallerFilePath] string sourceFilePath = "") + { + //This file is located at [Roslyn]\src\Tools\IdeBenchmarks\Program.cs + return Path.Combine(Path.GetDirectoryName(sourceFilePath), @"..\..\.."); + } + private static void Main(string[] args) { #if DEBUG @@ -19,6 +31,7 @@ private static void Main(string[] args) IConfig config = null; #endif + Environment.SetEnvironmentVariable(RoslynRootPathEnvVariableName, GetRoslynRootLocation()); new BenchmarkSwitcher(typeof(Program).Assembly).Run(args, config); } } diff --git a/src/Tools/IdeCoreBenchmarks/CloudCache/IdeCoreBenchmarksCloudCacheServiceProvider.cs b/src/Tools/IdeCoreBenchmarks/CloudCache/IdeCoreBenchmarksCloudCacheServiceFactory.cs similarity index 57% rename from src/Tools/IdeCoreBenchmarks/CloudCache/IdeCoreBenchmarksCloudCacheServiceProvider.cs rename to src/Tools/IdeCoreBenchmarks/CloudCache/IdeCoreBenchmarksCloudCacheServiceFactory.cs index d380cea968a65..4c4285eefcdec 100644 --- a/src/Tools/IdeCoreBenchmarks/CloudCache/IdeCoreBenchmarksCloudCacheServiceProvider.cs +++ b/src/Tools/IdeCoreBenchmarks/CloudCache/IdeCoreBenchmarksCloudCacheServiceFactory.cs @@ -12,17 +12,17 @@ namespace CloudCache { - [ExportWorkspaceService(typeof(ICloudCacheStorageServiceFactory), ServiceLayer.Host), Shared] - internal class IdeCoreBenchmarksCloudCacheServiceProvider : ICloudCacheStorageServiceFactory + [ExportWorkspaceServiceFactory(typeof(ICloudCacheStorageService), ServiceLayer.Host), Shared] + internal class IdeCoreBenchmarksCloudCacheServiceFactory : IWorkspaceServiceFactory { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public IdeCoreBenchmarksCloudCacheServiceProvider() + public IdeCoreBenchmarksCloudCacheServiceFactory() { - Console.WriteLine($"Instantiated {nameof(IdeCoreBenchmarksCloudCacheServiceProvider)}"); + Console.WriteLine($"Instantiated {nameof(IdeCoreBenchmarksCloudCacheServiceFactory)}"); } - public AbstractPersistentStorageService Create(IPersistentStorageConfiguration configuration) - => new MockCloudCachePersistentStorageService(configuration, @"C:\github\roslyn"); + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + => new MockCloudCachePersistentStorageService(workspaceServices.GetRequiredService(), @"C:\github\roslyn"); } } diff --git a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs index 6b0ae825e3c33..e6e437e549170 100644 --- a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs +++ b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs @@ -142,7 +142,7 @@ await service.SearchProjectAsync( results.Add(r); return Task.CompletedTask; - }, isFullyLoaded: true, CancellationToken.None); + }, CancellationToken.None); return results.Count; } diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/SourceFileHandlingTests.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/SourceFileHandlingTests.cs index 7e76edaafcdf3..72934a62bcfc4 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/SourceFileHandlingTests.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/SourceFileHandlingTests.cs @@ -144,7 +144,7 @@ public async Task ReorderSourceFilesBatch_CPS() project.ReorderSourceFiles(new[] { sourceFileFullPath5, sourceFileFullPath3, sourceFileFullPath1 }); - project.EndBatch(); + await project.EndBatchAsync(); var documents = GetCurrentDocuments().ToArray(); @@ -196,7 +196,7 @@ public async Task ReorderSourceFilesBatchWithReAdding_CPS() project.ReorderSourceFiles(new[] { sourceFileFullPath5, sourceFileFullPath4, sourceFileFullPath3, sourceFileFullPath2, sourceFileFullPath1 }); - project.EndBatch(); + await project.EndBatchAsync(); var documents = GetCurrentDocuments().ToArray(); @@ -234,7 +234,7 @@ public async Task ReorderSourceFilesBatchAddAfterReorder_CPS() project.AddSourceFile(sourceFileFullPath4); project.AddSourceFile(sourceFileFullPath5); - project.EndBatch(); + await project.EndBatchAsync(); project.ReorderSourceFiles(new[] { sourceFileFullPath5, sourceFileFullPath4, sourceFileFullPath3, sourceFileFullPath2, sourceFileFullPath1 }); @@ -277,7 +277,7 @@ public async Task ReorderSourceFilesBatchRemoveAfterReorder_CPS() project.RemoveSourceFile(sourceFileFullPath4); project.RemoveSourceFile(sourceFileFullPath5); - project.EndBatch(); + await project.EndBatchAsync(); project.ReorderSourceFiles(new[] { sourceFileFullPath2, sourceFileFullPath1 }); @@ -371,7 +371,7 @@ public async Task ReorderSourceFilesBatchExceptions_CPS() Assert.Throws(() => project.ReorderSourceFiles(new List())); Assert.Throws(() => project.ReorderSourceFiles(null)); - project.EndBatch(); + await project.EndBatchAsync(); } [WpfFact] @@ -395,7 +395,7 @@ public async Task ReorderSourceFilesBatchExceptionRemoveFile_CPS() project.RemoveSourceFile(sourceFileFullPath2); Assert.Throws(() => project.ReorderSourceFiles(new[] { sourceFileFullPath2 })); - project.EndBatch(); + await project.EndBatchAsync(); var documents = GetCurrentDocuments().ToArray(); diff --git a/src/VisualStudio/Core/Def/ExternalAccess/LegacyCodeAnalysis/LegacyCodeAnalysisVisualStudioSuppressionFixServiceAccessor.cs b/src/VisualStudio/Core/Def/ExternalAccess/LegacyCodeAnalysis/LegacyCodeAnalysisVisualStudioSuppressionFixServiceAccessor.cs index 0a4038e7becb2..51835b117629c 100644 --- a/src/VisualStudio/Core/Def/ExternalAccess/LegacyCodeAnalysis/LegacyCodeAnalysisVisualStudioSuppressionFixServiceAccessor.cs +++ b/src/VisualStudio/Core/Def/ExternalAccess/LegacyCodeAnalysis/LegacyCodeAnalysisVisualStudioSuppressionFixServiceAccessor.cs @@ -4,6 +4,7 @@ using System; using System.Composition; +using Microsoft.CodeAnalysis.Telemetry; using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.ExternalAccess.LegacyCodeAnalysis.Api; using Microsoft.CodeAnalysis.Host.Mef; @@ -43,6 +44,7 @@ public bool AddSuppressions(IVsHierarchy? projectHierarchy) { errorReportingService.ShowGlobalErrorInfo( string.Format(ServicesVSResources.Error_updating_suppressions_0, ex.Message), + TelemetryFeatureName.LegacySuppressionFix, ex, new InfoBarUI( WorkspacesResources.Show_Stack_Trace, @@ -63,7 +65,8 @@ public bool AddSuppressions(bool selectedErrorListEntriesOnly, bool suppressInSo catch (Exception ex) { errorReportingService.ShowGlobalErrorInfo( - string.Format(ServicesVSResources.Error_updating_suppressions_0, ex.Message), + message: string.Format(ServicesVSResources.Error_updating_suppressions_0, ex.Message), + TelemetryFeatureName.LegacySuppressionFix, ex, new InfoBarUI( WorkspacesResources.Show_Stack_Trace, @@ -84,7 +87,8 @@ public bool RemoveSuppressions(bool selectedErrorListEntriesOnly, IVsHierarchy? catch (Exception ex) { errorReportingService.ShowGlobalErrorInfo( - string.Format(ServicesVSResources.Error_updating_suppressions_0, ex.Message), + message: string.Format(ServicesVSResources.Error_updating_suppressions_0, ex.Message), + TelemetryFeatureName.LegacySuppressionFix, ex, new InfoBarUI( WorkspacesResources.Show_Stack_Trace, diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioLogHubLoggerFactory.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioLogHubLoggerFactory.cs index 9a36df0bd76bf..b0af40c5a04ae 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioLogHubLoggerFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioLogHubLoggerFactory.cs @@ -15,7 +15,6 @@ using Microsoft.VisualStudio.RpcContracts.Logging; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Shell.ServiceBroker; -using Microsoft.VisualStudio.Threading; using StreamJsonRpc; using VSShell = Microsoft.VisualStudio.Shell; @@ -24,11 +23,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageClient [Export(typeof(ILspLoggerFactory))] internal class VisualStudioLogHubLoggerFactory : ILspLoggerFactory { - /// - /// Command line flag name for the /log parameter when launching devenv. - /// - private const string LogCommandLineFlag = "log"; - /// /// A unique, always increasing, ID we use to identify this server in our loghub logs. Needed so that if our /// server is restarted that we can have a new logstream for the new server. @@ -36,20 +30,13 @@ internal class VisualStudioLogHubLoggerFactory : ILspLoggerFactory private static int s_logHubSessionId; private readonly VSShell.IAsyncServiceProvider _asyncServiceProvider; - private readonly IThreadingContext _threadingContext; - - private readonly AsyncLazy _wasVSStartedWithLogParameterLazy; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public VisualStudioLogHubLoggerFactory( - [Import(typeof(SAsyncServiceProvider))] VSShell.IAsyncServiceProvider asyncServiceProvider, - IThreadingContext threadingContext) + [Import(typeof(SAsyncServiceProvider))] VSShell.IAsyncServiceProvider asyncServiceProvider) { _asyncServiceProvider = asyncServiceProvider; - _threadingContext = threadingContext; - - _wasVSStartedWithLogParameterLazy = new AsyncLazy(WasVSStartedWithLogParameterAsync, _threadingContext.JoinableTaskFactory); } public async Task CreateLoggerAsync(string serverTypeName, string? clientName, JsonRpc jsonRpc, CancellationToken cancellationToken) @@ -62,20 +49,10 @@ public async Task CreateLoggerAsync(string serverTypeName, string? c var configuration = await TraceConfiguration.CreateTraceConfigurationInstanceAsync(service, ownsServiceBroker: true, cancellationToken).ConfigureAwait(false); - // Register the default log level as warning to avoid creating log files in the hundreds of GB. - // This level can be overriden by setting the environment variable 'LogLevel' to the desired source level. - // See https://dev.azure.com/devdiv/DevDiv/_git/VS?path=%2Fsrc%2FPlatform%2FUtilities%2FImpl%2FLogHub%2FLocalTraceHub.cs&version=GBmain&line=142&lineEnd=143&lineStartColumn=1&lineEndColumn=1&lineStyle=plain&_a=contents - // This should be switched back to SourceLevels.Information once Loghub adds support for recyclying logs while VS is open. - // See https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1359778/ - var loggingLevel = SourceLevels.ActivityTracing | SourceLevels.Warning; - - // If VS was explicitly started with /log, then record all information logs as well. - // This is extremely useful for development so that F5 deployment automatically logs everything. - var wasVSStartedWithLogParameter = await _wasVSStartedWithLogParameterLazy.GetValueAsync(cancellationToken).ConfigureAwait(false); - if (wasVSStartedWithLogParameter) - { - loggingLevel |= SourceLevels.Information; - } + // Register the default log level as information. + // Loghub will take care of cleaning up older logs from past sessions / current session + // if it decides the log file sizes are too large. + var loggingLevel = SourceLevels.ActivityTracing | SourceLevels.Information; var logOptions = new RpcContracts.Logging.LoggerOptions(new LoggingLevelSettings(loggingLevel)); var traceSource = await configuration.RegisterLogSourceAsync(logId, logOptions, cancellationToken).ConfigureAwait(false); @@ -86,17 +63,5 @@ public async Task CreateLoggerAsync(string serverTypeName, string? c return new LogHubLspLogger(configuration, traceSource); } - - private async Task WasVSStartedWithLogParameterAsync() - { - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); - var appCommandLiveService = await _asyncServiceProvider.GetServiceAsync(typeof(SVsAppCommandLine)).ConfigureAwait(true); - if (appCommandLiveService is IVsAppCommandLine commandLine) - { - return ErrorHandler.Succeeded(commandLine.GetOption(LogCommandLineFlag, out var present, out _)) && present == 1; - } - - return false; - } } } diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.VsCodeWindowManager.cs b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.VsCodeWindowManager.cs index 1833c1e95729f..e1daa6d2474fe 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.VsCodeWindowManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageService/AbstractLanguageService`2.VsCodeWindowManager.cs @@ -29,8 +29,6 @@ internal class VsCodeWindowManager : IVsCodeWindowManager, IVsCodeWindowEvents private readonly TLanguageService _languageService; private readonly IVsCodeWindow _codeWindow; private readonly ComEventSink _sink; - private readonly IThreadingContext _threadingContext; - private readonly IAsynchronousOperationListener _asynchronousOperationListener; private readonly IGlobalOptionService _globalOptions; private IDisposable? _navigationBarController; @@ -41,36 +39,12 @@ public VsCodeWindowManager(TLanguageService languageService, IVsCodeWindow codeW _languageService = languageService; _codeWindow = codeWindow; - var componentModel = languageService.Package.ComponentModel; - _threadingContext = componentModel.GetService(); - _globalOptions = componentModel.GetService(); - - var listenerProvider = componentModel.GetService(); - _asynchronousOperationListener = listenerProvider.GetListener(FeatureAttribute.NavigationBar); + _globalOptions = languageService.Package.ComponentModel.GetService(); _sink = ComEventSink.Advise(codeWindow, this); _globalOptions.OptionChanged += GlobalOptionChanged; } - private void OnWorkspaceRegistrationChanged(object sender, System.EventArgs e) - { - var token = _asynchronousOperationListener.BeginAsyncOperation(nameof(OnWorkspaceRegistrationChanged)); - - // Fire and forget to update the navbar based on the workspace registration - // to avoid blocking the caller and possible deadlocks workspace registration changed events under lock. - UpdateWorkspaceAsync().CompletesAsyncOperation(token).Forget(); - } - - private async Task UpdateWorkspaceAsync() - { - // This event may not be triggered on the main thread, but adding and removing the navbar - // must be done from the main thread. - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); - - // Trigger a check to see if the dropdown should be added / removed now that the buffer is in a different workspace. - AddOrRemoveDropdown(); - } - private void SetupView(IVsTextView view) => _languageService.SetupNewTextView(view); @@ -133,7 +107,7 @@ private void AddOrRemoveDropdown() Contract.ThrowIfFalse(_dropdownBarClient == null, "We shouldn't have a dropdown client if there isn't a dropdown"); } - AdddropdownBar(dropdownManager); + AddDropdownBar(dropdownManager); } else { @@ -168,7 +142,7 @@ private static IVsDropdownBarClient GetDropdownBarClient(IVsDropdownBar dropdown return dropdownBarClient; } - private void AdddropdownBar(IVsDropdownBarManager dropdownManager) + private void AddDropdownBar(IVsDropdownBarManager dropdownManager) { if (ErrorHandler.Failed(_codeWindow.GetBuffer(out var buffer))) { @@ -241,6 +215,7 @@ public int OnNewView(IVsTextView view) public int RemoveAdornments() { _sink.Unadvise(); + _globalOptions.OptionChanged -= GlobalOptionChanged; if (_codeWindow is IVsDropdownBarManager dropdownManager) { diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs index 4958aa8ace46a..54763a0a89ea2 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs @@ -37,11 +37,10 @@ public async Task GetGraphAsync(Solution solution, IGraphContext c _asyncListener, callback, _searchPattern, - searchCurrentDocument: false, NavigateToUtilities.GetKindsProvided(solution), _threadingContext.DisposalToken); - await searcher.SearchAsync(cancellationToken).ConfigureAwait(false); + await searcher.SearchAsync(searchCurrentDocument: false, cancellationToken).ConfigureAwait(false); return graphBuilder; } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContext.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContext.cs index 7ad681fccd36c..a2e3092ed2e61 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContext.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CPS/IWorkspaceProjectContext.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; namespace Microsoft.VisualStudio.LanguageServices.ProjectSystem @@ -75,7 +76,9 @@ internal interface IWorkspaceProjectContext : IDisposable void SetRuleSetFile(string filePath); void StartBatch(); + [Obsolete($"Use {nameof(EndBatchAsync)}.")] void EndBatch(); + ValueTask EndBatchAsync(); void ReorderSourceFiles(IEnumerable filePaths); } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs index 6b4f678fbe34a..12ca18c5a566c 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -37,11 +38,11 @@ internal sealed class VisualStudioProject private readonly ImmutableArray> _dynamicFileInfoProviders; /// - /// A gate taken for all mutation of any mutable field in this type. + /// A semaphore taken for all mutation of any mutable field in this type. /// /// This is, for now, intentionally pessimistic. There are no doubt ways that we could allow more to run in parallel, /// but the current tradeoff is for simplicity of code and "obvious correctness" than something that is subtle, fast, and wrong. - private readonly object _gate = new(); + private readonly SemaphoreSlim _gate = new SemaphoreSlim(initialCount: 1); /// /// The number of active batch scopes. If this is zero, we are not batching, non-zero means we are batching. @@ -199,38 +200,43 @@ internal VisualStudioProject( private void ChangeProjectProperty(ref T field, T newValue, Func withNewValue, bool logThrowAwayTelemetry = false) { - lock (_gate) + using (_gate.DisposableWait()) { - // If nothing is changing, we can skip entirely - if (object.Equals(field, newValue)) - { - return; - } + ChangeProjectProperty_NoLock(ref field, newValue, withNewValue, logThrowAwayTelemetry); + } + } - field = newValue; + private void ChangeProjectProperty_NoLock(ref T field, T newValue, Func withNewValue, bool logThrowAwayTelemetry = false) + { + // If nothing is changing, we can skip entirely + if (object.Equals(field, newValue)) + { + return; + } - // Importantly, we do not await/wait on the fullyLoadedStateTask. We do not want to ever be waiting on work - // that may end up touching the UI thread (As we can deadlock if GetTagsSynchronous waits on us). Instead, - // we only check if the Task is completed. Prior to that we will assume we are still loading. Once this - // task is completed, we know that the WaitUntilFullyLoadedAsync call will have actually finished and we're - // fully loaded. - var isFullyLoadedTask = _workspaceStatusService?.IsFullyLoadedAsync(CancellationToken.None); - var isFullyLoaded = isFullyLoadedTask is { IsCompleted: true } && isFullyLoadedTask.GetAwaiter().GetResult(); + field = newValue; - // We only log telemetry during solution open - if (logThrowAwayTelemetry && _telemetryService?.HasActiveSession == true && !isFullyLoaded) - { - TryReportCompilationThrownAway(_workspace.CurrentSolution.State, Id); - } + // Importantly, we do not await/wait on the fullyLoadedStateTask. We do not want to ever be waiting on work + // that may end up touching the UI thread (As we can deadlock if GetTagsSynchronous waits on us). Instead, + // we only check if the Task is completed. Prior to that we will assume we are still loading. Once this + // task is completed, we know that the WaitUntilFullyLoadedAsync call will have actually finished and we're + // fully loaded. + var isFullyLoadedTask = _workspaceStatusService?.IsFullyLoadedAsync(CancellationToken.None); + var isFullyLoaded = isFullyLoadedTask is { IsCompleted: true } && isFullyLoadedTask.GetAwaiter().GetResult(); - if (_activeBatchScopes > 0) - { - _projectPropertyModificationsInBatch.Add(withNewValue); - } - else - { - _workspace.ApplyChangeToWorkspace(Id, withNewValue); - } + // We only log telemetry during solution open + if (logThrowAwayTelemetry && _telemetryService?.HasActiveSession == true && !isFullyLoaded) + { + TryReportCompilationThrownAway(_workspace.CurrentSolution.State, Id); + } + + if (_activeBatchScopes > 0) + { + _projectPropertyModificationsInBatch.Add(withNewValue); + } + else + { + _workspace.ApplyChangeToWorkspace(Id, withNewValue); } } @@ -268,7 +274,7 @@ private static void TryReportCompilationThrownAway(SolutionState solutionState, private void ChangeProjectOutputPath(ref string? field, string? newValue, Func withNewValue) { - lock (_gate) + using (_gate.DisposableWait()) { // Skip if nothing changing if (field == newValue) @@ -286,7 +292,7 @@ private void ChangeProjectOutputPath(ref string? field, string? newValue, Func(); var analyzerConfigDocumentsToOpen = new List<(DocumentId documentId, SourceTextContainer textContainer)>(); - _workspace.ApplyBatchChangeToWorkspace(solution => + await _workspace.ApplyBatchChangeToWorkspaceMaybeAsync(useAsync, solution => { var solutionChanges = new SolutionChangeAccumulator(startingSolution: solution); @@ -563,8 +584,8 @@ private void OnBatchScopeDisposed() // Project reference adding... solutionChanges.UpdateSolutionForProjectAction( - Id, - newSolution: solutionChanges.Solution.AddProjectReferences(Id, _projectReferencesAddedInBatch)); + Id, + newSolution: solutionChanges.Solution.AddProjectReferences(Id, _projectReferencesAddedInBatch)); ClearAndZeroCapacity(_projectReferencesAddedInBatch); // Project reference removing... @@ -604,21 +625,21 @@ private void OnBatchScopeDisposed() ClearAndZeroCapacity(_projectPropertyModificationsInBatch); return solutionChanges; - }); + }).ConfigureAwait(false); foreach (var (documentId, textContainer) in documentsToOpen) { - _workspace.ApplyChangeToWorkspace(w => w.OnDocumentOpened(documentId, textContainer)); + await _workspace.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => w.OnDocumentOpened(documentId, textContainer)).ConfigureAwait(false); } foreach (var (documentId, textContainer) in additionalDocumentsToOpen) { - _workspace.ApplyChangeToWorkspace(w => w.OnAdditionalDocumentOpened(documentId, textContainer)); + await _workspace.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => w.OnAdditionalDocumentOpened(documentId, textContainer)).ConfigureAwait(false); } foreach (var (documentId, textContainer) in analyzerConfigDocumentsToOpen) { - _workspace.ApplyChangeToWorkspace(w => w.OnAnalyzerConfigDocumentOpened(documentId, textContainer)); + await _workspace.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => w.OnAnalyzerConfigDocumentOpened(documentId, textContainer)).ConfigureAwait(false); } // Check for those files being opened to start wire-up if necessary @@ -733,7 +754,7 @@ public void AddDynamicSourceFile(string dynamicFilePath, ImmutableArray } } - lock (_gate) + using (_gate.DisposableWait()) { if (_dynamicFilePathMaps.ContainsKey(dynamicFilePath)) { @@ -757,7 +778,7 @@ public void AddDynamicSourceFile(string dynamicFilePath, ImmutableArray // If fileInfo is not null, that means we found a provider so this should be not-null as well // since we had to go through the earlier assignment. Contract.ThrowIfNull(providerForFileInfo); - _sourceFiles.AddDynamicFile(providerForFileInfo, fileInfo, folders); + _sourceFiles.AddDynamicFile_NoLock(providerForFileInfo, fileInfo, folders); } } } @@ -778,7 +799,7 @@ public void RemoveDynamicSourceFile(string dynamicFilePath) { IDynamicFileInfoProvider provider; - lock (_gate) + using (_gate.DisposableWait()) { if (!_dynamicFilePathMaps.TryGetValue(dynamicFilePath, out var sourceFilePath)) { @@ -794,7 +815,7 @@ public void RemoveDynamicSourceFile(string dynamicFilePath) return; } - provider = _sourceFiles.RemoveDynamicFile(sourceFilePath); + provider = _sourceFiles.RemoveDynamicFile_NoLock(sourceFilePath); } // provider is free-threaded. so fine to call Wait rather than JTF @@ -806,7 +827,7 @@ private void OnDynamicFileInfoUpdated(object sender, string dynamicFilePath) { string? fileInfoPath; - lock (_gate) + using (_gate.DisposableWait()) { if (!_dynamicFilePathMaps.TryGetValue(dynamicFilePath, out fileInfoPath)) { @@ -836,7 +857,7 @@ public void AddAnalyzerReference(string fullPath) Id, Language); - lock (_gate) + using (_gate.DisposableWait()) { if (_analyzerPathsToAnalyzers.ContainsKey(fullPath)) { @@ -863,7 +884,7 @@ public void RemoveAnalyzerReference(string fullPath) throw new ArgumentException("message", nameof(fullPath)); } - lock (_gate) + using (_gate.DisposableWait()) { if (!_analyzerPathsToAnalyzers.TryGetValue(fullPath, out var visualStudioAnalyzer)) { @@ -901,9 +922,9 @@ public void AddMetadataReference(string fullPath, MetadataReferenceProperties pr throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); } - lock (_gate) + using (_gate.DisposableWait()) { - if (ContainsMetadataReference(fullPath, properties)) + if (ContainsMetadataReference_NoLock(fullPath, properties)) { throw new InvalidOperationException("The metadata reference has already been added to the project."); } @@ -939,19 +960,26 @@ public void AddMetadataReference(string fullPath, MetadataReferenceProperties pr public bool ContainsMetadataReference(string fullPath, MetadataReferenceProperties properties) { - lock (_gate) + using (_gate.DisposableWait()) { - return GetPropertiesForMetadataReference(fullPath).Contains(properties); + return ContainsMetadataReference_NoLock(fullPath, properties); } } + private bool ContainsMetadataReference_NoLock(string fullPath, MetadataReferenceProperties properties) + { + Debug.Assert(_gate.CurrentCount == 0); + + return _allMetadataReferences.TryGetValue(fullPath, out var propertiesList) && propertiesList.Contains(properties); + } + /// /// Returns the properties being used for the current metadata reference added to this project. May return multiple properties if /// the reference has been added multiple times with different properties. /// public ImmutableArray GetPropertiesForMetadataReference(string fullPath) { - lock (_gate) + using (_gate.DisposableWait()) { return _allMetadataReferences.TryGetValue(fullPath, out var list) ? list : ImmutableArray.Empty; } @@ -964,9 +992,9 @@ public void RemoveMetadataReference(string fullPath, MetadataReferenceProperties throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); } - lock (_gate) + using (_gate.DisposableWait()) { - if (!ContainsMetadataReference(fullPath, properties)) + if (!ContainsMetadataReference_NoLock(fullPath, properties)) { throw new InvalidOperationException("The metadata reference does not exist in this project."); } @@ -1016,9 +1044,9 @@ public void AddProjectReference(ProjectReference projectReference) throw new ArgumentNullException(nameof(projectReference)); } - lock (_gate) + using (_gate.DisposableWait()) { - if (ContainsProjectReference(projectReference)) + if (ContainsProjectReference_NoLock(projectReference)) { throw new ArgumentException("The project reference has already been added to the project."); } @@ -1044,25 +1072,32 @@ public bool ContainsProjectReference(ProjectReference projectReference) throw new ArgumentNullException(nameof(projectReference)); } - lock (_gate) + using (_gate.DisposableWait()) { - if (_projectReferencesRemovedInBatch.Contains(projectReference)) - { - return false; - } + return ContainsProjectReference_NoLock(projectReference); + } + } - if (_projectReferencesAddedInBatch.Contains(projectReference)) - { - return true; - } + private bool ContainsProjectReference_NoLock(ProjectReference projectReference) + { + Debug.Assert(_gate.CurrentCount == 0); - return _workspace.CurrentSolution.GetRequiredProject(Id).AllProjectReferences.Contains(projectReference); + if (_projectReferencesRemovedInBatch.Contains(projectReference)) + { + return false; } + + if (_projectReferencesAddedInBatch.Contains(projectReference)) + { + return true; + } + + return _workspace.CurrentSolution.GetRequiredProject(Id).AllProjectReferences.Contains(projectReference); } public IReadOnlyList GetProjectReferences() { - lock (_gate) + using (_gate.DisposableWait()) { // If we're not batching, then this is cheap: just fetch from the workspace and we're done var projectReferencesInWorkspace = _workspace.CurrentSolution.GetRequiredProject(Id).AllProjectReferences; @@ -1088,8 +1123,13 @@ public void RemoveProjectReference(ProjectReference projectReference) throw new ArgumentNullException(nameof(projectReference)); } - lock (_gate) + using (_gate.DisposableWait()) { + if (!ContainsProjectReference_NoLock(projectReference)) + { + throw new ArgumentException("The project does not contain that project reference."); + } + if (_activeBatchScopes > 0) { if (!_projectReferencesAddedInBatch.Remove(projectReference)) @@ -1108,10 +1148,13 @@ public void RemoveProjectReference(ProjectReference projectReference) public void RemoveFromWorkspace() { - _documentFileChangeContext.Dispose(); - - lock (_gate) + using (_gate.DisposableWait()) { + if (!_workspace.CurrentSolution.ContainsProject(Id)) + { + throw new InvalidOperationException("The project has already been removed."); + } + // clear tracking to external components foreach (var provider in _eventSubscriptionTracker) { @@ -1121,6 +1164,8 @@ public void RemoveFromWorkspace() _eventSubscriptionTracker.Clear(); } + _documentFileChangeContext.Dispose(); + IReadOnlyList? remainingMetadataReferences = null; _workspace.ApplyChangeToWorkspace(w => @@ -1196,7 +1241,7 @@ private static void ClearAndZeroCapacity(ImmutableArray.Builder list) /// and additional files. /// /// This class should be free-threaded, and any synchronization is done via . - /// This class is otehrwise free to operate on private members of if needed. + /// This class is otherwise free to operate on private members of if needed. private sealed class BatchingDocumentCollection { private readonly VisualStudioProject _project; @@ -1269,7 +1314,7 @@ public DocumentId AddFile(string fullPath, SourceCodeKind sourceCodeKind, Immuta filePath: fullPath, isGenerated: false); - lock (_project._gate) + using (_project._gate.DisposableWait()) { if (_documentPathsToDocumentIds.ContainsKey(fullPath)) { @@ -1316,7 +1361,7 @@ public DocumentId AddTextContainer(SourceTextContainer textContainer, string ful designTimeOnly: designTimeOnly, documentServiceProvider: documentServiceProvider); - lock (_project._gate) + using (_project._gate.DisposableWait()) { if (_sourceTextContainersToDocumentIds.ContainsKey(textContainer)) { @@ -1343,7 +1388,7 @@ public DocumentId AddTextContainer(SourceTextContainer textContainer, string ful { _project._workspace.ApplyChangeToWorkspace(w => { - _project._workspace.AddDocumentToDocumentsNotFromFiles(documentInfo.Id); + _project._workspace.AddDocumentToDocumentsNotFromFiles_NoLock(documentInfo.Id); _documentAddAction(w, documentInfo); w.OnDocumentOpened(documentInfo.Id, textContainer); }); @@ -1353,68 +1398,66 @@ public DocumentId AddTextContainer(SourceTextContainer textContainer, string ful return documentId; } - public void AddDynamicFile(IDynamicFileInfoProvider fileInfoProvider, DynamicFileInfo fileInfo, ImmutableArray folders) + public void AddDynamicFile_NoLock(IDynamicFileInfoProvider fileInfoProvider, DynamicFileInfo fileInfo, ImmutableArray folders) { + Debug.Assert(_project._gate.CurrentCount == 0); + var documentInfo = CreateDocumentInfoFromFileInfo(fileInfo, folders.NullToEmpty()); // Generally, DocumentInfo.FilePath can be null, but we always have file paths for dynamic files. Contract.ThrowIfNull(documentInfo.FilePath); var documentId = documentInfo.Id; - lock (_project._gate) + var filePath = documentInfo.FilePath; + if (_documentPathsToDocumentIds.ContainsKey(filePath)) { - var filePath = documentInfo.FilePath; - if (_documentPathsToDocumentIds.ContainsKey(filePath)) - { - throw new ArgumentException($"'{filePath}' has already been added to this project.", nameof(filePath)); - } + throw new ArgumentException($"'{filePath}' has already been added to this project.", nameof(filePath)); + } - // If we have an ordered document ids batch, we need to add the document id to the end of it as well. - _orderedDocumentsInBatch = _orderedDocumentsInBatch?.Add(documentId); + // If we have an ordered document ids batch, we need to add the document id to the end of it as well. + _orderedDocumentsInBatch = _orderedDocumentsInBatch?.Add(documentId); - _documentPathsToDocumentIds.Add(filePath, documentId); + _documentPathsToDocumentIds.Add(filePath, documentId); - _documentIdToDynamicFileInfoProvider.Add(documentId, fileInfoProvider); + _documentIdToDynamicFileInfoProvider.Add(documentId, fileInfoProvider); - if (_project._eventSubscriptionTracker.Add(fileInfoProvider)) - { - // subscribe to the event when we use this provider the first time - fileInfoProvider.Updated += _project.OnDynamicFileInfoUpdated; - } + if (_project._eventSubscriptionTracker.Add(fileInfoProvider)) + { + // subscribe to the event when we use this provider the first time + fileInfoProvider.Updated += _project.OnDynamicFileInfoUpdated; + } - if (_project._activeBatchScopes > 0) - { - _documentsAddedInBatch.Add(documentInfo); - } - else - { - // right now, assumption is dynamically generated file can never be opened in editor - _project._workspace.ApplyChangeToWorkspace(w => _documentAddAction(w, documentInfo)); - } + if (_project._activeBatchScopes > 0) + { + _documentsAddedInBatch.Add(documentInfo); + } + else + { + // right now, assumption is dynamically generated file can never be opened in editor + _project._workspace.ApplyChangeToWorkspace(w => _documentAddAction(w, documentInfo)); } } - public IDynamicFileInfoProvider RemoveDynamicFile(string fullPath) + public IDynamicFileInfoProvider RemoveDynamicFile_NoLock(string fullPath) { + Debug.Assert(_project._gate.CurrentCount == 0); + if (string.IsNullOrEmpty(fullPath)) { throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); } - lock (_project._gate) + if (!_documentPathsToDocumentIds.TryGetValue(fullPath, out var documentId) || + !_documentIdToDynamicFileInfoProvider.TryGetValue(documentId, out var fileInfoProvider)) { - if (!_documentPathsToDocumentIds.TryGetValue(fullPath, out var documentId) || - !_documentIdToDynamicFileInfoProvider.TryGetValue(documentId, out var fileInfoProvider)) - { - throw new ArgumentException($"'{fullPath}' is not a dynamic file of this project."); - } + throw new ArgumentException($"'{fullPath}' is not a dynamic file of this project."); + } - _documentIdToDynamicFileInfoProvider.Remove(documentId); + _documentIdToDynamicFileInfoProvider.Remove(documentId); - RemoveFileInternal(documentId, fullPath); + RemoveFileInternal(documentId, fullPath); - return fileInfoProvider; - } + return fileInfoProvider; } public void RemoveFile(string fullPath) @@ -1424,7 +1467,7 @@ public void RemoveFile(string fullPath) throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); } - lock (_project._gate) + using (_project._gate.DisposableWait()) { if (!_documentPathsToDocumentIds.TryGetValue(fullPath, out var documentId)) { @@ -1479,7 +1522,7 @@ public void RemoveTextContainer(SourceTextContainer textContainer) throw new ArgumentNullException(nameof(textContainer)); } - lock (_project._gate) + using (_project._gate.DisposableWait()) { if (!_sourceTextContainersToDocumentIds.TryGetValue(textContainer, out var documentId)) { @@ -1516,7 +1559,7 @@ public void RemoveTextContainer(SourceTextContainer textContainer) // TODO: Can't we just remove the document without closing it? w.OnDocumentClosed(documentId, new SourceTextLoader(textContainer, filePath: null)); _documentRemoveAction(w, documentId); - _project._workspace.RemoveDocumentToDocumentsNotFromFiles(documentId); + _project._workspace.RemoveDocumentToDocumentsNotFromFiles_NoLock(documentId); }); } } @@ -1541,7 +1584,7 @@ public bool ContainsFile(string fullPath) throw new ArgumentException($"{nameof(fullPath)} isn't a valid path.", nameof(fullPath)); } - lock (_project._gate) + using (_project._gate.DisposableWait()) { return _documentPathsToDocumentIds.ContainsKey(fullPath); } @@ -1557,15 +1600,21 @@ public void ProcessFileChange(string filePath) /// filepath used in workspace. it might be different than projectSystemFilePath. ex) dynamic file public void ProcessFileChange(string projectSystemFilePath, string workspaceFilePath) { - lock (_project._gate) + using (_project._gate.DisposableWait()) { + // If our project has already been removed, this is a stale notification, and we can disregard. + if (!_project._workspace.CurrentSolution.ContainsProject(_project.Id)) + { + return; + } + if (_documentPathsToDocumentIds.TryGetValue(workspaceFilePath, out var documentId)) { // We create file watching prior to pushing the file to the workspace in batching, so it's // possible we might see a file change notification early. In this case, toss it out. Since // all adds/removals of documents for this project happen under our lock, it's safe to do this - // check without taking the main workspace lock - + // check without taking the main workspace lock. We don't have to check for documents removed in + // the batch, since those have already been removed out of _documentPathsToDocumentIds. if (_documentsAddedInBatch.Any(d => d.Id == documentId)) { return; @@ -1621,7 +1670,7 @@ public void ReorderFiles(ImmutableArray filePaths) throw new ArgumentOutOfRangeException("The specified files are empty.", nameof(filePaths)); } - lock (_project._gate) + using (_project._gate.DisposableWait()) { if (_documentPathsToDocumentIds.Count != filePaths.Length) { @@ -1648,14 +1697,7 @@ public void ReorderFiles(ImmutableArray filePaths) } else { - _project._workspace.ApplyBatchChangeToWorkspace(solution => - { - var solutionChanges = new SolutionChangeAccumulator(solution); - solutionChanges.UpdateSolutionForProjectAction( - _project.Id, - solutionChanges.Solution.WithProjectDocumentsOrder(_project.Id, documentIds.ToImmutable())); - return solutionChanges; - }); + _project._workspace.ApplyChangeToWorkspace(_project.Id, solution => solution.WithProjectDocumentsOrder(_project.Id, documentIds.ToImmutable())); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectFactory.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectFactory.cs index e1f2038048348..6ed24981086a0 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectFactory.cs @@ -129,10 +129,10 @@ public async Task CreateAndAddToWorkspaceAsync( { w.OnProjectAdded(projectInfo); } - - _visualStudioWorkspaceImpl.RefreshProjectExistsUIContextForLanguage(language); }); + _visualStudioWorkspaceImpl.RefreshProjectExistsUIContextForLanguage(language); + return project; static Guid GetSolutionSessionId() diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs index b40bafbb39a97..22bc495039551 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs @@ -144,7 +144,7 @@ private void TryOpeningDocumentsForMoniker(string moniker, ITextBuffer textBuffe } else { - activeContextProjectId = GetActiveContextProjectIdAndWatchHierarchies(moniker, documentIds.Select(d => d.ProjectId), hierarchy); + activeContextProjectId = GetActiveContextProjectIdAndWatchHierarchies_NoLock(moniker, documentIds.Select(d => d.ProjectId), hierarchy); } var textContainer = textBuffer.AsTextContainer(); @@ -172,7 +172,7 @@ private void TryOpeningDocumentsForMoniker(string moniker, ITextBuffer textBuffe }); } - private ProjectId GetActiveContextProjectIdAndWatchHierarchies(string moniker, IEnumerable projectIds, IVsHierarchy? hierarchy) + private ProjectId GetActiveContextProjectIdAndWatchHierarchies_NoLock(string moniker, IEnumerable projectIds, IVsHierarchy? hierarchy) { _foregroundAffinitization.AssertIsForeground(); @@ -221,7 +221,7 @@ void WatchHierarchy(IVsHierarchy hierarchyToWatch) if (contextProjectNameObject is string contextProjectName) { - var project = _workspace.GetProjectWithHierarchyAndName(hierarchy, contextProjectName); + var project = _workspace.GetProjectWithHierarchyAndName_NoLock(hierarchy, contextProjectName); if (project != null && projectIds.Contains(project.Id)) { @@ -272,7 +272,7 @@ private void RefreshContextForMoniker(string moniker, IVsHierarchy hierarchy) return; } - var activeProjectId = GetActiveContextProjectIdAndWatchHierarchies(moniker, documentIds.Select(d => d.ProjectId), hierarchy); + var activeProjectId = GetActiveContextProjectIdAndWatchHierarchies_NoLock(moniker, documentIds.Select(d => d.ProjectId), hierarchy); w.OnDocumentContextUpdated(documentIds.First(d => d.ProjectId == activeProjectId)); }); } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs index 3a5bcbee39896..449aea089d7f2 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -65,7 +65,7 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac private readonly ITextBufferCloneService _textBufferCloneService; - private readonly object _gate = new(); + private readonly SemaphoreSlim _gate = new SemaphoreSlim(initialCount: 1); /// /// A to make assertions that stuff is on the right thread. @@ -203,7 +203,7 @@ public async Task InitializeUIAffinitizedServicesAsync(IAsyncServiceProvider asy var openFileTracker = await OpenFileTracker.CreateAsync(this, asyncServiceProvider).ConfigureAwait(true); // Update our fields first, so any asynchronous work that needs to use these is able to see the service. - lock (_gate) + using (await _gate.DisposableWaitAsync().ConfigureAwait(true)) { _openFileTracker = openFileTracker; } @@ -211,7 +211,7 @@ public async Task InitializeUIAffinitizedServicesAsync(IAsyncServiceProvider asy var memoryListener = await VirtualMemoryNotificationListener.CreateAsync(this, _threadingContext, asyncServiceProvider, _globalOptions, _threadingContext.DisposalToken).ConfigureAwait(true); // Update our fields first, so any asynchronous work that needs to use these is able to see the service. - lock (_gate) + using (await _gate.DisposableWaitAsync().ConfigureAwait(true)) { _memoryListener = memoryListener; } @@ -227,7 +227,7 @@ public void ProcessQueuedWorkOnUIThread() internal void AddProjectToInternalMaps(VisualStudioProject project, IVsHierarchy? hierarchy, Guid guid, string projectSystemName) { - lock (_gate) + using (_gate.DisposableWait()) { _projectToHierarchyMap = _projectToHierarchyMap.Add(project.Id, hierarchy); _projectToGuidMap = _projectToGuidMap.Add(project.Id, guid); @@ -237,26 +237,23 @@ internal void AddProjectToInternalMaps(VisualStudioProject project, IVsHierarchy internal void AddProjectRuleSetFileToInternalMaps(VisualStudioProject project, Func ruleSetFilePathFunc) { - lock (_gate) + using (_gate.DisposableWait()) { _projectToRuleSetFilePath.Add(project.Id, ruleSetFilePathFunc); } } - internal void AddDocumentToDocumentsNotFromFiles(DocumentId documentId) + internal void AddDocumentToDocumentsNotFromFiles_NoLock(DocumentId documentId) { - lock (_gate) - { - _documentsNotFromFiles = _documentsNotFromFiles.Add(documentId); - } + Contract.ThrowIfFalse(_gate.CurrentCount == 0); + + _documentsNotFromFiles = _documentsNotFromFiles.Add(documentId); } - internal void RemoveDocumentToDocumentsNotFromFiles(DocumentId documentId) + internal void RemoveDocumentToDocumentsNotFromFiles_NoLock(DocumentId documentId) { - lock (_gate) - { - _documentsNotFromFiles = _documentsNotFromFiles.Remove(documentId); - } + Contract.ThrowIfFalse(_gate.CurrentCount == 0); + _documentsNotFromFiles = _documentsNotFromFiles.Remove(documentId); } [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] @@ -288,18 +285,23 @@ internal VisualStudioProjectTracker ProjectTracker internal VisualStudioProject? GetProjectWithHierarchyAndName(IVsHierarchy hierarchy, string projectName) { - lock (_gate) + using (_gate.DisposableWait()) + { + return GetProjectWithHierarchyAndName_NoLock(hierarchy, projectName); + } + } + + private VisualStudioProject? GetProjectWithHierarchyAndName_NoLock(IVsHierarchy hierarchy, string projectName) + { + if (_projectSystemNameToProjectsMap.TryGetValue(projectName, out var projects)) { - if (_projectSystemNameToProjectsMap.TryGetValue(projectName, out var projects)) + foreach (var project in projects) { - foreach (var project in projects) + if (_projectToHierarchyMap.TryGetValue(project.Id, out var projectHierarchy)) { - if (_projectToHierarchyMap.TryGetValue(project.Id, out var projectHierarchy)) + if (projectHierarchy == hierarchy) { - if (projectHierarchy == hierarchy) - { - return project; - } + return project; } } } @@ -312,7 +314,7 @@ internal VisualStudioProjectTracker ProjectTracker // Solution Explorer in the SolutionExplorerShim, where if we just could more directly get to the rule set file it'd simplify this. internal override string? TryGetRuleSetPathForProject(ProjectId projectId) { - lock (_gate) + using (_gate.DisposableWait()) { if (_projectToRuleSetFilePath.TryGetValue(projectId, out var ruleSetPathFunc)) { @@ -417,7 +419,7 @@ internal bool IsCPSProject(ProjectId projectId) internal bool IsPrimaryProject(ProjectId projectId) { - lock (_gate) + using (_gate.DisposableWait()) { foreach (var (_, projects) in _projectSystemNameToProjectsMap) { @@ -1169,7 +1171,6 @@ protected override void ApplyAnalyzerConfigDocumentTextChanged(DocumentId docume private void ApplyTextDocumentChange(DocumentId documentId, SourceText newText) { - EnsureEditableDocuments(documentId); var containedDocument = TryGetContainedDocument(documentId); if (containedDocument != null) @@ -1330,7 +1331,7 @@ internal override void SetDocumentContext(DocumentId documentId) // Note: this method does not actually call into any workspace code here to change the workspace's context. The assumption is updating the running document table or // IVsHierarchies will raise the appropriate events which we are subscribed to. - lock (_gate) + using (_gate.DisposableWait()) { var hierarchy = GetHierarchy(documentId.ProjectId); if (hierarchy == null) @@ -1440,9 +1441,6 @@ public void EnsureEditableDocuments(IEnumerable documents) } } - public void EnsureEditableDocuments(params DocumentId[] documents) - => this.EnsureEditableDocuments((IEnumerable)documents); - internal override bool CanAddProjectReference(ProjectId referencingProject, ProjectId referencedProject) { _foregroundObject.AssertIsForeground(); @@ -1522,7 +1520,18 @@ internal override bool CanAddProjectReference(ProjectId referencingProject, Proj /// public void ApplyChangeToWorkspace(Action action) { - lock (_gate) + using (_gate.DisposableWait()) + { + action(this); + } + } + + /// + /// Applies a single operation to the workspace. should be a call to one of the protected Workspace.On* methods. + /// + public async ValueTask ApplyChangeToWorkspaceMaybeAsync(bool useAsync, Action action) + { + using (useAsync ? await _gate.DisposableWaitAsync().ConfigureAwait(false) : _gate.DisposableWait()) { action(this); } @@ -1534,7 +1543,7 @@ public void ApplyChangeToWorkspace(Action action) /// public void ApplyChangeToWorkspace(ProjectId projectId, Func solutionTransformation) { - lock (_gate) + using (_gate.DisposableWait()) { SetCurrentSolution(solutionTransformation, WorkspaceChangeKind.ProjectChanged, projectId); } @@ -1545,9 +1554,9 @@ public void ApplyChangeToWorkspace(ProjectId projectId, Func /// This is needed to synchronize with to avoid any races. This /// method could be moved down to the core Workspace layer and then could use the synchronization lock there. - public void ApplyBatchChangeToWorkspace(Func mutation) + public async ValueTask ApplyBatchChangeToWorkspaceMaybeAsync(bool useAsync, Func mutation) { - lock (_gate) + using (useAsync ? await _gate.DisposableWaitAsync().ConfigureAwait(false) : _gate.DisposableWait()) { var oldSolution = this.CurrentSolution; var solutionChangeAccumulator = mutation(oldSolution); @@ -1563,7 +1572,10 @@ public void ApplyBatchChangeToWorkspace(Func new ProjectReferenceInformation()); } protected internal override void OnProjectRemoved(ProjectId projectId) { - lock (_gate) - { - var languageName = CurrentSolution.GetRequiredProject(projectId).Language; + string? languageName; - if (_projectReferenceInfoMap.TryGetValue(projectId, out var projectReferenceInfo)) - { - // If we still had any output paths, we'll want to remove them to cause conversion back to metadata references. - // The call below implicitly is modifying the collection we've fetched, so we'll make a copy. - foreach (var outputPath in projectReferenceInfo.OutputPaths.ToList()) - { - RemoveProjectOutputPath(projectId, outputPath); - } + Contract.ThrowIfFalse(_gate.CurrentCount == 0); + + languageName = CurrentSolution.GetRequiredProject(projectId).Language; - _projectReferenceInfoMap.Remove(projectId); + if (_projectReferenceInfoMap.TryGetValue(projectId, out var projectReferenceInfo)) + { + // If we still had any output paths, we'll want to remove them to cause conversion back to metadata references. + // The call below implicitly is modifying the collection we've fetched, so we'll make a copy. + foreach (var outputPath in projectReferenceInfo.OutputPaths.ToList()) + { + RemoveProjectOutputPath_NoLock(projectId, outputPath); } - _projectToHierarchyMap = _projectToHierarchyMap.Remove(projectId); - _projectToGuidMap = _projectToGuidMap.Remove(projectId); - // _projectToMaxSupportedLangVersionMap needs to be updated with ImmutableInterlocked since it can be mutated outside the lock - ImmutableInterlocked.TryRemove(ref _projectToMaxSupportedLangVersionMap, projectId, out _); - // _projectToDependencyNodeTargetIdentifier needs to be updated with ImmutableInterlocked since it can be mutated outside the lock - ImmutableInterlocked.TryRemove(ref _projectToDependencyNodeTargetIdentifier, projectId, out _); - _projectToRuleSetFilePath.Remove(projectId); + _projectReferenceInfoMap.Remove(projectId); + } - foreach (var (projectName, projects) in _projectSystemNameToProjectsMap) + _projectToHierarchyMap = _projectToHierarchyMap.Remove(projectId); + _projectToGuidMap = _projectToGuidMap.Remove(projectId); + // _projectToMaxSupportedLangVersionMap needs to be updated with ImmutableInterlocked since it can be mutated outside the lock + ImmutableInterlocked.TryRemove(ref _projectToMaxSupportedLangVersionMap, projectId, out _); + // _projectToDependencyNodeTargetIdentifier needs to be updated with ImmutableInterlocked since it can be mutated outside the lock + ImmutableInterlocked.TryRemove(ref _projectToDependencyNodeTargetIdentifier, projectId, out _); + _projectToRuleSetFilePath.Remove(projectId); + + foreach (var (projectName, projects) in _projectSystemNameToProjectsMap) + { + if (projects.RemoveAll(p => p.Id == projectId) > 0) { - if (projects.RemoveAll(p => p.Id == projectId) > 0) + if (projects.Count == 0) { - if (projects.Count == 0) - { - _projectSystemNameToProjectsMap.Remove(projectName); - } - - break; + _projectSystemNameToProjectsMap.Remove(projectName); } + + break; } + } - base.OnProjectRemoved(projectId); + base.OnProjectRemoved(projectId); - // Try to update the UI context info. But cancel that work if we're shutting down. - _threadingContext.RunWithShutdownBlockAsync(async cancellationToken => - { - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - RefreshProjectExistsUIContextForLanguage(languageName); - }); - } + // Try to update the UI context info. But cancel that work if we're shutting down. + var listenerProvider = Services.GetRequiredService(); + var asyncToken = listenerProvider.GetListener().BeginAsyncOperation(nameof(RefreshProjectExistsUIContextForLanguage)); + _threadingContext.RunWithShutdownBlockAsync(async cancellationToken => + { + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + RefreshProjectExistsUIContextForLanguage(languageName); + asyncToken.Dispose(); + }); } private sealed class ProjectReferenceInformation @@ -1647,39 +1663,44 @@ private sealed class ProjectReferenceInformation public void AddProjectOutputPath(ProjectId projectId, string outputPath) { - lock (_gate) + using (_gate.DisposableWait()) { - var projectReferenceInformation = GetReferenceInfo_NoLock(projectId); + AddProjectOutputPath_NoLock(projectId, outputPath); + } + } - projectReferenceInformation.OutputPaths.Add(outputPath); - _projectsByOutputPath.MultiAdd(outputPath, projectId); + private void AddProjectOutputPath_NoLock(ProjectId projectId, string outputPath) + { + var projectReferenceInformation = GetReferenceInfo_NoLock(projectId); - var projectsForOutputPath = _projectsByOutputPath[outputPath]; - var distinctProjectsForOutputPath = projectsForOutputPath.Distinct().ToList(); + projectReferenceInformation.OutputPaths.Add(outputPath); + _projectsByOutputPath.MultiAdd(outputPath, projectId); - // If we have exactly one, then we're definitely good to convert - if (projectsForOutputPath.Count == 1) - { - ConvertMetadataReferencesToProjectReferences_NoLock(projectId, outputPath); - } - else if (distinctProjectsForOutputPath.Count == 1) - { - // The same project has multiple output paths that are the same. Any project would have already been converted - // by the prior add, so nothing further to do - } - else + var projectsForOutputPath = _projectsByOutputPath[outputPath]; + var distinctProjectsForOutputPath = projectsForOutputPath.Distinct().ToList(); + + // If we have exactly one, then we're definitely good to convert + if (projectsForOutputPath.Count == 1) + { + ConvertMetadataReferencesToProjectReferences_NoLock(projectId, outputPath); + } + else if (distinctProjectsForOutputPath.Count == 1) + { + // The same project has multiple output paths that are the same. Any project would have already been converted + // by the prior add, so nothing further to do + } + else + { + // We have more than one project outputting to the same path. This shouldn't happen but we'll convert back + // because now we don't know which project to reference. + foreach (var otherProjectId in projectsForOutputPath) { - // We have more than one project outputting to the same path. This shouldn't happen but we'll convert back - // because now we don't know which project to reference. - foreach (var otherProjectId in projectsForOutputPath) + // We know that since we're adding a path to projectId and we're here that we couldn't have already + // had a converted reference to us, instead we need to convert things that are pointing to the project + // we're colliding with + if (otherProjectId != projectId) { - // We know that since we're adding a path to projectId and we're here that we couldn't have already - // had a converted reference to us, instead we need to convert things that are pointing to the project - // we're colliding with - if (otherProjectId != projectId) - { - ConvertProjectReferencesToMetadataReferences_NoLock(otherProjectId, outputPath); - } + ConvertProjectReferencesToMetadataReferences_NoLock(otherProjectId, outputPath); } } } @@ -1694,7 +1715,7 @@ public void AddProjectOutputPath(ProjectId projectId, string outputPath) Constraint = "Avoid calling " + nameof(CodeAnalysis.Solution.GetProject) + " to avoid realizing all projects.")] private void ConvertMetadataReferencesToProjectReferences_NoLock(ProjectId projectId, string outputPath) { - Debug.Assert(Monitor.IsEntered(_gate)); + Contract.ThrowIfFalse(_gate.CurrentCount == 0); var modifiedSolution = this.CurrentSolution; using var _ = PooledHashSet.GetInstance(out var projectIdsChanged); @@ -1734,7 +1755,7 @@ private void ConvertMetadataReferencesToProjectReferences_NoLock(ProjectId proje Constraint = "Avoid calling " + nameof(CodeAnalysis.Solution.GetProject) + " to avoid realizing all projects.")] private bool CanConvertMetadataReferenceToProjectReference_NoLock(ProjectId projectIdWithMetadataReference, ProjectId referencedProjectId) { - Debug.Assert(Monitor.IsEntered(_gate)); + Contract.ThrowIfFalse(_gate.CurrentCount == 0); // We can never make a project reference ourselves. This isn't a meaningful scenario, but if somebody does this by accident // we do want to throw exceptions. @@ -1785,7 +1806,7 @@ private bool CanConvertMetadataReferenceToProjectReference_NoLock(ProjectId proj Constraint = "Update ConvertedProjectReferences in place to avoid duplicate list allocations.")] private void ConvertProjectReferencesToMetadataReferences_NoLock(ProjectId projectId, string outputPath) { - Debug.Assert(Monitor.IsEntered(_gate)); + Contract.ThrowIfFalse(_gate.CurrentCount == 0); var modifiedSolution = this.CurrentSolution; using var _ = PooledHashSet.GetInstance(out var projectIdsChanged); @@ -1831,7 +1852,7 @@ private void ConvertProjectReferencesToMetadataReferences_NoLock(ProjectId proje { // Any conversion to or from project references must be done under the global workspace lock, // since that needs to be coordinated with updating all projects simultaneously. - Debug.Assert(Monitor.IsEntered(_gate)); + Contract.ThrowIfFalse(_gate.CurrentCount == 0); if (_projectsByOutputPath.TryGetValue(path, out var ids) && ids.Distinct().Count() == 1) { @@ -1863,7 +1884,7 @@ private void ConvertProjectReferencesToMetadataReferences_NoLock(ProjectId proje { // Any conversion to or from project references must be done under the global workspace lock, // since that needs to be coordinated with updating all projects simultaneously. - Debug.Assert(Monitor.IsEntered(_gate)); + Contract.ThrowIfFalse(_gate.CurrentCount == 0); var projectReferenceInformation = GetReferenceInfo_NoLock(referencingProject); foreach (var convertedProject in projectReferenceInformation.ConvertedProjectReferences) @@ -1907,49 +1928,54 @@ private void SetSolutionAndRaiseWorkspaceChanged_NoLock(CodeAnalysis.Solution mo public void RemoveProjectOutputPath(ProjectId projectId, string outputPath) { - lock (_gate) + using (_gate.DisposableWait()) { - var projectReferenceInformation = GetReferenceInfo_NoLock(projectId); - if (!projectReferenceInformation.OutputPaths.Contains(outputPath)) - { - throw new ArgumentException($"Project does not contain output path '{outputPath}'", nameof(outputPath)); - } + RemoveProjectOutputPath_NoLock(projectId, outputPath); + } + } + + private void RemoveProjectOutputPath_NoLock(ProjectId projectId, string outputPath) + { + var projectReferenceInformation = GetReferenceInfo_NoLock(projectId); + if (!projectReferenceInformation.OutputPaths.Contains(outputPath)) + { + throw new ArgumentException($"Project does not contain output path '{outputPath}'", nameof(outputPath)); + } + + projectReferenceInformation.OutputPaths.Remove(outputPath); + _projectsByOutputPath.MultiRemove(outputPath, projectId); - projectReferenceInformation.OutputPaths.Remove(outputPath); - _projectsByOutputPath.MultiRemove(outputPath, projectId); - - // When a project is closed, we may need to convert project references to metadata references (or vice - // versa). Failure to convert the references could leave a project in the workspace with a project - // reference to a project which is not open. - // - // For the specific case where the entire solution is closing, we do not need to update the state for - // remaining projects as each project closes, because we know those projects will be closed without - // further use. Avoiding reference conversion when the solution is closing improves performance for both - // IDE close scenarios and solution reload scenarios that occur after complex branch switches. - if (!_solutionClosing) + // When a project is closed, we may need to convert project references to metadata references (or vice + // versa). Failure to convert the references could leave a project in the workspace with a project + // reference to a project which is not open. + // + // For the specific case where the entire solution is closing, we do not need to update the state for + // remaining projects as each project closes, because we know those projects will be closed without + // further use. Avoiding reference conversion when the solution is closing improves performance for both + // IDE close scenarios and solution reload scenarios that occur after complex branch switches. + if (!_solutionClosing) + { + if (_projectsByOutputPath.TryGetValue(outputPath, out var remainingProjectsForOutputPath)) { - if (_projectsByOutputPath.TryGetValue(outputPath, out var remainingProjectsForOutputPath)) + var distinctRemainingProjects = remainingProjectsForOutputPath.Distinct(); + if (distinctRemainingProjects.Count() == 1) { - var distinctRemainingProjects = remainingProjectsForOutputPath.Distinct(); - if (distinctRemainingProjects.Count() == 1) - { - // We had more than one project outputting to the same path. Now we're back down to one - // so we can reference that one again - ConvertMetadataReferencesToProjectReferences_NoLock(distinctRemainingProjects.Single(), outputPath); - } - } - else - { - // No projects left, we need to convert back to metadata references - ConvertProjectReferencesToMetadataReferences_NoLock(projectId, outputPath); + // We had more than one project outputting to the same path. Now we're back down to one + // so we can reference that one again + ConvertMetadataReferencesToProjectReferences_NoLock(distinctRemainingProjects.Single(), outputPath); } } + else + { + // No projects left, we need to convert back to metadata references + ConvertProjectReferencesToMetadataReferences_NoLock(projectId, outputPath); + } } } private void RefreshMetadataReferencesForFile(object sender, string fullFilePath) { - lock (_gate) + using (_gate.DisposableWait()) { var newSolution = CurrentSolution; using var _ = PooledHashSet.GetInstance(out var changedProjectIds); @@ -2023,7 +2049,7 @@ internal void RefreshProjectExistsUIContextForLanguage(string language) // We must assert the call is on the foreground as setting UIContext.IsActive would otherwise do a COM RPC. _foregroundObject.AssertIsForeground(); - lock (_gate) + using (_gate.DisposableWait()) { var uiContext = _languageToProjectExistsUIContext.GetOrAdd( diff --git a/src/VisualStudio/Core/Def/Implementation/TaskList/ProjectExternalErrorReporter.cs b/src/VisualStudio/Core/Def/Implementation/TaskList/ProjectExternalErrorReporter.cs index 8ceb6391db09f..4e199c11a3099 100644 --- a/src/VisualStudio/Core/Def/Implementation/TaskList/ProjectExternalErrorReporter.cs +++ b/src/VisualStudio/Core/Def/Implementation/TaskList/ProjectExternalErrorReporter.cs @@ -63,7 +63,7 @@ private bool CanHandle(string errorId) if (errorId == null) { // record NFW to see who violates contract. - WatsonReporter.ReportNonFatal(new Exception("errorId is null")); + FatalError.ReportAndCatch(new Exception("errorId is null")); return false; } diff --git a/src/VisualStudio/Core/Def/Implementation/VirtualMemoryNotificationListener.cs b/src/VisualStudio/Core/Def/Implementation/VirtualMemoryNotificationListener.cs index 67d3b6710f620..4accbd453df69 100644 --- a/src/VisualStudio/Core/Def/Implementation/VirtualMemoryNotificationListener.cs +++ b/src/VisualStudio/Core/Def/Implementation/VirtualMemoryNotificationListener.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Options; using Microsoft.CodeAnalysis.SolutionCrawler; +using Microsoft.CodeAnalysis.Telemetry; using Microsoft.VisualStudio.LanguageServices.Implementation; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; using Microsoft.VisualStudio.Shell.Interop; @@ -162,7 +163,9 @@ private void ShowInfoBarIfRequired() // Show info bar. _workspace.Services.GetRequiredService() - .ShowGlobalErrorInfo(ServicesVSResources.Visual_Studio_has_suspended_some_advanced_features_to_improve_performance, + .ShowGlobalErrorInfo( + message: ServicesVSResources.Visual_Studio_has_suspended_some_advanced_features_to_improve_performance, + TelemetryFeatureName.VirtualMemoryNotification, exception: null, new InfoBarUI(ServicesVSResources.Re_enable, InfoBarUI.UIKind.Button, RenableBackgroundAnalysis), new InfoBarUI(ServicesVSResources.Learn_more, InfoBarUI.UIKind.HyperLink, diff --git a/src/VisualStudio/Core/Def/Implementation/Watson/WatsonReporter.cs b/src/VisualStudio/Core/Def/Implementation/Watson/WatsonReporter.cs index 4ef4db8cf8d17..b8a095a1e9111 100644 --- a/src/VisualStudio/Core/Def/Implementation/Watson/WatsonReporter.cs +++ b/src/VisualStudio/Core/Def/Implementation/Watson/WatsonReporter.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.IO; using System.Reflection; +using System.Threading; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Remote; using Microsoft.VisualStudio.LanguageServices.Telemetry; @@ -23,12 +24,14 @@ internal static class WatsonReporter private static ImmutableArray s_telemetrySessions = ImmutableArray.Empty; private static ImmutableArray s_loggers = ImmutableArray.Empty; + private static int s_dumpsSubmitted; + public static void InitializeFatalErrorHandlers() { // Set both handlers to non-fatal Watson. Never fail-fast the ServiceHub process. // Any exception that is not recovered from shall be propagated and communicated to the client. - var nonFatalHandler = new Action(ReportNonFatal); - var fatalHandler = nonFatalHandler; + var nonFatalHandler = WatsonReporter.ReportNonFatal; + var fatalHandler = new Action(static (exception) => ReportNonFatal(exception, forceDump: false)); FatalError.Handler = fatalHandler; FatalError.NonFatalHandler = nonFatalHandler; @@ -78,7 +81,9 @@ public static void UnregisterLogger(TraceSource logger) /// Report Non-Fatal Watson for a given unhandled exception. /// /// Exception that triggered this non-fatal error - public static void ReportNonFatal(Exception exception) + /// Force a dump to be created, even if the telemetry system is not + /// requesting one; we will still do a client-side limit to avoid sending too much at once. + public static void ReportNonFatal(Exception exception, bool forceDump) { try { @@ -99,6 +104,13 @@ public static void ReportNonFatal(Exception exception) exceptionObject: exception, gatherEventDetails: faultUtility => { + if (forceDump) + { + // Let's just send a maximum of three; number chosen arbitrarily + if (Interlocked.Increment(ref s_dumpsSubmitted) <= 3) + faultUtility.AddProcessDump(currentProcess.Id); + } + if (faultUtility is FaultEvent { IsIncludedInWatsonSample: true }) { // add ServiceHub log files: @@ -194,8 +206,7 @@ private static List CollectServiceHubLogFilePaths() // name our services more consistently to simplify filtering // filter logs that are not relevant to Roslyn investigation - if (!name.Contains("-" + ServiceDescriptors.ServiceNameTopLevelPrefix) && - !name.Contains("-" + ServiceDescriptors.Prefix) && + if (!name.Contains("-" + ServiceDescriptor.ServiceNameTopLevelPrefix) && !name.Contains("-CodeLens") && !name.Contains("-ManagedLanguage.IDE.RemoteHostClient") && !name.Contains("-hub")) diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioErrorReportingService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioErrorReportingService.cs index ee419a8960001..90f228488045b 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioErrorReportingService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioErrorReportingService.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Telemetry; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; @@ -35,14 +36,17 @@ public VisualStudioErrorReportingService( public string HostDisplayName => "Visual Studio"; - public void ShowGlobalErrorInfo(string message, Exception? exception, params InfoBarUI[] items) + public void ShowGlobalErrorInfo(string message, TelemetryFeatureName featureName, Exception? exception, params InfoBarUI[] items) { - var detailedMessage = exception is null ? "" : GetFormattedExceptionStack(exception); - LogGlobalErrorToActivityLog(message, detailedMessage); + var stackTrace = exception is null ? "" : GetFormattedExceptionStack(exception); + LogGlobalErrorToActivityLog(message, stackTrace); _infoBarService.ShowInfoBar(message, items); - // Have to use KeyValueLogMessage so it gets reported in telemetry - Logger.Log(FunctionId.VS_ErrorReportingService_ShowGlobalErrorInfo, message, LogLevel.Information); + Logger.Log(FunctionId.VS_ErrorReportingService_ShowGlobalErrorInfo, KeyValueLogMessage.Create(LogType.UserAction, m => + { + m["Message"] = message; + m["FeatureName"] = featureName.ToString(); + })); } public void ShowDetailedErrorInfo(Exception exception) @@ -51,7 +55,7 @@ public void ShowDetailedErrorInfo(Exception exception) new DetailedErrorInfoDialog(exception.Message, errorInfo).ShowModal(); } - public void ShowFeatureNotAvailableErrorInfo(string message, Exception? exception) + public void ShowFeatureNotAvailableErrorInfo(string message, TelemetryFeatureName featureName, Exception? exception) { var infoBarUIs = new List(); @@ -64,7 +68,7 @@ public void ShowFeatureNotAvailableErrorInfo(string message, Exception? exceptio closeAfterAction: true)); } - ShowGlobalErrorInfo(message, exception, infoBarUIs.ToArray()); + ShowGlobalErrorInfo(message, featureName, exception, infoBarUIs.ToArray()); } private void LogGlobalErrorToActivityLog(string message, string? detailedError) diff --git a/src/VisualStudio/Core/Def/Interactive/VsInteractiveWindowPackage.cs b/src/VisualStudio/Core/Def/Interactive/VsInteractiveWindowPackage.cs index f62327c0c741f..9eee1f44e1e29 100644 --- a/src/VisualStudio/Core/Def/Interactive/VsInteractiveWindowPackage.cs +++ b/src/VisualStudio/Core/Def/Interactive/VsInteractiveWindowPackage.cs @@ -51,16 +51,15 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke // Set both handlers to non-fatal Watson. Never fail-fast the VS process. // Any exception that is not recovered from shall be propagated. - var nonFatalHandler = new Action(WatsonReporter.ReportNonFatal); - var fatalHandler = nonFatalHandler; - - InteractiveHostFatalError.Handler = fatalHandler; - InteractiveHostFatalError.NonFatalHandler = nonFatalHandler; + var nonFatalHandler = new Action(static (exception) => WatsonReporter.ReportNonFatal(exception, forceDump: false)); + InteractiveHostFatalError.Handler = nonFatalHandler; + InteractiveHostFatalError.NonFatalHandler = WatsonReporter.ReportNonFatal; // Load the Roslyn package so that its FatalError handlers are hooked up. shell.LoadPackage(Guids.RoslynPackageId, out var roslynPackage); // Explicitly set up FatalError handlers for the InteractiveWindowPackage. + var fatalHandler = nonFatalHandler; SetErrorHandlers(typeof(IInteractiveWindow).Assembly, fatalHandler, nonFatalHandler); SetErrorHandlers(typeof(IVsInteractiveWindow).Assembly, fatalHandler, nonFatalHandler); diff --git a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj index 7568fc0d1575c..2dd33442b401e 100644 --- a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj +++ b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj @@ -109,6 +109,7 @@ + @@ -124,7 +125,6 @@ - diff --git a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef index 77fd32347afb3..0aaefc43c49a5 100644 --- a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef +++ b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef @@ -31,7 +31,7 @@ "Title"="C#/VB LSP semantic tokens experience" "PreviewPaneChannels"="IntPreview,int.main" -[$RootKey$\FeatureFlags\Roslyn\OOPCoreClr] +[$RootKey$\FeatureFlags\Roslyn\ServiceHubCore] "Description"="Run C#/VB out-of-process code analysis on .Net 6 ServiceHub host." "Value"=dword:00000000 "Title"="Run C#/VB code analysis on .Net 6 (requires restart)" diff --git a/src/VisualStudio/Core/Def/Storage/AbstractCloudCachePersistentStorageService.cs b/src/VisualStudio/Core/Def/Storage/AbstractCloudCachePersistentStorageService.cs index eddd0f3159d42..31854a1866153 100644 --- a/src/VisualStudio/Core/Def/Storage/AbstractCloudCachePersistentStorageService.cs +++ b/src/VisualStudio/Core/Def/Storage/AbstractCloudCachePersistentStorageService.cs @@ -9,12 +9,13 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Storage; +using Microsoft.CodeAnalysis.Storage.CloudCache; using Microsoft.VisualStudio.RpcContracts.Caching; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Storage { - internal abstract class AbstractCloudCachePersistentStorageService : AbstractPersistentStorageService + internal abstract class AbstractCloudCachePersistentStorageService : AbstractPersistentStorageService, ICloudCacheStorageService { private const string StorageExtension = "CloudCache"; diff --git a/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageService.cs b/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageService.cs index 86de6d0edd662..17bc690f7b627 100644 --- a/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageService.cs +++ b/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageService.cs @@ -2,10 +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. +using System; using System.Collections.Immutable; +using System.Composition; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Storage.CloudCache; using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.RpcContracts.Caching; using Microsoft.VisualStudio.Shell; @@ -16,6 +20,20 @@ namespace Microsoft.VisualStudio.LanguageServices.Storage { internal class VisualStudioCloudCacheStorageService : AbstractCloudCachePersistentStorageService { + [ExportWorkspaceServiceFactory(typeof(ICloudCacheStorageService), ServiceLayer.Host), Shared] + internal class ServiceFactory : IWorkspaceServiceFactory + { + private readonly IAsyncServiceProvider _serviceProvider; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public ServiceFactory(SVsServiceProvider serviceProvider) + => _serviceProvider = (IAsyncServiceProvider)serviceProvider; + + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + => new VisualStudioCloudCacheStorageService(_serviceProvider, workspaceServices.GetRequiredService()); + } + private readonly IAsyncServiceProvider _serviceProvider; public VisualStudioCloudCacheStorageService(IAsyncServiceProvider serviceProvider, IPersistentStorageConfiguration configuration) diff --git a/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageServiceFactory.cs b/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageServiceFactory.cs deleted file mode 100644 index b49961e3213c1..0000000000000 --- a/src/VisualStudio/Core/Def/Storage/VisualStudioCloudCacheStorageServiceFactory.cs +++ /dev/null @@ -1,28 +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.Composition; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Storage; -using Microsoft.CodeAnalysis.Storage.CloudCache; -using Microsoft.VisualStudio.Shell; - -namespace Microsoft.VisualStudio.LanguageServices.Storage -{ - [ExportWorkspaceService(typeof(ICloudCacheStorageServiceFactory), ServiceLayer.Host), Shared] - internal class VisualStudioCloudCacheStorageServiceFactory : ICloudCacheStorageServiceFactory - { - private readonly IAsyncServiceProvider _serviceProvider; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioCloudCacheStorageServiceFactory(SVsServiceProvider serviceProvider) - => _serviceProvider = (IAsyncServiceProvider)serviceProvider; - - public AbstractPersistentStorageService Create(IPersistentStorageConfiguration locationService) - => new VisualStudioCloudCacheStorageService(_serviceProvider, locationService); - } -} diff --git a/src/VisualStudio/Core/Def/Telemetry/IVsSQM.cs b/src/VisualStudio/Core/Def/Telemetry/IVsSQM.cs deleted file mode 100644 index c65ee1d727d77..0000000000000 --- a/src/VisualStudio/Core/Def/Telemetry/IVsSQM.cs +++ /dev/null @@ -1,330 +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 - -// WARNING: THIS FILE IS BEING CONSUMED THROUGH INTERNALSVISIBLETO -// BY THE RULSET EDITOR. Until you fix that, do not delete this file. - -/////////////////////////////////////////////////////////////////////////////// -// -// -/////////////////////////////////////////////////////////////////////////////// - -#pragma warning disable 3001 - -using System; -using System.Runtime.InteropServices; - -namespace Microsoft.VisualStudio.Shell.Interop -{ - [ComImport()] - [ComVisible(false)] - [Guid("C1F63D0C-4CAE-4907-BE74-EEB75D386ECB")] - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IVsSqm - { - void GetSessionStartTime( - [Out] out System.Runtime.InteropServices.ComTypes.FILETIME time - ); - void GetFlags( - [Out, MarshalAs(UnmanagedType.U4)] out System.UInt32 flags - ); - void SetFlags( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 flags - ); - void ClearFlags( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 flags - ); - void AddItemToStream( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - void SetDatapoint( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - // OBSOLETE IN SQMAPI.DLL. DO NOT CALL. - void GetDatapoint( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [Out, MarshalAs(UnmanagedType.U4)] out System.UInt32 value - ); - void EnterTaggedAssert( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dwTag, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dwPossibleBuild, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dwActualBuild - ); - void RecordCmdData( - [In] ref Guid pguidCmdGroup, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - void GetHashOfGuid( - [In] ref Guid hashGuid, - [Out, MarshalAs(UnmanagedType.U4)] out System.UInt32 resultantHash - ); - void GetHashOfString( - [In, MarshalAs(UnmanagedType.BStr)] string hashString, - [Out, MarshalAs(UnmanagedType.U4)] out System.UInt32 resultantHash - ); - void IncrementDatapoint( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - - void SetDatapointBits( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - - void SetDatapointIfMax( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - void SetDatapointIfMin( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - void AddToDatapointAverage( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - void StartDatapointTimer( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID - ); - void RecordDatapointTimer( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID - ); - void AccumulateDatapointTimer( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID - ); - void AddTimerToDatapointAverage( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID - ); - void AddArrayToStream( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U4, SizeParamIndex = 2)] System.UInt32[] data, - [In, MarshalAs(UnmanagedType.I4)] int count - ); - } - - [ComImport()] - [ComVisible(false)] - [Guid("BE5F55EB-F02D-4217-BCB6-A290800AF6C4")] - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IVsSqm2 - { - void SetBoolDatapoint( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 fValue - ); - - void SetStringDatapoint( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.BStr)] string strValue - ); - - void AddToStreamDWord( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 cTuple, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - - void AddToStreamString( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 cTuple, - [In, MarshalAs(UnmanagedType.BStr)] string strValue - ); - - void GetObfuscatedString( - [In, MarshalAs(UnmanagedType.BStr)] string input, - [Out, MarshalAs(UnmanagedType.BStr)] out string output - ); - } - - [ComImport()] - [ComVisible(false)] - [Guid("B17A7D4A-C1A3-45A2-B916-826C3ABA067E")] - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IVsSqmMulti - { -#pragma warning disable CS0618 // Type or member is obsolete - [return: MarshalAs(UnmanagedType.VariantBool)] -#pragma warning restore CS0618 // Type or member is obsolete - bool GetOptInStatus(); - - void UnloadSessions( - ); - void EndAllSessionsAndAbortUploads( - ); - - void BeginSession( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionType, -#pragma warning disable CS0618 // Type or member is obsolete - [In, MarshalAs(UnmanagedType.VariantBool)] System.Boolean alwaysSend, -#pragma warning restore CS0618 // Type or member is obsolete - [Out, MarshalAs(UnmanagedType.U4)] out System.UInt32 sessionHandle - ); - - void EndSession( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle - ); - void RegisterSessionHandle( - [In] ref Guid sessionIdentifier, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dwSessionHandle - ); - [return: MarshalAs(UnmanagedType.U4)] - int GetSessionHandleByIdentifier( - [In] ref Guid sessionIdentifier - ); - void GetSessionStartTime( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [Out] out System.Runtime.InteropServices.ComTypes.FILETIME time - ); - Guid GetGlobalSessionGuid(); - [return: MarshalAs(UnmanagedType.U4)] - int GetGlobalSessionHandle(); - void SetGlobalSessionGuid( - [In] ref Guid pguidSessionGuid - ); - void GetFlags( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [Out, MarshalAs(UnmanagedType.U4)] out System.UInt32 flags - ); - void SetFlags( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 flags - ); - void ClearFlags( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 flags - ); - void SetDatapoint( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - void SetBoolDatapoint( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 fValue - ); - void SetStringDatapoint( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.BStr)] string strValue - ); - void SetDatapointBits( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - void IncrementDatapoint( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - - void SetDatapointIfMax( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - void SetDatapointIfMin( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - void AddToDatapointAverage( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - void StartDatapointTimer( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID - ); - void RecordDatapointTimer( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID - ); - void AccumulateDatapointTimer( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID - ); - void AddTimerToDatapointAverage( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID - ); - void AddItemToStream( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - void AddArrayToStream( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U4, SizeParamIndex = 2)] System.UInt32[] data, - [In, MarshalAs(UnmanagedType.I4)] int count - ); - void AddToStreamDWord( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 cTuple, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - void AddToStreamString( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 cTuple, - [In, MarshalAs(UnmanagedType.BStr)] string strValue - ); - void RecordCmdData( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 sessionHandle, - [In] ref Guid pguidCmdGroup, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 dataPointID, - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 value - ); - void GetHashOfGuid( - [In] ref Guid hashGuid, - [Out, MarshalAs(UnmanagedType.U4)] out System.UInt32 resultantHash - ); - void GetHashOfString( - [In, MarshalAs(UnmanagedType.BStr)] string hashString, - [Out, MarshalAs(UnmanagedType.U4)] out System.UInt32 resultantHash - ); - void SetProperty( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 propid, - [In] ref Guid varKey, - [In] object varValue - ); - void Get64BitHashOfString( - [In, MarshalAs(UnmanagedType.BStr)] string hashString, - [Out, MarshalAs(UnmanagedType.U8)] out System.UInt64 resultantHash - ); - } - - [ComImport()] - [ComVisible(false)] - [Guid("16be4288-950b-4265-b0dc-280b89ca9979")] - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IVsSqmOptinManager - { - void GetOptinStatus( - [Out, MarshalAs(UnmanagedType.U4)] out System.UInt32 optinStatus, - [Out, MarshalAs(UnmanagedType.U4)] out System.UInt32 preferences - ); - - void SetOptinStatus( - [In, MarshalAs(UnmanagedType.U4)] System.UInt32 optinStatus - ); - } - - [ComImport()] - [ComVisible(false)] - [Guid("2508FDF0-EF80-4366-878E-C9F024B8D981")] - internal interface SVsLog - { - } -} diff --git a/src/VisualStudio/Core/Def/Telemetry/VSTelemetryLogger.cs b/src/VisualStudio/Core/Def/Telemetry/VSTelemetryLogger.cs index e31883c4499a7..ecb35c8ec9cb6 100644 --- a/src/VisualStudio/Core/Def/Telemetry/VSTelemetryLogger.cs +++ b/src/VisualStudio/Core/Def/Telemetry/VSTelemetryLogger.cs @@ -5,7 +5,9 @@ #nullable disable using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.VisualStudio.Telemetry; @@ -152,7 +154,7 @@ private static TelemetryEvent UpdateEvent(TelemetryEvent telemetryEvent, Functio { if (logMessage is KeyValueLogMessage kvLogMessage) { - telemetryEvent = AppendProperties(telemetryEvent, functionId, kvLogMessage); + AppendProperties(telemetryEvent, functionId, kvLogMessage); } else { @@ -167,26 +169,22 @@ private static TelemetryEvent UpdateEvent(TelemetryEvent telemetryEvent, Functio return telemetryEvent; } - private static T AppendProperties(T @event, FunctionId functionId, KeyValueLogMessage logMessage) - where T : TelemetryEvent + private static void AppendProperties(TelemetryEvent telemetryEvent, FunctionId functionId, KeyValueLogMessage logMessage) { - if (!logMessage.ContainsProperty) + foreach (var (key, value) in logMessage.Properties) { - return @event; - } - - foreach (var kv in logMessage.Properties) - { - var propertyName = functionId.GetPropertyName(kv.Key); - // call SetProperty. VS telemetry will take care of finding correct // API based on given object type for us. // // numeric data will show up in ES with measurement prefix. - @event.Properties.Add(propertyName, kv.Value); - } - return @event; + telemetryEvent.Properties.Add(functionId.GetPropertyName(key), value switch + { + PiiValue pii => new TelemetryPiiProperty(pii.Value), + IEnumerable items => new TelemetryComplexProperty(items.Select(item => (item is PiiValue pii) ? pii.Value : item)), + _ => value + }); + } } } } diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs index 2d3281104ca32..478855d6f4944 100644 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs @@ -8,6 +8,7 @@ using System.Collections.Immutable; using System.IO; using System.Linq; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host; using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; @@ -270,12 +271,19 @@ public void SetRuleSetFile(string filePath) public void StartBatch() => _batchScopes.Enqueue(_visualStudioProject.CreateBatchScope()); + [Obsolete($"Use {nameof(EndBatchAsync)}.")] public void EndBatch() { Contract.ThrowIfFalse(_batchScopes.TryDequeue(out var scope)); scope.Dispose(); } + public ValueTask EndBatchAsync() + { + Contract.ThrowIfFalse(_batchScopes.TryDequeue(out var scope)); + return scope.DisposeAsync(); + } + public void ReorderSourceFiles(IEnumerable? filePaths) => _visualStudioProject.ReorderSourceFiles(filePaths.ToImmutableArrayOrEmpty()); diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs index 76984963f5936..e0936a9ada637 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs @@ -248,7 +248,7 @@ private void OnWorkpaceChanged(object sender, WorkspaceChangeEventArgs e) cancellationToken.ThrowIfCancellationRequested(); return UpdateSourceGeneratedFileItemsAsync(_workspace.CurrentSolution, cancellationToken); - }, cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default).CompletesAsyncOperation(asyncToken); + }, cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default).Unwrap().CompletesAsyncOperation(asyncToken); } } } diff --git a/src/VisualStudio/Core/Test.Next/Services/LspDiagnosticsTests.cs b/src/VisualStudio/Core/Test.Next/Services/LspDiagnosticsTests.cs index 60cc464083bcf..5807417ef27d9 100644 --- a/src/VisualStudio/Core/Test.Next/Services/LspDiagnosticsTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/LspDiagnosticsTests.cs @@ -39,7 +39,8 @@ public class LspDiagnosticsTests : AbstractLanguageServerProtocolTests [Fact] public async Task AddDiagnosticTestAsync() { - using var workspace = (await CreateTestLspServerAsync("")).TestWorkspace; + using var server = await CreateTestLspServerAsync(""); + var workspace = server.TestWorkspace; var document = workspace.CurrentSolution.Projects.First().Documents.First(); var diagnosticsMock = new Mock(MockBehavior.Strict); @@ -61,7 +62,8 @@ public async Task AddDiagnosticTestAsync() [Fact] public async Task NoDiagnosticsWhenInPullMode() { - using var workspace = (await CreateTestLspServerAsync("")).TestWorkspace; + using var server = await CreateTestLspServerAsync(""); + var workspace = server.TestWorkspace; workspace.SetOptions(workspace.Options.WithChangedOption( InternalDiagnosticsOptions.NormalDiagnosticMode, DiagnosticMode.Pull)); @@ -78,7 +80,8 @@ public async Task NoDiagnosticsWhenInPullMode() [Fact] public async Task AddDiagnosticWithMappedFilesTestAsync() { - using var workspace = (await CreateTestLspServerAsync("")).TestWorkspace; + using var server = await CreateTestLspServerAsync(""); + var workspace = server.TestWorkspace; var document = workspace.CurrentSolution.Projects.First().Documents.First(); var diagnosticsMock = new Mock(MockBehavior.Strict); @@ -105,7 +108,8 @@ public async Task AddDiagnosticWithMappedFilesTestAsync() [Fact] public async Task AddDiagnosticWithMappedFileToManyDocumentsTestAsync() { - using var workspace = (await CreateTestLspServerAsync(new string[] { "", "" })).TestWorkspace; + using var server = await CreateTestLspServerAsync(new string[] { "", "" }); + var workspace = server.TestWorkspace; var documents = workspace.CurrentSolution.Projects.First().Documents.ToImmutableArray(); var diagnosticsMock = new Mock(MockBehavior.Strict); @@ -141,7 +145,8 @@ public async Task AddDiagnosticWithMappedFileToManyDocumentsTestAsync() [Fact] public async Task RemoveDiagnosticTestAsync() { - using var workspace = (await CreateTestLspServerAsync("")).TestWorkspace; + using var server = await CreateTestLspServerAsync(""); + var workspace = server.TestWorkspace; var document = workspace.CurrentSolution.Projects.First().Documents.First(); var diagnosticsMock = new Mock(MockBehavior.Strict); @@ -173,7 +178,8 @@ await CreateMockDiagnosticDataAsync(document, "id").ConfigureAwait(false), [Fact] public async Task RemoveDiagnosticForMappedFilesTestAsync() { - using var workspace = (await CreateTestLspServerAsync("")).TestWorkspace; + using var server = await CreateTestLspServerAsync(""); + var workspace = server.TestWorkspace; var document = workspace.CurrentSolution.Projects.First().Documents.First(); var diagnosticsMock = new Mock(MockBehavior.Strict); @@ -224,7 +230,8 @@ await CreateMockDiagnosticDatasWithMappedLocationAsync(document, ("id1", mappedF [Fact] public async Task RemoveDiagnosticForMappedFileToManyDocumentsTestAsync() { - using var workspace = (await CreateTestLspServerAsync(new string[] { "", "" })).TestWorkspace; + using var server = await CreateTestLspServerAsync(new string[] { "", "" }); + var workspace = server.TestWorkspace; var documents = workspace.CurrentSolution.Projects.First().Documents.ToImmutableArray(); var diagnosticsMock = new Mock(MockBehavior.Strict); @@ -272,7 +279,8 @@ public async Task RemoveDiagnosticForMappedFileToManyDocumentsTestAsync() [Fact] public async Task ClearAllDiagnosticsForMappedFilesTestAsync() { - using var workspace = (await CreateTestLspServerAsync("")).TestWorkspace; + using var server = await CreateTestLspServerAsync(""); + var workspace = server.TestWorkspace; var document = workspace.CurrentSolution.Projects.First().Documents.First(); var diagnosticsMock = new Mock(MockBehavior.Strict); @@ -318,7 +326,8 @@ await CreateMockDiagnosticDatasWithMappedLocationAsync(document, ("id1", mappedF [Fact] public async Task ClearAllDiagnosticsForMappedFileToManyDocumentsTestAsync() { - using var workspace = (await CreateTestLspServerAsync(new string[] { "", "" })).TestWorkspace; + using var server = await CreateTestLspServerAsync(new string[] { "", "" }); + var workspace = server.TestWorkspace; var documents = workspace.CurrentSolution.Projects.First().Documents.ToImmutableArray(); var diagnosticsMock = new Mock(MockBehavior.Strict); @@ -423,7 +432,7 @@ static VisualStudioInProcLanguageServer CreateLanguageServer(Stream inputStream, ProtocolConstants.RoslynLspLanguages, clientName: null, userVisibleServerName: string.Empty, - telemetryServerTypeName: string.Empty); + telemetryServerTypeName: "TestPushDiagnosticsServer"); jsonRpc.StartListening(); return languageServer; diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index 9bbd8a6a68365..20cd05af64b22 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -744,6 +744,29 @@ public async Task TestPartialProjectSync_Options1() Assert.Contains(LanguageNames.VisualBasic, project2Options.GetTestAccessor().Languages); } + [Fact] + public async Task TestPartialProjectSync_ReferenceToNonExistentProject() + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + + // This reference a project that doesn't exist. + // Ensure that it's still fine to get the checksum for this project we have. + project1 = project1.AddProjectReference(new ProjectReference(ProjectId.CreateNewId())); + + solution = project1.Solution; + + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + + var project1Checksum = await solution.State.GetChecksumAsync(project1.Id, CancellationToken.None); + } + private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) { using var workspace = TestWorkspace.CreateCSharp(code); diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/FileChangeTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/FileChangeTests.vb new file mode 100644 index 0000000000000..f51fd89f10137 --- /dev/null +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/FileChangeTests.vb @@ -0,0 +1,63 @@ +' 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.Threading +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +Imports Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework +Imports Roslyn.Test.Utilities + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim + <[UseExportProvider]> + Public Class FileChangeTests + + + Public Async Function FileChangeAfterRemovalInUncommittedBatchIgnored(withDirectoryWatch As Boolean) As Task + Using environment = New TestEnvironment() + Dim projectInfo = New VisualStudioProjectCreationInfo + + ' If we have a project directory, then we'll also have a watch for the entire directory; + ' test both cases + If withDirectoryWatch Then + projectInfo.FilePath = "Z:\Project.csproj" + End If + + Dim project = Await environment.ProjectFactory.CreateAndAddToWorkspaceAsync( + "project", LanguageNames.CSharp, projectInfo, CancellationToken.None) + + project.AddSourceFile("Z:\Foo.cs") + + Using project.CreateBatchScope() + ' This shouldn't throw + Await environment.RaiseStaleFileChangeAsync("Z:\Foo.cs", Sub() project.RemoveSourceFile("Z:\Foo.cs")) + End Using + End Using + End Function + + + + Public Async Function FileChangeAfterRemoveOfProjectIgnored(withDirectoryWatch As Boolean) As Task + Using environment = New TestEnvironment() + Dim projectInfo = New VisualStudioProjectCreationInfo + + ' If we have a project directory, then we'll also have a watch for the entire directory; + ' test both cases + If withDirectoryWatch Then + projectInfo.FilePath = "Z:\Project.csproj" + End If + + Dim project = Await environment.ProjectFactory.CreateAndAddToWorkspaceAsync( + "project", LanguageNames.CSharp, projectInfo, CancellationToken.None) + + project.AddSourceFile("Z:\Foo.cs") + + Using project.CreateBatchScope() + ' This shouldn't throw + Await environment.RaiseStaleFileChangeAsync("Z:\Foo.cs", Sub() project.RemoveFromWorkspace()) + End Using + End Using + End Function + End Class +End Namespace diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/SourceTextContainerTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/SourceTextContainerTests.vb new file mode 100644 index 0000000000000..b891a639ef2ca --- /dev/null +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/SourceTextContainerTests.vb @@ -0,0 +1,37 @@ +' 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.Threading +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework +Imports Microsoft.VisualStudio.Text +Imports Roslyn.Test.Utilities + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim + <[UseExportProvider]> + Public Class SourceTextContainerTests + + Public Async Function AddAndRemoveWorks() As Task + Using environment = New TestEnvironment(GetType(TestDynamicFileInfoProviderThatProducesNoFiles)) + Dim project = Await environment.ProjectFactory.CreateAndAddToWorkspaceAsync( + "project", LanguageNames.CSharp, CancellationToken.None) + + Dim textBufferFactory = environment.ExportProvider.GetExportedValue(Of ITextBufferFactoryService)() + Dim sourceTextContainer = textBufferFactory.CreateTextBuffer().AsTextContainer() + + project.AddSourceTextContainer(sourceTextContainer, "Z:\Test.cs") + + Assert.Single(environment.Workspace.GetOpenDocumentIds()) + + project.RemoveSourceTextContainer(sourceTextContainer) + + Assert.Empty(environment.Workspace.GetOpenDocumentIds()) + End Using + End Function + End Class +End Namespace + diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/AbstractEditorTest.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/AbstractEditorTest.cs index 5fdf07fc266a7..510299cd8280d 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/AbstractEditorTest.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/AbstractEditorTest.cs @@ -5,11 +5,9 @@ #nullable disable using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.IntegrationTest.Utilities; using Microsoft.VisualStudio.IntegrationTest.Utilities.Common; using Roslyn.Test.Utilities; -using Xunit.Abstractions; using ProjectUtils = Microsoft.VisualStudio.IntegrationTest.Utilities.Common.ProjectUtils; namespace Roslyn.VisualStudio.IntegrationTests @@ -61,11 +59,6 @@ not WellKnownProjectTemplates.CSharpNetCoreClassLibrary and VisualStudio.Editor.SetUseSuggestionMode(false); ClearEditor(); } - - // Work around potential hangs in 16.10p2 caused by the roslyn LSP server not completing initialization before solution closed. - // By waiting for the async operation tracking roslyn LSP server activation to complete we should never - // encounter the scenario where the solution closes while activation is incomplete. - VisualStudio.Workspace.WaitForAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.LanguageServer); } } diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpBuild.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpBuild.cs index f1df3a86a9a7d..b7f9ba263ff59 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpBuild.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpBuild.cs @@ -50,7 +50,7 @@ static void Main(string[] args) // TODO: Validate build works as expected } - [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/18204"), Trait(Traits.Feature, Traits.Features.Build)] + [WpfFact, Trait(Traits.Feature, Traits.Features.Build)] public void BuildWithCommandLine() { VisualStudio.SolutionExplorer.SaveAll(); diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpProjectExistsUIContext.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpProjectExistsUIContext.cs index 6fb26b2f79b64..23f72c96df4c7 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpProjectExistsUIContext.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpProjectExistsUIContext.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.IntegrationTest.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -39,6 +40,7 @@ public void ProjectContextChanges() Assert.True(VisualStudio.Shell.IsUIContextActive(Guids.CSharpProjectExistsInWorkspaceUIContext)); VisualStudio.SolutionExplorer.CloseSolution(); + VisualStudio.Workspace.WaitForAllAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.Workspace); Assert.False(VisualStudio.Shell.IsUIContextActive(Guids.CSharpProjectExistsInWorkspaceUIContext)); } diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicNavigationBar.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicNavigationBar.cs index 4de1f04ea65d4..f6a423f11940d 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicNavigationBar.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicNavigationBar.cs @@ -101,6 +101,75 @@ Public Sub New() VisualStudio.Editor.Verify.CurrentLineText("$$", assertCaretPosition: true); } + [WpfFact] + [Trait(Traits.Feature, Traits.Features.NavigationBar)] + public void VerifyEvents() + { + SetUpEditor(@" +$$Class Item1 + Public Event EvA As Action + Public Event EvB As Action +End Class + +Class Item2 + Public Event EvX As Action + Public Event EvY As Action +End Class + +Partial Class C + WithEvents item1 As Item1 + WithEvents item2 As Item2 +End Class + +Partial Class C + Private Sub item1_EvA() Handles item1.EvA + ' 1 + End Sub + + Private Sub item1_EvB() Handles item1.EvB + ' 2 + End Sub + + Private Sub item2_EvX() Handles item2.EvX + ' 3 + End Sub + + Private Sub item2_EvY() Handles item2.EvY + ' 4 + End Sub +End Class"); + + VisualStudio.Editor.PlaceCaret("' 1"); + + VerifyLeftSelected("item1"); + VerifyRightSelected("EvA"); + + VisualStudio.Editor.PlaceCaret("' 2"); + + VerifyLeftSelected("item1"); + VerifyRightSelected("EvB"); + + VisualStudio.Editor.PlaceCaret("' 3"); + + VerifyLeftSelected("item2"); + VerifyRightSelected("EvX"); + + VisualStudio.Editor.PlaceCaret("' 4"); + + VerifyLeftSelected("item2"); + VerifyRightSelected("EvY"); + + // Selecting an event should update the selected member in the type list. + VisualStudio.Editor.SelectMemberNavBarItem("EvX"); + VisualStudio.Editor.Verify.CurrentLineText(" $$' 3", assertCaretPosition: true, trimWhitespace: false); + + // Selecting an WithEvents member in the type list should have no impact on position. + // But it should update the items in the member list. + VisualStudio.Editor.SelectTypeNavBarItem("item1"); + VisualStudio.Editor.Verify.CurrentLineText(" $$' 3", assertCaretPosition: true, trimWhitespace: false); + Assert.Equal(new[] { "EvA", "EvB" }, VisualStudio.Editor.GetMemberNavBarItems()); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.NavigationBar)] public void VerifyOption() { diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicProjectExistsUIContext.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicProjectExistsUIContext.cs index cffe7c9a8505e..83822a4f21dae 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicProjectExistsUIContext.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicProjectExistsUIContext.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.IntegrationTest.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -37,6 +38,7 @@ public void ProjectContextChanges() Assert.True(VisualStudio.Shell.IsUIContextActive(Guids.VisualBasicProjectExistsInWorkspaceUIContext)); VisualStudio.SolutionExplorer.CloseSolution(); + VisualStudio.Workspace.WaitForAllAsyncOperations(Helper.HangMitigatingTimeout, FeatureAttribute.Workspace); Assert.False(VisualStudio.Shell.IsUIContextActive(Guids.VisualBasicProjectExistsInWorkspaceUIContext)); } diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/AbstractEditorTest.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/AbstractEditorTest.cs new file mode 100644 index 0000000000000..92db1c1eb0ec6 --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/AbstractEditorTest.cs @@ -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. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.IntegrationTest.Utilities; +using Roslyn.Test.Utilities; +using Roslyn.Utilities; +using Roslyn.VisualStudio.IntegrationTests.InProcess; + +namespace Roslyn.VisualStudio.IntegrationTests +{ + public abstract class AbstractEditorTest : AbstractIntegrationTest + { + private readonly string? _solutionName; + private readonly string? _projectTemplate; + + protected AbstractEditorTest() + { + } + + protected AbstractEditorTest(string solutionName) + : this(solutionName, WellKnownProjectTemplates.ClassLibrary) + { + } + + protected AbstractEditorTest(string solutionName, string projectTemplate) + { + _solutionName = solutionName; + _projectTemplate = projectTemplate; + } + + protected abstract string LanguageName { get; } + + public override async Task InitializeAsync() + { + await base.InitializeAsync().ConfigureAwait(true); + + if (_solutionName != null) + { + RoslynDebug.AssertNotNull(_projectTemplate); + + await TestServices.SolutionExplorer.CreateSolutionAsync(_solutionName, HangMitigatingCancellationToken); + await TestServices.SolutionExplorer.AddProjectAsync(ProjectName, _projectTemplate, LanguageName, HangMitigatingCancellationToken); + await TestServices.SolutionExplorer.RestoreNuGetPackagesAsync(ProjectName, HangMitigatingCancellationToken); + + // Winforms and XAML do not open text files on creation + // so these editor tasks will not work if that is the project template being used. + if (_projectTemplate is not WellKnownProjectTemplates.WinFormsApplication and + not WellKnownProjectTemplates.WpfApplication and + not WellKnownProjectTemplates.CSharpNetCoreClassLibrary and + not WellKnownProjectTemplates.VisualBasicNetCoreClassLibrary) + { + await TestServices.Editor.SetUseSuggestionModeAsync(false, HangMitigatingCancellationToken); + await ClearEditorAsync(HangMitigatingCancellationToken); + } + } + } + + protected async Task ClearEditorAsync(CancellationToken cancellationToken) + => await SetUpEditorAsync("$$", cancellationToken); + + protected async Task SetUpEditorAsync(string markupCode, CancellationToken cancellationToken) + { + MarkupTestFile.GetPosition(markupCode, out var code, out int caretPosition); + + await TestServices.Editor.DismissCompletionSessionsAsync(cancellationToken); + await TestServices.Editor.DismissLightBulbSessionAsync(cancellationToken); + + var originalValue = await TestServices.Workspace.IsPrettyListingOnAsync(LanguageName, cancellationToken); + + await TestServices.Workspace.SetPrettyListingAsync(LanguageName, false, cancellationToken); + try + { + await TestServices.Editor.SetTextAsync(code, cancellationToken); + await TestServices.Editor.MoveCaretAsync(caretPosition, cancellationToken); + await TestServices.Editor.ActivateAsync(cancellationToken); + } + finally + { + await TestServices.Workspace.SetPrettyListingAsync(LanguageName, originalValue, cancellationToken); + } + } + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/AbstractIntegrationTest.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/AbstractIntegrationTest.cs new file mode 100644 index 0000000000000..639044fdc9197 --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/AbstractIntegrationTest.cs @@ -0,0 +1,130 @@ +// 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 System.Windows; +using System.Windows.Threading; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; +using Roslyn.VisualStudio.IntegrationTests.InProcess; +using Xunit; + +namespace Roslyn.VisualStudio.IntegrationTests +{ + [IdeSettings(MinVersion = VisualStudioVersion.VS2022, RootSuffix = "RoslynDev")] + public abstract class AbstractIntegrationTest : IAsyncLifetime, IDisposable + { + protected const string ProjectName = "TestProj"; + protected const string SolutionName = "TestSolution"; + + /// + /// A long timeout used to avoid hangs in tests, where a test failure manifests as an operation never occurring. + /// + public static readonly TimeSpan HangMitigatingTimeout = TimeSpan.FromMinutes(4); + + private JoinableTaskContext? _joinableTaskContext; + private JoinableTaskCollection? _joinableTaskCollection; + private JoinableTaskFactory? _joinableTaskFactory; + + private TestServices? _testServices; + + private readonly CancellationTokenSource _hangMitigatingCancellationTokenSource; + + protected AbstractIntegrationTest() + { + Assert.True(Application.Current.Dispatcher.CheckAccess()); + + JoinableTaskContext = ThreadHelper.JoinableTaskContext; + + _hangMitigatingCancellationTokenSource = new CancellationTokenSource(HangMitigatingTimeout); + } + + [NotNull] + protected JoinableTaskContext? JoinableTaskContext + { + get + { + return _joinableTaskContext ?? throw new InvalidOperationException(); + } + + private set + { + if (value == _joinableTaskContext) + { + return; + } + + if (value is null) + { + _joinableTaskContext = null; + _joinableTaskCollection = null; + _joinableTaskFactory = null; + } + else + { + _joinableTaskContext = value; + _joinableTaskCollection = value.CreateCollection(); + _joinableTaskFactory = value.CreateFactory(_joinableTaskCollection).WithPriority(Application.Current.Dispatcher, DispatcherPriority.Background); + } + } + } + + [NotNull] + private protected TestServices? TestServices + { + get + { + return _testServices ?? throw new InvalidOperationException(); + } + + private set + { + _testServices = value; + } + } + + protected JoinableTaskFactory JoinableTaskFactory + => _joinableTaskFactory ?? throw new InvalidOperationException(); + + protected CancellationToken HangMitigatingCancellationToken + => _hangMitigatingCancellationTokenSource.Token; + + public virtual async Task InitializeAsync() + { + TestServices = await CreateTestServicesAsync(); + } + + /// + /// This method implements , and is used for releasing resources + /// created by . This method is only called if + /// completes successfully. + /// + public virtual async Task DisposeAsync() + { + await TestServices.SolutionExplorer.CloseSolutionAsync(HangMitigatingCancellationToken); + + if (_joinableTaskCollection is object) + { + await _joinableTaskCollection.JoinTillEmptyAsync(HangMitigatingCancellationToken); + } + + JoinableTaskContext = null; + } + + /// + /// This method provides the implementation for . + /// This method is called via the interface if the constructor completes successfully. + /// The may or may not have completed successfully. + /// + public virtual void Dispose() + { + } + + private protected virtual async Task CreateTestServicesAsync() + => await TestServices.CreateAsync(JoinableTaskFactory); + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs new file mode 100644 index 0000000000000..bedab5b9b22f7 --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs @@ -0,0 +1,525 @@ +// 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.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editor.Implementation.Suggestions; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.IntegrationTest.Utilities; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Utilities; +using IOleCommandTarget = Microsoft.VisualStudio.OLE.Interop.IOleCommandTarget; +using OLECMDEXECOPT = Microsoft.VisualStudio.OLE.Interop.OLECMDEXECOPT; + +namespace Roslyn.VisualStudio.IntegrationTests.InProcess +{ + internal class EditorInProcess : InProcComponent + { + public EditorInProcess(TestServices testServices) + : base(testServices) + { + } + + public async Task GetActiveTextViewAsync(CancellationToken cancellationToken) + => (await GetActiveTextViewHostAsync(cancellationToken)).TextView; + + public async Task SetTextAsync(string text, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + var textSnapshot = view.TextSnapshot; + var replacementSpan = new SnapshotSpan(textSnapshot, 0, textSnapshot.Length); + view.TextBuffer.Replace(replacementSpan, text); + } + + public async Task MoveCaretAsync(int position, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + + var subjectBuffer = view.GetBufferContainingCaret(); + Assumes.Present(subjectBuffer); + + var point = new SnapshotPoint(subjectBuffer.CurrentSnapshot, position); + + view.Caret.MoveTo(point); + } + + public async Task ActivateAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var dte = await GetRequiredGlobalServiceAsync(cancellationToken); + dte.ActiveDocument.Activate(); + } + + public async Task IsUseSuggestionModeOnAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var textView = await GetActiveTextViewAsync(cancellationToken); + + var subjectBuffer = textView.GetBufferContainingCaret(); + Assumes.Present(subjectBuffer); + + var options = textView.Options.GlobalOptions; + EditorOptionKey optionKey; + bool defaultOption; + if (IsDebuggerTextView(textView)) + { + optionKey = new EditorOptionKey(PredefinedCompletionNames.SuggestionModeInDebuggerCompletionOptionName); + defaultOption = true; + } + else + { + optionKey = new EditorOptionKey(PredefinedCompletionNames.SuggestionModeInCompletionOptionName); + defaultOption = false; + } + + if (!options.IsOptionDefined(optionKey, localScopeOnly: false)) + { + return defaultOption; + } + + return options.GetOptionValue(optionKey); + + static bool IsDebuggerTextView(IWpfTextView textView) + => textView.Roles.Contains("DEBUGVIEW"); + } + + public async Task SetUseSuggestionModeAsync(bool value, CancellationToken cancellationToken) + { + if (await IsUseSuggestionModeOnAsync(cancellationToken) != value) + { + var dispatcher = await GetRequiredGlobalServiceAsync(cancellationToken); + ErrorHandler.ThrowOnFailure(dispatcher.Exec(typeof(VSConstants.VSStd2KCmdID).GUID, (uint)VSConstants.VSStd2KCmdID.ToggleConsumeFirstCompletionMode, (uint)OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT, IntPtr.Zero, IntPtr.Zero)); + + if (await IsUseSuggestionModeOnAsync(cancellationToken) != value) + { + throw new InvalidOperationException($"{WellKnownCommandNames.Edit_ToggleCompletionMode} did not leave the editor in the expected state."); + } + } + + if (!value) + { + // For blocking completion mode, make sure we don't have responsive completion interfering when + // integration tests run slowly. + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + var options = view.Options.GlobalOptions; + options.SetOptionValue(DefaultOptions.ResponsiveCompletionOptionId, false); + + var latencyGuardOptionKey = new EditorOptionKey("EnableTypingLatencyGuard"); + options.SetOptionValue(latencyGuardOptionKey, false); + } + } + + public async Task DismissLightBulbSessionAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + + var broker = await GetComponentModelServiceAsync(cancellationToken); + broker.DismissSession(view); + } + + public async Task DismissCompletionSessionsAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + await WaitForCompletionSetAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + + var broker = await GetComponentModelServiceAsync(cancellationToken); + broker.DismissAllSessions(view); + } + + public async Task ShowLightBulbAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var shell = await GetRequiredGlobalServiceAsync(cancellationToken); + var cmdGroup = typeof(VSConstants.VSStd14CmdID).GUID; + var cmdExecOpt = OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER; + + var cmdID = VSConstants.VSStd14CmdID.ShowQuickFixes; + object? obj = null; + shell.PostExecCommand(cmdGroup, (uint)cmdID, (uint)cmdExecOpt, ref obj); + + var view = await GetActiveTextViewAsync(cancellationToken); + var broker = await GetComponentModelServiceAsync(cancellationToken); + await LightBulbHelper.WaitForLightBulbSessionAsync(broker, view, cancellationToken); + } + + public async Task InvokeCodeActionListAsync(CancellationToken cancellationToken) + { + await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.SolutionCrawler, cancellationToken); + await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.DiagnosticService, cancellationToken); + + await ShowLightBulbAsync(cancellationToken); + await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.LightBulb, cancellationToken); + } + + public async Task IsLightBulbSessionExpandedAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + + var broker = await GetComponentModelServiceAsync(cancellationToken); + if (!broker.IsLightBulbSessionActive(view)) + { + return false; + } + + var session = broker.GetSession(view); + if (session == null || !session.IsExpanded) + { + return false; + } + + return true; + } + + public async Task GetLightBulbActionsAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + + var broker = await GetComponentModelServiceAsync(cancellationToken); + return (await GetLightBulbActionsAsync(broker, view, cancellationToken)).Select(a => a.DisplayText).ToArray(); + } + + public async Task ApplyLightBulbActionAsync(string actionName, FixAllScope? fixAllScope, bool blockUntilComplete, CancellationToken cancellationToken) + { + var lightBulbAction = GetLightBulbApplicationAction(actionName, fixAllScope, blockUntilComplete); + + var listenerProvider = await GetComponentModelServiceAsync(cancellationToken); + var listener = listenerProvider.GetListener(FeatureAttribute.LightBulb); + + var task = JoinableTaskFactory.RunAsync(async () => + { + using var _ = listener.BeginAsyncOperation(nameof(ApplyLightBulbActionAsync)); + + await JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + + var activeTextView = await GetActiveTextViewAsync(cancellationToken); + return await lightBulbAction(activeTextView, cancellationToken); + }); + + if (blockUntilComplete) + { + var result = await task.JoinAsync(cancellationToken); + await DismissLightBulbSessionAsync(cancellationToken); + return result; + } + + return true; + } + + private Func> GetLightBulbApplicationAction(string actionName, FixAllScope? fixAllScope, bool willBlockUntilComplete) + { + return async (view, cancellationToken) => + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var broker = await GetComponentModelServiceAsync(cancellationToken); + + var actions = (await GetLightBulbActionsAsync(broker, view, cancellationToken)).ToArray(); + var action = actions.FirstOrDefault(a => a.DisplayText == actionName); + + if (action == null) + { + var sb = new StringBuilder(); + foreach (var item in actions) + { + sb.AppendLine("Actual ISuggestedAction: " + item.DisplayText); + } + + var bufferType = view.TextBuffer.ContentType.DisplayName; + throw new InvalidOperationException( + $"ISuggestedAction {actionName} not found. Buffer content type={bufferType}\r\nActions: {sb}"); + } + + if (fixAllScope != null) + { + if (!action.HasActionSets) + { + throw new InvalidOperationException($"Suggested action '{action.DisplayText}' does not support FixAllOccurrences."); + } + + var actionSetsForAction = await action.GetActionSetsAsync(cancellationToken); + var fixAllAction = await GetFixAllSuggestedActionAsync(actionSetsForAction, fixAllScope.Value, cancellationToken); + if (fixAllAction == null) + { + throw new InvalidOperationException($"Unable to find FixAll in {fixAllScope} code fix for suggested action '{action.DisplayText}'."); + } + + action = fixAllAction; + + if (willBlockUntilComplete + && action is FixAllSuggestedAction fixAllSuggestedAction + && fixAllSuggestedAction.CodeAction is FixSomeCodeAction fixSomeCodeAction) + { + // Ensure the preview changes dialog will not be shown. Since the operation 'willBlockUntilComplete', + // the caller would not be able to interact with the preview changes dialog, and the tests would + // either timeout or deadlock. + fixSomeCodeAction.GetTestAccessor().ShowPreviewChangesDialog = false; + } + + if (string.IsNullOrEmpty(actionName)) + { + return false; + } + + // Dismiss the lightbulb session as we not invoking the original code fix. + broker.DismissSession(view); + } + + if (action is not SuggestedAction suggestedAction) + return true; + + broker.DismissSession(view); + var threadOperationExecutor = await GetComponentModelServiceAsync(cancellationToken); + var guardedOperations = await GetComponentModelServiceAsync(cancellationToken); + threadOperationExecutor.Execute( + title: "Execute Suggested Action", + defaultDescription: Accelerator.StripAccelerators(action.DisplayText, '_'), + allowCancellation: true, + showProgress: true, + action: context => + { + guardedOperations.CallExtensionPoint( + errorSource: suggestedAction, + call: () => suggestedAction.Invoke(context), + exceptionGuardFilter: e => e is not OperationCanceledException); + }); + + return true; + }; + } + + private async Task> GetLightBulbActionsAsync(ILightBulbBroker broker, IWpfTextView view, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + if (!broker.IsLightBulbSessionActive(view)) + { + var bufferType = view.TextBuffer.ContentType.DisplayName; + throw new Exception($"No light bulb session in View! Buffer content type={bufferType}"); + } + + var activeSession = broker.GetSession(view); + if (activeSession == null) + { + var bufferType = view.TextBuffer.ContentType.DisplayName; + throw new InvalidOperationException($"No expanded light bulb session found after View.ShowSmartTag. Buffer content type={bufferType}"); + } + + var actionSets = await LightBulbHelper.WaitForItemsAsync(broker, view, cancellationToken); + return await SelectActionsAsync(actionSets, cancellationToken); + } + + private async Task> SelectActionsAsync(IEnumerable actionSets, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var actions = new List(); + + if (actionSets != null) + { + foreach (var actionSet in actionSets) + { + if (actionSet.Actions != null) + { + foreach (var action in actionSet.Actions) + { + actions.Add(action); + var nestedActionSets = await action.GetActionSetsAsync(cancellationToken); + var nestedActions = await SelectActionsAsync(nestedActionSets, cancellationToken); + actions.AddRange(nestedActions); + } + } + } + } + + return actions; + } + + private async Task GetFixAllSuggestedActionAsync(IEnumerable actionSets, FixAllScope fixAllScope, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + foreach (var actionSet in actionSets) + { + foreach (var action in actionSet.Actions) + { + if (action is FixAllSuggestedAction fixAllSuggestedAction) + { + var fixAllCodeAction = fixAllSuggestedAction.CodeAction as FixSomeCodeAction; + if (fixAllCodeAction?.FixAllState?.Scope == fixAllScope) + { + return fixAllSuggestedAction; + } + } + + if (action.HasActionSets) + { + var nestedActionSets = await action.GetActionSetsAsync(cancellationToken); + var fixAllCodeAction = await GetFixAllSuggestedActionAsync(nestedActionSets, fixAllScope, cancellationToken); + if (fixAllCodeAction != null) + { + return fixAllCodeAction; + } + } + } + } + + return null; + } + + public Task PlaceCaretAsync(string marker, int charsOffset, CancellationToken cancellationToken) + => PlaceCaretAsync(marker, charsOffset, occurrence: 0, extendSelection: false, selectBlock: false, cancellationToken); + + public async Task PlaceCaretAsync( + string marker, + int charsOffset, + int occurrence, + bool extendSelection, + bool selectBlock, + CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + + var dte = await GetRequiredGlobalServiceAsync(cancellationToken); + dte.Find.FindWhat = marker; + dte.Find.MatchCase = true; + dte.Find.MatchInHiddenText = true; + dte.Find.Target = EnvDTE.vsFindTarget.vsFindTargetCurrentDocument; + dte.Find.Action = EnvDTE.vsFindAction.vsFindActionFind; + + var originalPosition = await GetCaretPositionAsync(cancellationToken); + view.Caret.MoveTo(new SnapshotPoint(view.GetBufferContainingCaret()!.CurrentSnapshot, 0)); + + if (occurrence > 0) + { + var result = EnvDTE.vsFindResult.vsFindResultNotFound; + for (var i = 0; i < occurrence; i++) + { + result = dte.Find.Execute(); + } + + if (result != EnvDTE.vsFindResult.vsFindResultFound) + { + throw new Exception("Occurrence " + occurrence + " of marker '" + marker + "' not found in text: " + view.TextSnapshot.GetText()); + } + } + else + { + var result = dte.Find.Execute(); + if (result != EnvDTE.vsFindResult.vsFindResultFound) + { + throw new Exception("Marker '" + marker + "' not found in text: " + view.TextSnapshot.GetText()); + } + } + + if (charsOffset > 0) + { + for (var i = 0; i < charsOffset - 1; i++) + { + view.Caret.MoveToNextCaretPosition(); + } + + view.Selection.Clear(); + } + + if (charsOffset < 0) + { + // On the first negative charsOffset, move to anchor-point position, as if the user hit the LEFT key + view.Caret.MoveTo(new SnapshotPoint(view.TextSnapshot, view.Selection.AnchorPoint.Position.Position)); + + for (var i = 0; i < -charsOffset - 1; i++) + { + view.Caret.MoveToPreviousCaretPosition(); + } + + view.Selection.Clear(); + } + + if (extendSelection) + { + var newPosition = view.Selection.ActivePoint.Position.Position; + view.Selection.Select(new VirtualSnapshotPoint(view.TextSnapshot, originalPosition), new VirtualSnapshotPoint(view.TextSnapshot, newPosition)); + view.Selection.Mode = selectBlock ? TextSelectionMode.Box : TextSelectionMode.Stream; + } + } + + public async Task GetCaretPositionAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + + var subjectBuffer = view.GetBufferContainingCaret(); + Assumes.Present(subjectBuffer); + + var bufferPosition = view.Caret.Position.BufferPosition; + return bufferPosition.Position; + } + + private async Task WaitForCompletionSetAsync(CancellationToken cancellationToken) + { + await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.CompletionSet, cancellationToken); + } + + private async Task GetActiveTextViewHostAsync(CancellationToken cancellationToken) + { + // The active text view might not have finished composing yet, waiting for the application to 'idle' + // means that it is done pumping messages (including WM_PAINT) and the window should return the correct text + // view. + await WaitForApplicationIdleAsync(cancellationToken); + + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var activeVsTextView = (IVsUserData)await GetActiveVsTextViewAsync(cancellationToken); + + ErrorHandler.ThrowOnFailure(activeVsTextView.GetData(DefGuidList.guidIWpfTextViewHost, out var wpfTextViewHost)); + + return (IWpfTextViewHost)wpfTextViewHost; + } + + private async Task GetActiveVsTextViewAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var vsTextManager = await GetRequiredGlobalServiceAsync(cancellationToken); + + ErrorHandler.ThrowOnFailure(vsTextManager.GetActiveView(fMustHaveFocus: 1, pBuffer: null, ppView: out var vsTextView)); + + return vsTextView; + } + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorVerifierInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorVerifierInProcess.cs new file mode 100644 index 0000000000000..0c39b6fdfe3d4 --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorVerifierInProcess.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; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.VisualStudio.IntegrationTest.Utilities; +using Roslyn.Utilities; + +namespace Roslyn.VisualStudio.IntegrationTests.InProcess +{ + internal class EditorVerifierInProcess : InProcComponent + { + public EditorVerifierInProcess(TestServices testServices) + : base(testServices) + { + } + + public async Task CodeActionAsync( + string expectedItem, + bool applyFix = false, + bool verifyNotShowing = false, + bool ensureExpectedItemsAreOrdered = false, + FixAllScope? fixAllScope = null, + bool blockUntilComplete = true, + CancellationToken cancellationToken = default) + { + var expectedItems = new[] { expectedItem }; + + bool? applied; + do + { + cancellationToken.ThrowIfCancellationRequested(); + + applied = await CodeActionsAsync(expectedItems, applyFix ? expectedItem : null, verifyNotShowing, + ensureExpectedItemsAreOrdered, fixAllScope, blockUntilComplete, cancellationToken); + } while (applied is false); + } + + /// + /// + /// if is specified and the fix is successfully applied + /// if is specified but the fix is not successfully applied + /// if is false, so there is no fix to apply + /// + /// + public async Task CodeActionsAsync( + IEnumerable expectedItems, + string? applyFix = null, + bool verifyNotShowing = false, + bool ensureExpectedItemsAreOrdered = false, + FixAllScope? fixAllScope = null, + bool blockUntilComplete = true, + CancellationToken cancellationToken = default) + { + await TestServices.Editor.ShowLightBulbAsync(cancellationToken); + + if (verifyNotShowing) + { + await CodeActionsNotShowingAsync(cancellationToken); + return null; + } + + var actions = await TestServices.Editor.GetLightBulbActionsAsync(cancellationToken); + + if (expectedItems != null && expectedItems.Any()) + { + if (ensureExpectedItemsAreOrdered) + { + TestUtilities.ThrowIfExpectedItemNotFoundInOrder( + actions, + expectedItems); + } + else + { + TestUtilities.ThrowIfExpectedItemNotFound( + actions, + expectedItems); + } + } + + if (fixAllScope.HasValue) + { + Assumes.Present(applyFix); + } + + if (!RoslynString.IsNullOrEmpty(applyFix)) + { + var result = await TestServices.Editor.ApplyLightBulbActionAsync(applyFix, fixAllScope, blockUntilComplete, cancellationToken); + + if (blockUntilComplete) + { + // wait for action to complete + await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.LightBulb, cancellationToken); + } + + return result; + } + + return null; + } + + public async Task CodeActionsNotShowingAsync(CancellationToken cancellationToken) + { + if (await TestServices.Editor.IsLightBulbSessionExpandedAsync(cancellationToken)) + { + throw new InvalidOperationException("Expected no light bulb session, but one was found."); + } + } + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ErrorListInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ErrorListInProcess.cs new file mode 100644 index 0000000000000..23379c20c4316 --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ErrorListInProcess.cs @@ -0,0 +1,14 @@ +// 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 Roslyn.VisualStudio.IntegrationTests.InProcess +{ + internal class ErrorListInProcess : InProcComponent + { + public ErrorListInProcess(TestServices testServices) + : base(testServices) + { + } + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/Helper.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/Helper.cs new file mode 100644 index 0000000000000..60f92beb53343 --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/Helper.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; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Roslyn.VisualStudio.IntegrationTests.InProcess +{ + internal static class Helper + { + /// + /// This method will retry the asynchronous action represented by , + /// waiting for time after each retry. If a given retry returns a value + /// other than the default value of , this value is returned. + /// + /// the asynchronous action to retry + /// the amount of time to wait between retries + /// type of return value + /// the return value of + public static Task RetryAsync(Func> action, TimeSpan delay, CancellationToken cancellationToken) + { + return RetryAsyncHelper( + async cancellationToken => + { + try + { + return await action(cancellationToken); + } + catch (COMException) + { + // Devenv can throw COMExceptions if it's busy when we make DTE calls. + return default; + } + }, + delay, + cancellationToken); + } + + private static async Task RetryAsyncHelper(Func> action, TimeSpan delay, CancellationToken cancellationToken) + { + while (true) + { + var retval = await action(cancellationToken).ConfigureAwait(true); + if (!Equals(default(T), retval)) + { + return retval; + } + + await Task.Delay(delay, cancellationToken).ConfigureAwait(true); + } + } + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/InProcComponent.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/InProcComponent.cs new file mode 100644 index 0000000000000..49ac125c185fa --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/InProcComponent.cs @@ -0,0 +1,67 @@ +// 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 System.Windows; +using System.Windows.Threading; +using Microsoft; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Threading; +using Xunit.Threading; + +namespace Roslyn.VisualStudio.IntegrationTests.InProcess +{ + internal abstract class InProcComponent + { + protected InProcComponent(TestServices testServices) + { + TestServices = testServices ?? throw new ArgumentNullException(nameof(testServices)); + } + + public TestServices TestServices { get; } + + protected JoinableTaskFactory JoinableTaskFactory => TestServices.JoinableTaskFactory; + + protected async Task GetRequiredGlobalServiceAsync(CancellationToken cancellationToken) + where TService : class + where TInterface : class + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var serviceProvider = (IAsyncServiceProvider2?)await AsyncServiceProvider.GlobalProvider.GetServiceAsync(typeof(SAsyncServiceProvider)).WithCancellation(cancellationToken); + Assumes.Present(serviceProvider); + + var @interface = (TInterface?)await serviceProvider.GetServiceAsync(typeof(TService)).WithCancellation(cancellationToken); + Assumes.Present(@interface); + return @interface; + } + + protected async Task GetComponentModelServiceAsync(CancellationToken cancellationToken) + where TService : class + { + var componentModel = await GetRequiredGlobalServiceAsync(cancellationToken); + return componentModel.GetService(); + } + + /// + /// Waiting for the application to 'idle' means that it is done pumping messages (including WM_PAINT). + /// + /// The cancellation token that the operation will observe. + /// A representing the asynchronous operation. + protected static async Task WaitForApplicationIdleAsync(CancellationToken cancellationToken) + { + var synchronizationContext = new DispatcherSynchronizationContext(Application.Current.Dispatcher, DispatcherPriority.ApplicationIdle); + var taskScheduler = new SynchronizationContextTaskScheduler(synchronizationContext); + await Task.Factory.StartNew( + () => { }, + cancellationToken, + TaskCreationOptions.None, + taskScheduler); + } + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/LightBulbHelper.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/LightBulbHelper.cs new file mode 100644 index 0000000000000..e9dccfb8b3a47 --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/LightBulbHelper.cs @@ -0,0 +1,90 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Threading; + +namespace Roslyn.VisualStudio.IntegrationTests.InProcess +{ + public static class LightBulbHelper + { + public static async Task WaitForLightBulbSessionAsync(ILightBulbBroker broker, IWpfTextView view, CancellationToken cancellationToken) + { + var startTime = DateTimeOffset.Now; + + var active = await Helper.RetryAsync(async cancellationToken => + { + if (broker.IsLightBulbSessionActive(view)) + { + return true; + } + + if (cancellationToken.IsCancellationRequested) + { + throw new InvalidOperationException("Expected a light bulb session to appear."); + } + + // checking whether there is any suggested action is async up to editor layer and our waiter doesn't track up to that point. + // so here, we have no other way than sleep (with timeout) to see LB is available. + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + + return broker.IsLightBulbSessionActive(view); + }, TimeSpan.FromMilliseconds(1), cancellationToken); + + if (!active) + return false; + + await WaitForItemsAsync(broker, view, cancellationToken); + return true; + } + + public static async Task> WaitForItemsAsync(ILightBulbBroker broker, IWpfTextView view, CancellationToken cancellationToken) + { + var activeSession = broker.GetSession(view); + if (activeSession == null) + { + var bufferType = view.TextBuffer.ContentType.DisplayName; + throw new InvalidOperationException($"No expanded light bulb session found after View.ShowSmartTag. Buffer content type={bufferType}"); + } + + var asyncSession = (IAsyncLightBulbSession)activeSession; + var tcs = new TaskCompletionSource>(); + + EventHandler? handler = null; + handler = (s, e) => + { + // ignore these. we care about when the lightbulb items are all completed. + if (e.Status == QuerySuggestedActionCompletionStatus.InProgress) + return; + + if (e.Status == QuerySuggestedActionCompletionStatus.Completed) + tcs.SetResult(e.ActionSets.ToList()); + else + tcs.SetException(new InvalidOperationException($"Light bulb transitioned to non-complete state: {e.Status}")); + + asyncSession.SuggestedActionsUpdated -= handler; + }; + + asyncSession.SuggestedActionsUpdated += handler; + + asyncSession.Dismissed += (_, _) => tcs.TrySetCanceled(new CancellationToken(true)); + + if (asyncSession.IsDismissed) + tcs.TrySetCanceled(new CancellationToken(true)); + + // Calling PopulateWithData ensures the underlying session will call SuggestedActionsUpdated at least once + // with the latest data computed. This is needed so that if the lightbulb computation is already complete + // that we hear about the results. + asyncSession.PopulateWithData(overrideRequestedActionCategories: null, operationContext: null); + + return await tcs.Task.WithCancellation(cancellationToken); + } + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/SolutionExplorerInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/SolutionExplorerInProcess.cs new file mode 100644 index 0000000000000..6dac30e8125d1 --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/SolutionExplorerInProcess.cs @@ -0,0 +1,553 @@ +// 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.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft; +using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.IntegrationTest.Utilities; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Threading; +using NuGet.SolutionRestoreManager; +using IAsyncDisposable = System.IAsyncDisposable; +using Reference = VSLangProj.Reference; +using VSProject = VSLangProj.VSProject; + +namespace Roslyn.VisualStudio.IntegrationTests.InProcess +{ + internal class SolutionExplorerInProcess : InProcComponent + { + public SolutionExplorerInProcess(TestServices testServices) + : base(testServices) + { + } + + public async Task CreateSolutionAsync(string solutionName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var solutionPath = CreateTemporaryPath(); + await CreateSolutionAsync(solutionPath, solutionName, cancellationToken); + } + + public async Task CreateSolutionAsync(string solutionName, XElement solutionElement, CancellationToken cancellationToken) + { + if (solutionElement.Name != "Solution") + { + throw new ArgumentException(nameof(solutionElement)); + } + + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + await CreateSolutionAsync(solutionName, cancellationToken); + + foreach (var projectElement in solutionElement.Elements("Project")) + { + await CreateProjectAsync(projectElement, cancellationToken); + } + + foreach (var projectElement in solutionElement.Elements("Project")) + { + var projectReferences = projectElement.Attribute("ProjectReferences")?.Value; + if (projectReferences != null) + { + var projectName = projectElement.Attribute("ProjectName").Value; + foreach (var projectReference in projectReferences.Split(';')) + { + await AddProjectReferenceAsync(projectName, projectReference, cancellationToken); + } + } + } + } + + private async Task CreateProjectAsync(XElement projectElement, CancellationToken cancellationToken) + { + const string language = "Language"; + const string name = "ProjectName"; + const string template = "ProjectTemplate"; + var languageName = projectElement.Attribute(language)?.Value + ?? throw new ArgumentException($"You must specify an attribute called '{language}' on a project element."); + var projectName = projectElement.Attribute(name)?.Value + ?? throw new ArgumentException($"You must specify an attribute called '{name}' on a project element."); + var projectTemplate = projectElement.Attribute(template)?.Value + ?? throw new ArgumentException($"You must specify an attribute called '{template}' on a project element."); + + var projectPath = Path.Combine(await GetDirectoryNameAsync(cancellationToken), projectName); + var projectTemplatePath = await GetProjectTemplatePathAsync(projectTemplate, ConvertLanguageName(languageName), cancellationToken); + + var solution = (await GetRequiredGlobalServiceAsync(cancellationToken)).Solution; + Assumes.Present(solution); + + solution.AddFromTemplate(projectTemplatePath, projectPath, projectName, Exclusive: false); + foreach (var documentElement in projectElement.Elements("Document")) + { + var fileName = documentElement.Attribute("FileName").Value; + await UpdateOrAddFileAsync(projectName, fileName, contents: documentElement.Value, cancellationToken: cancellationToken); + } + } + + public async Task AddProjectReferenceAsync(string projectName, string projectToReferenceName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var project = await GetProjectAsync(projectName, cancellationToken); + var projectToReference = await GetProjectAsync(projectToReferenceName, cancellationToken); + ((VSProject)project.Object).References.AddProject(projectToReference); + } + + private async Task CreateSolutionAsync(string solutionPath, string solutionName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + await CloseSolutionAsync(cancellationToken); + + var solutionFileName = Path.ChangeExtension(solutionName, ".sln"); + Directory.CreateDirectory(solutionPath); + + // Make sure the shell debugger package is loaded so it doesn't try to load during the synchronous portion + // of IVsSolution.CreateSolution. + // + // TODO: Identify the correct tracking bug + _ = await GetRequiredGlobalServiceAsync(cancellationToken); + + var solution = await GetRequiredGlobalServiceAsync(cancellationToken); + ErrorHandler.ThrowOnFailure(solution.CreateSolution(solutionPath, solutionFileName, (uint)__VSCREATESOLUTIONFLAGS.CSF_SILENT)); + ErrorHandler.ThrowOnFailure(solution.SaveSolutionElement((uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, null, 0)); + } + + public async Task GetAssemblyReferencesAsync(string projectName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var project = await GetProjectAsync(projectName, cancellationToken); + var references = ((VSProject)project.Object).References.Cast() + .Where(x => x.SourceProject == null) + .Select(x => x.Name + "," + x.Version + "," + x.PublicKeyToken).ToArray(); + return references; + } + + public async Task GetProjectReferencesAsync(string projectName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var project = await GetProjectAsync(projectName, cancellationToken); + var references = ((VSProject)project.Object).References.Cast().Where(x => x.SourceProject != null).Select(x => x.Name).ToArray(); + return references; + } + + public async Task AddProjectAsync(string projectName, string projectTemplate, string languageName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var projectPath = Path.Combine(await GetDirectoryNameAsync(cancellationToken), projectName); + var projectTemplatePath = await GetProjectTemplatePathAsync(projectTemplate, ConvertLanguageName(languageName), cancellationToken); + var solution = await GetRequiredGlobalServiceAsync(cancellationToken); + ErrorHandler.ThrowOnFailure(solution.AddNewProjectFromTemplate(projectTemplatePath, null, null, projectPath, projectName, null, out _)); + } + + public async Task RestoreNuGetPackagesAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var dte = await GetRequiredGlobalServiceAsync(cancellationToken); + var solution = (EnvDTE80.Solution2)dte.Solution; + foreach (var project in solution.Projects.OfType()) + { + await RestoreNuGetPackagesAsync(project.FullName, cancellationToken); + } + } + + public async Task RestoreNuGetPackagesAsync(string projectName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + await TestServices.Workspace.WaitForProjectSystemAsync(cancellationToken); + + var solutionRestoreService = await GetComponentModelServiceAsync(cancellationToken); + await solutionRestoreService.CurrentRestoreOperation; + + var projectFullPath = (await GetProjectAsync(projectName, cancellationToken)).FullName; + var solutionRestoreStatusProvider = await GetComponentModelServiceAsync(cancellationToken); + if (await solutionRestoreStatusProvider.IsRestoreCompleteAsync(cancellationToken)) + { + return; + } + + var solutionRestoreService2 = (IVsSolutionRestoreService2)solutionRestoreService; + await solutionRestoreService2.NominateProjectAsync(projectFullPath, cancellationToken); + + // Check IsRestoreCompleteAsync until it returns true (this stops the retry because true != default(bool)) + await Helper.RetryAsync( + cancellationToken => solutionRestoreStatusProvider.IsRestoreCompleteAsync(cancellationToken), + TimeSpan.FromMilliseconds(50), + cancellationToken); + } + + public async Task OpenFileAsync(string projectName, string relativeFilePath, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var filePath = await GetAbsolutePathForProjectRelativeFilePathAsync(projectName, relativeFilePath, cancellationToken); + VsShellUtilities.OpenDocument(ServiceProvider.GlobalProvider, filePath, VSConstants.LOGVIEWID.Code_guid, out _, out _, out _, out var view); + + // Reliably set focus using NavigateToLineAndColumn + var textManager = await GetRequiredGlobalServiceAsync(cancellationToken); + ErrorHandler.ThrowOnFailure(view.GetBuffer(out var textLines)); + ErrorHandler.ThrowOnFailure(view.GetCaretPos(out var line, out var column)); + ErrorHandler.ThrowOnFailure(textManager.NavigateToLineAndColumn(textLines, VSConstants.LOGVIEWID.Code_guid, line, column, line, column)); + } + + public async Task CloseCodeFileAsync(string projectName, string relativeFilePath, bool saveFile, CancellationToken cancellationToken) + { + await CloseFileAsync(projectName, relativeFilePath, VSConstants.LOGVIEWID.Code_guid, saveFile, cancellationToken); + } + + private async Task CloseFileAsync(string projectName, string relativeFilePath, Guid logicalView, bool saveFile, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var filePath = await GetAbsolutePathForProjectRelativeFilePathAsync(projectName, relativeFilePath, cancellationToken); + if (!VsShellUtilities.IsDocumentOpen(ServiceProvider.GlobalProvider, filePath, logicalView, out _, out _, out var windowFrame)) + { + throw new InvalidOperationException($"File '{filePath}' is not open in logical view '{logicalView}'"); + } + + var frameClose = saveFile ? __FRAMECLOSE.FRAMECLOSE_SaveIfDirty : __FRAMECLOSE.FRAMECLOSE_NoSave; + ErrorHandler.ThrowOnFailure(windowFrame.CloseFrame((uint)frameClose)); + } + + /// + /// Update the given file if it already exists in the project, otherwise add a new file to the project. + /// + /// The project that contains the file. + /// The name of the file to update or add. + /// The contents of the file to overwrite if the file already exists or set if the file it created. Empty string is used if null is passed. + /// Whether to open the file after it has been updated/created. + public async Task UpdateOrAddFileAsync(string projectName, string fileName, string? contents = null, bool open = false, CancellationToken cancellationToken = default) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var project = await GetProjectAsync(projectName, cancellationToken); + if (project.ProjectItems.Cast().Any(x => x.Name == fileName)) + { + await UpdateFileAsync(projectName, fileName, contents, open, cancellationToken); + } + else + { + await AddFileAsync(projectName, fileName, contents, open, cancellationToken); + } + } + + /// + /// Update the given file to have the contents given. + /// + /// The project that contains the file. + /// The name of the file to update or add. + /// The contents of the file to overwrite. Empty string is used if null is passed. + /// Whether to open the file after it has been updated. + public async Task UpdateFileAsync(string projectName, string fileName, string? contents = null, bool open = false, CancellationToken cancellationToken = default) + { + async Task SetTextAsync(string text, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + // The active text view might not have finished composing yet, waiting for the application to 'idle' + // means that it is done pumping messages (including WM_PAINT) and the window should return the correct text view + await WaitForApplicationIdleAsync(cancellationToken); + + var vsTextManager = await GetRequiredGlobalServiceAsync(cancellationToken); + var hresult = vsTextManager.GetActiveView(fMustHaveFocus: 1, pBuffer: null, ppView: out var vsTextView); + Marshal.ThrowExceptionForHR(hresult); + var activeVsTextView = (IVsUserData)vsTextView; + + hresult = activeVsTextView.GetData(DefGuidList.guidIWpfTextViewHost, out var wpfTextViewHost); + Marshal.ThrowExceptionForHR(hresult); + + var view = ((IWpfTextViewHost)wpfTextViewHost).TextView; + var textSnapshot = view.TextSnapshot; + var replacementSpan = new SnapshotSpan(textSnapshot, 0, textSnapshot.Length); + view.TextBuffer.Replace(replacementSpan, text); + } + + await OpenFileAsync(projectName, fileName, cancellationToken); + await SetTextAsync(contents ?? string.Empty, cancellationToken); + await CloseCodeFileAsync(projectName, fileName, saveFile: true, cancellationToken); + if (open) + { + await OpenFileAsync(projectName, fileName, cancellationToken); + } + } + + /// + /// Add new file to project. + /// + /// The project that contains the file. + /// The name of the file to add. + /// The contents of the file to overwrite. An empty file is create if null is passed. + /// Whether to open the file after it has been updated. + public async Task AddFileAsync(string projectName, string fileName, string? contents = null, bool open = false, CancellationToken cancellationToken = default) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var project = await GetProjectAsync(projectName, cancellationToken); + var projectDirectory = Path.GetDirectoryName(project.FullName); + var filePath = Path.Combine(projectDirectory, fileName); + var directoryPath = Path.GetDirectoryName(filePath); + Directory.CreateDirectory(directoryPath); + + if (contents != null) + { + File.WriteAllText(filePath, contents); + } + else if (!File.Exists(filePath)) + { + File.Create(filePath).Dispose(); + } + + _ = project.ProjectItems.AddFromFile(filePath); + + if (open) + { + await OpenFileAsync(projectName, fileName, cancellationToken); + } + } + + private static string ConvertLanguageName(string languageName) + { + return languageName switch + { + LanguageNames.CSharp => "CSharp", + LanguageNames.VisualBasic => "VisualBasic", + _ => throw new ArgumentException($"'{languageName}' is not supported.", nameof(languageName)), + }; + } + + private async Task GetAbsolutePathForProjectRelativeFilePathAsync(string projectName, string relativeFilePath, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var dte = await GetRequiredGlobalServiceAsync(cancellationToken); + var solution = dte.Solution; + Assumes.Present(solution); + + var project = solution.Projects.Cast().First(x => x.Name == projectName); + var projectPath = Path.GetDirectoryName(project.FullName); + return Path.Combine(projectPath, relativeFilePath); + } + + private async Task IsSolutionOpenAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var solution = await GetRequiredGlobalServiceAsync(cancellationToken); + ErrorHandler.ThrowOnFailure(solution.GetProperty((int)__VSPROPID.VSPROPID_IsSolutionOpen, out var isOpen)); + return (bool)isOpen; + } + + /// + /// Close the currently open solution without saving. + /// + public async Task CloseSolutionAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var solution = await GetRequiredGlobalServiceAsync(cancellationToken); + if (!await IsSolutionOpenAsync(cancellationToken)) + { + return; + } + +#pragma warning disable IDE0007 // Use implicit type (implicit type introduces a compiler warning) + using SemaphoreSlim semaphore = new SemaphoreSlim(1); +#pragma warning restore IDE0007 // Use implicit type + await using var solutionEvents = new SolutionEvents(JoinableTaskFactory, solution); + + await semaphore.WaitAsync(cancellationToken); + + void HandleAfterCloseSolution(object sender, EventArgs e) + => semaphore.Release(); + + solutionEvents.AfterCloseSolution += HandleAfterCloseSolution; + try + { + ErrorHandler.ThrowOnFailure(solution.CloseSolutionElement((uint)__VSSLNCLOSEOPTIONS.SLNCLOSEOPT_DeleteProject | (uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_NoSave, null, 0)); + await semaphore.WaitAsync(cancellationToken); + } + finally + { + solutionEvents.AfterCloseSolution -= HandleAfterCloseSolution; + } + } + + private async Task GetDirectoryNameAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var solution = await GetRequiredGlobalServiceAsync(cancellationToken); + ErrorHandler.ThrowOnFailure(solution.GetSolutionInfo(out _, out var solutionFileFullPath, out _)); + if (string.IsNullOrEmpty(solutionFileFullPath)) + { + throw new InvalidOperationException(); + } + + return Path.GetDirectoryName(solutionFileFullPath); + } + + private async Task GetProjectTemplatePathAsync(string projectTemplate, string languageName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var dte = await GetRequiredGlobalServiceAsync(cancellationToken); + var solution = (EnvDTE80.Solution2)dte.Solution; + + var hostLocale = await GetRequiredGlobalServiceAsync(cancellationToken); + ErrorHandler.ThrowOnFailure(hostLocale.GetUILocale(out var localeID)); + + if (string.Equals(languageName, "csharp", StringComparison.OrdinalIgnoreCase) + && GetCSharpProjectTemplates(localeID).TryGetValue(projectTemplate, out var csharpProjectTemplate)) + { + return solution.GetProjectTemplate(csharpProjectTemplate, languageName); + } + + if (string.Equals(languageName, "visualbasic", StringComparison.OrdinalIgnoreCase) + && GetVisualBasicProjectTemplates(localeID).TryGetValue(projectTemplate, out var visualBasicProjectTemplate)) + { + return solution.GetProjectTemplate(visualBasicProjectTemplate, languageName); + } + + return solution.GetProjectTemplate(projectTemplate, languageName); + + static ImmutableDictionary GetCSharpProjectTemplates(uint localeID) + { + var builder = ImmutableDictionary.CreateBuilder(); + builder[WellKnownProjectTemplates.ClassLibrary] = $@"Windows\{localeID}\ClassLibrary.zip"; + builder[WellKnownProjectTemplates.ConsoleApplication] = "Microsoft.CSharp.ConsoleApplication"; + builder[WellKnownProjectTemplates.Website] = "EmptyWeb.zip"; + builder[WellKnownProjectTemplates.WinFormsApplication] = "WindowsApplication.zip"; + builder[WellKnownProjectTemplates.WpfApplication] = "WpfApplication.zip"; + builder[WellKnownProjectTemplates.WebApplication] = "WebApplicationProject40"; + return builder.ToImmutable(); + } + + static ImmutableDictionary GetVisualBasicProjectTemplates(uint localeID) + { + var builder = ImmutableDictionary.CreateBuilder(); + builder[WellKnownProjectTemplates.ClassLibrary] = $@"Windows\{localeID}\ClassLibrary.zip"; + builder[WellKnownProjectTemplates.ConsoleApplication] = "Microsoft.VisualBasic.Windows.ConsoleApplication"; + builder[WellKnownProjectTemplates.Website] = "EmptyWeb.zip"; + builder[WellKnownProjectTemplates.WinFormsApplication] = "WindowsApplication.zip"; + builder[WellKnownProjectTemplates.WpfApplication] = "WpfApplication.zip"; + builder[WellKnownProjectTemplates.WebApplication] = "WebApplicationProject40"; + return builder.ToImmutable(); + } + } + + private static string CreateTemporaryPath() + { + return Path.Combine(Path.GetTempPath(), "roslyn-test", Path.GetRandomFileName()); + } + + private async Task GetProjectAsync(string nameOrFileName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var dte = await GetRequiredGlobalServiceAsync(cancellationToken); + var solution = (EnvDTE80.Solution2)dte.Solution; + return solution.Projects.OfType().First( + project => + { + ThreadHelper.ThrowIfNotOnUIThread(); + return string.Equals(project.FileName, nameOrFileName, StringComparison.OrdinalIgnoreCase) + || string.Equals(project.Name, nameOrFileName, StringComparison.OrdinalIgnoreCase); + }); + } + + private sealed class SolutionEvents : IVsSolutionEvents, IAsyncDisposable + { + private readonly JoinableTaskFactory _joinableTaskFactory; + private readonly IVsSolution _solution; + private readonly uint _cookie; + + public SolutionEvents(JoinableTaskFactory joinableTaskFactory, IVsSolution solution) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + _joinableTaskFactory = joinableTaskFactory; + _solution = solution; + ErrorHandler.ThrowOnFailure(solution.AdviseSolutionEvents(this, out _cookie)); + } + + public event EventHandler? AfterCloseSolution; + + public async ValueTask DisposeAsync() + { + await _joinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None); + ErrorHandler.ThrowOnFailure(_solution.UnadviseSolutionEvents(_cookie)); + } + + public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) + { + return VSConstants.S_OK; + } + + public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) + { + return VSConstants.S_OK; + } + + public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) + { + return VSConstants.S_OK; + } + + public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) + { + return VSConstants.S_OK; + } + + public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) + { + return VSConstants.S_OK; + } + + public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) + { + return VSConstants.S_OK; + } + + public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution) + { + return VSConstants.S_OK; + } + + public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel) + { + return VSConstants.S_OK; + } + + public int OnBeforeCloseSolution(object pUnkReserved) + { + return VSConstants.S_OK; + } + + public int OnAfterCloseSolution(object pUnkReserved) + { + AfterCloseSolution?.Invoke(this, EventArgs.Empty); + return VSConstants.S_OK; + } + } + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/SolutionVerifierInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/SolutionVerifierInProcess.cs new file mode 100644 index 0000000000000..5293ae8cbe03e --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/SolutionVerifierInProcess.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.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Roslyn.VisualStudio.IntegrationTests.InProcess +{ + internal class SolutionVerifierInProcess : InProcComponent + { + public SolutionVerifierInProcess(TestServices testServices) + : base(testServices) + { + } + + public async Task AssemblyReferencePresentAsync(string projectName, string assemblyName, string assemblyVersion, string assemblyPublicKeyToken, CancellationToken cancellationToken) + { + var assemblyReferences = await TestServices.SolutionExplorer.GetAssemblyReferencesAsync(projectName, cancellationToken); + var expectedAssemblyReference = assemblyName + "," + assemblyVersion + "," + assemblyPublicKeyToken.ToUpper(); + Assert.Contains(expectedAssemblyReference, assemblyReferences); + } + + public async Task ProjectReferencePresent(string projectName, string referencedProjectName, CancellationToken cancellationToken) + { + var projectReferences = await TestServices.SolutionExplorer.GetProjectReferencesAsync(projectName, cancellationToken); + Assert.Contains(referencedProjectName, projectReferences); + } + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/TestServices.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/TestServices.cs new file mode 100644 index 0000000000000..53283f3305890 --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/TestServices.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.Threading.Tasks; +using Microsoft.VisualStudio.Threading; + +namespace Roslyn.VisualStudio.IntegrationTests.InProcess +{ + internal class TestServices + { + protected TestServices(JoinableTaskFactory joinableTaskFactory) + { + JoinableTaskFactory = joinableTaskFactory; + + Editor = new EditorInProcess(this); + EditorVerifier = new EditorVerifierInProcess(this); + ErrorList = new ErrorListInProcess(this); + SolutionExplorer = new SolutionExplorerInProcess(this); + SolutionVerifier = new SolutionVerifierInProcess(this); + Workspace = new WorkspaceInProcess(this); + } + + public JoinableTaskFactory JoinableTaskFactory { get; } + + public EditorInProcess Editor { get; } + + public EditorVerifierInProcess EditorVerifier { get; } + + public ErrorListInProcess ErrorList { get; } + + public SolutionExplorerInProcess SolutionExplorer { get; } + + public SolutionVerifierInProcess SolutionVerifier { get; } + + public WorkspaceInProcess Workspace { get; } + + internal static async Task CreateAsync(JoinableTaskFactory joinableTaskFactory) + { + var services = new TestServices(joinableTaskFactory); + await services.InitializeAsync(); + return services; + } + + protected virtual Task InitializeAsync() + { + WorkspaceInProcess.EnableAsynchronousOperationTracking(); + return Task.CompletedTask; + } + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/WorkspaceInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/WorkspaceInProcess.cs new file mode 100644 index 0000000000000..46be3bee8f831 --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/WorkspaceInProcess.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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Shared.Options; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.VisualStudio.OperationProgress; +using Microsoft.VisualStudio.Threading; + +namespace Roslyn.VisualStudio.IntegrationTests.InProcess +{ + internal class WorkspaceInProcess : InProcComponent + { + public WorkspaceInProcess(TestServices testServices) + : base(testServices) + { + } + + internal static void EnableAsynchronousOperationTracking() + { + AsynchronousOperationListenerProvider.Enable(true); + } + + public async Task IsPrettyListingOnAsync(string languageName, CancellationToken cancellationToken) + { + var globalOptions = await GetComponentModelServiceAsync(cancellationToken); + return globalOptions.GetOption(FeatureOnOffOptions.PrettyListing, languageName); + } + + public async Task SetPrettyListingAsync(string languageName, bool value, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var globalOptions = await GetComponentModelServiceAsync(cancellationToken); + globalOptions.SetGlobalOption(new OptionKey(FeatureOnOffOptions.PrettyListing, languageName), value); + } + + public Task WaitForAsyncOperationsAsync(string featuresToWaitFor, CancellationToken cancellationToken) + => WaitForAsyncOperationsAsync(featuresToWaitFor, waitForWorkspaceFirst: true, cancellationToken); + + public async Task WaitForAsyncOperationsAsync(string featuresToWaitFor, bool waitForWorkspaceFirst, CancellationToken cancellationToken) + { + if (waitForWorkspaceFirst || featuresToWaitFor == FeatureAttribute.Workspace) + { + await WaitForProjectSystemAsync(cancellationToken); + } + + var listenerProvider = await GetComponentModelServiceAsync(cancellationToken); + + if (waitForWorkspaceFirst) + { + var workspaceWaiter = listenerProvider.GetWaiter(FeatureAttribute.Workspace); + await workspaceWaiter.ExpeditedWaitAsync().WithCancellation(cancellationToken); + } + + var featureWaiter = listenerProvider.GetWaiter(featuresToWaitFor); + await featureWaiter.ExpeditedWaitAsync().WithCancellation(cancellationToken); + } + + public async Task WaitForProjectSystemAsync(CancellationToken cancellationToken) + { + var operationProgressStatus = await GetRequiredGlobalServiceAsync(cancellationToken); + var stageStatus = operationProgressStatus.GetStageStatus(CommonOperationProgressStageIds.Intellisense); + await stageStatus.WaitForCompletionAsync().WithCancellation(cancellationToken); + } + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/Microsoft.VisualStudio.LanguageServices.New.IntegrationTests.csproj b/src/VisualStudio/IntegrationTest/New.IntegrationTests/Microsoft.VisualStudio.LanguageServices.New.IntegrationTests.csproj new file mode 100644 index 0000000000000..f5b62145f9964 --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/Microsoft.VisualStudio.LanguageServices.New.IntegrationTests.csproj @@ -0,0 +1,50 @@ + + + + + Library + Roslyn.VisualStudio.IntegrationTests + net472 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicAddMissingReference.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicAddMissingReference.cs new file mode 100644 index 0000000000000..126faa390ddcd --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicAddMissingReference.cs @@ -0,0 +1,168 @@ +// 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.Tasks; +using System.Xml.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.VisualStudio.IntegrationTest.Utilities; +using Roslyn.VisualStudio.IntegrationTests.InProcess; +using Xunit; + +namespace Roslyn.VisualStudio.IntegrationTests.VisualBasic +{ + public class BasicAddMissingReference : AbstractEditorTest + { + private const string FileInLibraryProject1 = @"Public Class Class1 + Inherits System.Windows.Forms.Form + Public Sub goo() + + End Sub +End Class + +Public Class class2 + Public Sub goo(ByVal x As System.Windows.Forms.Form) + + End Sub + + Public Event ee As System.Windows.Forms.ColumnClickEventHandler +End Class + +Public Class class3 + Implements System.Windows.Forms.IButtonControl + + Public Property DialogResult() As System.Windows.Forms.DialogResult Implements System.Windows.Forms.IButtonControl.DialogResult + Get + + End Get + Set(ByVal Value As System.Windows.Forms.DialogResult) + + End Set + End Property + + Public Sub NotifyDefault(ByVal value As Boolean) Implements System.Windows.Forms.IButtonControl.NotifyDefault + + End Sub + + Public Sub PerformClick() Implements System.Windows.Forms.IButtonControl.PerformClick + + End Sub +End Class +"; + private const string FileInLibraryProject2 = @"Public Class Class1 + Inherits System.Xml.XmlAttribute + Sub New() + MyBase.New(Nothing, Nothing, Nothing, Nothing) + End Sub + Sub goo() + + End Sub + Public bar As ClassLibrary3.Class1 +End Class +"; + private const string FileInLibraryProject3 = @"Public Class Class1 + Public Enum E + E1 + E2 + End Enum + + Public Function Goo() As ADODB.Recordset + Dim x As ADODB.Recordset = Nothing + Return x + End Function + + +End Class +"; + private const string FileInConsoleProject1 = @"Imports System.Data.XLinq + +Module Module1 + + Sub Main() + 'ERRID_UnreferencedAssembly3 + Dim y As New ClassLibrary1.class2 + y.goo(Nothing) + + 'ERRID_UnreferencedAssemblyEvent3 + AddHandler y.ee, Nothing + + 'ERRID_UnreferencedAssemblyBase3 + Dim x As New ClassLibrary1.Class1 + x.goo() + 'ERRID_UnreferencedAssemblyImplements3 + Dim z As New ClassLibrary1.class3 + Dim xxx = z.DialogResult + 'ERRID_SymbolFromUnreferencedProject3 + Dim a As New ClassLibrary2.Class1 + Dim c As Boolean = a.HasChildNodes() + + Dim d = a.bar + End Sub + +End Module +"; + + private const string ClassLibrary1Name = "ClassLibrary1"; + private const string ClassLibrary2Name = "ClassLibrary2"; + private const string ClassLibrary3Name = "ClassLibrary3"; + private const string ConsoleProjectName = "ConsoleApplication1"; + + protected override string LanguageName => LanguageNames.VisualBasic; + + public override async Task InitializeAsync() + { + await base.InitializeAsync().ConfigureAwait(true); + + await TestServices.SolutionExplorer.CreateSolutionAsync("ReferenceErrors", solutionElement: XElement.Parse( + "" + + $" " + + " " + + " " + + " " + + $" " + + " " + + " " + + " " + + $" " + + " " + + " " + + " " + + $" " + + " " + + " " + + " " + + ""), HangMitigatingCancellationToken); + } + + [IdeFact, Trait(Traits.Feature, Traits.Features.AddMissingReference)] + public async Task InvokeSomeFixesInVisualBasicThenVerifyReferences() + { + await TestServices.SolutionExplorer.OpenFileAsync(ConsoleProjectName, "Module1.vb", HangMitigatingCancellationToken); + await TestServices.Editor.PlaceCaretAsync("y.goo", charsOffset: 1, HangMitigatingCancellationToken); + await TestServices.Editor.InvokeCodeActionListAsync(HangMitigatingCancellationToken); + await TestServices.EditorVerifier.CodeActionAsync("Add reference to 'System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.", applyFix: true, cancellationToken: HangMitigatingCancellationToken); + await TestServices.SolutionVerifier.AssemblyReferencePresentAsync( + projectName: ConsoleProjectName, + assemblyName: "System.Windows.Forms", + assemblyVersion: "4.0.0.0", + assemblyPublicKeyToken: "b77a5c561934e089", + HangMitigatingCancellationToken); + await TestServices.Editor.PlaceCaretAsync("a.bar", charsOffset: 1, HangMitigatingCancellationToken); + await TestServices.Editor.InvokeCodeActionListAsync(HangMitigatingCancellationToken); + await TestServices.EditorVerifier.CodeActionAsync("Add project reference to 'ClassLibrary3'.", applyFix: true, cancellationToken: HangMitigatingCancellationToken); + await TestServices.SolutionVerifier.ProjectReferencePresent( + projectName: ConsoleProjectName, + referencedProjectName: ClassLibrary3Name, + HangMitigatingCancellationToken); + } + } +} diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.cs b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.cs index f091d2f6b2c63..6627043f9f16c 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.cs @@ -359,12 +359,20 @@ private static Process StartNewVisualStudioProcess(string installationPath, int { Process.Start(CreateSilentStartInfo(vsRegEditExeFile, $"set \"{installationPath}\" {Settings.Default.VsRootSuffix} HKCU \"Roslyn\\Internal\\OnOff\\Features\" OOP64Bit dword 0")).WaitForExit(); } + else + { + Process.Start(CreateSilentStartInfo(vsRegEditExeFile, $"set \"{installationPath}\" {Settings.Default.VsRootSuffix} HKCU \"Roslyn\\Internal\\OnOff\\Features\" OOP64Bit dword 1")).WaitForExit(); + } // Configure RemoteHostOptions.OOPCoreClrFeatureFlag for testing if (string.Equals(Environment.GetEnvironmentVariable("ROSLYN_OOPCORECLR"), "true", StringComparison.OrdinalIgnoreCase)) { Process.Start(CreateSilentStartInfo(vsRegEditExeFile, $"set \"{installationPath}\" {Settings.Default.VsRootSuffix} HKCU \"FeatureFlags\\Roslyn\\ServiceHubCore\" Value dword 1")).WaitForExit(); } + else + { + Process.Start(CreateSilentStartInfo(vsRegEditExeFile, $"set \"{installationPath}\" {Settings.Default.VsRootSuffix} HKCU \"FeatureFlags\\Roslyn\\ServiceHubCore\" Value dword 0")).WaitForExit(); + } _firstLaunch = false; } diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockVsFileChangeEx.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockVsFileChangeEx.vb index 70ed593557f92..36a02aba67183 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockVsFileChangeEx.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/MockVsFileChangeEx.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.Runtime.InteropServices Imports System.Threading Imports Microsoft.VisualStudio.Shell @@ -14,8 +15,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr Implements IVsAsyncFileChangeEx Private ReadOnly _lock As New Object - Private ReadOnly _watchedFiles As New List(Of WatchedEntity) - Private ReadOnly _watchedDirectories As New List(Of WatchedEntity) + Private _watchedFiles As ImmutableList(Of WatchedEntity) = ImmutableList(Of WatchedEntity).Empty + Private _watchedDirectories As ImmutableList(Of WatchedEntity) = ImmutableList(Of WatchedEntity).Empty Private _nextCookie As UInteger Public Function AdviseDirChange(pszDir As String, fWatchSubDir As Integer, pFCE As IVsFileChangeEvents, ByRef pvsCookie As UInteger) As Integer Implements IVsFileChangeEx.AdviseDirChange @@ -36,13 +37,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr Return VSConstants.S_OK End Function - Private Function AdviseDirectoryOrFileChange(watchedList As List(Of WatchedEntity), + Private Function AdviseDirectoryOrFileChange(ByRef watchedList As ImmutableList(Of WatchedEntity), pszMkDocument As String, pFCE As IVsFileChangeEvents) As UInteger SyncLock _lock Dim cookie = _nextCookie - watchedList.Add(New WatchedEntity(cookie, pszMkDocument, DirectCast(pFCE, IVsFreeThreadedFileChangeEvents2))) + watchedList = watchedList.Add(New WatchedEntity(cookie, pszMkDocument, DirectCast(pFCE, IVsFreeThreadedFileChangeEvents2))) _nextCookie += 1UI Return cookie @@ -59,7 +60,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr Public Function UnadviseDirChange(VSCOOKIE As UInteger) As Integer Implements IVsFileChangeEx.UnadviseDirChange SyncLock _lock - _watchedDirectories.RemoveAll(Function(t) t.Cookie = VSCOOKIE) + _watchedDirectories = _watchedDirectories.RemoveAll(Function(t) t.Cookie = VSCOOKIE) Return VSConstants.S_OK End SyncLock @@ -67,32 +68,52 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr Public Function UnadviseFileChange(VSCOOKIE As UInteger) As Integer Implements IVsFileChangeEx.UnadviseFileChange SyncLock _lock - _watchedFiles.RemoveAll(Function(t) t.Cookie = VSCOOKIE) + _watchedFiles = _watchedFiles.RemoveAll(Function(t) t.Cookie = VSCOOKIE) Return VSConstants.S_OK End SyncLock End Function Public Sub FireUpdate(filename As String) + FireUpdate(filename, _watchedFiles, _watchedDirectories) + End Sub + + ''' + ''' Raises a file change that raised after is ran. + ''' + ''' + ''' File change notifications are inherently asynchronous -- it's always possible that a file change + ''' notification may have been started for something we unsubscribe a moment later -- there's always a window of time + ''' between the final check and the entry into Roslyn code which is unavoidable as long as notifications aren't called + ''' by the shell under a lock, which they aren't for deadlock reasons. + ''' + Public Sub FireStaleUpdate(filename As String, unsubscribingAction As Action) + Dim watchedFiles = _watchedFiles + Dim watchedDirectories = _watchedDirectories + + unsubscribingAction() + + FireUpdate(filename, watchedFiles, watchedDirectories) + End Sub + + Private Shared Sub FireUpdate(filename As String, watchedFiles As ImmutableList(Of WatchedEntity), watchedDirectories As ImmutableList(Of WatchedEntity)) Dim actionsToFire As List(Of Action) = New List(Of Action)() - SyncLock _lock - For Each watchedFile In _watchedFiles - If String.Equals(watchedFile.Path, filename, StringComparison.OrdinalIgnoreCase) Then - actionsToFire.Add(Sub() - watchedFile.Sink.FilesChanged(1, {watchedFile.Path}, {CType(_VSFILECHANGEFLAGS.VSFILECHG_Time, UInteger)}) - End Sub) - End If - Next + For Each watchedFile In watchedFiles + If String.Equals(watchedFile.Path, filename, StringComparison.OrdinalIgnoreCase) Then + actionsToFire.Add(Sub() + watchedFile.Sink.FilesChanged(1, {watchedFile.Path}, {CType(_VSFILECHANGEFLAGS.VSFILECHG_Time, UInteger)}) + End Sub) + End If + Next - For Each watchedDirectory In _watchedDirectories - If FileNameMatchesFilter(filename, watchedDirectory) Then - actionsToFire.Add(Sub() - watchedDirectory.Sink.DirectoryChangedEx2(watchedDirectory.Path, 1, {filename}, {CType(_VSFILECHANGEFLAGS.VSFILECHG_Time, UInteger)}) - End Sub) - End If - Next - End SyncLock + For Each watchedDirectory In watchedDirectories + If FileNameMatchesFilter(filename, watchedDirectory) Then + actionsToFire.Add(Sub() + watchedDirectory.Sink.DirectoryChangedEx2(watchedDirectory.Path, 1, {filename}, {CType(_VSFILECHANGEFLAGS.VSFILECHG_Time, UInteger)}) + End Sub) + End If + Next If actionsToFire.Count > 0 Then For Each actionToFire In actionsToFire @@ -103,7 +124,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr End If End Sub - Private Function FileNameMatchesFilter(filename As String, watchedDirectory As WatchedEntity) As Boolean + Private Shared Function FileNameMatchesFilter(filename As String, watchedDirectory As WatchedEntity) As Boolean If Not filename.StartsWith(watchedDirectory.Path, StringComparison.OrdinalIgnoreCase) Then Return False End If diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb index 6caf57435b3f6..6f57591f23cc5 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb @@ -274,12 +274,23 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr End Function End Class - Friend Async Function RaiseFileChangeAsync(path As String) As Task + Friend Async Function GetFileChangeServiceAsync() As Task(Of MockVsFileChangeEx) ' Ensure we've pushed everything to the file change watcher Dim fileChangeProvider = ExportProvider.GetExportedValue(Of FileChangeWatcherProvider) Dim mockFileChangeService = Assert.IsType(Of MockVsFileChangeEx)(ServiceProvider.GetService(GetType(SVsFileChangeEx))) Await ExportProvider.GetExportedValue(Of AsynchronousOperationListenerProvider)().GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync() - mockFileChangeService.FireUpdate(path) + Return mockFileChangeService + End Function + + Friend Async Function RaiseFileChangeAsync(path As String) As Task + Dim service = Await GetFileChangeServiceAsync() + service.FireUpdate(path) + End Function + + ''' + Friend Async Function RaiseStaleFileChangeAsync(path As String, unsubscribingAction As Action) As Task + Dim service = Await GetFileChangeServiceAsync() + service.FireStaleUpdate(path, unsubscribingAction) End Function Private Class MockVsSmartOpenScope diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllLogger.cs b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllLogger.cs index 979ead7be9708..9451c5258413c 100644 --- a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllLogger.cs +++ b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/FixAllLogger.cs @@ -46,7 +46,7 @@ public static void LogState(FixAllState fixAllState, bool isInternalCodeFixProvi if (isInternalCodeFixProvider) { - m[CodeFixProvider] = fixAllState.CodeFixProvider.GetType().FullName; + m[CodeFixProvider] = fixAllState.CodeFixProvider.GetType().FullName!; m[CodeActionEquivalenceKey] = fixAllState.CodeActionEquivalenceKey; m[LanguageName] = fixAllState.Project.Language; } diff --git a/src/Workspaces/Core/Portable/ExtensionManager/IErrorReportingService.cs b/src/Workspaces/Core/Portable/ExtensionManager/IErrorReportingService.cs index 5747fb6442c80..44a47f51142dc 100644 --- a/src/Workspaces/Core/Portable/ExtensionManager/IErrorReportingService.cs +++ b/src/Workspaces/Core/Portable/ExtensionManager/IErrorReportingService.cs @@ -4,6 +4,7 @@ using System; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Telemetry; namespace Microsoft.CodeAnalysis.Extensions { @@ -20,10 +21,10 @@ internal interface IErrorReportingService : IWorkspaceService /// this kind error info should be something that affects whole roslyn such as /// background compilation is disabled due to memory issue and etc /// - void ShowGlobalErrorInfo(string message, Exception? exception, params InfoBarUI[] items); + void ShowGlobalErrorInfo(string message, TelemetryFeatureName featureName, Exception? exception, params InfoBarUI[] items); void ShowDetailedErrorInfo(Exception exception); - void ShowFeatureNotAvailableErrorInfo(string message, Exception? exception); + void ShowFeatureNotAvailableErrorInfo(string message, TelemetryFeatureName featureName, Exception? exception); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs index 9dfc71ad26461..3308d17f2bd31 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs @@ -39,7 +39,7 @@ private NoOpStreamingFindReferencesProgress() private class NoOpProgressTracker : IStreamingProgressTracker { public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) => default; - public ValueTask ItemCompletedAsync(CancellationToken cancellationToken) => default; + public ValueTask ItemsCompletedAsync(int count, CancellationToken cancellationToken) => default; } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs index 0fbb148cf1944..91ec2dc507de8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs @@ -18,7 +18,7 @@ internal interface IRemoteSymbolFinderService internal interface ICallback { ValueTask AddReferenceItemsAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken); - ValueTask ReferenceItemCompletedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); + 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); @@ -27,7 +27,7 @@ internal interface ICallback ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup group, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference, CancellationToken cancellationToken); ValueTask AddLiteralItemsAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken); - ValueTask LiteralItemCompletedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); + ValueTask LiteralItemsCompletedAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken); ValueTask OnLiteralReferenceFoundAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, TextSpan span, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs index ef2f07cb0041d..60041b002f09e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs @@ -34,8 +34,8 @@ private FindReferencesServerCallback GetFindReferencesCallback(RemoteServiceCall public ValueTask AddReferenceItemsAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken) => GetFindReferencesCallback(callbackId).AddItemsAsync(count, cancellationToken); - public ValueTask ReferenceItemCompletedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) - => GetFindReferencesCallback(callbackId).ItemCompletedAsync(cancellationToken); + public ValueTask ReferenceItemsCompletedAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken) + => GetFindReferencesCallback(callbackId).ItemsCompletedAsync(count, cancellationToken); public ValueTask OnCompletedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) => GetFindReferencesCallback(callbackId).OnCompletedAsync(cancellationToken); @@ -60,8 +60,8 @@ public ValueTask OnStartedAsync(RemoteServiceCallbackId callbackId, Cancellation public ValueTask AddLiteralItemsAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken) => GetFindLiteralsCallback(callbackId).AddItemsAsync(count, cancellationToken); - public ValueTask LiteralItemCompletedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) - => GetFindLiteralsCallback(callbackId).ItemCompletedAsync(cancellationToken); + public ValueTask LiteralItemsCompletedAsync(RemoteServiceCallbackId callbackId, int count, CancellationToken cancellationToken) + => GetFindLiteralsCallback(callbackId).ItemsCompletedAsync(count, cancellationToken); public ValueTask OnLiteralReferenceFoundAsync(RemoteServiceCallbackId callbackId, DocumentId documentId, TextSpan span, CancellationToken cancellationToken) => GetFindLiteralsCallback(callbackId).OnLiteralReferenceFoundAsync(documentId, span, cancellationToken); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindLiteralsServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindLiteralsServerCallback.cs index 53115374d148a..1b0d8e6b7afe4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindLiteralsServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindLiteralsServerCallback.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.FindSymbols @@ -27,8 +28,8 @@ public FindLiteralsServerCallback( public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) => _progress.ProgressTracker.AddItemsAsync(count, cancellationToken); - public ValueTask ItemCompletedAsync(CancellationToken cancellationToken) - => _progress.ProgressTracker.ItemCompletedAsync(cancellationToken); + public ValueTask ItemsCompletedAsync(int count, CancellationToken cancellationToken) + => _progress.ProgressTracker.ItemsCompletedAsync(count, cancellationToken); public async ValueTask OnLiteralReferenceFoundAsync(DocumentId documentId, TextSpan span, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 77d5c5640eb39..181b7893b4cff 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols @@ -40,8 +41,8 @@ public FindReferencesServerCallback( public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) => _progress.ProgressTracker.AddItemsAsync(count, cancellationToken); - public ValueTask ItemCompletedAsync(CancellationToken cancellationToken) - => _progress.ProgressTracker.ItemCompletedAsync(cancellationToken); + public ValueTask ItemsCompletedAsync(int count, CancellationToken cancellationToken) + => _progress.ProgressTracker.ItemsCompletedAsync(count, cancellationToken); public ValueTask OnStartedAsync(CancellationToken cancellationToken) => _progress.OnStartedAsync(cancellationToken); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Persistence.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Persistence.cs index 8f56ed81264c6..3bd405e7ebb6c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Persistence.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Persistence.cs @@ -24,17 +24,17 @@ internal sealed partial class SyntaxTreeIndex : IObjectWritable { var solution = document.Project.Solution; var database = solution.Options.GetPersistentStorageDatabase(); - return LoadAsync(solution.Workspace.Services, DocumentKey.ToDocumentKey(document), checksum, database, GetStringTable(document.Project), cancellationToken); + + var storageService = solution.Workspace.Services.GetPersistentStorageService(database); + return LoadAsync(storageService, DocumentKey.ToDocumentKey(document), checksum, GetStringTable(document.Project), cancellationToken); } public static async Task LoadAsync( - HostWorkspaceServices services, DocumentKey documentKey, Checksum? checksum, StorageDatabase database, StringTable stringTable, CancellationToken cancellationToken) + IChecksummedPersistentStorageService storageService, DocumentKey documentKey, Checksum? checksum, StringTable stringTable, CancellationToken cancellationToken) { try { - var persistentStorageService = services.GetPersistentStorageService(database); - - var storage = await persistentStorageService.GetStorageAsync(documentKey.Project.Solution, cancellationToken).ConfigureAwait(false); + var storage = await storageService.GetStorageAsync(documentKey.Project.Solution, cancellationToken).ConfigureAwait(false); await using var _ = storage.ConfigureAwait(false); // attempt to load from persisted state diff --git a/src/Workspaces/Core/Portable/Log/KeyValueLogMessage.cs b/src/Workspaces/Core/Portable/Log/KeyValueLogMessage.cs index 2cf943c2020b0..88ff57df6bf22 100644 --- a/src/Workspaces/Core/Portable/Log/KeyValueLogMessage.cs +++ b/src/Workspaces/Core/Portable/Log/KeyValueLogMessage.cs @@ -2,12 +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. -#nullable disable - using System; +using System.Diagnostics; using System.Collections.Generic; using System.Linq; +using System.Text; using Microsoft.CodeAnalysis.PooledObjects; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.CodeAnalysis.Internal.Log { @@ -24,10 +25,10 @@ internal sealed class KeyValueLogMessage : LogMessage /// Creates a with default , since /// KV Log Messages are by default more informational and should be logged as such. /// - public static KeyValueLogMessage Create(Action> propertySetter, LogLevel logLevel = LogLevel.Information) + public static KeyValueLogMessage Create(Action> propertySetter, LogLevel logLevel = LogLevel.Information) { var logMessage = s_pool.Allocate(); - logMessage.Construct(LogType.Trace, propertySetter, logLevel); + logMessage.Initialize(LogType.Trace, propertySetter, logLevel); return logMessage; } @@ -35,17 +36,17 @@ public static KeyValueLogMessage Create(Action> prope public static KeyValueLogMessage Create(LogType kind, LogLevel logLevel = LogLevel.Information) => Create(kind, propertySetter: null, logLevel); - public static KeyValueLogMessage Create(LogType kind, Action> propertySetter, LogLevel logLevel = LogLevel.Information) + public static KeyValueLogMessage Create(LogType kind, Action>? propertySetter, LogLevel logLevel = LogLevel.Information) { var logMessage = s_pool.Allocate(); - logMessage.Construct(kind, propertySetter, logLevel); + logMessage.Initialize(kind, propertySetter, logLevel); return logMessage; } private LogType _kind; - private Dictionary _map; - private Action> _propertySetter; + private Dictionary? _lazyMap; + private Action>? _propertySetter; private KeyValueLogMessage() { @@ -53,7 +54,7 @@ private KeyValueLogMessage() _kind = LogType.Trace; } - private void Construct(LogType kind, Action> propertySetter, LogLevel logLevel) + private void Initialize(LogType kind, Action>? propertySetter, LogLevel logLevel) { _kind = kind; _propertySetter = propertySetter; @@ -67,23 +68,73 @@ public bool ContainsProperty get { EnsureMap(); - return _map.Count > 0; + return _lazyMap.Count > 0; } } - public IEnumerable> Properties + public IEnumerable> Properties { get { EnsureMap(); - return _map; + return _lazyMap; } } protected override string CreateMessage() { EnsureMap(); - return string.Join("|", _map.Select(kv => string.Format("{0}={1}", kv.Key, kv.Value))); + + const char PairSeparator = '|'; + const char KeyValueSeparator = '='; + const char ItemSeparator = ','; + + using var _ = PooledStringBuilder.GetInstance(out var builder); + + foreach (var entry in _lazyMap) + { + if (builder.Length > 0) + { + builder.Append(PairSeparator); + } + + Append(builder, entry.Key); + builder.Append(KeyValueSeparator); + + if (entry.Value is IEnumerable items) + { + var first = true; + foreach (var item in items) + { + if (first) + { + first = false; + } + else + { + builder.Append(ItemSeparator); + } + + Append(builder, item); + } + } + else + { + Append(builder, entry.Value); + } + } + + static void Append(StringBuilder builder, object? value) + { + if (value != null) + { + var str = value.ToString(); + Debug.Assert(str != null && !str.Contains(PairSeparator) && !str.Contains(KeyValueSeparator) && !str.Contains(ItemSeparator)); + builder.Append(str); + } + } + + return builder.ToString(); } protected override void FreeCore() @@ -93,30 +144,25 @@ protected override void FreeCore() return; } - if (_map != null) + if (_lazyMap != null) { - SharedPools.Default>().ClearAndFree(_map); - _map = null; + SharedPools.Default>().ClearAndFree(_lazyMap); + _lazyMap = null; } - if (_propertySetter != null) - { - _propertySetter = null; - } + _propertySetter = null; // always pool it back s_pool.Free(this); } + [MemberNotNull(nameof(_lazyMap))] private void EnsureMap() { // always create _map - if (_map == null) - { - _map = SharedPools.Default>().AllocateAndClear(); - } + _lazyMap ??= SharedPools.Default>().AllocateAndClear(); - _propertySetter?.Invoke(_map); + _propertySetter?.Invoke(_lazyMap); } } diff --git a/src/Workspaces/Core/Portable/Log/PiiValue.cs b/src/Workspaces/Core/Portable/Log/PiiValue.cs new file mode 100644 index 0000000000000..775cf2227ab26 --- /dev/null +++ b/src/Workspaces/Core/Portable/Log/PiiValue.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; + +namespace Microsoft.CodeAnalysis.Internal.Log +{ + /// + /// Represents telemetry data that's classified as personally identifiable information. + /// + internal sealed class PiiValue + { + public readonly object Value; + + public PiiValue(object value) + => Value = value; + + public override string? ToString() + => Value.ToString(); + } +} diff --git a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj index 84d5ad632ea87..69768a3e7c101 100644 --- a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj @@ -115,6 +115,7 @@ + diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs index c9d90f4b3ee81..91fc904f7519b 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs @@ -53,16 +53,20 @@ public async Task Delay(TimeSpan delay, CancellationToken cancellationToke using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, expeditedDelayCancellationToken); - try - { - await Task.Delay(delay, cancellationTokenSource.Token).ConfigureAwait(false); - return true; - } - catch (OperationCanceledException) when (expeditedDelayCancellationToken.IsCancellationRequested && !cancellationToken.IsCancellationRequested) + var delayTask = Task.Delay(delay, cancellationTokenSource.Token); + await delayTask.NoThrowAwaitableInternal(false); + + if (delayTask.Status != TaskStatus.RanToCompletion) { - // The cancellation only occurred due to a request to expedite the operation - return false; + cancellationToken.ThrowIfCancellationRequested(); + + // The cancellation only occurred due to a request to expedite the operation. + // Don't use try-catch to reduce the number of first chance exceptions. + if (expeditedDelayCancellationToken.IsCancellationRequested) + return false; } + + return true; } public IAsyncToken BeginAsyncOperation(string name, object? tag = null, [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/TaskExtensions.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/TaskExtensions.cs index 2ce99ac285c07..ac2fb3cb78fd3 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/TaskExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/TaskExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Roslyn.Utilities; @@ -45,5 +46,122 @@ static Task CompletesTrackingOperationSlow(Task task, IDisposable token) TaskScheduler.Default); } } + + // Following code is copied from Microsoft.VisualStudio.Threading.TplExtensions (renamed to avoid ambiguity) + // https://github.com/microsoft/vs-threading/blob/main/src/Microsoft.VisualStudio.Threading/TplExtensions.cs + + /// + /// Returns an awaitable for the specified task that will never throw, even if the source task + /// faults or is canceled. + /// + /// The task whose completion should signal the completion of the returned awaitable. + /// if set to true the continuation will be scheduled on the caller's context; false to always execute the continuation on the threadpool. + /// An awaitable. + public static NoThrowTaskAwaitable NoThrowAwaitableInternal(this Task task, bool captureContext = true) + { + return new NoThrowTaskAwaitable(task, captureContext); + } + + /// + /// An awaitable that wraps a task and never throws an exception when waited on. + /// + public readonly struct NoThrowTaskAwaitable + { + /// + /// The task. + /// + private readonly Task _task; + + /// + /// A value indicating whether the continuation should be scheduled on the current sync context. + /// + private readonly bool _captureContext; + + /// + /// Initializes a new instance of the struct. + /// + /// The task. + /// Whether the continuation should be scheduled on the current sync context. + public NoThrowTaskAwaitable(Task task, bool captureContext) + { + Contract.ThrowIfNull(task, nameof(task)); + _task = task; + _captureContext = captureContext; + } + + /// + /// Gets the awaiter. + /// + /// The awaiter. + public NoThrowTaskAwaiter GetAwaiter() + { + return new NoThrowTaskAwaiter(_task, _captureContext); + } + } + + /// + /// An awaiter that wraps a task and never throws an exception when waited on. + /// + public readonly struct NoThrowTaskAwaiter : ICriticalNotifyCompletion + { + /// + /// The task. + /// + private readonly Task _task; + + /// + /// A value indicating whether the continuation should be scheduled on the current sync context. + /// + private readonly bool _captureContext; + + /// + /// Initializes a new instance of the struct. + /// + /// The task. + /// if set to true [capture context]. + public NoThrowTaskAwaiter(Task task, bool captureContext) + { + Contract.ThrowIfNull(task, nameof(task)); + _task = task; + _captureContext = captureContext; + } + + /// + /// Gets a value indicating whether the task has completed. + /// + public bool IsCompleted + { + get { return _task.IsCompleted; } + } + + /// + /// Schedules a delegate for execution at the conclusion of a task's execution. + /// + /// The action. + public void OnCompleted(Action continuation) + { + _task.ConfigureAwait(_captureContext).GetAwaiter().OnCompleted(continuation); + } + + /// + /// Schedules a delegate for execution at the conclusion of a task's execution + /// without capturing the ExecutionContext. + /// + /// The action. + public void UnsafeOnCompleted(Action continuation) + { + _task.ConfigureAwait(_captureContext).GetAwaiter().UnsafeOnCompleted(continuation); + } + + /// + /// Does nothing. + /// +#pragma warning disable CA1822 // Mark members as static + public void GetResult() +#pragma warning restore CA1822 // Mark members as static + { + // Never throw here. + } + } } } diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTracker.cs b/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTracker.cs index 37e8cdd01a120..93405195491f0 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTracker.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTracker.cs @@ -10,6 +10,6 @@ namespace Microsoft.CodeAnalysis.Shared.Utilities internal interface IStreamingProgressTracker { ValueTask AddItemsAsync(int count, CancellationToken cancellationToken); - ValueTask ItemCompletedAsync(CancellationToken cancellationToken); + ValueTask ItemsCompletedAsync(int count, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTrackerExtensions.cs b/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTrackerExtensions.cs index e57a545b167e3..87544727ed625 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTrackerExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/IStreamingProgressTrackerExtensions.cs @@ -11,9 +11,8 @@ namespace Microsoft.CodeAnalysis.Shared.Utilities internal static class IStreamingProgressTrackerExtensions { /// - /// Returns an that will call on when it is disposed. + /// Returns an that will call on + /// when it is disposed. /// public static async Task AddSingleItemAsync(this IStreamingProgressTracker progressTracker, CancellationToken cancellationToken) { @@ -21,6 +20,9 @@ public static async Task AddSingleItemAsync(this IStreamingPro return new StreamingProgressDisposer(progressTracker, cancellationToken); } + public static ValueTask ItemCompletedAsync(this IStreamingProgressTracker tracker, CancellationToken cancellationToken) + => tracker.ItemsCompletedAsync(1, cancellationToken); + private class StreamingProgressDisposer : IAsyncDisposable { private readonly IStreamingProgressTracker _progressTracker; diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/StreamingProgressTracker.cs b/src/Workspaces/Core/Portable/Shared/Utilities/StreamingProgressTracker.cs index 59c57795661c2..69f59ccf8382f 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/StreamingProgressTracker.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/StreamingProgressTracker.cs @@ -27,20 +27,13 @@ public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) return UpdateAsync(cancellationToken); } - public ValueTask ItemCompletedAsync(CancellationToken cancellationToken) + public ValueTask ItemsCompletedAsync(int count, CancellationToken cancellationToken) { - Interlocked.Increment(ref _completedItems); + Interlocked.Add(ref _completedItems, count); return UpdateAsync(cancellationToken); } private ValueTask UpdateAsync(CancellationToken cancellationToken) - { - if (_updateAction == null) - { - return default; - } - - return _updateAction(_completedItems, _totalItems, cancellationToken); - } + => _updateAction?.Invoke(Volatile.Read(ref _completedItems), Volatile.Read(ref _totalItems), cancellationToken) ?? default; } } diff --git a/src/Workspaces/Core/Portable/Storage/CloudCache/ICloudCacheStorageServiceFactory.cs b/src/Workspaces/Core/Portable/Storage/CloudCache/ICloudCacheStorageServiceProvider.cs similarity index 64% rename from src/Workspaces/Core/Portable/Storage/CloudCache/ICloudCacheStorageServiceFactory.cs rename to src/Workspaces/Core/Portable/Storage/CloudCache/ICloudCacheStorageServiceProvider.cs index 96bfd1ebb5a82..5ea4652c701d7 100644 --- a/src/Workspaces/Core/Portable/Storage/CloudCache/ICloudCacheStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/Storage/CloudCache/ICloudCacheStorageServiceProvider.cs @@ -6,8 +6,7 @@ namespace Microsoft.CodeAnalysis.Storage.CloudCache { - internal interface ICloudCacheStorageServiceFactory : IWorkspaceService + internal interface ICloudCacheStorageService : IChecksummedPersistentStorageService, IWorkspaceService { - AbstractPersistentStorageService Create(IPersistentStorageConfiguration configuration); } } diff --git a/src/Workspaces/Core/Portable/Storage/ISQLiteStorageServiceFactory.cs b/src/Workspaces/Core/Portable/Storage/ISQLiteStorageServiceFactory.cs deleted file mode 100644 index afc9c25b62334..0000000000000 --- a/src/Workspaces/Core/Portable/Storage/ISQLiteStorageServiceFactory.cs +++ /dev/null @@ -1,16 +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 Microsoft.CodeAnalysis.Host; - -namespace Microsoft.CodeAnalysis.Storage -{ - /// - /// Factory for SQLite storage service - intentionally not included under SQLite directory since all sources under it are excluded from source build. - /// - internal interface ISQLiteStorageServiceFactory : IWorkspaceService - { - IChecksummedPersistentStorageService Create(IPersistentStorageConfiguration configuration); - } -} diff --git a/src/Workspaces/Core/Portable/Storage/PersistentStorageExtensions.cs b/src/Workspaces/Core/Portable/Storage/PersistentStorageExtensions.cs index 4ef4e0ba6ccee..c2e2b1b9ba564 100644 --- a/src/Workspaces/Core/Portable/Storage/PersistentStorageExtensions.cs +++ b/src/Workspaces/Core/Portable/Storage/PersistentStorageExtensions.cs @@ -6,6 +6,10 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Storage.CloudCache; +#if !DOTNET_BUILD_FROM_SOURCE +using Microsoft.CodeAnalysis.SQLite.v2; +#endif + namespace Microsoft.CodeAnalysis.Storage { internal static class PersistentStorageExtensions @@ -20,19 +24,18 @@ public static IChecksummedPersistentStorageService GetPersistentStorageService(t { var configuration = services.GetRequiredService(); - switch (database) + return database switch { - case StorageDatabase.SQLite: - return services.GetService()?.Create(configuration) ?? - NoOpPersistentStorageService.GetOrThrow(configuration); - - case StorageDatabase.CloudCache: - return services.GetService()?.Create(configuration) ?? - NoOpPersistentStorageService.GetOrThrow(configuration); - - default: - return NoOpPersistentStorageService.GetOrThrow(configuration); - } +#if !DOTNET_BUILD_FROM_SOURCE + StorageDatabase.SQLite + => services.GetService() ?? + NoOpPersistentStorageService.GetOrThrow(configuration), +#endif + StorageDatabase.CloudCache + => services.GetService() ?? + NoOpPersistentStorageService.GetOrThrow(configuration), + _ => NoOpPersistentStorageService.GetOrThrow(configuration), + }; } } } diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/AbstractSQLitePersistentStorageService.cs b/src/Workspaces/Core/Portable/Storage/SQLite/AbstractSQLitePersistentStorageService.cs deleted file mode 100644 index 3f5aeb06b04a5..0000000000000 --- a/src/Workspaces/Core/Portable/Storage/SQLite/AbstractSQLitePersistentStorageService.cs +++ /dev/null @@ -1,44 +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.InteropServices; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Storage; - -namespace Microsoft.CodeAnalysis.SQLite -{ - /// - /// Base type . Used as a common location for the common static - /// helpers to load the sqlite pcl library. - /// - internal abstract class AbstractSQLitePersistentStorageService : AbstractPersistentStorageService - { - protected static bool TryInitializeLibraries() => s_initialized.Value; - - private static readonly Lazy s_initialized = new(() => TryInitializeLibrariesLazy()); - - private static bool TryInitializeLibrariesLazy() - { - try - { - // Necessary to initialize SQLitePCL. - SQLitePCL.Batteries_V2.Init(); - } - catch (Exception e) when (e is DllNotFoundException or EntryPointNotFoundException) - { - StorageDatabaseLogger.LogException(e); - return false; - } - - return true; - } - - protected AbstractSQLitePersistentStorageService(IPersistentStorageConfiguration configuration) - : base(configuration) - { - } - } -} diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs index a9c881ff460f8..7530be69cad0d 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorageService.cs @@ -15,29 +15,51 @@ namespace Microsoft.CodeAnalysis.SQLite.v2 { - internal sealed class SQLitePersistentStorageService : AbstractSQLitePersistentStorageService + internal sealed class SQLitePersistentStorageService : AbstractPersistentStorageService, IWorkspaceService { - [ExportWorkspaceService(typeof(ISQLiteStorageServiceFactory)), Shared] - internal sealed class Factory : ISQLiteStorageServiceFactory + [ExportWorkspaceServiceFactory(typeof(SQLitePersistentStorageService)), Shared] + internal sealed class ServiceFactory : IWorkspaceServiceFactory { private readonly SQLiteConnectionPoolService _connectionPoolService; private readonly IAsynchronousOperationListener _asyncListener; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public Factory(SQLiteConnectionPoolService connectionPoolService, IAsynchronousOperationListenerProvider asyncOperationListenerProvider) + public ServiceFactory( + SQLiteConnectionPoolService connectionPoolService, + IAsynchronousOperationListenerProvider asyncOperationListenerProvider) { _connectionPoolService = connectionPoolService; _asyncListener = asyncOperationListenerProvider.GetListener(FeatureAttribute.PersistentStorage); } - public IChecksummedPersistentStorageService Create(IPersistentStorageConfiguration configuration) - => new SQLitePersistentStorageService(_connectionPoolService, configuration, _asyncListener); + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + => new SQLitePersistentStorageService(_connectionPoolService, workspaceServices.GetRequiredService(), _asyncListener); } private const string StorageExtension = "sqlite3"; private const string PersistentStorageFileName = "storage.ide"; + private static bool TryInitializeLibraries() => s_initialized.Value; + + private static readonly Lazy s_initialized = new(() => TryInitializeLibrariesLazy()); + + private static bool TryInitializeLibrariesLazy() + { + try + { + // Necessary to initialize SQLitePCL. + SQLitePCL.Batteries_V2.Init(); + } + catch (Exception e) when (e is DllNotFoundException or EntryPointNotFoundException) + { + StorageDatabaseLogger.LogException(e); + return false; + } + + return true; + } + private readonly SQLiteConnectionPoolService _connectionPoolService; private readonly IAsynchronousOperationListener _asyncListener; private readonly IPersistentStorageFaultInjector? _faultInjector; diff --git a/src/Workspaces/Core/Portable/Telemetry/TelemetryFeatureName.cs b/src/Workspaces/Core/Portable/Telemetry/TelemetryFeatureName.cs new file mode 100644 index 0000000000000..4c5ac1cb4cbaa --- /dev/null +++ b/src/Workspaces/Core/Portable/Telemetry/TelemetryFeatureName.cs @@ -0,0 +1,47 @@ +// 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.Telemetry +{ + /// + /// Feature name used in telemetry. + /// + internal readonly struct TelemetryFeatureName + { + private const string LocalKind = "Local"; + private const string RemoteKind = "Remote"; + private const string ExtensionKind = "Extension"; + + // Local services: + + public static readonly TelemetryFeatureName CodeFixProvider = GetClientFeatureName("CodeFixProvider"); + public static readonly TelemetryFeatureName InlineRename = GetClientFeatureName("InlineRename"); + public static readonly TelemetryFeatureName LegacySuppressionFix = GetClientFeatureName("TelemetryFeatureName"); + public static readonly TelemetryFeatureName VirtualMemoryNotification = GetClientFeatureName("VirtualMemoryNotification"); + + private readonly string _name; + private readonly string _kind; + + private TelemetryFeatureName(string name, string kind) + { + _name = name; + _kind = kind; + } + + private static TelemetryFeatureName GetClientFeatureName(string name) + => new(name, LocalKind); + + public static TelemetryFeatureName GetRemoteFeatureName(string componentName, string serviceName) + => new(componentName + ":" + serviceName, RemoteKind); + + public static TelemetryFeatureName GetExtensionName(Type type) + => new(type.Assembly.FullName?.StartsWith("Microsoft.", StringComparison.Ordinal) == true ? type.FullName! : "External", + ExtensionKind); + + public override string ToString() + => _kind + ":" + _name; + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/DocumentKey.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/DocumentKey.cs index 50365bbe8f508..677ee89e21db2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/DocumentKey.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/DocumentKey.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; using System.Collections.Generic; using System.Runtime.Serialization; using Microsoft.CodeAnalysis.Host; @@ -15,7 +16,7 @@ namespace Microsoft.CodeAnalysis.Storage /// solution load), but querying the data is still desired. /// [DataContract] - internal readonly struct DocumentKey : IEqualityComparer + internal readonly struct DocumentKey : IEqualityComparer, IEquatable { [DataMember(Order = 0)] public readonly ProjectKey Project; @@ -43,10 +44,19 @@ public static DocumentKey ToDocumentKey(Document document) public static DocumentKey ToDocumentKey(ProjectKey projectKey, TextDocumentState state) => new(projectKey, state.Id, state.FilePath, state.Name); + public override bool Equals(object? obj) + => obj is DocumentKey other && Equals(other); + + public bool Equals(DocumentKey other) + => this.Id == other.Id; + + public override int GetHashCode() + => this.Id.GetHashCode(); + public bool Equals(DocumentKey x, DocumentKey y) - => x.Id == y.Id; + => x.Equals(y); public int GetHashCode(DocumentKey obj) - => obj.Id.GetHashCode(); + => obj.GetHashCode(); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs index fe6b481c861ea..285b4bff6107b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs @@ -39,20 +39,25 @@ internal sealed class DefaultPersistentStorageConfiguration : IPersistentStorage /// private static readonly ImmutableArray s_invalidPathChars = Path.GetInvalidPathChars().Concat('/').ToImmutableArray(); - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DefaultPersistentStorageConfiguration() - { - } + private static readonly string s_cacheDirectory; + private static readonly string s_moduleFileName; - private static string GetCacheDirectory() + static DefaultPersistentStorageConfiguration() { // Store in the LocalApplicationData/Roslyn/hash folder (%appdatalocal%/... on Windows, // ~/.local/share/... on unix). This will place the folder in a location we can trust // to be able to get back to consistently as long as we're working with the same // solution and the same workspace kind. var appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData, Environment.SpecialFolderOption.Create); - return Path.Combine(appDataFolder, "Microsoft", "VisualStudio", "Roslyn", "Cache"); + s_cacheDirectory = Path.Combine(appDataFolder, "Microsoft", "VisualStudio", "Roslyn", "Cache"); + + s_moduleFileName = SafeName(Process.GetCurrentProcess().MainModule.FileName); + } + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DefaultPersistentStorageConfiguration() + { } public bool ThrowOnFailure => false; @@ -66,30 +71,30 @@ private static string GetCacheDirectory() // folder to store their data in. return Path.Combine( - GetCacheDirectory(), - SafeName(Process.GetCurrentProcess().MainModule.FileName), + s_cacheDirectory, + s_moduleFileName, SafeName(solutionKey.FilePath)); + } + + private static string SafeName(string fullPath) + { + var fileName = Path.GetFileName(fullPath); + + // we don't want to build too long a path. So only take a portion of the text we started with. + // However, we want to avoid collisions, so ensure we also append a safe short piece of text + // that is based on the full text. + const int MaxLength = 20; + var prefix = fileName.Length > MaxLength ? fileName.Substring(0, MaxLength) : fileName; + var suffix = Checksum.Create(fullPath); + var fullName = $"{prefix}-{suffix}"; + return StripInvalidPathChars(fullName); + } + + private static string StripInvalidPathChars(string val) + { + val = new string(val.Where(c => !s_invalidPathChars.Contains(c)).ToArray()); - static string SafeName(string fullPath) - { - var fileName = Path.GetFileName(fullPath); - - // we don't want to build too long a path. So only take a portion of the text we started with. - // However, we want to avoid collisions, so ensure we also append a safe short piece of text - // that is based on the full text. - const int MaxLength = 20; - var prefix = fileName.Length > MaxLength ? fileName.Substring(0, MaxLength) : fileName; - var suffix = Checksum.Create(fullPath); - var fullName = $"{prefix}-{suffix}"; - return StripInvalidPathChars(fullName); - } - - static string StripInvalidPathChars(string val) - { - val = new string(val.Where(c => !s_invalidPathChars.Contains(c)).ToArray()); - - return string.IsNullOrWhiteSpace(val) ? "None" : val; - } + return string.IsNullOrWhiteSpace(val) ? "None" : val; } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/CachedSkeletonReferences.cs b/src/Workspaces/Core/Portable/Workspace/Solution/CachedSkeletonReferences.cs deleted file mode 100644 index eef55c75232da..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Solution/CachedSkeletonReferences.cs +++ /dev/null @@ -1,250 +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.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Threading; -using Microsoft.CodeAnalysis.Host; - -namespace Microsoft.CodeAnalysis; - -/// -/// Caches the skeleton references produced for a given project/compilation under the varying -/// it might be referenced by. Skeletons are used in the compilation -/// tracker to allow cross-language project references with live semantic updating between VB/C# and vice versa. -/// Specifically, in a cross language case we will build a skeleton ref for the referenced project and have the -/// referrer use that to understand its semantics. -/// -/// This approach works, but has the caveat that live cross-language semantics are only possible when the -/// skeleton assembly can be built. This should always be the case for correct code, but it may not be the -/// case for code with errors depending on if the respective language compiler is resilient to those errors or not. -/// In that case though where the skeleton cannot be built, this type provides mechanisms to fallback to the last -/// successfully built skeleton so that a somewhat reasonable experience can be maintained. If we failed to do this -/// and instead returned nothing, a user would find that practically all semantic experiences that depended on -/// that particular project would fail or be seriously degraded (e.g. diagnostics). To that end, it's better to -/// limp along with stale date, then barrel on ahead with no data. -/// -/// The implementation works by keeping metadata references around associated with a specific -/// for a project. As long as the for that project -/// is the same, then all the references of it can be reused. When an forks -/// itself, it will also this, allowing previously computed references to be used by later forks. -/// However, this means that later forks (esp. ones that fail to produce a skeleton, or which produce a skeleton for -/// different semantics) will not leak backward to a prior , causing it to see a view of the world -/// inapplicable to its current snapshot. -/// -internal class CachedSkeletonReferences -{ - /// - /// Mapping from compilation instance to metadata-references for it. This allows us to associate the same - /// to different compilations that may not be the same as the original - /// compilation we generated the set from. This allows us to use compilations as keys as long as they're - /// alive, but also associate the set with new compilations that are generated in the future if the older - /// compilations were thrown away. - /// - private static readonly ConditionalWeakTable s_compilationToReferenceMap = new(); - - private readonly object _gate = new(); - - /// - /// The version of the project that the - /// corresponds to. - /// - private VersionStamp? _version; - - /// - /// Mapping from metadata-reference-properties to the actual metadata reference for them. - /// - private SkeletonReferenceSet? _skeletonReferenceSet; - - public CachedSkeletonReferences() - : this(version: null, skeletonReferenceSet: null) - { - } - - private CachedSkeletonReferences( - VersionStamp? version, - SkeletonReferenceSet? skeletonReferenceSet) - { - _version = version; - _skeletonReferenceSet = skeletonReferenceSet; - } - - /// - /// Produces a copy of the , allowing forks of to - /// reuse s when their dependent semantic version matches ours. In the case where - /// the version is different, then the clone will attempt to make a new skeleton reference for that version. If it - /// succeeds, it will use that. If it fails however, it can still use our skeletons. - /// - public CachedSkeletonReferences Clone() - { - lock (_gate) - { - // pass along the best version/reference-set we computed for ourselves. That way future ProjectStates - // can use this data if either the version changed, or they weren't able to build a skeleton for themselves. - // By passing along a copy we ensure that if they have a different version, they'll end up producing a new - // SkeletonReferenceSet where they'll store their own data in which will not affect prior ProjectStates. - return new CachedSkeletonReferences(_version, _skeletonReferenceSet); - } - } - - public MetadataReference GetOrBuildReference( - Workspace workspace, - MetadataReferenceProperties properties, - Compilation finalCompilation, - VersionStamp version, - CancellationToken cancellationToken) - { - // first, check if we have a direct mapping from this compilation to a reference set. If so, use it. This - // ensures the same compilations will get same metadata reference. - if (s_compilationToReferenceMap.TryGetValue(finalCompilation, out var referenceSet)) - return referenceSet.GetMetadataReference(properties); - - // Didn't have a direct mapping to a reference set. Compute one for ourselves. - referenceSet = GetOrBuildReferenceSet(workspace, version, finalCompilation, cancellationToken); - - // another thread may have come in and beaten us to computing this. So attempt to actually cache this - // in the global map. if it succeeds, use our computed version. If it fails, use the one the other - // thread succeeded in storing. - referenceSet = s_compilationToReferenceMap.GetValue(finalCompilation, _ => referenceSet); - - lock (_gate) - { - // whoever won, still store this reference set against us with the provided version. - _version = version; - _skeletonReferenceSet = referenceSet; - } - - return referenceSet.GetMetadataReference(properties); - } - - private SkeletonReferenceSet GetOrBuildReferenceSet( - Workspace workspace, - VersionStamp version, - Compilation finalCompilation, - CancellationToken cancellationToken) - { - // First see if we already have a reference set for this version. if so, we're done and can return that. - var referenceSet = TryGetReferenceSet(version); - if (referenceSet != null) - { - workspace.LogTestMessage($"Succeeded at finding reference set corresponding to requested version."); - return referenceSet; - } - - // okay, we don't have one. so create one now. - - // first, prepare image - // * NOTE * image is cancellable, do not create it inside of conditional weak table. - var service = workspace.Services.GetService(); - var image = MetadataOnlyImage.Create(workspace, service, finalCompilation, cancellationToken); - - if (image.IsEmpty) - { - // unfortunately, we couldn't create one. see if we have one from previous compilation., it might be - // out-of-date big time, but better than nothing. - referenceSet = TryGetReferenceSet(version: null); - if (referenceSet != null) - { - workspace.LogTestMessage($"We failed to create metadata so we're using the one we just found from an earlier version."); - return referenceSet; - } - } - - return new SkeletonReferenceSet(image, new DeferredDocumentationProvider(finalCompilation)); - } - - /// - /// Tries to get the for this project matching . - /// if is , any cached - /// can be returned, even if it doesn't correspond to that version. This is useful in error tolerance cases - /// as building a skeleton assembly may easily fail. In that case it's better to use the last successfully - /// built skeleton than just have no semantic information for that project at all. - /// - private SkeletonReferenceSet? TryGetReferenceSet(VersionStamp? version) - { - // Otherwise, we don't have a direct mapping stored. Try to see if the cached reference we have is - // applicable to this project semantic version. - lock (_gate) - { - // if we don't have a skeleton cached, then we have nothing to return. - if (_skeletonReferenceSet == null) - return null; - - // if the caller is requiring a particular semantic version, it much match what we have cached. - if (version != null && version != _version) - return null; - - return _skeletonReferenceSet; - } - } - - /// - /// Return a metadata reference if we already have a reference-set computed for this particular . - /// If a reference already exists for the provided , the same instance will be returned. Otherwise, - /// a fresh instance will be returned. - /// - public MetadataReference? TryGetReference(VersionStamp version, MetadataReferenceProperties properties) - => TryGetReferenceSet(version)?.GetMetadataReference(properties); - - private sealed class SkeletonReferenceSet - { - private readonly object _gate = new(); - - // use WeakReference so we don't keep MetadataReference's alive if they are not being consumed - private readonly Dictionary> _metadataReferences = new(); - - private readonly MetadataOnlyImage _image; - - /// - /// The documentation provider used to lookup xml docs for any metadata reference we pass out. See - /// docs on for why this is safe to hold onto despite it - /// rooting a compilation internally. - /// - private readonly DeferredDocumentationProvider _documentationProvider; - - public SkeletonReferenceSet( - MetadataOnlyImage image, - DeferredDocumentationProvider documentationProvider) - { - _image = image; - _documentationProvider = documentationProvider; - } - - public MetadataReference GetMetadataReference(MetadataReferenceProperties properties) - { - // lookup first and eagerly return cached value if we have it. - lock (_gate) - { - if (TryGetExisting(out var metadataReference)) - return metadataReference; - } - - // otherwise, create the metadata outside of the lock, and then try to assign it if no one else beat us - { - var metadataReference = _image.CreateReference(properties.Aliases, properties.EmbedInteropTypes, _documentationProvider); - var weakMetadata = new WeakReference(metadataReference); - - lock (_gate) - { - // see if someone beat us to writing this. - if (TryGetExisting(out var existingMetadataReference)) - return existingMetadataReference; - - _metadataReferences[properties] = weakMetadata; - } - - return metadataReference; - } - - bool TryGetExisting([NotNullWhen(true)] out MetadataReference? metadataReference) - { - metadataReference = null; - return _metadataReferences.TryGetValue(properties, out var weakMetadata) && - weakMetadata.TryGetTarget(out metadataReference); - } - } - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/MetadataOnlyImage.cs b/src/Workspaces/Core/Portable/Workspace/Solution/MetadataOnlyImage.cs deleted file mode 100644 index 892c115128998..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Solution/MetadataOnlyImage.cs +++ /dev/null @@ -1,147 +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.Collections.Immutable; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using Microsoft.CodeAnalysis.Emit; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Internal.Log; - -namespace Microsoft.CodeAnalysis -{ - internal class MetadataOnlyImage - { - public static readonly MetadataOnlyImage Empty = new(storage: null, assemblyName: string.Empty); - private static readonly EmitOptions s_emitOptions = new(metadataOnly: true); - - private readonly ITemporaryStreamStorage _storage; - private readonly string _assemblyName; - - private MetadataOnlyImage(ITemporaryStreamStorage storage, string assemblyName) - { - _storage = storage; - _assemblyName = assemblyName; - } - - public bool IsEmpty - { - get { return _storage == null; } - } - - public static MetadataOnlyImage Create(Workspace workspace, ITemporaryStorageService service, Compilation compilation, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - workspace.LogTestMessage($"Beginning to create a skeleton assembly for {compilation.AssemblyName}..."); - - using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_EmitMetadataOnlyImage, cancellationToken)) - { - // TODO: make it to use SerializableBytes.WritableStream rather than MemoryStream so that - // we don't allocate anything for skeleton assembly. - using var stream = SerializableBytes.CreateWritableStream(); - // note: cloning compilation so we don't retain all the generated symbols after its emitted. - // * REVIEW * is cloning clone p2p reference compilation as well? - var emitResult = compilation.Clone().Emit(stream, options: s_emitOptions, cancellationToken: cancellationToken); - - if (emitResult.Success) - { - workspace.LogTestMessage($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); - var storage = service.CreateTemporaryStreamStorage(cancellationToken); - - stream.Position = 0; - storage.WriteStream(stream, cancellationToken); - - return new MetadataOnlyImage(storage, compilation.AssemblyName); - } - else - { - workspace.LogTestMessage($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); - - foreach (var diagnostic in emitResult.Diagnostics) - { - workspace.LogTestMessage(" " + diagnostic.GetMessage()); - } - - // log emit failures so that we can improve most common cases - Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => - { - // log errors in the format of - // CS0001:1;CS002:10;... - var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); - m["Errors"] = string.Join(";", groups); - })); - } - } - } - finally - { - workspace.LogTestMessage($"Done trying to create a skeleton assembly for {compilation.AssemblyName}"); - } - - return Empty; - } - - /// - /// A map to ensure that the streams from the temporary storage service that back the metadata we create stay alive as long - /// as the metadata is alive. - /// - private static readonly ConditionalWeakTable s_lifetime - = new(); - - public MetadataReference CreateReference(ImmutableArray aliases, bool embedInteropTypes, DocumentationProvider documentationProvider) - { - if (this.IsEmpty) - { - return null; - } - - // first see whether we can use native memory directly. - var stream = _storage.ReadStream(); - AssemblyMetadata metadata; - - if (stream is ISupportDirectMemoryAccess supportNativeMemory) - { - // this is unfortunate that if we give stream, compiler will just re-copy whole content to - // native memory again. this is a way to get around the issue by we getting native memory ourselves and then - // give them pointer to the native memory. also we need to handle lifetime ourselves. - metadata = AssemblyMetadata.Create(ModuleMetadata.CreateFromImage(supportNativeMemory.GetPointer(), (int)stream.Length)); - - // Tie lifetime of stream to metadata we created. It is important to tie this to the Metadata and not the - // metadata reference, as PE symbols hold onto just the Metadata. We can use Add here since we created - // a brand new object in AssemblyMetadata.Create above. - s_lifetime.Add(metadata, supportNativeMemory); - } - else - { - // Otherwise, we just let it use stream. Unfortunately, if we give stream, compiler will - // internally copy it to native memory again. since compiler owns lifetime of stream, - // it would be great if compiler can be little bit smarter on how it deals with stream. - - // We don't deterministically release the resulting metadata since we don't know - // when we should. So we leave it up to the GC to collect it and release all the associated resources. - metadata = AssemblyMetadata.CreateFromStream(stream); - } - - return metadata.GetReference( - documentation: documentationProvider, - aliases: aliases, - embedInteropTypes: embedInteropTypes, - display: _assemblyName); - } - - public void Cleanup() - { - if (_storage != null) - { - _storage.Dispose(); - } - } - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs index 2be093928cc1f..1ffe6c160f843 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs @@ -489,6 +489,34 @@ public Task GetDependentSemanticVersionAsync(CancellationToken can public Task GetSemanticVersionAsync(CancellationToken cancellationToken = default) => _projectState.GetSemanticVersionAsync(cancellationToken); + /// + /// Calculates a checksum that contains a project's checksum along with a checksum for each of the project's + /// transitive dependencies. + /// + /// + /// This checksum calculation can be used for cases where a feature needs to know if the semantics in this project + /// changed. For example, for diagnostics or caching computed semantic data. The goal is to ensure that changes to + /// + /// Files inside the current project + /// Project properties of the current project + /// Visible files in referenced projects + /// Project properties in referenced projects + /// + /// are reflected in the metadata we keep so that comparing solutions accurately tells us when we need to recompute + /// semantic work. + /// + /// This method of checking for changes has a few important properties that differentiate it from other methods of determining project version. + /// + /// Changes to methods inside the current project will be reflected to compute updated diagnostics. + /// does not change as it only returns top level changes. + /// Reloading a project without making any changes will re-use cached diagnostics. + /// changes as the project is removed, then added resulting in a version change. + /// + /// + /// + internal Task GetDependentChecksumAsync(CancellationToken cancellationToken) + => _solution.State.GetDependentChecksumAsync(this.Id, cancellationToken); + /// /// Creates a new instance of this project updated to have the new assembly name. /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs index 3e07d005bb680..3c136eebd317b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs @@ -381,7 +381,7 @@ private void TopologicalSort( /// /// Returns a sequence of sets, where each set contains items with shared interdependency, - /// and there is no dependency between sets. + /// and there is no dependency between sets. Each set returned will sorted in topological order. /// public IEnumerable> GetDependencySets(CancellationToken cancellationToken = default) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs index cbb722776a0bb..b6947a1036576 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs @@ -385,6 +385,12 @@ public Task GetLatestDocumentVersionAsync(CancellationToken cancel public async Task GetSemanticVersionAsync(CancellationToken cancellationToken = default) { var docVersion = await _lazyLatestDocumentTopLevelChangeVersion.GetValueAsync(cancellationToken).ConfigureAwait(false); + + // This is unfortunate, however the impact of this is that *any* change to our project-state version will + // cause us to think the semantic version of the project has changed. Thus, any change to a project property + // that does *not* flow into the compiler still makes us think the semantic version has changed. This is + // likely to not be too much of an issue as these changes should be rare, and it's better to be conservative + // and assume there was a change than to wrongly presume there was not. return docVersion.GetNewerVersion(this.Version); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs index e6504ebcc875b..df1b9727e7105 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs @@ -2,13 +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. +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.Diagnostics; +using Microsoft.CodeAnalysis.ErrorReporting; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis @@ -83,7 +84,17 @@ public TouchAdditionalDocumentAction(AdditionalDocumentState oldState, Additiona var oldText = _oldState.AdditionalText; var newText = _newState.AdditionalText; - return generatorDriver.ReplaceAdditionalText(oldText, newText); + try + { + return generatorDriver.ReplaceAdditionalText(oldText, newText); + } + catch (ArgumentException ex) when (FatalError.ReportAndCatch(ex)) + { + // For some reason, our generator driver has gotten out of sync; by returning null here we force + // ourselves to create a new driver in FinalizeCompilationAsync. This is the investigation for + // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1423058. + return null; + } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs index 086b5c44c4718..56fcfb22c30b0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs @@ -44,18 +44,18 @@ private partial class CompilationTracker : ICompilationTracker // guarantees only one thread is building at a time private readonly SemaphoreSlim _buildLock = new(initialCount: 1); - public CachedSkeletonReferences CachedSkeletonReferences { get; } + public SkeletonReferenceCache SkeletonReferenceCache { get; } private CompilationTracker( ProjectState project, CompilationTrackerState state, - CachedSkeletonReferences cachedSkeletonReferences) + SkeletonReferenceCache cachedSkeletonReferences) { Contract.ThrowIfNull(project); this.ProjectState = project; _stateDoNotAccessDirectly = state; - this.CachedSkeletonReferences = cachedSkeletonReferences; + this.SkeletonReferenceCache = cachedSkeletonReferences; } /// @@ -147,13 +147,13 @@ public ICompilationTracker Fork( var newState = CompilationTrackerState.Create( solutionServices, baseCompilation, state.GeneratorInfo, state.FinalCompilationWithGeneratedDocuments?.GetValueOrNull(cancellationToken), intermediateProjects); - return new CompilationTracker(newProject, newState, this.CachedSkeletonReferences.Clone()); + return new CompilationTracker(newProject, newState, this.SkeletonReferenceCache.Clone()); } else { // We have no compilation, but we might have information about generated docs. var newState = new NoCompilationState(state.GeneratorInfo.WithDocumentsAreFinal(false)); - return new CompilationTracker(newProject, newState, this.CachedSkeletonReferences.Clone()); + return new CompilationTracker(newProject, newState, this.SkeletonReferenceCache.Clone()); } } @@ -197,7 +197,7 @@ public ICompilationTracker FreezePartialStateWithTree(SolutionState solution, Do this.ProjectState.Id, metadataReferenceToProjectId); - return new CompilationTracker(inProgressProject, finalState, this.CachedSkeletonReferences.Clone()); + return new CompilationTracker(inProgressProject, finalState, this.SkeletonReferenceCache.Clone()); } /// @@ -944,40 +944,6 @@ private async Task FinalizeCompilationAsync( } } - public async Task GetMetadataReferenceAsync( - SolutionState solution, - ProjectState fromProject, - ProjectReference projectReference, - CancellationToken cancellationToken) - { - try - { - // if we already have the compilation and its right kind then use it. - if (this.ProjectState.LanguageServices == fromProject.LanguageServices - && this.TryGetCompilation(out var compilation)) - { - return compilation.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes); - } - - // If same language then we can wrap the other project's compilation into a compilation reference - if (this.ProjectState.LanguageServices == fromProject.LanguageServices) - { - // otherwise, base it off the compilation by building it first. - compilation = await this.GetCompilationAsync(solution, cancellationToken).ConfigureAwait(false); - return compilation.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes); - } - else - { - // otherwise get a metadata only image reference that is built by emitting the metadata from the referenced project's compilation and re-importing it. - return await this.GetMetadataOnlyImageReferenceAsync(solution, projectReference, cancellationToken).ConfigureAwait(false); - } - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) - { - throw ExceptionUtilities.Unreachable; - } - } - /// /// Attempts to get (without waiting) a metadata reference to a possibly in progress /// compilation. Only actual compilation references are returned. Could potentially @@ -1000,49 +966,6 @@ public async Task GetMetadataReferenceAsync( return null; } - /// - /// Gets a metadata reference to the metadata-only-image corresponding to the compilation. - /// - private async Task GetMetadataOnlyImageReferenceAsync( - SolutionState solution, ProjectReference projectReference, CancellationToken cancellationToken) - { - try - { - using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_GetMetadataOnlyImage, cancellationToken)) - { - var workspace = solution.Workspace; - var version = await this.GetDependentSemanticVersionAsync(solution, cancellationToken).ConfigureAwait(false); - - workspace.LogTestMessage($"Looking for a cached skeleton assembly for {projectReference.ProjectId} before taking the lock..."); - - var properties = new MetadataReferenceProperties(aliases: projectReference.Aliases, embedInteropTypes: projectReference.EmbedInteropTypes); - - // Attempt to reuse an existing skeleton cached for this compilation tracker. - var reference = this.CachedSkeletonReferences.TryGetReference(version, properties); - if (reference != null) - { - workspace.LogTestMessage($"Reusing the already cached skeleton assembly for {projectReference.ProjectId}"); - return reference; - } - - // using async build lock so we don't get multiple consumers attempting to build metadata-only images for the same compilation. - using (await _buildLock.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) - { - workspace.LogTestMessage($"Build lock taken for {ProjectState.Id}..."); - - // okay, we still don't have one. bring the compilation to final state since we are going to use it to create skeleton assembly - var compilationInfo = await this.GetOrBuildCompilationInfoAsync(solution, lockGate: false, cancellationToken: cancellationToken).ConfigureAwait(false); - return this.CachedSkeletonReferences.GetOrBuildReference( - workspace, properties, compilationInfo.Compilation, version, cancellationToken); - } - } - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) - { - throw ExceptionUtilities.Unreachable; - } - } - public Task HasSuccessfullyLoadedAsync(SolutionState solution, CancellationToken cancellationToken) { var state = this.ReadState(); @@ -1085,12 +1008,13 @@ public async ValueTask> GetSour return state is FinalState finalState ? finalState.GeneratorInfo.Documents.GetState(documentId) : null; } - #region Versions + #region Versions and Checksums // Dependent Versions are stored on compilation tracker so they are more likely to survive when unrelated solution branching occurs. private AsyncLazy? _lazyDependentVersion; private AsyncLazy? _lazyDependentSemanticVersion; + private AsyncLazy? _lazyDependentChecksum; public Task GetDependentVersionAsync(SolutionState solution, CancellationToken cancellationToken) { @@ -1155,6 +1079,52 @@ private async Task ComputeDependentSemanticVersionAsync(SolutionSt return version; } + + public Task GetDependentChecksumAsync(SolutionState solution, CancellationToken cancellationToken) + { + if (_lazyDependentChecksum == null) + { + var tmp = solution; // temp. local to avoid a closure allocation for the fast path + // note: solution is captured here, but it will go away once GetValueAsync executes. + Interlocked.CompareExchange(ref _lazyDependentChecksum, new AsyncLazy(c => ComputeDependentChecksumAsync(tmp, c), cacheResult: true), null); + } + + return _lazyDependentChecksum.GetValueAsync(cancellationToken); + } + + private async Task ComputeDependentChecksumAsync(SolutionState solution, CancellationToken cancellationToken) + { + using var tempChecksumArray = TemporaryArray.Empty; + + // Get the checksum for the project itself. + var projectChecksum = await this.ProjectState.GetChecksumAsync(cancellationToken).ConfigureAwait(false); + tempChecksumArray.Add(projectChecksum); + + // Calculate a checksum this project and for each dependent project that could affect semantics for + // this project. Ensure that the checksum calculation orders the projects consistently so that we get + // the same checksum across sessions of VS. Note: we use the project filepath+name as a unique way + // to reference a project. This matches the logic in our persistence-service implemention as to how + // information is associated with a project. + var transitiveDependencies = solution.GetProjectDependencyGraph().GetProjectsThatThisProjectTransitivelyDependsOn(this.ProjectState.Id); + var orderedProjectIds = transitiveDependencies.OrderBy(id => + { + var depProject = solution.GetRequiredProjectState(id); + return (depProject.FilePath, depProject.Name); + }); + + foreach (var projectId in orderedProjectIds) + { + var referencedProject = solution.GetRequiredProjectState(projectId); + + // Note that these checksums should only actually be calculated once, if the project is unchanged + // the same checksum will be returned. + var referencedProjectChecksum = await referencedProject.GetChecksumAsync(cancellationToken).ConfigureAwait(false); + tempChecksumArray.Add(referencedProjectChecksum); + } + + return Checksum.Create(tempChecksumArray.ToImmutableAndClear()); + } + #endregion } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.GeneratedFileReplacingCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.GeneratedFileReplacingCompilationTracker.cs index bdbc82658b6b8..d1d9a29bbaf33 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.GeneratedFileReplacingCompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.GeneratedFileReplacingCompilationTracker.cs @@ -23,6 +23,8 @@ private class GeneratedFileReplacingCompilationTracker : ICompilationTracker private readonly ICompilationTracker _underlyingTracker; private readonly SourceGeneratedDocumentState _replacedGeneratedDocumentState; + private AsyncLazy? _lazyDependentChecksum; + /// /// The lazily-produced compilation that has the generated document updated. This is initialized by call to /// . @@ -30,17 +32,16 @@ private class GeneratedFileReplacingCompilationTracker : ICompilationTracker [DisallowNull] private Compilation? _compilationWithReplacement; - public CachedSkeletonReferences CachedSkeletonReferences { get; } + public SkeletonReferenceCache SkeletonReferenceCache { get; } + public ProjectState ProjectState => _underlyingTracker.ProjectState; public GeneratedFileReplacingCompilationTracker(ICompilationTracker underlyingTracker, SourceGeneratedDocumentState replacementDocumentState) { _underlyingTracker = underlyingTracker; _replacedGeneratedDocumentState = replacementDocumentState; - CachedSkeletonReferences = underlyingTracker.CachedSkeletonReferences.Clone(); + SkeletonReferenceCache = underlyingTracker.SkeletonReferenceCache.Clone(); } - public ProjectState ProjectState => _underlyingTracker.ProjectState; - public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary) { if (_compilationWithReplacement == null) @@ -54,22 +55,6 @@ public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary) } } - public bool? ContainsSymbolsWithNameFromDeclarationOnlyCompilation(Func predicate, SymbolFilter filter, CancellationToken cancellationToken) - { - // TODO: This only needs to be implemented if a feature that operates from a source generated file needs to search for declarations - // with other names; those APIs are only used by certain code fixes which isn't a need for now. This will need to be fixed up when - // we complete https://github.com/dotnet/roslyn/issues/49533. - throw new NotImplementedException(); - } - - public bool? ContainsSymbolsWithNameFromDeclarationOnlyCompilation(string name, SymbolFilter filter, CancellationToken cancellationToken) - { - // TODO: This only needs to be implemented if a feature that operates from a source generated file needs to search for declarations - // with other names; those APIs are only used by certain code fixes which isn't a need for now. This will need to be fixed up when - // we complete https://github.com/dotnet/roslyn/issues/49533. - throw new NotImplementedException(); - } - public ICompilationTracker Fork(SolutionServices solutionServices, ProjectState newProject, CompilationAndGeneratorDriverTranslationAction? translate = null, CancellationToken cancellationToken = default) { // TODO: This only needs to be implemented if a feature that operates from a source generated file then makes @@ -122,31 +107,29 @@ public async Task GetCompilationAsync(SolutionState solution, Cance return _compilationWithReplacement; } - public Task GetDependentSemanticVersionAsync(SolutionState solution, CancellationToken cancellationToken) - { - return _underlyingTracker.GetDependentSemanticVersionAsync(solution, cancellationToken); - } - public Task GetDependentVersionAsync(SolutionState solution, CancellationToken cancellationToken) - { - return _underlyingTracker.GetDependentVersionAsync(solution, cancellationToken); - } + => _underlyingTracker.GetDependentVersionAsync(solution, cancellationToken); - public async Task GetMetadataReferenceAsync(SolutionState solution, ProjectState fromProject, ProjectReference projectReference, CancellationToken cancellationToken) - { - var compilation = await GetCompilationAsync(solution, cancellationToken).ConfigureAwait(false); + public Task GetDependentSemanticVersionAsync(SolutionState solution, CancellationToken cancellationToken) + => _underlyingTracker.GetDependentSemanticVersionAsync(solution, cancellationToken); - // If it's the same language we can just make a CompilationReference - if (this.ProjectState.LanguageServices == fromProject.LanguageServices) - return compilation.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes); + public Task GetDependentChecksumAsync(SolutionState solution, CancellationToken cancellationToken) + { + if (_lazyDependentChecksum == null) + { + var tmp = solution; // temp. local to avoid a closure allocation for the fast path + // note: solution is captured here, but it will go away once GetValueAsync executes. + Interlocked.CompareExchange(ref _lazyDependentChecksum, new AsyncLazy(c => ComputeDependentChecksumAsync(tmp, c), cacheResult: true), null); + } - // Otherwise we need to create a skeleton for this project. See if we can reuse an existing - // one, or create a new one when we can't. - var version = await GetDependentSemanticVersionAsync(solution, cancellationToken).ConfigureAwait(false); - var properties = new MetadataReferenceProperties(MetadataImageKind.Assembly, projectReference.Aliases, projectReference.EmbedInteropTypes); - return this.CachedSkeletonReferences.GetOrBuildReference(solution.Workspace, properties, compilation, version, cancellationToken); + return _lazyDependentChecksum.GetValueAsync(cancellationToken); } + private async Task ComputeDependentChecksumAsync(SolutionState solution, CancellationToken cancellationToken) + => Checksum.Create( + await _underlyingTracker.GetDependentChecksumAsync(solution, cancellationToken).ConfigureAwait(false), + await _replacedGeneratedDocumentState.GetChecksumAsync(cancellationToken).ConfigureAwait(false)); + public CompilationReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference) { // This method is used if you're forking a solution with partial semantics, and used to quickly produce references. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.ICompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.ICompilationTracker.cs index 1faea2548610d..dde184ad00b21 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.ICompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.ICompilationTracker.cs @@ -14,7 +14,7 @@ private interface ICompilationTracker { ProjectState ProjectState { get; } - CachedSkeletonReferences CachedSkeletonReferences { get; } + SkeletonReferenceCache SkeletonReferenceCache { get; } /// /// Returns if this / could produce the @@ -34,16 +34,11 @@ private interface ICompilationTracker ICompilationTracker Fork(SolutionServices solutionServices, ProjectState newProject, CompilationAndGeneratorDriverTranslationAction? translate = null, CancellationToken cancellationToken = default); ICompilationTracker FreezePartialStateWithTree(SolutionState solution, DocumentState docState, SyntaxTree tree, CancellationToken cancellationToken); Task GetCompilationAsync(SolutionState solution, CancellationToken cancellationToken); - Task GetDependentSemanticVersionAsync(SolutionState solution, CancellationToken cancellationToken); + Task GetDependentVersionAsync(SolutionState solution, CancellationToken cancellationToken); + Task GetDependentSemanticVersionAsync(SolutionState solution, CancellationToken cancellationToken); + Task GetDependentChecksumAsync(SolutionState solution, CancellationToken cancellationToken); - /// - /// Get a metadata reference to this compilation info's compilation with respect to - /// another project. For cross language references produce a skeletal assembly. If the - /// compilation is not available, it is built. If a skeletal assembly reference is - /// needed and does not exist, it is also built. - /// - Task GetMetadataReferenceAsync(SolutionState solution, ProjectState fromProject, ProjectReference projectReference, CancellationToken cancellationToken); CompilationReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference); ValueTask> GetSourceGeneratedDocumentStatesAsync(SolutionState solution, CancellationToken cancellationToken); Task HasSuccessfullyLoadedAsync(SolutionState solution, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SkeletonReferenceCache.cs new file mode 100644 index 0000000000000..e35383036b0d8 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SkeletonReferenceCache.cs @@ -0,0 +1,350 @@ +// 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.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Internal.Log; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis; + +internal partial class SolutionState +{ + /// + /// Caches the skeleton references produced for a given project/compilation under the varying + /// it might be referenced by. Skeletons are used in the compilation + /// tracker to allow cross-language project references with live semantic updating between VB/C# and vice versa. + /// Specifically, in a cross language case we will build a skeleton ref for the referenced project and have the + /// referrer use that to understand its semantics. + /// + /// This approach works, but has the caveat that live cross-language semantics are only possible when the + /// skeleton assembly can be built. This should always be the case for correct code, but it may not be the + /// case for code with errors depending on if the respective language compiler is resilient to those errors or not. + /// In that case though where the skeleton cannot be built, this type provides mechanisms to fallback to the last + /// successfully built skeleton so that a somewhat reasonable experience can be maintained. If we failed to do this + /// and instead returned nothing, a user would find that practically all semantic experiences that depended on + /// that particular project would fail or be seriously degraded (e.g. diagnostics). To that end, it's better to + /// limp along with stale date, then barrel on ahead with no data. + /// + /// The implementation works by keeping metadata references around associated with a specific + /// for a project. As long as the for that project + /// is the same, then all the references of it can be reused. When an forks + /// itself, it will also this, allowing previously computed references to be used by later forks. + /// However, this means that later forks (esp. ones that fail to produce a skeleton, or which produce a skeleton for + /// different semantics) will not leak backward to a prior , causing it to see a view of the world + /// inapplicable to its current snapshot. + /// + private partial class SkeletonReferenceCache + { + private static readonly EmitOptions s_metadataOnlyEmitOptions = new(metadataOnly: true); + + /// + /// Lock we take before emitting metadata. Metadata emit is extremely expensive. So we want to avoid cases + /// where N threads come in and try to get the skeleton for a particular project. This way they will instead + /// yield if something else is computing and will then use the single instance computed once one thread succeeds. + /// + private readonly SemaphoreSlim _emitGate = new(initialCount: 1); + + /// + /// Lock around and to ensure they are updated/read + /// in an atomic fashion. + /// + private readonly object _stateGate = new(); + + /// + /// The version of the project that the + /// corresponds to. + /// + private VersionStamp? _version; + + /// + /// Mapping from metadata-reference-properties to the actual metadata reference for them. + /// + private SkeletonReferenceSet? _skeletonReferenceSet; + + public SkeletonReferenceCache() + : this(version: null, skeletonReferenceSet: null) + { + } + + private SkeletonReferenceCache( + VersionStamp? version, + SkeletonReferenceSet? skeletonReferenceSet) + { + _version = version; + _skeletonReferenceSet = skeletonReferenceSet; + } + + /// + /// Produces a copy of the , allowing forks of to + /// reuse s when their dependent semantic version matches ours. In the case where + /// the version is different, then the clone will attempt to make a new skeleton reference for that version. If it + /// succeeds, it will use that. If it fails however, it can still use our skeletons. + /// + public SkeletonReferenceCache Clone() + { + lock (_stateGate) + { + // pass along the best version/reference-set we computed for ourselves. That way future ProjectStates + // can use this data if either the version changed, or they weren't able to build a skeleton for themselves. + // By passing along a copy we ensure that if they have a different version, they'll end up producing a new + // SkeletonReferenceSet where they'll store their own data in which will not affect prior ProjectStates. + return new SkeletonReferenceCache(_version, _skeletonReferenceSet); + } + } + + public async Task GetOrBuildReferenceAsync( + ICompilationTracker compilationTracker, + SolutionState solution, + MetadataReferenceProperties properties, + CancellationToken cancellationToken) + { + var version = await compilationTracker.GetDependentSemanticVersionAsync(solution, cancellationToken).ConfigureAwait(false); + var referenceSet = await TryGetOrCreateReferenceSetAsync( + compilationTracker, solution, version, cancellationToken).ConfigureAwait(false); + return referenceSet?.GetMetadataReference(properties); + } + + private async Task TryGetOrCreateReferenceSetAsync( + ICompilationTracker compilationTracker, + SolutionState solution, + VersionStamp version, + CancellationToken cancellationToken) + { + // First, just see if we have cached a reference set that is complimentary with the version of the project + // being passed in. If so, we can just reuse what we already computed before. + if (TryReadSkeletonReferenceSetAtThisVersion(version, out var referenceSet)) + return referenceSet; + + // okay, we don't have anything cached with this version. so create one now. Note: this is expensive + // so ensure only one thread is doing hte work to actually make the compilation and emit it. + using (await _emitGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + { + // after taking the gate, another thread may have succeeded. See if we can use their version if so: + if (TryReadSkeletonReferenceSetAtThisVersion(version, out referenceSet)) + return referenceSet; + + // Ok, first thread to get in and actually do this work. Build the compilation and try to emit it. + // Regardless of if we succeed or fail, store this result so this only happens once. + + var compilation = await compilationTracker.GetCompilationAsync(solution, cancellationToken).ConfigureAwait(false); + var storage = TryCreateMetadataStorage(solution.Workspace, compilation, cancellationToken); + + lock (_stateGate) + { + // If we successfully created the metadata storage, then create the new set that points to it. + // if we didn't, that's ok too, we'll just say that for this requested version, that we can + // return any prior computed reference set (including 'null' if we've never successfully made + // a skeleton). + if (storage != null) + _skeletonReferenceSet = new SkeletonReferenceSet(storage, compilation.AssemblyName, new DeferredDocumentationProvider(compilation)); + + _version = version; + + return _skeletonReferenceSet; + } + } + } + + private bool TryReadSkeletonReferenceSetAtThisVersion(VersionStamp version, out SkeletonReferenceSet? result) + { + lock (_stateGate) + { + // if we're asking about the same version as we've cached, then return whatever have (regardless of + // whether it succeeded or not. + if (version == _version) + { + result = _skeletonReferenceSet; + return true; + } + } + + result = null; + return false; + } + + private static ITemporaryStreamStorage? TryCreateMetadataStorage(Workspace workspace, Compilation compilation, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + workspace.LogTestMessage($"Beginning to create a skeleton assembly for {compilation.AssemblyName}..."); + + using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_EmitMetadataOnlyImage, cancellationToken)) + { + using var stream = SerializableBytes.CreateWritableStream(); + // note: cloning compilation so we don't retain all the generated symbols after its emitted. + // * REVIEW * is cloning clone p2p reference compilation as well? + var emitResult = compilation.Clone().Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); + + if (emitResult.Success) + { + workspace.LogTestMessage($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); + + var temporaryStorageService = workspace.Services.GetRequiredService(); + var storage = temporaryStorageService.CreateTemporaryStreamStorage(cancellationToken); + + stream.Position = 0; + storage.WriteStream(stream, cancellationToken); + + return storage; + } + else + { + workspace.LogTestMessage($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); + + foreach (var diagnostic in emitResult.Diagnostics) + { + workspace.LogTestMessage(" " + diagnostic.GetMessage()); + } + + // log emit failures so that we can improve most common cases + Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => + { + // log errors in the format of + // CS0001:1;CS002:10;... + var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); + m["Errors"] = string.Join(";", groups); + })); + + return null; + } + } + } + finally + { + workspace.LogTestMessage($"Done trying to create a skeleton assembly for {compilation.AssemblyName}"); + } + } + + private sealed class SkeletonReferenceSet + { + /// + /// A map to ensure that the streams from the temporary storage service that back the metadata we create stay alive as long + /// as the metadata is alive. + /// + private static readonly ConditionalWeakTable s_lifetime = new(); + + private readonly ITemporaryStreamStorage? _storage; + private readonly string? _assemblyName; + + /// + /// The documentation provider used to lookup xml docs for any metadata reference we pass out. See + /// docs on for why this is safe to hold onto despite it + /// rooting a compilation internally. + /// + private readonly DeferredDocumentationProvider _documentationProvider; + + /// + /// Use WeakReference so we don't keep MetadataReference's alive if they are not being consumed. + /// Note: if the weak-reference is actually (not that it points to null), + /// that means we know we were unable to generate a reference for those properties, and future + /// calls can early exit. + /// + /// + /// This instance should be locked when being read/written. + /// + private readonly Dictionary?> _metadataReferences = new(); + + public SkeletonReferenceSet( + ITemporaryStreamStorage? storage, + string? assemblyName, + DeferredDocumentationProvider documentationProvider) + { + _storage = storage; + _assemblyName = assemblyName; + _documentationProvider = documentationProvider; + } + + public MetadataReference? GetMetadataReference(MetadataReferenceProperties properties) + { + // lookup first and eagerly return cached value if we have it. + lock (_metadataReferences) + { + if (TryGetExisting_NoLock(properties, out var metadataReference)) + return metadataReference; + } + + // otherwise, create the metadata outside of the lock, and then try to assign it if no one else beat us + { + var metadataReference = CreateReference(properties.Aliases, properties.EmbedInteropTypes, _documentationProvider); + var weakMetadata = metadataReference == null ? null : new WeakReference(metadataReference); + + lock (_metadataReferences) + { + // see if someone beat us to writing this. + if (TryGetExisting_NoLock(properties, out var existingMetadataReference)) + return existingMetadataReference; + + _metadataReferences[properties] = weakMetadata; + } + + return metadataReference; + } + + bool TryGetExisting_NoLock(MetadataReferenceProperties properties, out MetadataReference? metadataReference) + { + metadataReference = null; + if (!_metadataReferences.TryGetValue(properties, out var weakMetadata)) + return false; + + // If we are pointing at a null-weak reference (not a weak reference that points to null), then we + // know we failed to create the metadata the last time around, and we can shortcircuit immediately, + // returning null *with* success to bubble that up. + if (weakMetadata == null) + return true; + + return weakMetadata.TryGetTarget(out metadataReference); + } + } + + private MetadataReference? CreateReference(ImmutableArray aliases, bool embedInteropTypes, DocumentationProvider documentationProvider) + { + if (_storage == null) + return null; + + // first see whether we can use native memory directly. + var stream = _storage.ReadStream(); + AssemblyMetadata metadata; + + if (stream is ISupportDirectMemoryAccess supportNativeMemory) + { + // this is unfortunate that if we give stream, compiler will just re-copy whole content to + // native memory again. this is a way to get around the issue by we getting native memory ourselves and then + // give them pointer to the native memory. also we need to handle lifetime ourselves. + metadata = AssemblyMetadata.Create(ModuleMetadata.CreateFromImage(supportNativeMemory.GetPointer(), (int)stream.Length)); + + // Tie lifetime of stream to metadata we created. It is important to tie this to the Metadata and not the + // metadata reference, as PE symbols hold onto just the Metadata. We can use Add here since we created + // a brand new object in AssemblyMetadata.Create above. + s_lifetime.Add(metadata, supportNativeMemory); + } + else + { + // Otherwise, we just let it use stream. Unfortunately, if we give stream, compiler will + // internally copy it to native memory again. since compiler owns lifetime of stream, + // it would be great if compiler can be little bit smarter on how it deals with stream. + + // We don't deterministically release the resulting metadata since we don't know + // when we should. So we leave it up to the GC to collect it and release all the associated resources. + metadata = AssemblyMetadata.CreateFromStream(stream); + } + + return metadata.GetReference( + documentation: documentationProvider, + aliases: aliases, + embedInteropTypes: embedInteropTypes, + display: _assemblyName); + } + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 4328809477ac6..f1a7f4d23bbb4 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Logging; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; @@ -399,6 +400,9 @@ public Task GetDependentVersionAsync(ProjectId projectId, Cancella public Task GetDependentSemanticVersionAsync(ProjectId projectId, CancellationToken cancellationToken) => this.GetCompilationTracker(projectId).GetDependentSemanticVersionAsync(this, cancellationToken); + public Task GetDependentChecksumAsync(ProjectId projectId, CancellationToken cancellationToken) + => this.GetCompilationTracker(projectId).GetDependentChecksumAsync(this, cancellationToken); + public ProjectState? GetProjectState(ProjectId projectId) { _projectIdToProjectStateMap.TryGetValue(projectId, out var state); @@ -1865,16 +1869,51 @@ public SolutionState WithFrozenSourceGeneratedDocument(SourceGeneratedDocumentId private static readonly ConditionalWeakTable s_assemblyOrModuleSymbolToProjectMap = new(); /// - /// Get a metadata reference for the project's compilation + /// Get a metadata reference for the project's compilation. Returns upon failure, which + /// can happen when trying to build a skeleton reference that fails to build. /// - public Task GetMetadataReferenceAsync(ProjectReference projectReference, ProjectState fromProject, CancellationToken cancellationToken) + public Task GetMetadataReferenceAsync(ProjectReference projectReference, ProjectState fromProject, CancellationToken cancellationToken) { try { // Get the compilation state for this project. If it's not already created, then this // will create it. Then force that state to completion and get a metadata reference to it. var tracker = this.GetCompilationTracker(projectReference.ProjectId); - return tracker.GetMetadataReferenceAsync(this, fromProject, projectReference, cancellationToken); + return GetMetadataReferenceAsync(tracker, fromProject, projectReference, cancellationToken); + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) + { + throw ExceptionUtilities.Unreachable; + } + } + + /// + /// Get a metadata reference to this compilation info's compilation with respect to + /// another project. For cross language references produce a skeletal assembly. If the + /// compilation is not available, it is built. If a skeletal assembly reference is + /// needed and does not exist, it is also built. + /// + private async Task GetMetadataReferenceAsync( + ICompilationTracker tracker, ProjectState fromProject, ProjectReference projectReference, CancellationToken cancellationToken) + { + try + { + // If same language then we can wrap the other project's compilation into a compilation reference + if (tracker.ProjectState.LanguageServices == fromProject.LanguageServices) + { + // otherwise, base it off the compilation by building it first. + var compilation = await tracker.GetCompilationAsync(this, cancellationToken).ConfigureAwait(false); + return compilation.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes); + } + + // otherwise get a metadata only image reference that is built by emitting the metadata from the + // referenced project's compilation and re-importing it. + using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_GetMetadataOnlyImage, cancellationToken)) + { + var properties = new MetadataReferenceProperties(aliases: projectReference.Aliases, embedInteropTypes: projectReference.EmbedInteropTypes); + return await tracker.SkeletonReferenceCache.GetOrBuildReferenceAsync( + tracker, this, properties, cancellationToken).ConfigureAwait(false); + } } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs index c77ddb14f1ada..82b71ec307175 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs @@ -88,7 +88,16 @@ void AddReferencedProjects(HashSet result, ProjectId projectId) return; foreach (var refProject in projectState.ProjectReferences) - AddReferencedProjects(result, refProject.ProjectId); + { + // Note: it's possible in the workspace to see project-ids that don't have a corresponding project + // state. While not desirable, we allow project's to have refs to projects that no longer exist + // anymore. This state is expected to be temporary until the project is explicitly told by the + // host to remove the reference. We do not expose this through the full Solution/Project which + // filters out this case already (in Project.ProjectReferences). However, becausde we're at the + // ProjectState level it cannot do that filtering unless examined through us (the SolutionState). + if (this.ProjectStates.ContainsKey(refProject.ProjectId)) + AddReferencedProjects(result, refProject.ProjectId); + } } } diff --git a/src/Workspaces/CoreTest/CodeCleanup/NormalizeModifiersOrOperatorsTests.cs b/src/Workspaces/CoreTest/CodeCleanup/NormalizeModifiersOrOperatorsTests.cs index 915bf26e3ce1a..c0c80f418b0a2 100644 --- a/src/Workspaces/CoreTest/CodeCleanup/NormalizeModifiersOrOperatorsTests.cs +++ b/src/Workspaces/CoreTest/CodeCleanup/NormalizeModifiersOrOperatorsTests.cs @@ -684,7 +684,7 @@ End Sub var expected = @"Class A Sub Method( _ ByVal _ - _ + _ t As String, ByRef t1 As String) End Sub End Class"; @@ -721,7 +721,7 @@ Private _ var expected = @"Class A Private _ Shared _ - _ + _ a As Integer = 1 End Class"; diff --git a/src/Workspaces/CoreTest/CodeCleanup/RemoveUnnecessaryLineContinuationTests.cs b/src/Workspaces/CoreTest/CodeCleanup/RemoveUnnecessaryLineContinuationTests.cs index 4116e8da48527..a1b5f39c1c13e 100644 --- a/src/Workspaces/CoreTest/CodeCleanup/RemoveUnnecessaryLineContinuationTests.cs +++ b/src/Workspaces/CoreTest/CodeCleanup/RemoveUnnecessaryLineContinuationTests.cs @@ -118,7 +118,7 @@ public async Task ColonTrivia_LineContinuation_Comment() var expected = @" - _ + _ ' test Console.WriteLine("")"; @@ -398,9 +398,9 @@ public async Task ColonToken_LineContinuation_AfterColonToken_Colon_Comment() var expected = @" Console.WriteLine() _ - _ + _ ' test - _ + _ Console.WriteLine()"; await VerifyAsync(CreateMethod(code), CreateMethod(expected)); @@ -471,7 +471,7 @@ public async Task ImplicitLineContinuation_Multiple() var expected = @" Dim i = _ - _ + _ 1 + 2"; @@ -811,7 +811,7 @@ End Sub var expected = @"Module Program Sub Main( - _ + _ args _ As String) End Sub diff --git a/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs b/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs index 9f34077201bca..94c0c22680e45 100644 --- a/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs +++ b/src/Workspaces/CoreTest/Remote/ServiceDescriptorTests.cs @@ -173,6 +173,7 @@ internal void GetFeatureDisplayName( ServiceDescriptor descriptorCoreClr64ServerGC) { Assert.NotNull(serviceInterface); + var expectedName = descriptor64.GetFeatureDisplayName(); // The service name couldn't be found. It may need to be added to RemoteWorkspacesResources.resx as FeatureName_{name} diff --git a/src/Workspaces/CoreTestUtilities/TestErrorReportingService.cs b/src/Workspaces/CoreTestUtilities/TestErrorReportingService.cs index 1ce19daf55af7..0d52027c1eac8 100644 --- a/src/Workspaces/CoreTestUtilities/TestErrorReportingService.cs +++ b/src/Workspaces/CoreTestUtilities/TestErrorReportingService.cs @@ -6,6 +6,7 @@ using System.Composition; using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Telemetry; using Xunit; namespace Microsoft.CodeAnalysis.Test.Utilities @@ -27,10 +28,10 @@ public string HostDisplayName public void ShowDetailedErrorInfo(Exception exception) => OnError(exception.Message); - public void ShowGlobalErrorInfo(string message, Exception? exception, params InfoBarUI[] items) + public void ShowGlobalErrorInfo(string message, TelemetryFeatureName featureName, Exception? exception, params InfoBarUI[] items) => OnError(message); - public void ShowFeatureNotAvailableErrorInfo(string message, Exception? exception) + public void ShowFeatureNotAvailableErrorInfo(string message, TelemetryFeatureName featureName, Exception? exception) => OnError($"{message} {exception}"); } } diff --git a/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs b/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs index 5de37592b52b2..cc0016c040cc3 100644 --- a/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs +++ b/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Telemetry; using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; @@ -453,9 +454,6 @@ private bool ReportUnexpectedException(Exception exception, CancellationToken ca return true; } - // report telemetry event: - Logger.Log(FunctionId.FeatureNotAvailable, $"{_serviceDescriptor.Moniker}: {exception.GetType()}: {exception.Message}"); - return FatalError.ReportAndCatch(exception); } @@ -508,7 +506,7 @@ private void OnUnexpectedException(Exception exception, CancellationToken cancel internalException = exception; } - _errorReportingService.ShowFeatureNotAvailableErrorInfo(message, internalException); + _errorReportingService.ShowFeatureNotAvailableErrorInfo(message, TelemetryFeatureName.GetRemoteFeatureName(_serviceDescriptor.ComponentName, _serviceDescriptor.SimpleName), internalException); } } } diff --git a/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj b/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj index 42a589b5eeb8d..deda983a2a852 100644 --- a/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj +++ b/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj @@ -4,7 +4,7 @@ Library Microsoft.CodeAnalysis.Remote - netcoreapp3.1;netstandard2.0 + net6.0;netstandard2.0 true full diff --git a/src/Workspaces/Remote/Core/ServiceDescriptor.cs b/src/Workspaces/Remote/Core/ServiceDescriptor.cs index 4f87b62ac4f78..496a137d09634 100644 --- a/src/Workspaces/Remote/Core/ServiceDescriptor.cs +++ b/src/Workspaces/Remote/Core/ServiceDescriptor.cs @@ -3,10 +3,9 @@ // See the LICENSE file in the project root for more information. using System; -using MessagePack; -using MessagePack.Resolvers; +using System.Diagnostics; using Microsoft.ServiceHub.Framework; -using Nerdbank.Streams; +using Roslyn.Utilities; using StreamJsonRpc; namespace Microsoft.CodeAnalysis.Remote @@ -17,6 +16,11 @@ namespace Microsoft.CodeAnalysis.Remote /// internal sealed class ServiceDescriptor : ServiceJsonRpcDescriptor { + /// + /// Brokered services must be defined in Microsoft.VisualStudio service namespace in order to be considered first party. + /// + internal const string ServiceNameTopLevelPrefix = "Microsoft.VisualStudio."; + private static readonly JsonRpcTargetOptions s_jsonRpcTargetOptions = new() { // Do not allow JSON-RPC to automatically subscribe to events and remote their calls. @@ -26,12 +30,23 @@ internal sealed class ServiceDescriptor : ServiceJsonRpcDescriptor AllowNonPublicInvocation = false }; + internal readonly string ComponentName; + internal readonly string SimpleName; + private readonly Func _featureDisplayNameProvider; private readonly RemoteSerializationOptions _serializationOptions; - private ServiceDescriptor(ServiceMoniker serviceMoniker, RemoteSerializationOptions serializationOptions, Func displayNameProvider, Type? clientInterface) + private ServiceDescriptor( + ServiceMoniker serviceMoniker, + string componentName, + string simpleName, + RemoteSerializationOptions serializationOptions, + Func displayNameProvider, + Type? clientInterface) : base(serviceMoniker, clientInterface, serializationOptions.Formatter, serializationOptions.MessageDelimiters, serializationOptions.MultiplexingStreamOptions) { + ComponentName = componentName; + SimpleName = simpleName; _featureDisplayNameProvider = displayNameProvider; _serializationOptions = serializationOptions; } @@ -39,15 +54,20 @@ private ServiceDescriptor(ServiceMoniker serviceMoniker, RemoteSerializationOpti private ServiceDescriptor(ServiceDescriptor copyFrom) : base(copyFrom) { + ComponentName = copyFrom.ComponentName; + SimpleName = copyFrom.SimpleName; _featureDisplayNameProvider = copyFrom._featureDisplayNameProvider; _serializationOptions = copyFrom._serializationOptions; } - public static ServiceDescriptor CreateRemoteServiceDescriptor(string serviceName, RemoteSerializationOptions options, Func featureDisplayNameProvider, Type? clientInterface) - => new(new ServiceMoniker(serviceName), options, featureDisplayNameProvider, clientInterface); + public static ServiceDescriptor CreateRemoteServiceDescriptor(string componentName, string simpleName, string suffix, RemoteSerializationOptions options, Func featureDisplayNameProvider, Type? clientInterface) + => new(CreateMoniker(componentName, simpleName, suffix), componentName, simpleName, options, featureDisplayNameProvider, clientInterface); + + public static ServiceDescriptor CreateInProcServiceDescriptor(string componentName, string simpleName, string suffix, Func featureDisplayNameProvider) + => new(CreateMoniker(componentName, simpleName, suffix), componentName, simpleName, RemoteSerializationOptions.Default, featureDisplayNameProvider, clientInterface: null); - public static ServiceDescriptor CreateInProcServiceDescriptor(string serviceName, Func featureDisplayNameProvider) - => new(new ServiceMoniker(serviceName), RemoteSerializationOptions.Default, featureDisplayNameProvider, clientInterface: null); + private static ServiceMoniker CreateMoniker(string componentName, string simpleName, string suffix) + => new(ServiceNameTopLevelPrefix + componentName + "." + simpleName + suffix); protected override ServiceRpcDescriptor Clone() => new ServiceDescriptor(this); @@ -64,6 +84,6 @@ protected override JsonRpcConnection CreateConnection(JsonRpc jsonRpc) } internal string GetFeatureDisplayName() - => _featureDisplayNameProvider(Moniker.Name); + => _featureDisplayNameProvider(SimpleName); } } diff --git a/src/Workspaces/Remote/Core/ServiceDescriptors.cs b/src/Workspaces/Remote/Core/ServiceDescriptors.cs index 910f8ac49a213..63a9ca51c3fa9 100644 --- a/src/Workspaces/Remote/Core/ServiceDescriptors.cs +++ b/src/Workspaces/Remote/Core/ServiceDescriptors.cs @@ -38,17 +38,11 @@ namespace Microsoft.CodeAnalysis.Remote /// internal sealed class ServiceDescriptors { - /// - /// Brokered services must be defined in Microsoft.VisualStudio service namespace in order to be considered first party. - /// - internal const string ServiceNameTopLevelPrefix = "Microsoft.VisualStudio."; - internal const string ComponentName = "LanguageServices"; private const string InterfaceNamePrefix = "IRemote"; private const string InterfaceNameSuffix = "Service"; - internal const string Prefix = "roslyn"; private const string Suffix64 = "64"; private const string SuffixServerGC = "S"; private const string SuffixCoreClr = "Core"; @@ -102,7 +96,7 @@ public ServiceDescriptors( _descriptors = interfaces.ToImmutableDictionary(i => i.serviceInterface, i => CreateDescriptors(i.serviceInterface, i.callbackInterface)); } - internal static string GetServiceName(Type serviceInterface) + internal static string GetSimpleName(Type serviceInterface) { Contract.ThrowIfFalse(serviceInterface.IsInterface); var interfaceName = serviceInterface.Name; @@ -112,18 +106,15 @@ internal static string GetServiceName(Type serviceInterface) return interfaceName.Substring(InterfaceNamePrefix.Length, interfaceName.Length - InterfaceNamePrefix.Length - InterfaceNameSuffix.Length); } - internal string GetQualifiedServiceName(Type serviceInterface) - => ServiceNameTopLevelPrefix + _componentName + "." + GetServiceName(serviceInterface); - private (ServiceDescriptor, ServiceDescriptor, ServiceDescriptor, ServiceDescriptor) CreateDescriptors(Type serviceInterface, Type? callbackInterface) { Contract.ThrowIfFalse(callbackInterface == null || callbackInterface.IsInterface); - var qualifiedServiceName = GetQualifiedServiceName(serviceInterface); - var descriptor64 = ServiceDescriptor.CreateRemoteServiceDescriptor(qualifiedServiceName + Suffix64, Options, _featureDisplayNameProvider, callbackInterface); - var descriptor64ServerGC = ServiceDescriptor.CreateRemoteServiceDescriptor(qualifiedServiceName + Suffix64 + SuffixServerGC, Options, _featureDisplayNameProvider, callbackInterface); - var descriptorCoreClr64 = ServiceDescriptor.CreateRemoteServiceDescriptor(qualifiedServiceName + SuffixCoreClr + Suffix64, Options, _featureDisplayNameProvider, callbackInterface); - var descriptorCoreClr64ServerGC = ServiceDescriptor.CreateRemoteServiceDescriptor(qualifiedServiceName + SuffixCoreClr + Suffix64 + SuffixServerGC, Options, _featureDisplayNameProvider, callbackInterface); + var simpleName = GetSimpleName(serviceInterface); + var descriptor64 = ServiceDescriptor.CreateRemoteServiceDescriptor(_componentName, simpleName, Suffix64, Options, _featureDisplayNameProvider, callbackInterface); + var descriptor64ServerGC = ServiceDescriptor.CreateRemoteServiceDescriptor(_componentName, simpleName, Suffix64 + SuffixServerGC, Options, _featureDisplayNameProvider, callbackInterface); + var descriptorCoreClr64 = ServiceDescriptor.CreateRemoteServiceDescriptor(_componentName, simpleName, SuffixCoreClr + Suffix64, Options, _featureDisplayNameProvider, callbackInterface); + var descriptorCoreClr64ServerGC = ServiceDescriptor.CreateRemoteServiceDescriptor(_componentName, simpleName, SuffixCoreClr + Suffix64 + SuffixServerGC, Options, _featureDisplayNameProvider, callbackInterface); return (descriptor64, descriptor64ServerGC, descriptorCoreClr64, descriptorCoreClr64ServerGC); } @@ -143,37 +134,11 @@ public ServiceDescriptor GetServiceDescriptor(Type serviceType, bool isRemoteHos }; } - internal static string GetFeatureDisplayName(string qualifiedServiceName) - { - var prefixLength = qualifiedServiceName.LastIndexOf('.') + 1; - Contract.ThrowIfFalse(prefixLength > 0); - - int suffixLength; - if (qualifiedServiceName.EndsWith(SuffixCoreClr + Suffix64, StringComparison.Ordinal)) - { - suffixLength = SuffixCoreClr.Length + Suffix64.Length; - } - else if (qualifiedServiceName.EndsWith(SuffixCoreClr + Suffix64 + SuffixServerGC, StringComparison.Ordinal)) - { - suffixLength = SuffixCoreClr.Length + Suffix64.Length + SuffixServerGC.Length; - } - else if (qualifiedServiceName.EndsWith(Suffix64, StringComparison.Ordinal)) - { - suffixLength = Suffix64.Length; - } - else if (qualifiedServiceName.EndsWith(Suffix64 + SuffixServerGC, StringComparison.Ordinal)) - { - suffixLength = Suffix64.Length + SuffixServerGC.Length; - } - else - { - suffixLength = 0; - } - - var shortName = qualifiedServiceName.Substring(prefixLength, qualifiedServiceName.Length - prefixLength - suffixLength); - - return RemoteWorkspacesResources.GetResourceString("FeatureName_" + shortName); - } + /// + /// is a short service name, e.g. "EditAndContinue". + /// + internal static string GetFeatureDisplayName(string serviceName) + => RemoteWorkspacesResources.GetResourceString("FeatureName_" + serviceName); internal TestAccessor GetTestAccessor() => new(this); diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index 042b8cc103ada..15e727cd763ff 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -20,9 +20,9 @@ namespace Microsoft.CodeAnalysis.Remote /// internal sealed class SolutionAssetProvider : ISolutionAssetProvider { - public const string ServiceName = ServiceDescriptors.ServiceNameTopLevelPrefix + ServiceDescriptors.ComponentName + ".SolutionAssetProvider"; + public const string ServiceName = "SolutionAssetProvider"; - internal static ServiceDescriptor ServiceDescriptor { get; } = ServiceDescriptor.CreateInProcServiceDescriptor(ServiceName, ServiceDescriptors.GetFeatureDisplayName); + internal static ServiceDescriptor ServiceDescriptor { get; } = ServiceDescriptor.CreateInProcServiceDescriptor(ServiceDescriptors.ComponentName, ServiceName, suffix: "", ServiceDescriptors.GetFeatureDisplayName); private readonly HostWorkspaceServices _services; diff --git a/src/Workspaces/Remote/ServiceHub.CoreComponents/Microsoft.CodeAnalysis.Remote.ServiceHub.CoreComponents.csproj b/src/Workspaces/Remote/ServiceHub.CoreComponents/Microsoft.CodeAnalysis.Remote.ServiceHub.CoreComponents.csproj index 3b2df03b5d00a..953c4176682ff 100644 --- a/src/Workspaces/Remote/ServiceHub.CoreComponents/Microsoft.CodeAnalysis.Remote.ServiceHub.CoreComponents.csproj +++ b/src/Workspaces/Remote/ServiceHub.CoreComponents/Microsoft.CodeAnalysis.Remote.ServiceHub.CoreComponents.csproj @@ -3,7 +3,7 @@ Exe - netcoreapp3.1 + net6.0 false diff --git a/src/Workspaces/Remote/ServiceHub/Host/Storage/RemoteCloudCachePersistentStorageService.cs b/src/Workspaces/Remote/ServiceHub/Host/Storage/RemoteCloudCachePersistentStorageService.cs new file mode 100644 index 0000000000000..369589e98a6af --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Host/Storage/RemoteCloudCachePersistentStorageService.cs @@ -0,0 +1,67 @@ +// 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; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Remote.Host; +using Microsoft.CodeAnalysis.Storage.CloudCache; +using Microsoft.ServiceHub.Framework; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.LanguageServices.Storage; +using Microsoft.VisualStudio.RpcContracts.Caching; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote.Storage +{ + internal class RemoteCloudCachePersistentStorageService : AbstractCloudCachePersistentStorageService + { + [ExportWorkspaceServiceFactory(typeof(ICloudCacheStorageService), WorkspaceKind.RemoteWorkspace), Shared] + internal class ServiceFactory : IWorkspaceServiceFactory + { + private readonly IGlobalServiceBroker _globalServiceBroker; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public ServiceFactory(IGlobalServiceBroker globalServiceBroker) + { + _globalServiceBroker = globalServiceBroker; + } + + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + => new RemoteCloudCachePersistentStorageService(_globalServiceBroker, workspaceServices.GetRequiredService()); + } + + private readonly IGlobalServiceBroker _globalServiceBroker; + + public RemoteCloudCachePersistentStorageService( + IGlobalServiceBroker globalServiceBroker, + IPersistentStorageConfiguration configuration) + : base(configuration) + { + _globalServiceBroker = globalServiceBroker; + } + + protected override async ValueTask CreateCacheServiceAsync(string solutionFolder, CancellationToken cancellationToken) + { + var serviceBroker = _globalServiceBroker.Instance; + +#pragma warning disable ISB001 // Dispose of proxies + // cache service will be disposed inside RemoteCloudCacheService.Dispose + var cacheService = await serviceBroker.GetProxyAsync( + VisualStudioServices.VS2019_10.CacheService, + // replace with CacheService.RelativePathBaseActivationArgKey once available. + new ServiceActivationOptions { ActivationArguments = ImmutableDictionary.Empty.Add("RelativePathBase", solutionFolder) }, + cancellationToken).ConfigureAwait(false); +#pragma warning restore ISB001 // Dispose of proxies + + Contract.ThrowIfNull(cacheService); + return cacheService; + } + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Host/Storage/RemoteCloudCacheStorageServiceFactory.cs b/src/Workspaces/Remote/ServiceHub/Host/Storage/RemoteCloudCacheStorageServiceFactory.cs deleted file mode 100644 index ed8fff8a5aa66..0000000000000 --- a/src/Workspaces/Remote/ServiceHub/Host/Storage/RemoteCloudCacheStorageServiceFactory.cs +++ /dev/null @@ -1,66 +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 System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Remote.Host; -using Microsoft.CodeAnalysis.Storage; -using Microsoft.CodeAnalysis.Storage.CloudCache; -using Microsoft.ServiceHub.Framework; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.LanguageServices.Storage; -using Microsoft.VisualStudio.RpcContracts.Caching; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Remote.Storage -{ - [ExportWorkspaceService(typeof(ICloudCacheStorageServiceFactory), WorkspaceKind.RemoteWorkspace), Shared] - internal class RemoteCloudCacheStorageServiceFactory : ICloudCacheStorageServiceFactory - { - private readonly IGlobalServiceBroker _globalServiceBroker; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public RemoteCloudCacheStorageServiceFactory(IGlobalServiceBroker globalServiceBroker) - { - _globalServiceBroker = globalServiceBroker; - } - - public AbstractPersistentStorageService Create(IPersistentStorageConfiguration configuration) - => new RemoteCloudCachePersistentStorageService(_globalServiceBroker, configuration); - - private class RemoteCloudCachePersistentStorageService : AbstractCloudCachePersistentStorageService - { - private readonly IGlobalServiceBroker _globalServiceBroker; - - public RemoteCloudCachePersistentStorageService(IGlobalServiceBroker globalServiceBroker, IPersistentStorageConfiguration configuration) - : base(configuration) - { - _globalServiceBroker = globalServiceBroker; - } - - protected override async ValueTask CreateCacheServiceAsync(string solutionFolder, CancellationToken cancellationToken) - { - var serviceBroker = _globalServiceBroker.Instance; - -#pragma warning disable ISB001 // Dispose of proxies - // cache service will be disposed inside RemoteCloudCacheService.Dispose - var cacheService = await serviceBroker.GetProxyAsync( - VisualStudioServices.VS2019_10.CacheService, - // replace with CacheService.RelativePathBaseActivationArgKey once available. - new ServiceActivationOptions { ActivationArguments = ImmutableDictionary.Empty.Add("RelativePathBase", solutionFolder) }, - cancellationToken).ConfigureAwait(false); -#pragma warning restore ISB001 // Dispose of proxies - - Contract.ThrowIfNull(cacheService); - return cacheService; - } - } - } -} diff --git a/src/Workspaces/Remote/ServiceHub/Host/ThrowingTraceListener.cs b/src/Workspaces/Remote/ServiceHub/Host/ThrowingTraceListener.cs index d4837a5aa5104..bd2e9a423b5f6 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/ThrowingTraceListener.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/ThrowingTraceListener.cs @@ -9,7 +9,7 @@ namespace Microsoft.CodeAnalysis.Remote { internal sealed class ThrowingTraceListener : TraceListener { - public override void Fail(string message, string detailMessage) + public override void Fail(string? message, string? detailMessage) { var stackTrace = new StackTrace(); foreach (var frame in stackTrace.GetFrames()) @@ -26,35 +26,35 @@ public override void Fail(string message, string detailMessage) (string.IsNullOrEmpty(detailMessage) ? "" : Environment.NewLine + detailMessage)); } - public override void Write(object o) + public override void Write(object? o) { } - public override void Write(object o, string category) + public override void Write(object? o, string? category) { } - public override void Write(string message) + public override void Write(string? message) { } - public override void Write(string message, string category) + public override void Write(string? message, string? category) { } - public override void WriteLine(object o) + public override void WriteLine(object? o) { } - public override void WriteLine(object o, string category) + public override void WriteLine(object? o, string? category) { } - public override void WriteLine(string message) + public override void WriteLine(string? message) { } - public override void WriteLine(string message, string category) + public override void WriteLine(string? message, string? category) { } } diff --git a/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj b/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj index f330551a2603f..e12cc3d6601fb 100644 --- a/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj +++ b/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj @@ -4,7 +4,7 @@ Library Microsoft.CodeAnalysis.Remote - netcoreapp3.1;netstandard2.0 + net6.0;netstandard2.0 true true diff --git a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs index 181dc59d90b6d..8565fa9469b77 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.FactoryBase.cs @@ -97,7 +97,7 @@ internal TService Create( GlobalServiceBroker.RegisterServiceBroker(serviceBroker); var descriptor = ServiceDescriptors.Instance.GetServiceDescriptorForServiceFactory(typeof(TService)); - var serviceHubTraceSource = (TraceSource)hostProvidedServices.GetService(typeof(TraceSource)); + var serviceHubTraceSource = (TraceSource?)hostProvidedServices.GetService(typeof(TraceSource)); var serverConnection = descriptor.WithTraceSource(serviceHubTraceSource).ConstructRpcConnection(pipe); var args = new ServiceConstructionArguments(hostProvidedServices, serviceBroker); diff --git a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs index 82a3dd87a421b..ba4d67a0683a1 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs @@ -55,7 +55,9 @@ static BrokeredServiceBase() protected BrokeredServiceBase(in ServiceConstructionArguments arguments) { - TraceLogger = (TraceSource)arguments.ServiceProvider.GetService(typeof(TraceSource)); + var traceSource = (TraceSource?)arguments.ServiceProvider.GetService(typeof(TraceSource)); + Contract.ThrowIfNull(traceSource); + TraceLogger = traceSource; TestData = (RemoteHostTestData?)arguments.ServiceProvider.GetService(typeof(RemoteHostTestData)); WorkspaceManager = TestData?.WorkspaceManager ?? RemoteWorkspaceManager.Default; diff --git a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs index 47941c1013ad5..d81e3f5f7d760 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs @@ -13,7 +13,7 @@ using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; +using Microsoft.CodeAnalysis.EditAndContinue.Contracts; namespace Microsoft.CodeAnalysis.EditAndContinue { @@ -25,7 +25,7 @@ protected override IRemoteEditAndContinueService CreateService(in ServiceConstru => new RemoteEditAndContinueService(arguments, callback); } - private sealed class ManagedEditAndContinueDebuggerService : IManagedEditAndContinueDebuggerService + private sealed class ManagedEditAndContinueDebuggerService : IManagedHotReloadService { private readonly RemoteCallback _callback; private readonly RemoteServiceCallbackId _callbackId; @@ -36,17 +36,17 @@ public ManagedEditAndContinueDebuggerService(RemoteCallback> IManagedEditAndContinueDebuggerService.GetActiveStatementsAsync(CancellationToken cancellationToken) - => _callback.InvokeAsync((callback, cancellationToken) => callback.GetActiveStatementsAsync(_callbackId, cancellationToken), cancellationToken).AsTask(); + ValueTask> IManagedHotReloadService.GetActiveStatementsAsync(CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.GetActiveStatementsAsync(_callbackId, cancellationToken), cancellationToken); - Task IManagedEditAndContinueDebuggerService.GetAvailabilityAsync(Guid moduleVersionId, CancellationToken cancellationToken) - => _callback.InvokeAsync((callback, cancellationToken) => callback.GetAvailabilityAsync(_callbackId, moduleVersionId, cancellationToken), cancellationToken).AsTask(); + ValueTask IManagedHotReloadService.GetAvailabilityAsync(Guid moduleVersionId, CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.GetAvailabilityAsync(_callbackId, moduleVersionId, cancellationToken), cancellationToken); - Task> IManagedEditAndContinueDebuggerService.GetCapabilitiesAsync(CancellationToken cancellationToken) - => _callback.InvokeAsync((callback, cancellationToken) => callback.GetCapabilitiesAsync(_callbackId, cancellationToken), cancellationToken).AsTask(); + ValueTask> IManagedHotReloadService.GetCapabilitiesAsync(CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.GetCapabilitiesAsync(_callbackId, cancellationToken), cancellationToken); - Task IManagedEditAndContinueDebuggerService.PrepareModuleForUpdateAsync(Guid moduleVersionId, CancellationToken cancellationToken) - => _callback.InvokeAsync((callback, cancellationToken) => callback.PrepareModuleForUpdateAsync(_callbackId, moduleVersionId, cancellationToken), cancellationToken).AsTask(); + ValueTask IManagedHotReloadService.PrepareModuleForUpdateAsync(Guid moduleVersionId, CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.PrepareModuleForUpdateAsync(_callbackId, moduleVersionId, cancellationToken), cancellationToken); } private readonly RemoteCallback _callback; diff --git a/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs b/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs index 8ec4923b98d2c..4aebd853b3d4f 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/FindUsages/RemoteFindUsagesService.cs @@ -93,8 +93,8 @@ public RemoteFindUsageContext(RemoteCallback public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) => _callback.InvokeAsync((callback, cancellationToken) => callback.AddItemsAsync(_callbackId, count, cancellationToken), cancellationToken); - public ValueTask ItemCompletedAsync(CancellationToken cancellationToken) - => _callback.InvokeAsync((callback, cancellationToken) => callback.ItemCompletedAsync(_callbackId, cancellationToken), cancellationToken); + public ValueTask ItemsCompletedAsync(int count, CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.ItemsCompletedAsync(_callbackId, count, cancellationToken), cancellationToken); #endregion diff --git a/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs b/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs index f9d44baaa0df3..03dca149a8f8f 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs @@ -49,7 +49,7 @@ public ValueTask HydrateAsync(PinnedSolutionInfo solutionInfo, CancellationToken }, cancellationToken); } - public ValueTask SearchFullyLoadedDocumentAsync( + public ValueTask SearchDocumentAsync( PinnedSolutionInfo solutionInfo, DocumentId documentId, string searchPattern, @@ -63,12 +63,12 @@ public ValueTask SearchFullyLoadedDocumentAsync( var document = solution.GetRequiredDocument(documentId); var callback = GetCallback(callbackId, cancellationToken); - await AbstractNavigateToSearchService.SearchFullyLoadedDocumentInCurrentProcessAsync( + await AbstractNavigateToSearchService.SearchDocumentInCurrentProcessAsync( document, searchPattern, kinds.ToImmutableHashSet(), callback, cancellationToken).ConfigureAwait(false); }, cancellationToken); } - public ValueTask SearchFullyLoadedProjectAsync( + public ValueTask SearchProjectAsync( PinnedSolutionInfo solutionInfo, ProjectId projectId, ImmutableArray priorityDocumentIds, @@ -85,11 +85,30 @@ public ValueTask SearchFullyLoadedProjectAsync( var priorityDocuments = priorityDocumentIds.SelectAsArray(d => solution.GetRequiredDocument(d)); - await AbstractNavigateToSearchService.SearchFullyLoadedProjectInCurrentProcessAsync( + await AbstractNavigateToSearchService.SearchProjectInCurrentProcessAsync( project, priorityDocuments, searchPattern, kinds.ToImmutableHashSet(), callback, cancellationToken).ConfigureAwait(false); }, cancellationToken); } + public ValueTask SearchGeneratedDocumentsAsync( + PinnedSolutionInfo solutionInfo, + ProjectId projectId, + string searchPattern, + ImmutableArray kinds, + RemoteServiceCallbackId callbackId, + CancellationToken cancellationToken) + { + return RunServiceAsync(async cancellationToken => + { + var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false); + var project = solution.GetRequiredProject(projectId); + var callback = GetCallback(callbackId, cancellationToken); + + await AbstractNavigateToSearchService.SearchGeneratedDocumentsInCurrentProcessAsync( + project, searchPattern, kinds.ToImmutableHashSet(), callback, cancellationToken).ConfigureAwait(false); + }, cancellationToken); + } + public ValueTask SearchCachedDocumentsAsync(ImmutableArray documentKeys, ImmutableArray priorityDocumentKeys, StorageDatabase database, string searchPattern, ImmutableArray kinds, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) { return RunServiceAsync(async cancellationToken => @@ -98,9 +117,9 @@ public ValueTask SearchCachedDocumentsAsync(ImmutableArray document // synchronizing the solution over to the remote side. Instead, we just directly // check whatever cached data we have from the previous vs session. var callback = GetCallback(callbackId, cancellationToken); - + var storageService = GetWorkspaceServices().GetPersistentStorageService(database); await AbstractNavigateToSearchService.SearchCachedDocumentsInCurrentProcessAsync( - GetWorkspaceServices(), documentKeys, priorityDocumentKeys, database, searchPattern, kinds.ToImmutableHashSet(), callback, cancellationToken).ConfigureAwait(false); + storageService, documentKeys, priorityDocumentKeys, searchPattern, kinds.ToImmutableHashSet(), callback, cancellationToken).ConfigureAwait(false); }, cancellationToken); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs b/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs index dfa97c1d7fd3c..429081f4e9acd 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs @@ -44,7 +44,7 @@ private static void Fail(string message) Debug.Fail(message); // record NFW to see who violates contract. - WatsonReporter.ReportNonFatal(new InvalidOperationException(message)); + FatalError.ReportAndCatch(new InvalidOperationException(message)); } } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs index d1fe987bbfb55..2ebe75563578a 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs @@ -201,8 +201,8 @@ public ValueTask OnReferenceFoundAsync(Document document, TextSpan span, Cancell public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) => _callback.InvokeAsync((callback, cancellationToken) => callback.AddLiteralItemsAsync(_callbackId, count, cancellationToken), cancellationToken); - public ValueTask ItemCompletedAsync(CancellationToken cancellationToken) - => _callback.InvokeAsync((callback, cancellationToken) => callback.LiteralItemCompletedAsync(_callbackId, cancellationToken), cancellationToken); + public ValueTask ItemsCompletedAsync(int count, CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.LiteralItemsCompletedAsync(_callbackId, count, cancellationToken), cancellationToken); } private sealed class FindReferencesProgressCallback : IStreamingFindReferencesProgress, IStreamingProgressTracker @@ -254,8 +254,8 @@ public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, Re public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken) => _callback.InvokeAsync((callback, cancellationToken) => callback.AddReferenceItemsAsync(_callbackId, count, cancellationToken), cancellationToken); - public ValueTask ItemCompletedAsync(CancellationToken cancellationToken) - => _callback.InvokeAsync((callback, cancellationToken) => callback.ReferenceItemCompletedAsync(_callbackId, cancellationToken), cancellationToken); + public ValueTask ItemsCompletedAsync(int count, CancellationToken cancellationToken) + => _callback.InvokeAsync((callback, cancellationToken) => callback.ReferenceItemsCompletedAsync(_callbackId, count, cancellationToken), cancellationToken); } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Engine/Trivia/CSharpTriviaFormatter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Engine/Trivia/CSharpTriviaFormatter.cs index 69db054a73b53..f0f3136d38008 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Engine/Trivia/CSharpTriviaFormatter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Engine/Trivia/CSharpTriviaFormatter.cs @@ -354,5 +354,19 @@ private LineColumnDelta FormatStructuredTrivia( return GetLineColumnDelta(lineColumn, docComment); } + + protected override bool LineContinuationFollowedByWhitespaceComment(SyntaxTrivia trivia, SyntaxTrivia nextTrivia) + { + return false; + } + + /// + /// C# never passes a VB Comment + /// + /// + protected override bool IsVisualBasicComment(SyntaxTrivia trivia) + { + throw ExceptionUtilities.Unreachable; + } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index bd5474dd5ac2a..39fec62037ab5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -192,6 +192,7 @@ + diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/Common/EmbeddedSeparatedSyntaxNodeList.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/Common/EmbeddedSeparatedSyntaxNodeList.cs new file mode 100644 index 0000000000000..4cd837cabae59 --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/Common/EmbeddedSeparatedSyntaxNodeList.cs @@ -0,0 +1,107 @@ +// 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.Diagnostics; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.EmbeddedLanguages.Common +{ + internal readonly struct EmbeddedSeparatedSyntaxNodeList + where TSyntaxKind : struct + where TSyntaxNode : EmbeddedSyntaxNode + where TDerivedNode : TSyntaxNode + { + public ImmutableArray> NodesAndTokens { get; } + public int Length { get; } + public int SeparatorLength { get; } + + public static readonly EmbeddedSeparatedSyntaxNodeList Empty + = new(ImmutableArray>.Empty); + + public EmbeddedSeparatedSyntaxNodeList( + ImmutableArray> nodesAndTokens) + { + Contract.ThrowIfTrue(nodesAndTokens.IsDefault); + NodesAndTokens = nodesAndTokens; + + var allLength = NodesAndTokens.Length; + Length = (allLength + 1) / 2; + SeparatorLength = allLength / 2; + + Verify(); + } + + [Conditional("DEBUG")] + private void Verify() + { + for (var i = 0; i < NodesAndTokens.Length; i++) + { + if ((i & 1) == 0) + { + // All even values should be TNode + Debug.Assert(NodesAndTokens[i].IsNode); + Debug.Assert(NodesAndTokens[i].Node is EmbeddedSyntaxNode); + } + else + { + // All odd values should be separator tokens + Debug.Assert(!NodesAndTokens[i].IsNode); + } + } + } + + /// + /// Retrieves only nodes, skipping the separator tokens + /// + public TDerivedNode this[int index] + { + get + { + if (index < Length && index >= 0) + { + // x2 here to get only even indexed numbers. Follows same logic + // as SeparatedSyntaxList in that the separator tokens are not returned + var nodeOrToken = NodesAndTokens[index * 2]; + Debug.Assert(nodeOrToken.IsNode); + RoslynDebug.AssertNotNull(nodeOrToken.Node); + return (TDerivedNode)nodeOrToken.Node; + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + } + + public Enumerator GetEnumerator() => new(this); + + public struct Enumerator + { + private readonly EmbeddedSeparatedSyntaxNodeList _list; + private int _currentIndex; + + public Enumerator(EmbeddedSeparatedSyntaxNodeList list) + { + _list = list; + _currentIndex = -1; + Current = null!; + } + + public TDerivedNode Current { get; private set; } + + public bool MoveNext() + { + _currentIndex++; + if (_currentIndex >= _list.Length) + { + Current = null!; + return false; + } + + Current = _list[_currentIndex]; + return true; + } + } + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/Common/EmbeddedSyntaxNode.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/Common/EmbeddedSyntaxNode.cs index 1d7c493a5381f..2c5689b88beb7 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/Common/EmbeddedSyntaxNode.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/Common/EmbeddedSyntaxNode.cs @@ -47,6 +47,9 @@ protected EmbeddedSyntaxNode(TSyntaxKind kind) internal abstract int ChildCount { get; } internal abstract EmbeddedSyntaxNodeOrToken ChildAt(int index); + public EmbeddedSyntaxNodeOrToken this[int index] => ChildAt(index); + public EmbeddedSyntaxNodeOrToken this[Index index] => this[index.GetOffset(this.ChildCount)]; + public TextSpan GetSpan() { var start = int.MaxValue; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/Common/EmbeddedSyntaxNodeOrToken.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/Common/EmbeddedSyntaxNodeOrToken.cs index 7861db94a9b91..8024776f059b4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/Common/EmbeddedSyntaxNodeOrToken.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EmbeddedLanguages/Common/EmbeddedSyntaxNodeOrToken.cs @@ -15,9 +15,8 @@ internal struct EmbeddedSyntaxNodeOrToken public readonly TSyntaxNode? Node; public readonly EmbeddedSyntaxToken Token; - private EmbeddedSyntaxNodeOrToken(TSyntaxNode node) : this() + private EmbeddedSyntaxNodeOrToken(TSyntaxNode? node) : this() { - RoslynDebug.AssertNotNull(node); Node = node; } @@ -30,7 +29,7 @@ private EmbeddedSyntaxNodeOrToken(EmbeddedSyntaxToken token) : this [MemberNotNullWhen(true, nameof(Node))] public bool IsNode => Node != null; - public static implicit operator EmbeddedSyntaxNodeOrToken(TSyntaxNode node) + public static implicit operator EmbeddedSyntaxNodeOrToken(TSyntaxNode? node) => new(node); public static implicit operator EmbeddedSyntaxNodeOrToken(EmbeddedSyntaxToken token) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/TriviaEngine/AbstractTriviaFormatter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/TriviaEngine/AbstractTriviaFormatter.cs index 345b03127858c..3973bdc0d7dc3 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/TriviaEngine/AbstractTriviaFormatter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/TriviaEngine/AbstractTriviaFormatter.cs @@ -121,6 +121,18 @@ public AbstractTriviaFormatter( /// protected abstract bool IsEndOfLine(SyntaxTrivia trivia); + /// + /// true if previoustrivia is _ and nextTrivia is a Visual Basic comment + /// + protected abstract bool LineContinuationFollowedByWhitespaceComment(SyntaxTrivia previousTrivia, SyntaxTrivia nextTrivia); + + /// + /// check whether given trivia is a Comment in VB or not + /// It is never reachable in C# since it follows a test for + /// LineContinuation Character. + /// + protected abstract bool IsVisualBasicComment(SyntaxTrivia trivia); + /// /// check whether given string is either null or whitespace /// @@ -279,8 +291,13 @@ private LineColumn FormatTrivia(Formatter formatter, WhitespaceAppender var implicitLineBreak = false; var list = new TriviaList(this.Token1.TrailingTrivia, this.Token2.LeadingTrivia); - foreach (var trivia in list) + + // Holds last position before _ ' Comment so we can reset after processing comment + var previousLineColumn = LineColumn.Default; + SyntaxTrivia trivia; + for (var i = 0; i < list.Count; i++) { + trivia = list[i]; if (trivia.RawKind == 0) { continue; @@ -288,18 +305,31 @@ private LineColumn FormatTrivia(Formatter formatter, WhitespaceAppender if (IsWhitespaceOrEndOfLine(trivia)) { + existingWhitespaceDelta = existingWhitespaceDelta.With( + GetLineColumnOfWhitespace( + lineColumn, + previousTrivia, + previousWhitespaceTrivia, + existingWhitespaceDelta, + trivia)); + if (IsEndOfLine(trivia)) { implicitLineBreak = false; + // If we are on a new line we don't want to continue + // reseting indenting this handles the case of a NewLine + // followed by whitespace and a comment + previousLineColumn = LineColumn.Default; + } + else if (LineContinuationFollowedByWhitespaceComment(previousTrivia, (i + 1) < list.Count ? list[i + 1] : default)) + { + // we have a comment following an underscore space the formatter + // thinks this next line should be shifted to right by + // indentation value. Since we know through the test above that + // this is the special case of _ ' Comment we don't want the extra indent + // so we set the LineColumn value back to where it was before the comment + previousLineColumn = lineColumn; } - - existingWhitespaceDelta = existingWhitespaceDelta.With( - GetLineColumnOfWhitespace( - lineColumn, - previousTrivia, - previousWhitespaceTrivia, - existingWhitespaceDelta, - trivia)); previousWhitespaceTrivia = trivia; continue; @@ -313,6 +343,16 @@ private LineColumn FormatTrivia(Formatter formatter, WhitespaceAppender formatter, whitespaceAdder, changes, implicitLineBreak, cancellationToken); + if (previousLineColumn.Column != 0 + && previousLineColumn.Column < lineColumn.Column + && IsVisualBasicComment(trivia)) + { + lineColumn = previousLineColumn; + // When we see a NewLine we don't want any special handling + // for _ ' Comment + previousLineColumn = LineColumn.Default; + } + implicitLineBreak = implicitLineBreak || ContainsImplicitLineBreak(trivia); existingWhitespaceDelta = LineColumnDelta.Default; @@ -418,7 +458,7 @@ private LineColumnRule GetOverallLineColumnRuleBetween(SyntaxTrivia trivia1, Lin } /// - /// if the given trivia is the very first or the last trivia between two normal tokens and + /// if the given trivia is the very first or the last trivia between two normal tokens and /// if the trivia is structured trivia, get one token that belongs to the structured trivia and one belongs to the normal token stream /// private void GetTokensAtEdgeOfStructureTrivia(SyntaxTrivia trivia1, SyntaxTrivia trivia2, out SyntaxToken token1, out SyntaxToken token2) @@ -475,7 +515,7 @@ private bool ContainsOnlyWhitespace(int start, int end) /// private bool FirstLineBlank() { - // if we see elastic trivia as the first trivia in the trivia list, + // if we see elastic trivia as the first trivia in the trivia list, // we consider it as blank line if (this.Token1.TrailingTrivia.Count > 0 && this.Token1.TrailingTrivia[0].IsElastic()) @@ -601,7 +641,7 @@ private void AddExtraLines(int linesBetweenTokens, ArrayBuilder ch private int GetInsertionIndex(ArrayBuilder changes) { - // first line is blank or there is no changes. + // first line is blank or there is no changes. // just insert at the head if (_firstLineBlank || changes.Count == 0) @@ -690,7 +730,7 @@ private bool TryGetMatchingChangeIndex(ArrayBuilder changes, out int private TextSpan GetInsertionSpan(ArrayBuilder changes) { - // first line is blank or there is no changes. + // first line is blank or there is no changes. // just insert at the head if (_firstLineBlank || changes.Count == 0) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/TriviaEngine/LineColumnRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/TriviaEngine/LineColumnRule.cs index 9bb56d568750d..998497b4e439a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/TriviaEngine/LineColumnRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/TriviaEngine/LineColumnRule.cs @@ -120,14 +120,14 @@ public static LineColumnRule ForceSpacesOrUseDefaultIndentation(int spaces) spaces, indentation: -1); - public static LineColumnRule ForceSpacesOrUseAbsoluteIndentation(int spacesOrIndentation) + public static LineColumnRule ForceSpacesOrUseFollowIndentation(int indentation) => new( SpaceOperations.Force, LineOperations.Preserve, - IndentationOperations.Absolute, + IndentationOperations.Follow, lines: 0, - spacesOrIndentation, - spacesOrIndentation); + spaces: 1, + indentation); public enum SpaceOperations { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs index 445b72ce521ab..081c383687dcb 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs @@ -494,7 +494,7 @@ internal enum FunctionId DependentTypeFinder_FindAndCacheImplementingTypesAsync = 432, RemoteSemanticClassificationCacheService_ExceptionInCacheRead = 440, - FeatureNotAvailable = 441, + // obsolete: FeatureNotAvailable = 441, LSPCompletion_MissingLSPCompletionTriggerKind = 450, LSPCompletion_MissingLSPCompletionInvokeKind = 451, diff --git a/src/Workspaces/VisualBasic/Portable/Formatting/Engine/Trivia/TriviaDataFactory.Analyzer.vb b/src/Workspaces/VisualBasic/Portable/Formatting/Engine/Trivia/TriviaDataFactory.Analyzer.vb index 582b99edce2eb..0a292f4e50fb5 100644 --- a/src/Workspaces/VisualBasic/Portable/Formatting/Engine/Trivia/TriviaDataFactory.Analyzer.vb +++ b/src/Workspaces/VisualBasic/Portable/Formatting/Engine/Trivia/TriviaDataFactory.Analyzer.vb @@ -35,12 +35,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting If list.Count = 0 Then Return End If - + Dim previousTrivia As New SyntaxTrivia For Each trivia In list If trivia.Kind = SyntaxKind.WhitespaceTrivia Then AnalyzeWhitespacesInTrivia(trivia, result) ElseIf trivia.Kind = SyntaxKind.EndOfLineTrivia Then - AnalyzeLineBreak(trivia, result) + AnalyzeLineBreak(previousTrivia, trivia, result) ElseIf trivia.Kind = SyntaxKind.CommentTrivia OrElse trivia.Kind = SyntaxKind.DocumentationCommentTrivia Then result.HasComments = True ElseIf trivia.Kind = SyntaxKind.DisabledTextTrivia OrElse trivia.Kind = SyntaxKind.SkippedTokensTrivia Then @@ -56,6 +56,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting result.HasPreprocessor = True End If + previousTrivia = trivia Next End Sub @@ -71,19 +72,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting result.Tab = 0 End Sub - Private Shared Sub AnalyzeLineBreak(trivia As SyntaxTrivia, ByRef result As AnalysisResult) - ' if there was any space before line break, then we have trailing spaces - If result.Space > 0 OrElse result.Tab > 0 Then + Private Shared Sub AnalyzeLineBreak(previousTrivia As SyntaxTrivia, trivia As SyntaxTrivia, ByRef result As AnalysisResult) + ' if there was any space immediately before line break, then we have trailing spaces + If previousTrivia.Kind = SyntaxKind.WhitespaceTrivia AndAlso previousTrivia.Width > 0 Then result.HasTrailingSpace = True + result.HasTabAfterSpace = False + result.Space = 0 + result.Tab = 0 + result.TreatAsElastic = result.TreatAsElastic Or trivia.IsElastic() End If ' reset space and tab information result.LineBreaks += 1 - result.HasTabAfterSpace = False - result.Space = 0 - result.Tab = 0 - result.TreatAsElastic = result.TreatAsElastic Or trivia.IsElastic() End Sub Private Shared Sub AnalyzeWhitespacesInTrivia(trivia As SyntaxTrivia, ByRef result As AnalysisResult) diff --git a/src/Workspaces/VisualBasic/Portable/Formatting/Engine/Trivia/VisualBasicTriviaFormatter.vb b/src/Workspaces/VisualBasic/Portable/Formatting/Engine/Trivia/VisualBasicTriviaFormatter.vb index 49f2b67e72a32..2e66ea75c529c 100644 --- a/src/Workspaces/VisualBasic/Portable/Formatting/Engine/Trivia/VisualBasicTriviaFormatter.vb +++ b/src/Workspaces/VisualBasic/Portable/Formatting/Engine/Trivia/VisualBasicTriviaFormatter.vb @@ -65,7 +65,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting ' line continuation If trivia2.Kind = SyntaxKind.LineContinuationTrivia Then - Return LineColumnRule.ForceSpacesOrUseAbsoluteIndentation(spacesOrIndentation:=1) + Return LineColumnRule.ForceSpacesOrUseFollowIndentation(indentation:=0) End If If IsStartOrEndOfFile(trivia1, trivia2) Then @@ -299,5 +299,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting Return singlelineDocCommentTrivia.ElementAt(0) End Function + Protected Overrides Function LineContinuationFollowedByWhitespaceComment(trivia As SyntaxTrivia, nextTrivia As SyntaxTrivia) As Boolean + Return trivia.Kind = SyntaxKind.LineContinuationTrivia AndAlso nextTrivia.Kind = SyntaxKind.CommentTrivia + End Function + + Protected Overrides Function IsVisualBasicComment(trivia As SyntaxTrivia) As Boolean + Return trivia.Kind = SyntaxKind.CommentTrivia + End Function End Class End Namespace diff --git a/src/Workspaces/VisualBasicTest/Formatting/FormattingTests.vb b/src/Workspaces/VisualBasicTest/Formatting/FormattingTests.vb index 6160378410beb..6004b5d2fa16a 100644 --- a/src/Workspaces/VisualBasicTest/Formatting/FormattingTests.vb +++ b/src/Workspaces/VisualBasicTest/Formatting/FormattingTests.vb @@ -869,11 +869,11 @@ End Class" Dim expected = $"Class C Sub Method(Optional ByVal i As Integer = 1) Dim aa = 1 + {continuation} - {continuation} - {continuation} - {continuation} - {continuation} - {continuation} + {continuation} + {continuation} + {continuation} + {continuation} + {continuation} 2 + {continuation} 3 End Sub @@ -902,11 +902,11 @@ End Class" Dim expected = $"Class C Sub Method(Optional ByVal i As Integer = 1) Dim aa = 1 + {continuation} - {continuation} - {continuation} - {continuation} - {continuation} - {continuation} + {continuation} + {continuation} + {continuation} + {continuation} + {continuation} 2 + {continuation} 3 End Sub @@ -1086,8 +1086,8 @@ End Class" Dim expected = $"Class C Sub Method(Optional ByVal i As Integer = 1) Dim a = {continuation} - {continuation} - {continuation} + {continuation} + {continuation} 1 End Sub End Class" @@ -2421,7 +2421,7 @@ End Module Dim code = _ - Dim expected = _ + Dim expected = _ Await AssertFormatLf2CrLfAsync(code.Value, expected.Value)