diff --git a/docs/features/ExpressionVariables.md b/docs/features/ExpressionVariables.md index e901e544bf7f0..b8bdaba9d3e44 100644 --- a/docs/features/ExpressionVariables.md +++ b/docs/features/ExpressionVariables.md @@ -6,7 +6,7 @@ containing expression variables (out variable declarations and declaration patte initializers, property initializers, ctor-initializers, and query clauses. See https://github.com/dotnet/csharplang/issues/32 and -https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.3/expression-variables-in-initializers.md +https://github.com/dotnet/csharplang/blob/main/proposals/expression-variables-in-initializers.md for more information. Current state of the feature: @@ -14,4 +14,4 @@ Current state of the feature: [X] Permit in field initializers [X] Permit in property initializers [ ] Permit in ctor-initializers -[X] Permit in query clauses +[X] Permit in query clauses \ No newline at end of file diff --git a/dotnet-tools.json b/dotnet-tools.json index 0d36407e3c7fa..6ab0998319770 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -2,7 +2,7 @@ "isRoot": true, "tools": { "dotnet-format": { - "version": "6.0.240501", + "version": "6.0.231801", "commands": [ "dotnet-format" ] diff --git a/eng/Versions.props b/eng/Versions.props index a89a4458ec461..08726f9e09f32 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -30,8 +30,7 @@ 3.3.3-beta1.21105.3 6.0.0-rc1.21366.2 1.1.0-beta1.21322.2 - - 4.0.0-3.final + 3.10.0 16.10.230 17.0.278-preview 5.0.0-alpha1.19409.1 diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs b/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs index ebdb4fefe70d6..46a2cd45f3ced 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs @@ -187,7 +187,7 @@ bool tryBindAsHandlerType([NotNullWhen(true)] out BoundInterpolatedString? resul { result = null; - if (InExpressionTree || !ValidateInterpolatedStringParts(unconvertedInterpolatedString)) + if (InExpressionTree || unconvertedInterpolatedString.Parts.ContainsAwaitExpression()) { return false; } @@ -204,12 +204,8 @@ bool tryBindAsHandlerType([NotNullWhen(true)] out BoundInterpolatedString? resul } } - private static bool ValidateInterpolatedStringParts(BoundUnconvertedInterpolatedString unconvertedInterpolatedString) - => !unconvertedInterpolatedString.Parts.ContainsAwaitExpression() - && unconvertedInterpolatedString.Parts.All(p => p is not BoundStringInsert { Value.Type.TypeKind: TypeKind.Dynamic }); - private static bool AllInterpolatedStringPartsAreStrings(ImmutableArray parts) - => parts.All(p => p is BoundLiteral or BoundStringInsert { Value.Type.SpecialType: SpecialType.System_String, Alignment: null, Format: null }); + => parts.All(p => p is BoundLiteral or BoundStringInsert { Value: { Type: { SpecialType: SpecialType.System_String } }, Alignment: null, Format: null }); private bool TryBindUnconvertedBinaryOperatorToDefaultInterpolatedStringHandler(BoundBinaryOperator binaryOperator, BindingDiagnosticBag diagnostics, [NotNullWhen(true)] out BoundBinaryOperator? convertedBinaryOperator) { @@ -244,7 +240,7 @@ private bool TryBindUnconvertedBinaryOperatorToDefaultInterpolatedStringHandler( isConstant = isConstant && current.Right.ConstantValue is not null; var rightInterpolatedString = (BoundUnconvertedInterpolatedString)current.Right; - if (!ValidateInterpolatedStringParts(rightInterpolatedString)) + if (rightInterpolatedString.Parts.ContainsAwaitExpression()) { // Exception to case 3. Delegate to standard binding. stack.Free(); @@ -263,7 +259,7 @@ private bool TryBindUnconvertedBinaryOperatorToDefaultInterpolatedStringHandler( case BoundUnconvertedInterpolatedString interpolatedString: isConstant = isConstant && interpolatedString.ConstantValue is not null; - if (!ValidateInterpolatedStringParts(interpolatedString)) + if (interpolatedString.Parts.ContainsAwaitExpression()) { // Exception to case 3. Delegate to standard binding. stack.Free(); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 9d66957b6315d..34f2a42406241 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1878,15 +1878,6 @@ internal void GenerateAnonymousFunctionConversionError(BindingDiagnosticBag diag return; } - if (reason == LambdaConversionResult.CannotInferDelegateType) - { - Debug.Assert(targetType.SpecialType == SpecialType.System_Delegate || targetType.IsNonGenericExpressionType()); - Error(diagnostics, ErrorCode.ERR_CannotInferDelegateType, syntax); - var lambda = anonymousFunction.BindForErrorRecovery(); - diagnostics.AddRange(lambda.Diagnostics); - return; - } - // At this point we know that we have either a delegate type or an expression type for the target. // The target type is a valid delegate or expression tree type. Is there something wrong with the diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs index d8f2705d5f980..41a8a249334f4 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -1393,8 +1394,7 @@ private static LambdaConversionResult IsAnonymousFunctionCompatibleWithExpressio if (delegateType is null) { - Debug.Assert(IsFeatureInferredDelegateTypeEnabled(anonymousFunction)); - return GetInferredDelegateTypeResult(anonymousFunction); + return LambdaConversionResult.Success; } return IsAnonymousFunctionCompatibleWithDelegate(anonymousFunction, delegateType, isTargetExpressionTree: true); @@ -1409,7 +1409,7 @@ public static LambdaConversionResult IsAnonymousFunctionCompatibleWithType(Unbou { if (IsFeatureInferredDelegateTypeEnabled(anonymousFunction)) { - return GetInferredDelegateTypeResult(anonymousFunction); + return LambdaConversionResult.Success; } } else if (type.IsDelegateType()) @@ -1432,14 +1432,6 @@ internal static bool IsFeatureInferredDelegateTypeEnabled(BoundExpression expr) return expr.Syntax.IsFeatureEnabled(MessageID.IDS_FeatureInferredDelegateType); } - private static LambdaConversionResult GetInferredDelegateTypeResult(UnboundLambda anonymousFunction) - { - var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; - return anonymousFunction.InferDelegateType(ref discardedUseSiteInfo) is null ? - LambdaConversionResult.CannotInferDelegateType : - LambdaConversionResult.Success; - } - private static bool HasAnonymousFunctionConversion(BoundExpression source, TypeSymbol destination) { Debug.Assert(source != null); diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/LambdaConversionResult.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/LambdaConversionResult.cs index 227ec23eb7b40..5a10903cb935a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/LambdaConversionResult.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/LambdaConversionResult.cs @@ -16,7 +16,6 @@ internal enum LambdaConversionResult StaticTypeInImplicitlyTypedLambda, ExpressionTreeMustHaveDelegateTypeArgument, ExpressionTreeFromAnonymousMethod, - CannotInferDelegateType, BindingFailed } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 264e2968947b4..2b02d006d6e53 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -3650,7 +3650,7 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde memberNames.Free(); // Synthesizing non-readonly properties in struct would require changing readonly logic for PrintMembers method synthesis - Debug.Assert(isRecordClass || !members.Any(m => m is PropertySymbol { GetMethod.IsEffectivelyReadOnly: false })); + Debug.Assert(isRecordClass || !members.Any(m => m is PropertySymbol { GetMethod.IsEffectivelyReadOnly : false })); // We put synthesized record members first so that errors about conflicts show up on user-defined members rather than all // going to the record declaration diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordCopyCtor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordCopyCtor.cs index 76f19629921d9..a580b862007f9 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordCopyCtor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordCopyCtor.cs @@ -101,7 +101,7 @@ internal override void GenerateMethodBodyStatements(SyntheticBoundNodeFactory F, internal static bool IsCopyConstructor(Symbol member) { - if (member is MethodSymbol { ContainingType.IsRecordStruct: false, MethodKind: MethodKind.Constructor } method) + if (member is MethodSymbol { ContainingType: { IsRecordStruct: false }, MethodKind: MethodKind.Constructor } method) { return HasCopyConstructorSignature(method); } diff --git a/src/Compilers/CSharp/Portable/Syntax/InternalSyntax/CSharpSyntaxNode.cs b/src/Compilers/CSharp/Portable/Syntax/InternalSyntax/CSharpSyntaxNode.cs index 2c39131b36ccb..2073cd5fb160c 100644 --- a/src/Compilers/CSharp/Portable/Syntax/InternalSyntax/CSharpSyntaxNode.cs +++ b/src/Compilers/CSharp/Portable/Syntax/InternalSyntax/CSharpSyntaxNode.cs @@ -4,7 +4,6 @@ #nullable disable -using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -280,8 +279,8 @@ public override bool IsTriviaWithEndOfLine() } // Use conditional weak table so we always return same identity for structured trivia - private static readonly ConditionalWeakTable>> s_structuresTable - = new ConditionalWeakTable>>(); + private static readonly ConditionalWeakTable> s_structuresTable + = new ConditionalWeakTable>(); /// /// Gets the syntax node represented the structure of this trivia, if any. The HasStructure property can be used to @@ -309,15 +308,10 @@ public override SyntaxNode GetStructure(Microsoft.CodeAnalysis.SyntaxTrivia triv var structsInParent = s_structuresTable.GetOrCreateValue(parent); lock (structsInParent) { - if (!structsInParent.TryGetValue(trivia, out var weakStructure)) + if (!structsInParent.TryGetValue(trivia, out structure)) { structure = CSharp.Syntax.StructuredTriviaSyntax.Create(trivia); - structsInParent.Add(trivia, new WeakReference(structure)); - } - else if (!weakStructure.TryGetTarget(out structure)) - { - structure = CSharp.Syntax.StructuredTriviaSyntax.Create(trivia); - weakStructure.SetTarget(structure); + structsInParent.Add(trivia, structure); } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs index 404b9a4487de1..ff2fa54d329e5 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs @@ -1682,9 +1682,12 @@ static void Main() comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); comp.VerifyDiagnostics( - // (6,30): error CS0019: Operator '==' cannot be applied to operands of type '' and 'lambda expression' + // (6,65): error CS8917: The delegate type could not be inferred. // System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })); - Diagnostic(ErrorCode.ERR_BadBinaryOps, "(null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })").WithArguments("==", "", "lambda expression").WithLocation(6, 30)); + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "x => x").WithLocation(6, 65), + // (6,65): error CS8917: The delegate type could not be inferred. + // System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })); + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "x => x").WithLocation(6, 65)); verify(comp, inferDelegate: true); static void verify(CSharpCompilation comp, bool inferDelegate) @@ -1711,7 +1714,7 @@ static void verify(CSharpCompilation comp, bool inferDelegate) // ... its first lambda ... var firstLambda = tuple2.Arguments[1].Expression; Assert.Null(model.GetTypeInfo(firstLambda).Type); - verifyType("System.Delegate", model.GetTypeInfo(firstLambda).ConvertedType, inferDelegate: false); // cannot infer delegate type for x => x + verifyType("System.Delegate", model.GetTypeInfo(firstLambda).ConvertedType, inferDelegate); // ... its method group ... var methodGroup = tuple2.Arguments[2].Expression; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs index 27eb4db64382d..76d15d826bc57 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs @@ -389,9 +389,9 @@ static void Main(string[] args) if (expectedType is null) { comp.VerifyDiagnostics( - // (5,38): error CS8917: The delegate type could not be inferred. + // (5,20): error CS8917: The delegate type could not be inferred. // object o = (System.Delegate)(x => x); - Diagnostic(ErrorCode.ERR_CannotInferDelegateType, anonymousFunction).WithLocation(5, 38)); + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, $"(System.Delegate)({anonymousFunction})").WithLocation(5, 20)); } else { @@ -474,9 +474,9 @@ static void Main(string[] args) if (expectedType is null) { comp.VerifyDiagnostics( - // (5,57): error CS8917: The delegate type could not be inferred. + // (5,20): error CS8917: The delegate type could not be inferred. // object o = (System.Linq.Expressions.Expression)(x => x); - Diagnostic(ErrorCode.ERR_CannotInferDelegateType, anonymousFunction).WithLocation(5, 57)); + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, $"(System.Linq.Expressions.Expression)({anonymousFunction})").WithLocation(5, 20)); } else { @@ -1978,17 +1978,7 @@ static void Main() F(delegate () { return string.Empty; }); } }"; - - var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); - comp.VerifyDiagnostics( - // (9,11): error CS1660: Cannot convert anonymous method to type 'Expression' because it is not a delegate type - // F(delegate () { return 0; }); - Diagnostic(ErrorCode.ERR_AnonMethToNonDel, "delegate () { return 0; }").WithArguments("anonymous method", "System.Linq.Expressions.Expression").WithLocation(9, 11), - // (10,11): error CS1660: Cannot convert anonymous method to type 'Expression' because it is not a delegate type - // F(delegate () { return string.Empty; }); - Diagnostic(ErrorCode.ERR_AnonMethToNonDel, "delegate () { return string.Empty; }").WithArguments("anonymous method", "System.Linq.Expressions.Expression").WithLocation(10, 11)); - - comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); comp.VerifyDiagnostics( // (9,11): error CS1946: An anonymous method expression cannot be converted to an expression tree // F(delegate () { return 0; }); @@ -1998,143 +1988,6 @@ static void Main() Diagnostic(ErrorCode.ERR_AnonymousMethodToExpressionTree, "delegate () { return string.Empty; }").WithLocation(10, 11)); } - [WorkItem(55319, "https://github.com/dotnet/roslyn/issues/55319")] - [Fact] - public void OverloadResolution_08() - { - var source = -@"using System; -using static System.Console; -class C -{ - static void Main() - { - var c = new C(); - c.F(x => x); - c.F((int x) => x); - } - void F(Delegate d) => Write(""instance, ""); -} -static class Extensions -{ - public static void F(this C c, Func f) => Write(""extension, ""); -}"; - - CompileAndVerify(source, parseOptions: TestOptions.Regular9, expectedOutput: "extension, extension, "); - CompileAndVerify(source, parseOptions: TestOptions.Regular10, expectedOutput: "extension, instance, "); - CompileAndVerify(source, expectedOutput: "extension, instance, "); - } - - [WorkItem(55319, "https://github.com/dotnet/roslyn/issues/55319")] - [Fact] - public void OverloadResolution_09() - { - var source = -@"using System; -using System.Linq.Expressions; -using static System.Console; -class C -{ - static void Main() - { - var c = new C(); - c.F(x => x); - c.F((int x) => x); - } - void F(Expression e) => Write(""instance, ""); -} -static class Extensions -{ - public static void F(this C c, Expression> e) => Write(""extension, ""); -}"; - - CompileAndVerify(source, parseOptions: TestOptions.Regular9, expectedOutput: "extension, extension, "); - CompileAndVerify(source, parseOptions: TestOptions.Regular10, expectedOutput: "extension, instance, "); - CompileAndVerify(source, expectedOutput: "extension, instance, "); - } - - [WorkItem(55319, "https://github.com/dotnet/roslyn/issues/55319")] - [Fact] - public void OverloadResolution_10() - { - var source = -@"using System; -using static System.Console; -class C -{ - static object M1(object o) => o; - static int M1(int i) => i; - static int M2(int i) => i; - static void Main() - { - var c = new C(); - c.F(M1); - c.F(M2); - } - void F(Delegate d) => Write(""instance, ""); -} -static class Extensions -{ - public static void F(this C c, Func f) => Write(""extension, ""); -}"; - - CompileAndVerify(source, parseOptions: TestOptions.Regular9, expectedOutput: "extension, extension, "); - CompileAndVerify(source, parseOptions: TestOptions.Regular10, expectedOutput: "extension, instance, "); - CompileAndVerify(source, expectedOutput: "extension, instance, "); - } - - [Fact] - public void OverloadResolution_11() - { - var source = -@"using System; -using System.Linq.Expressions; -class C -{ - static object M1(object o) => o; - static int M1(int i) => i; - static void Main() - { - F1(x => x); - F1(M1); - F2(x => x); - } - static void F1(Delegate d) { } - static void F2(Expression e) { } -}"; - - var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); - comp.VerifyDiagnostics( - // (9,12): error CS1660: Cannot convert lambda expression to type 'Delegate' because it is not a delegate type - // F1(x => x); - Diagnostic(ErrorCode.ERR_AnonMethToNonDel, "x => x").WithArguments("lambda expression", "System.Delegate").WithLocation(9, 12), - // (10,12): error CS1503: Argument 1: cannot convert from 'method group' to 'Delegate' - // F1(M1); - Diagnostic(ErrorCode.ERR_BadArgType, "M1").WithArguments("1", "method group", "System.Delegate").WithLocation(10, 12), - // (11,12): error CS1660: Cannot convert lambda expression to type 'Expression' because it is not a delegate type - // F2(x => x); - Diagnostic(ErrorCode.ERR_AnonMethToNonDel, "x => x").WithArguments("lambda expression", "System.Linq.Expressions.Expression").WithLocation(11, 12)); - - var expectedDiagnostics10AndLater = new[] - { - // (9,12): error CS8917: The delegate type could not be inferred. - // F1(x => x); - Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "x => x").WithLocation(9, 12), - // (10,12): error CS1503: Argument 1: cannot convert from 'method group' to 'Delegate' - // F1(M1); - Diagnostic(ErrorCode.ERR_BadArgType, "M1").WithArguments("1", "method group", "System.Delegate").WithLocation(10, 12), - // (11,12): error CS8917: The delegate type could not be inferred. - // F2(x => x); - Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "x => x").WithLocation(11, 12) - }; - - comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); - comp.VerifyDiagnostics(expectedDiagnostics10AndLater); - - comp = CreateCompilation(source); - comp.VerifyDiagnostics(expectedDiagnostics10AndLater); - } - [Fact] public void ImplicitlyTypedVariables_01() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs index 8600ab575b158..c5784fb39764e 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs @@ -2761,110 +2761,6 @@ .locals init (int V_0, "); } - [Theory, WorkItem(55609, "https://github.com/dotnet/roslyn/issues/55609")] - [InlineData(@"$""base{hole}""")] - [InlineData(@"$""base"" + $""{hole}""")] - public void DynamicInHoles_UsesFormat(string expression) - { - var source = @" -using System; -using System.Threading.Tasks; - -dynamic hole = 1; -Console.WriteLine(" + expression + @"); -"; - - var interpolatedStringBuilder = GetInterpolatedStringHandlerDefinition(includeSpanOverloads: false, useDefaultParameters: false, useBoolReturns: false); - - var verifier = CompileAndVerifyWithCSharp(new[] { source, interpolatedStringBuilder }, expectedOutput: @"base1"); - - verifier.VerifyIL("", expression.Contains('+') -? @" -{ - // Code size 34 (0x22) - .maxstack 3 - .locals init (object V_0) //hole - IL_0000: ldc.i4.1 - IL_0001: box ""int"" - IL_0006: stloc.0 - IL_0007: ldstr ""base"" - IL_000c: ldstr ""{0}"" - IL_0011: ldloc.0 - IL_0012: call ""string string.Format(string, object)"" - IL_0017: call ""string string.Concat(string, string)"" - IL_001c: call ""void System.Console.WriteLine(string)"" - IL_0021: ret -} -" -: @" -{ - // Code size 24 (0x18) - .maxstack 2 - .locals init (object V_0) //hole - IL_0000: ldc.i4.1 - IL_0001: box ""int"" - IL_0006: stloc.0 - IL_0007: ldstr ""base{0}"" - IL_000c: ldloc.0 - IL_000d: call ""string string.Format(string, object)"" - IL_0012: call ""void System.Console.WriteLine(string)"" - IL_0017: ret -} -"); - } - - [Theory, WorkItem(55609, "https://github.com/dotnet/roslyn/issues/55609")] - [InlineData(@"$""{hole}base""")] - [InlineData(@"$""{hole}"" + $""base""")] - public void DynamicInHoles_UsesFormat2(string expression) - { - var source = @" -using System; -using System.Threading.Tasks; - -dynamic hole = 1; -Console.WriteLine(" + expression + @"); -"; - - var interpolatedStringBuilder = GetInterpolatedStringHandlerDefinition(includeSpanOverloads: false, useDefaultParameters: false, useBoolReturns: false); - - var verifier = CompileAndVerifyWithCSharp(new[] { source, interpolatedStringBuilder }, expectedOutput: @"1base"); - - verifier.VerifyIL("", expression.Contains('+') -? @" -{ - // Code size 34 (0x22) - .maxstack 2 - .locals init (object V_0) //hole - IL_0000: ldc.i4.1 - IL_0001: box ""int"" - IL_0006: stloc.0 - IL_0007: ldstr ""{0}"" - IL_000c: ldloc.0 - IL_000d: call ""string string.Format(string, object)"" - IL_0012: ldstr ""base"" - IL_0017: call ""string string.Concat(string, string)"" - IL_001c: call ""void System.Console.WriteLine(string)"" - IL_0021: ret -} -" -: @" -{ - // Code size 24 (0x18) - .maxstack 2 - .locals init (object V_0) //hole - IL_0000: ldc.i4.1 - IL_0001: box ""int"" - IL_0006: stloc.0 - IL_0007: ldstr ""{0}base"" - IL_000c: ldloc.0 - IL_000d: call ""string string.Format(string, object)"" - IL_0012: call ""void System.Console.WriteLine(string)"" - IL_0017: ret -} -"); - } - [Fact] public void MissingCreate_01() { @@ -12668,25 +12564,5 @@ .locals init (System.ValueTuple V_0, //t } "); } - - - [Theory, WorkItem(55609, "https://github.com/dotnet/roslyn/issues/55609")] - [InlineData(@"$""{h1}{h2}""")] - [InlineData(@"$""{h1}"" + $""{h2}""")] - public void RefStructHandler_DynamicInHole(string expression) - { - var code = @" -dynamic h1 = 1; -dynamic h2 = 2; -CustomHandler c = " + expression + ";"; - - var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "ref struct", useBoolReturns: false); - - var comp = CreateCompilationWithCSharp(new[] { code, handler }); - - // Note: We don't give any errors when mixing dynamic and ref structs today. If that ever changes, we should get an - // error here. This will crash at runtime because of this. - comp.VerifyEmitDiagnostics(); - } } } diff --git a/src/Compilers/VisualBasic/Portable/Syntax/InternalSyntax/SyntaxNode.vb b/src/Compilers/VisualBasic/Portable/Syntax/InternalSyntax/SyntaxNode.vb index c42cc07a761d7..75e57c177b88c 100644 --- a/src/Compilers/VisualBasic/Portable/Syntax/InternalSyntax/SyntaxNode.vb +++ b/src/Compilers/VisualBasic/Portable/Syntax/InternalSyntax/SyntaxNode.vb @@ -223,7 +223,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax End Function ' Use conditional weak table so we always return same identity for structured trivia - Private Shared ReadOnly s_structuresTable As New ConditionalWeakTable(Of SyntaxNode, Dictionary(Of Microsoft.CodeAnalysis.SyntaxTrivia, WeakReference(Of SyntaxNode))) + Private Shared ReadOnly s_structuresTable As New ConditionalWeakTable(Of SyntaxNode, Dictionary(Of Microsoft.CodeAnalysis.SyntaxTrivia, SyntaxNode)) Public Overrides Function GetStructure(trivia As Microsoft.CodeAnalysis.SyntaxTrivia) As SyntaxNode If Not trivia.HasStructure Then @@ -239,13 +239,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax Dim structsInParent = s_structuresTable.GetOrCreateValue(parent) SyncLock structsInParent - Dim weakStructure As WeakReference(Of SyntaxNode) = Nothing - If Not structsInParent.TryGetValue(trivia, weakStructure) Then + If Not structsInParent.TryGetValue(trivia, [structure]) Then [structure] = VisualBasic.Syntax.StructuredTriviaSyntax.Create(trivia) - structsInParent.Add(trivia, New WeakReference(Of SyntaxNode)([structure])) - ElseIf Not weakStructure.TryGetTarget([structure]) Then - [structure] = VisualBasic.Syntax.StructuredTriviaSyntax.Create(trivia) - weakStructure.SetTarget([structure]) + structsInParent.Add(trivia, [structure]) End If End SyncLock diff --git a/src/EditorFeatures/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs b/src/EditorFeatures/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs index 277e602674dd5..cfb74399cb6a2 100644 --- a/src/EditorFeatures/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs +++ b/src/EditorFeatures/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs @@ -4,8 +4,6 @@ #nullable disable -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp.ConvertToInterpolatedString; @@ -21,129 +19,27 @@ public class ConvertPlaceholderToInterpolatedStringTests : AbstractCSharpCodeAct protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) => new CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider(); - private static readonly string[] CompositeFormattedMethods = new[] - { - "Console.Write", - "Console.WriteLine", - "Debug.WriteLine", - "Debug.Print", - "Trace.TraceError", - "Trace.TraceWarning", - "Trace.TraceInformation", - }; - - public static IEnumerable InvocationData - { - get - { - // Every API so far starts to use a params object after 4 paramters following the formatted - const int ParametersToCheck = 4; - - // string.Format gets replaced with just the interpolated string - for (var i = 1; i <= ParametersToCheck; i++) - { - var invocation = $"string.Format({MakeFormattedParameters(i)})"; - var result = $"${MakeInterpolatedString(i)}"; - yield return new[] { invocation, result }; - } - - // Composite Formatted methods do not get replaced, but instead - // take the string as the only parameter - for (var i = 1; i <= ParametersToCheck; i++) - { - foreach (var function in CompositeFormattedMethods) - { - var invocation = $"{function}({MakeFormattedParameters(i)})"; - var result = $"{function}(${MakeInterpolatedString(i)})"; - yield return new[] { invocation, result }; - } - } - - // Makes a string of form "{0} {1}..." - static string MakeInterpolatedString(int numberOfParameters) - { - var interpolatedString = "\""; - - for (var i = 0; i < numberOfParameters; i++) - { - interpolatedString += $"{{{i}}}"; - } - - return interpolatedString + "\""; - } - - // Makes a string of form "{0} {1}..., 0, 1, ..." - static string MakeFormattedParameters(int numberOfParameters) - { - var formatString = MakeInterpolatedString(numberOfParameters); - return formatString + "," + string.Join(",", Enumerable.Range(0, numberOfParameters)); - - } - } - } - - [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)] - [MemberData(nameof(InvocationData))] - public async Task TestInvocationSubstitution(string before, string after) - { - await TestInRegularAndScriptAsync( -@$"using System; -using System.Diagnostics; - -class T -{{ - void M() - {{ - [|{before}|]; - }} -}}", -@$"using System; -using System.Diagnostics; - -class T -{{ - void M() - {{ - {after}; - }} -}}"); - } - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)] - [WorkItem(55053, "https://github.com/dotnet/roslyn/issues/55053")] - public async Task TestMissing_ConsoleWriteLine() + public async Task TestSingleItemSubstitution() { - await TestMissingAsync( + await TestInRegularAndScriptAsync( @"using System; class T { void M() { - var i = 25; - [|Console.WriteLine(GetString(), i)|]; + var a = [|string.Format(""{0}"", 1)|]; } - - string GetString() => """"; -}"); - } - - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)] - [WorkItem(55053, "https://github.com/dotnet/roslyn/issues/55053")] - public async Task TestMissing_ConsoleWrite() - { - await TestMissingAsync( +}", @"using System; class T { void M() { - var i = 25; - [|Console.Write(GetString(), i)|]; + var a = $""{1}""; } - - string GetString() => """"; }"); } diff --git a/src/EditorFeatures/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs b/src/EditorFeatures/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs index b00df5aa1aadd..1c1e3de724945 100644 --- a/src/EditorFeatures/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs +++ b/src/EditorFeatures/CSharpTest/ConvertTupleToStruct/ConvertTupleToStructTests.cs @@ -139,7 +139,7 @@ class Test { void Method() { - var t1 = [||](a: 1, B: 2); + var t1 = [||](a: 1, b: 2); } } "; @@ -148,20 +148,20 @@ class Test { void Method() { - var t1 = new NewStruct(a: 1, B: 2); + var t1 = new NewStruct(a: 1, b: 2); } } -internal record struct NewStruct(int a, int B) +internal record struct NewStruct(int a, int b) { - public static implicit operator (int a, int B)(NewStruct value) + public static implicit operator (int a, int b)(NewStruct value) { - return (value.a, value.B); + return (value.a, value.b); } - public static implicit operator NewStruct((int a, int B) value) + public static implicit operator NewStruct((int a, int b) value) { - return new NewStruct(value.a, value.B); + return new NewStruct(value.a, value.b); } }"; await TestAsync(text, expected, languageVersion: LanguageVersion.Preview, options: PreferImplicitTypeWithInfo(), testHost: host); @@ -208,7 +208,7 @@ public static implicit operator NewStruct((int a, int b) value) } [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)] - public async Task ConvertSingleTupleTypeToRecord_MatchedNameCasing(TestHost host) + public async Task ConvertSingleTupleTypeToRecord_MismatchedNameCasing(TestHost host) { var text = @" class Test @@ -224,12 +224,27 @@ class Test { void Method() { - var t1 = new NewStruct(A: 1, B: 2); + var t1 = new NewStruct(a: 1, b: 2); } } -internal record struct NewStruct(int A, int B) +internal record struct NewStruct { + public int A; + public int B; + + public NewStruct(int a, int b) + { + A = a; + B = b; + } + + public void Deconstruct(out int a, out int b) + { + a = A; + b = B; + } + public static implicit operator (int A, int B)(NewStruct value) { return (value.A, value.B); diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/ISuggestedActionCallback.cs b/src/EditorFeatures/Core.Wpf/Suggestions/ISuggestedActionCallback.cs new file mode 100644 index 0000000000000..8e2c8479f159c --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/Suggestions/ISuggestedActionCallback.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions +{ + internal interface ISuggestedActionCallback + { + void OnSuggestedActionExecuted(SuggestedAction action); + } +} diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs index 6c08f4a7e7d8b..6a3104f5f8724 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActions/SuggestedAction.cs @@ -123,6 +123,8 @@ private void Invoke(IProgressTracker progressTracker, CancellationToken cancella using (SourceProvider.OperationListener.BeginAsyncOperation($"{nameof(SuggestedAction)}.{nameof(Invoke)}")) { InnerInvoke(progressTracker, cancellationToken); + foreach (var actionCallback in SourceProvider.ActionCallbacks) + actionCallback.Value.OnSuggestedActionExecuted(this); } } diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSourceProvider.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSourceProvider.cs index 82fe59e46c693..3ce6e3070d354 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSourceProvider.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSourceProvider.cs @@ -51,6 +51,7 @@ internal partial class SuggestedActionsSourceProvider : ISuggestedActionsSourceP public readonly ICodeActionEditHandlerService EditHandler; public readonly IAsynchronousOperationListener OperationListener; public readonly IUIThreadOperationExecutor UIThreadOperationExecutor; + public readonly ImmutableArray> ActionCallbacks; public readonly ImmutableArray> ImageIdServices; @@ -66,7 +67,8 @@ public SuggestedActionsSourceProvider( ISuggestedActionCategoryRegistryService suggestedActionCategoryRegistry, IAsynchronousOperationListenerProvider listenerProvider, IGlobalOptionService optionService, - [ImportMany] IEnumerable> imageIdServices) + [ImportMany] IEnumerable> imageIdServices, + [ImportMany] IEnumerable> actionCallbacks) { _threadingContext = threadingContext; _codeRefactoringService = codeRefactoringService; @@ -74,6 +76,7 @@ public SuggestedActionsSourceProvider( _codeFixService = codeFixService; _suggestedActionCategoryRegistry = suggestedActionCategoryRegistry; _optionService = optionService; + ActionCallbacks = actionCallbacks.ToImmutableArray(); EditHandler = editHandler; UIThreadOperationExecutor = uiThreadOperationExecutor; OperationListener = listenerProvider.GetListener(FeatureAttribute.LightBulb); diff --git a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs index f4943e33c4618..f31048d1adae7 100644 --- a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs +++ b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs @@ -65,7 +65,6 @@ protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, I TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.DisplayAllOverride), TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.EnabledForParameters), TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForLiteralParameters), - TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForIndexerParameters), TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForObjectCreationParameters), TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.ForOtherParameters), TaggerEventSources.OnOptionChanged(subjectBuffer, InlineHintsOptions.SuppressForParametersThatMatchMethodIntent), diff --git a/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs b/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs index 2bb8424b5110d..3ecd69d10d544 100644 --- a/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs +++ b/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs @@ -1714,11 +1714,7 @@ private void Process(DocumentId _, CancellationToken cancellationToken) } public bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs e) - { - return e.Option == TestOption - || e.Option == SolutionCrawlerOptions.BackgroundAnalysisScopeOption - || e.Option == SolutionCrawlerOptions.SolutionBackgroundAnalysisScopeOption; - } + => e.Option == TestOption; #region unused public Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken) diff --git a/src/EditorFeatures/Test2/InlineHints/CSharpInlineParameterNameHintsTests.vb b/src/EditorFeatures/Test2/InlineHints/CSharpInlineParameterNameHintsTests.vb index 65221d267a656..4911523a67697 100644 --- a/src/EditorFeatures/Test2/InlineHints/CSharpInlineParameterNameHintsTests.vb +++ b/src/EditorFeatures/Test2/InlineHints/CSharpInlineParameterNameHintsTests.vb @@ -675,51 +675,5 @@ class A Await VerifyParamHints(input) End Function - - - - Public Async Function TestIndexerParameter() As Task - Dim input = - - - -public class TempRecord -{ - // Array of temperature values - float[] temps = new float[10] - { - 56.2F, 56.7F, 56.5F, 56.9F, 58.8F, - 61.3F, 65.9F, 62.1F, 59.2F, 57.5F - }; - - // To enable client code to validate input - // when accessing your indexer. - public int Length => temps.Length; - - // Indexer declaration. - // If index is out of range, the temps array will throw the exception. - public float this[int index] - { - get => temps[index]; - set => temps[index] = value; - } -} - -class Program -{ - static void Main() - { - var tempRecord = new TempRecord(); - - // Use the indexer's set accessor - var temp = tempRecord[{|index:|}3]; - } -} - - - - - Await VerifyParamHints(input) - End Function End Class End Namespace diff --git a/src/ExpressionEvaluator/CSharp/Test/ResultProvider/ResultProviderTests.cs b/src/ExpressionEvaluator/CSharp/Test/ResultProvider/ResultProviderTests.cs deleted file mode 100644 index 1f71fa8bd7178..0000000000000 --- a/src/ExpressionEvaluator/CSharp/Test/ResultProvider/ResultProviderTests.cs +++ /dev/null @@ -1,23 +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.Linq; -using Microsoft.CodeAnalysis.ExpressionEvaluator; -using Microsoft.VisualStudio.Debugger.Evaluation; -using Xunit; - -namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.UnitTests -{ - public class ResultProviderTests - { - [Fact] - public void DkmEvaluationFlagsConflict() - { - var values = Enum.GetValues(typeof(DkmEvaluationFlags)).Cast().ToArray(); - Assert.False(values.Contains(ResultProvider.NoResults)); - Assert.False(values.Contains(ResultProvider.NotRoot)); - } - } -} diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.cs b/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.cs index 769be393a9114..4f2fa760946bf 100644 --- a/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.cs +++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/ResultProvider.cs @@ -33,24 +33,16 @@ namespace Microsoft.CodeAnalysis.ExpressionEvaluator /// public abstract class ResultProvider : IDkmClrResultProvider { - // TODO: There is a potential that these values will conflict with debugger defined flags in future. - // It'd be better if we attached these flags to the DkmClrValue object via data items, however DkmClrValue is currently mutable - // and we can't clone it -- in some cases we might need to attach different flags in different code paths and it wouldn't be possible - // to do so due to mutability. - // See https://github.com/dotnet/roslyn/issues/55676. - internal const DkmEvaluationFlags NotRoot = (DkmEvaluationFlags)(1 << 30); - internal const DkmEvaluationFlags NoResults = (DkmEvaluationFlags)(1 << 31); + static ResultProvider() + { + FatalError.Handler = FailFast.OnFatalException; + } // Fields should be removed and replaced with calls through DkmInspectionContext. // (see https://github.com/dotnet/roslyn/issues/6899). internal readonly IDkmClrFormatter2 Formatter2; internal readonly IDkmClrFullNameProvider FullNameProvider; - static ResultProvider() - { - FatalError.Handler = FailFast.OnFatalException; - } - internal ResultProvider(IDkmClrFormatter2 formatter2, IDkmClrFullNameProvider fullNameProvider) { Formatter2 = formatter2; @@ -109,6 +101,9 @@ DkmClrValue IDkmClrResultProvider.GetClrValue(DkmSuccessEvaluationResult evaluat } } + internal const DkmEvaluationFlags NotRoot = (DkmEvaluationFlags)0x20000; + internal const DkmEvaluationFlags NoResults = (DkmEvaluationFlags)0x40000; + void IDkmClrResultProvider.GetChildren(DkmEvaluationResult evaluationResult, DkmWorkList workList, int initialRequestSize, DkmInspectionContext inspectionContext, DkmCompletionRoutine completionRoutine) { var dataItem = evaluationResult.GetDataItem(); diff --git a/src/Features/CSharp/Portable/InlineHints/CSharpInlineParameterNameHintsService.cs b/src/Features/CSharp/Portable/InlineHints/CSharpInlineParameterNameHintsService.cs index 3448a64cc01c5..f757edebde3f4 100644 --- a/src/Features/CSharp/Portable/InlineHints/CSharpInlineParameterNameHintsService.cs +++ b/src/Features/CSharp/Portable/InlineHints/CSharpInlineParameterNameHintsService.cs @@ -86,10 +86,5 @@ private static HintKind GetKind(ExpressionSyntax arg) PostfixUnaryExpressionSyntax(SyntaxKind.SuppressNullableWarningExpression) postfix => GetKind(postfix.Operand), _ => HintKind.Other, }; - - protected override bool IsIndexer(SyntaxNode node, IParameterSymbol parameter) - { - return node is BracketedArgumentListSyntax; - } } } diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs index de61d2d025cca..c451741201d0a 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs @@ -44,10 +44,8 @@ public static async Task> GetUn var receiverTypeSymbolKeyData = SymbolKey.CreateString(receiverTypeSymbol, cancellationToken); var targetTypesSymbolKeyData = targetTypesSymbols.SelectAsArray(s => SymbolKey.CreateString(s, cancellationToken)); - // Call the project overload. Add-import-for-extension-method doesn't search outside of the current - // project cone. var result = await client.TryInvokeAsync( - project, + project.Solution, (service, solutionInfo, cancellationToken) => service.GetUnimportedExtensionMethodsAsync( solutionInfo, document.Id, position, receiverTypeSymbolKeyData, namespaceInScope.ToImmutableArray(), targetTypesSymbolKeyData, forceIndexCreation, cancellationToken), diff --git a/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider.cs b/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider.cs index 288f3c8b93c68..0b9023f1750a1 100644 --- a/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider.cs +++ b/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -28,17 +27,6 @@ internal abstract class AbstractConvertPlaceholderToInterpolatedStringRefactorin where TLiteralExpressionSyntax : SyntaxNode where TArgumentListExpressionSyntax : SyntaxNode { - - // Methods that are not string.Format but still should qualify to be replaced. - // Ex: Console.WriteLine("{0}", a) => Console.WriteLine($"{a}"); - private static readonly (string typeName, string[] methods)[] CompositeFormattedMethods = new (string, string[])[] - { - (typeof(Console).FullName!, new[] { nameof(Console.Write), nameof(Console.WriteLine) }), - (typeof(Debug).FullName!, new[] { nameof(Debug.WriteLine), nameof(Debug.Print)}), - (typeof(Trace).FullName!, new[] { nameof(Trace.TraceError), nameof(Trace.TraceWarning), nameof(Trace.TraceInformation)}), - (typeof(TraceSource).FullName!, new[] { nameof(TraceSource.TraceInformation)}) - }; - protected abstract SyntaxNode GetInterpolatedString(string text); public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) @@ -47,19 +35,18 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var stringType = semanticModel.Compilation.GetSpecialType(SpecialType.System_String); - if (stringType.IsErrorType()) + if (stringType == null) { return; } - var stringInvocationMethods = CollectMethods(stringType, nameof(string.Format)); - var compositeFormattedInvocationMethods = CompositeFormattedMethods - .SelectMany(pair => CollectMethods(semanticModel.Compilation.GetTypeByMetadataName(pair.typeName), pair.methods)) + var formatMethods = stringType + .GetMembers(nameof(string.Format)) + .OfType() + .Where(ShouldIncludeFormatMethod) .ToImmutableArray(); - var allInvocationMethods = stringInvocationMethods.AddRange(compositeFormattedInvocationMethods); - - if (allInvocationMethods.Length == 0) + if (formatMethods.Length == 0) { return; } @@ -70,39 +57,14 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; } - var (invocationSyntax, invocationSymbol) = await TryFindInvocationAsync(textSpan, document, semanticModel, allInvocationMethods, syntaxFactsService, context.CancellationToken).ConfigureAwait(false); - if (invocationSyntax is null || invocationSymbol is null) + var (invocation, invocationSymbol) = await TryFindInvocationAsync(textSpan, document, semanticModel, formatMethods, syntaxFactsService, context.CancellationToken).ConfigureAwait(false); + if (invocation != null && invocationSymbol != null && + IsArgumentListCorrect(syntaxFactsService.GetArgumentsOfInvocationExpression(invocation), invocationSymbol, formatMethods, semanticModel, syntaxFactsService, cancellationToken)) { - return; - } - - if (!IsArgumentListCorrect(syntaxFactsService.GetArgumentsOfInvocationExpression(invocationSyntax), invocationSymbol, allInvocationMethods, semanticModel, syntaxFactsService, cancellationToken)) - { - return; - } - - var shouldReplaceInvocation = stringInvocationMethods.Contains(invocationSymbol); - - context.RegisterRefactoring( + context.RegisterRefactoring( new ConvertToInterpolatedStringCodeAction( - c => CreateInterpolatedStringAsync(invocationSyntax, document, syntaxFactsService, shouldReplaceInvocation, c)), - invocationSyntax.Span); - - // Local Functions - - static ImmutableArray CollectMethods(INamedTypeSymbol? typeSymbol, params string[] methodNames) - { - if (typeSymbol is null) - { - return ImmutableArray.Empty; - } - - return typeSymbol - .GetMembers() - .OfType() - .Where(m => methodNames.Contains(m.Name)) - .Where(ShouldIncludeFormatMethod) - .ToImmutableArray(); + c => CreateInterpolatedStringAsync(invocation, document, syntaxFactsService, c)), + invocation.Span); } } @@ -110,13 +72,13 @@ static ImmutableArray CollectMethods(INamedTypeSymbol? typeSymbol TextSpan span, Document document, SemanticModel semanticModel, - ImmutableArray applicableMethods, + ImmutableArray formatMethods, ISyntaxFactsService syntaxFactsService, CancellationToken cancellationToken) { // If selection is empty there can be multiple matching invocations (we can be deep in), need to go through all of them var possibleInvocations = await document.GetRelevantNodesAsync(span, cancellationToken).ConfigureAwait(false); - var invocation = possibleInvocations.FirstOrDefault(invocation => IsValidPlaceholderToInterpolatedString(invocation, syntaxFactsService, semanticModel, applicableMethods, this, cancellationToken)); + var invocation = possibleInvocations.FirstOrDefault(invocation => IsValidPlaceholderToInterpolatedString(invocation, syntaxFactsService, semanticModel, formatMethods, this, cancellationToken)); // User selected the whole invocation of format. if (invocation != null) @@ -127,7 +89,7 @@ static ImmutableArray CollectMethods(INamedTypeSymbol? typeSymbol // User selected a single argument of the invocation (expression / format string) instead of the whole invocation. var argument = await document.TryGetRelevantNodeAsync(span, cancellationToken).ConfigureAwait(false); invocation = argument?.Parent?.Parent as TInvocationExpressionSyntax; - if (invocation != null && IsValidPlaceholderToInterpolatedString(invocation, syntaxFactsService, semanticModel, applicableMethods, this, cancellationToken)) + if (invocation != null && IsValidPlaceholderToInterpolatedString(invocation, syntaxFactsService, semanticModel, formatMethods, this, cancellationToken)) { return (invocation, semanticModel.GetSymbolInfo(invocation, cancellationToken).Symbol); } @@ -135,7 +97,7 @@ static ImmutableArray CollectMethods(INamedTypeSymbol? typeSymbol // User selected the whole argument list: string format with placeholders plus all expressions var argumentList = await document.TryGetRelevantNodeAsync(span, cancellationToken).ConfigureAwait(false); invocation = argumentList?.Parent as TInvocationExpressionSyntax; - if (invocation != null && IsValidPlaceholderToInterpolatedString(invocation, syntaxFactsService, semanticModel, applicableMethods, this, cancellationToken)) + if (invocation != null && IsValidPlaceholderToInterpolatedString(invocation, syntaxFactsService, semanticModel, formatMethods, this, cancellationToken)) { return (invocation, semanticModel.GetSymbolInfo(invocation, cancellationToken).Symbol); } @@ -145,7 +107,7 @@ static ImmutableArray CollectMethods(INamedTypeSymbol? typeSymbol static bool IsValidPlaceholderToInterpolatedString(TInvocationExpressionSyntax invocation, ISyntaxFactsService syntaxFactsService, SemanticModel semanticModel, - ImmutableArray applicableMethods, + ImmutableArray formatMethods, AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider< TInvocationExpressionSyntax, TExpressionSyntax, TArgumentSyntax, TLiteralExpressionSyntax, @@ -159,7 +121,7 @@ static bool IsValidPlaceholderToInterpolatedString(TInvocationExpressionSyntax i syntaxFactsService.IsStringLiteral(firstArgumentExpression.GetFirstToken())) { var invocationSymbol = semanticModel.GetSymbolInfo(invocation, cancellationToken).Symbol; - if (applicableMethods.Contains(invocationSymbol)) + if (formatMethods.Contains(invocationSymbol)) { return true; } @@ -201,7 +163,6 @@ private async Task CreateInterpolatedStringAsync( TInvocationExpressionSyntax invocation, Document document, ISyntaxFactsService syntaxFactsService, - bool shouldReplaceInvocation, CancellationToken cancellationToken) { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); @@ -213,21 +174,8 @@ private async Task CreateInterpolatedStringAsync( var expandedArguments = GetExpandedArguments(semanticModel, arguments, syntaxGenerator, syntaxFactsService); var interpolatedString = GetInterpolatedString(text); var newInterpolatedString = VisitArguments(expandedArguments, interpolatedString, syntaxFactsService); - - SyntaxNode? replacementNode; - if (shouldReplaceInvocation) - { - replacementNode = newInterpolatedString; - } - else - { - replacementNode = syntaxGenerator.InvocationExpression( - syntaxFactsService.GetExpressionOfInvocationExpression(invocation), - newInterpolatedString); - } - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var newRoot = root.ReplaceNode(invocation, replacementNode.WithTriviaFrom(invocation)); + var newRoot = root.ReplaceNode(invocation, newInterpolatedString.WithTriviaFrom(invocation)); return document.WithSyntaxRoot(newRoot); } diff --git a/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs b/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs index f07d17dd0827a..3e3fc667d467b 100644 --- a/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs @@ -277,8 +277,6 @@ private async Task ConvertToStructInCurrentProcessAsync( // Get the rule that will name the parameter according to the users preferences for this document // (and importantly not any of the documents where we change the call sites, below) - // For records we don't use this however, but rather leave the parameters exactly as the tuple elements - // were defined, since they function as both the parameters and the property names. var parameterNamingRule = await document.GetApplicableNamingRuleAsync(SymbolKind.Parameter, Accessibility.NotApplicable, cancellationToken).ConfigureAwait(false); // Next, generate the full struct that will be used to replace all instances of this @@ -296,7 +294,7 @@ await ReplaceExpressionAndTypesInScopeAsync( documentToEditorMap, documentsToUpdate, tupleExprOrTypeNode, tupleType, structName, capturedTypeParameters, - containingNamespace, parameterNamingRule, isRecord, cancellationToken).ConfigureAwait(false); + containingNamespace, parameterNamingRule, cancellationToken).ConfigureAwait(false); await GenerateStructIntoContainingNamespaceAsync( document, tupleExprOrTypeNode, namedTypeSymbol, @@ -313,8 +311,7 @@ private async Task ReplaceExpressionAndTypesInScopeAsync( ImmutableArray documentsToUpdate, SyntaxNode tupleExprOrTypeNode, INamedTypeSymbol tupleType, string structName, ImmutableArray typeParameters, - INamespaceSymbol containingNamespace, NamingRule parameterNamingRule, - bool isRecord, CancellationToken cancellationToken) + INamespaceSymbol containingNamespace, NamingRule parameterNamingRule, CancellationToken cancellationToken) { // Process the documents one project at a time. foreach (var group in documentsToUpdate.GroupBy(d => d.Document.Project)) @@ -370,7 +367,7 @@ private async Task ReplaceExpressionAndTypesInScopeAsync( foreach (var container in nodesToUpdate) { replaced |= await ReplaceTupleExpressionsAndTypesInDocumentAsync( - document, parameterNamingRule, isRecord, editor, tupleExprOrTypeNode, tupleType, + document, parameterNamingRule, editor, tupleExprOrTypeNode, tupleType, fullTypeName, structName, typeParameters, container, cancellationToken).ConfigureAwait(false); } @@ -598,14 +595,14 @@ private static async Task ApplyChangesAsync( } private async Task ReplaceTupleExpressionsAndTypesInDocumentAsync( - Document document, NamingRule parameterNamingRule, bool isRecord, SyntaxEditor editor, - SyntaxNode startingNode, INamedTypeSymbol tupleType, TNameSyntax fullyQualifiedStructName, + Document document, NamingRule parameterNamingRule, SyntaxEditor editor, SyntaxNode startingNode, + INamedTypeSymbol tupleType, TNameSyntax fullyQualifiedStructName, string structName, ImmutableArray typeParameters, SyntaxNode containerToUpdate, CancellationToken cancellationToken) { var changed = false; changed |= await ReplaceMatchingTupleExpressionsAsync( - document, parameterNamingRule, isRecord, editor, startingNode, tupleType, + document, parameterNamingRule, editor, startingNode, tupleType, fullyQualifiedStructName, structName, typeParameters, containerToUpdate, cancellationToken).ConfigureAwait(false); @@ -618,8 +615,8 @@ private async Task ReplaceTupleExpressionsAndTypesInDocumentAsync( } private async Task ReplaceMatchingTupleExpressionsAsync( - Document document, NamingRule parameterNamingRule, bool isRecord, SyntaxEditor editor, - SyntaxNode startingNode, INamedTypeSymbol tupleType, TNameSyntax qualifiedTypeName, + Document document, NamingRule parameterNamingRule, SyntaxEditor editor, SyntaxNode startingNode, + INamedTypeSymbol tupleType, TNameSyntax qualifiedTypeName, string typeName, ImmutableArray typeParameters, SyntaxNode containingMember, CancellationToken cancellationToken) { @@ -643,7 +640,7 @@ private async Task ReplaceMatchingTupleExpressionsAsync( { changed = true; ReplaceWithObjectCreation( - editor, typeName, typeParameters, qualifiedTypeName, startingNode, childCreation, parameterNamingRule, isRecord); + editor, typeName, typeParameters, qualifiedTypeName, startingNode, childCreation, parameterNamingRule); } } @@ -675,8 +672,7 @@ private static bool NamesMatch( private void ReplaceWithObjectCreation( SyntaxEditor editor, string typeName, ImmutableArray typeParameters, - TNameSyntax qualifiedTypeName, SyntaxNode startingCreationNode, TTupleExpressionSyntax childCreation, - NamingRule parameterNamingRule, bool isRecord) + TNameSyntax qualifiedTypeName, SyntaxNode startingCreationNode, TTupleExpressionSyntax childCreation, NamingRule parameterNamingRule) { // Use the callback form as tuples types may be nested, and we want to // properly replace them even in that case. @@ -694,26 +690,26 @@ private void ReplaceWithObjectCreation( var syntaxFacts = g.SyntaxFacts; syntaxFacts.GetPartsOfTupleExpression( currentTupleExpr, out var openParen, out var arguments, out var closeParen); - arguments = ConvertArguments(g, parameterNamingRule, isRecord, arguments); + arguments = ConvertArguments(g, parameterNamingRule, arguments); return g.ObjectCreationExpression(typeNameNode, openParen, arguments, closeParen) .WithAdditionalAnnotations(Formatter.Annotation); }); } - private SeparatedSyntaxList ConvertArguments(SyntaxGenerator generator, NamingRule parameterNamingRule, bool isRecord, SeparatedSyntaxList arguments) - => generator.SeparatedList(ConvertArguments(generator, parameterNamingRule, isRecord, arguments.GetWithSeparators())); + private SeparatedSyntaxList ConvertArguments(SyntaxGenerator generator, NamingRule parameterNamingRule, SeparatedSyntaxList arguments) + => generator.SeparatedList(ConvertArguments(generator, parameterNamingRule, arguments.GetWithSeparators())); - private SyntaxNodeOrTokenList ConvertArguments(SyntaxGenerator generator, NamingRule parameterNamingRule, bool isRecord, SyntaxNodeOrTokenList list) - => new(list.Select(v => ConvertArgumentOrToken(generator, parameterNamingRule, isRecord, v))); + private SyntaxNodeOrTokenList ConvertArguments(SyntaxGenerator generator, NamingRule parameterNamingRule, SyntaxNodeOrTokenList list) + => new(list.Select(v => ConvertArgumentOrToken(generator, parameterNamingRule, v))); - private SyntaxNodeOrToken ConvertArgumentOrToken(SyntaxGenerator generator, NamingRule parameterNamingRule, bool isRecord, SyntaxNodeOrToken arg) + private SyntaxNodeOrToken ConvertArgumentOrToken(SyntaxGenerator generator, NamingRule parameterNamingRule, SyntaxNodeOrToken arg) => arg.IsToken ? arg - : ConvertArgument(generator, parameterNamingRule, isRecord, (TArgumentSyntax)arg.AsNode()!); + : ConvertArgument(generator, parameterNamingRule, (TArgumentSyntax)arg.AsNode()!); private TArgumentSyntax ConvertArgument( - SyntaxGenerator generator, NamingRule parameterNamingRule, bool isRecord, TArgumentSyntax argument) + SyntaxGenerator generator, NamingRule parameterNamingRule, TArgumentSyntax argument) { // If the original arguments had names then we keep them, but convert the case to match the // the constructor parameters they now refer to. It helps keep the code self-documenting. @@ -723,7 +719,7 @@ private TArgumentSyntax ConvertArgument( if (expr is TLiteralExpressionSyntax) { var argumentName = generator.SyntaxFacts.GetNameForArgument(argument); - var newArgumentName = isRecord ? argumentName : parameterNamingRule.NamingStyle.MakeCompliant(argumentName).First(); + var newArgumentName = GetConstructorParameterName(parameterNamingRule, argumentName); return GetArgumentWithChangedName(argument, newArgumentName); } @@ -801,7 +797,7 @@ private static async Task GenerateFinalNamedTypeAsync( var generator = SyntaxGenerator.GetGenerator(document); - var constructor = CreateConstructor(semanticModel, isRecord, structName, fields, generator, parameterNamingRule); + var (constructor, isPrimaryConstructor) = CreateConstructor(semanticModel, isRecord, structName, fields, generator, parameterNamingRule); // Generate Equals/GetHashCode. We can defer to our existing language service for this // so that we generate the same Equals/GetHashCode that our other IDE features generate. @@ -816,20 +812,21 @@ private static async Task GenerateFinalNamedTypeAsync( using var _ = ArrayBuilder.GetInstance(out var members); - // A record doesn't need fields because we always use a primary constructor - if (!isRecord) - members.AddRange(fields); - + members.AddRange(fields); members.Add(constructor); - // No need to generate Equals/GetHashCode/Deconstruct in a record. The compiler already synthesizes those for us. + // No need to generate Equals/GetHashCode in a record. The compiler already synthesizes those for us. if (!isRecord) { members.Add(equalsMethod); members.Add(getHashCodeMethod); - members.Add(GenerateDeconstructMethod(semanticModel, generator, tupleType, constructor)); } + // If we have a primary constructor, don't bother making a deconstruct method as the compiler + // already synthesizes those for us. + if (!isPrimaryConstructor) + members.Add(GenerateDeconstructMethod(semanticModel, generator, tupleType, constructor)); + AddConversions(generator, members, tupleType, namedTypeWithoutMembers); var namedTypeSymbol = CreateNamedType( @@ -905,7 +902,7 @@ private static INamedTypeSymbol CreateNamedType( TypeKind.Struct, structName, typeParameters, members: members, containingAssembly: containingAssembly); } - private static IMethodSymbol CreateConstructor( + private static (IMethodSymbol constructor, bool isPrimaryConstructor) CreateConstructor( SemanticModel semanticModel, bool isRecord, string className, ImmutableArray fields, SyntaxGenerator generator, NamingRule parameterNamingRule) @@ -915,26 +912,32 @@ private static IMethodSymbol CreateConstructor( using var _ = PooledDictionary.GetInstance(out var parameterToPropMap); var parameters = fields.SelectAsArray(field => { - var parameterName = isRecord ? field.Name : parameterNamingRule.NamingStyle.MakeCompliant(field.Name).First(); var parameter = CodeGenerationSymbolFactory.CreateParameterSymbol( - field.Type, parameterName); + field.Type, GetConstructorParameterName(parameterNamingRule, field.Name)); parameterToPropMap[parameter.Name] = field; return parameter; }); + // If we're making a record-struct and the parameters and properties all have the same name, then we can + // make a primary constructor here. + var isPrimaryConstructor = isRecord && parameterToPropMap.All(kvp => kvp.Key == kvp.Value.Name); + var assignmentStatements = generator.CreateAssignmentStatements( semanticModel, parameters, parameterToPropMap, ImmutableDictionary.Empty, addNullChecks: false, preferThrowExpression: false); var constructor = CodeGenerationSymbolFactory.CreateConstructorSymbol( attributes: default, Accessibility.Public, modifiers: default, - className, parameters, assignmentStatements, isPrimaryConstructor: isRecord); + className, parameters, assignmentStatements, isPrimaryConstructor: isPrimaryConstructor); - return constructor; + return (constructor, isPrimaryConstructor); } + private static string GetConstructorParameterName(NamingRule parameterNamingRule, string name) + => parameterNamingRule.NamingStyle.MakeCompliant(name).First(); + private class MyCodeAction : CodeAction.SolutionChangeAction { public MyCodeAction( diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 4ed0e4a29a4b6..dfe9aaba981bf 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -75,7 +75,6 @@ public bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs return e.Option.Feature == nameof(SimplificationOptions) || e.Option.Feature == nameof(CodeStyleOptions) || e.Option == SolutionCrawlerOptions.BackgroundAnalysisScopeOption || - e.Option == SolutionCrawlerOptions.SolutionBackgroundAnalysisScopeOption || #pragma warning disable CS0618 // Type or member is obsolete - F# is still on the older ClosedFileDiagnostic option. e.Option == SolutionCrawlerOptions.ClosedFileDiagnostic; #pragma warning restore CS0618 // Type or member is obsolete diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index f775b51e9b8ea..89b9e32a07948 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -125,7 +125,7 @@ private async Task AnalyzeProjectAsync(Project project, bool forceAnalyzerRun, C { try { - var stateSets = GetStateSetsForFullSolutionAnalysis(_stateManager.GetOrUpdateStateSets(project), project); + var stateSets = GetStateSetsForFullSolutionAnalysis(_stateManager.GetOrUpdateStateSets(project), project).ToList(); var options = project.Solution.Options; // PERF: get analyzers that are not suppressed and marked as open file only @@ -358,7 +358,7 @@ private static bool AnalysisEnabled(TextDocument document, IDocumentTrackingServ /// /// Return list of to be used for full solution analysis. /// - private IReadOnlyList GetStateSetsForFullSolutionAnalysis(IEnumerable stateSets, Project project) + private IEnumerable GetStateSetsForFullSolutionAnalysis(IEnumerable stateSets, Project project) { // If full analysis is off, remove state that is created from build. // this will make sure diagnostics from build (converted from build to live) will never be cleared @@ -371,7 +371,7 @@ private IReadOnlyList GetStateSetsForFullSolutionAnalysis(IEnumerable< // include all analyzers if option is on if (project.Solution.Workspace.Options.GetOption(InternalDiagnosticsOptions.ProcessHiddenDiagnostics)) { - return stateSets.ToList(); + return stateSets; } // Compute analyzer config options for computing effective severity. @@ -380,7 +380,7 @@ private IReadOnlyList GetStateSetsForFullSolutionAnalysis(IEnumerable< // Include only analyzers we want to run for full solution analysis. // Analyzers not included here will never be saved because result is unknown. - return stateSets.Where(s => IsCandidateForFullSolutionAnalysis(s.Analyzer, project, analyzerConfigOptions)).ToList(); + return stateSets.Where(s => IsCandidateForFullSolutionAnalysis(s.Analyzer, project, analyzerConfigOptions)); } private bool IsCandidateForFullSolutionAnalysis(DiagnosticAnalyzer analyzer, Project project, AnalyzerConfigOptionsResult? analyzerConfigOptions) diff --git a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs index 6c446977a8006..5de998092c07e 100644 --- a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs +++ b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs @@ -31,10 +31,8 @@ public async Task> GetDocumentHighlightsAsync var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false); if (client != null) { - // Call the project overload. We don't need the full solution synchronized over to the OOP - // in order to highlight values in this document. var result = await client.TryInvokeAsync>( - document.Project, + solution, (service, solutionInfo, cancellationToken) => service.GetDocumentHighlightsAsync(solutionInfo, document.Id, position, documentsToSearch.SelectAsArray(d => d.Id), cancellationToken), cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs b/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs index 03eabdbfafab9..4ae0e8529db09 100644 --- a/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs +++ b/src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs @@ -28,8 +28,6 @@ protected abstract void AddAllParameterNameHintLocations( ArrayBuilder<(int position, IParameterSymbol? parameter, HintKind kind)> buffer, CancellationToken cancellationToken); - protected abstract bool IsIndexer(SyntaxNode node, IParameterSymbol parameter); - public async Task> GetInlineHintsAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) { var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); @@ -46,7 +44,6 @@ public async Task> GetInlineHintsAsync(Document docum if (!literalParameters && !objectCreationParameters && !otherParameters) return ImmutableArray.Empty; - var indexerParameters = displayAllOverride || options.GetOption(InlineHintsOptions.ForIndexerParameters); var suppressForParametersThatDifferOnlyBySuffix = !displayAllOverride && options.GetOption(InlineHintsOptions.SuppressForParametersThatDifferOnlyBySuffix); var suppressForParametersThatMatchMethodIntent = !displayAllOverride && options.GetOption(InlineHintsOptions.SuppressForParametersThatMatchMethodIntent); @@ -63,14 +60,14 @@ public async Task> GetInlineHintsAsync(Document docum if (buffer.Count > 0) { - AddHintsIfAppropriate(node); + AddHintsIfAppropriate(); buffer.Clear(); } } return result.ToImmutable(); - void AddHintsIfAppropriate(SyntaxNode node) + void AddHintsIfAppropriate() { if (suppressForParametersThatDifferOnlyBySuffix && ParametersDifferOnlyBySuffix(buffer)) return; @@ -83,11 +80,6 @@ void AddHintsIfAppropriate(SyntaxNode node) if (suppressForParametersThatMatchMethodIntent && MatchesMethodIntent(parameter)) continue; - if (!indexerParameters && IsIndexer(node, parameter)) - { - continue; - } - if (HintMatches(kind, literalParameters, objectCreationParameters, otherParameters)) { result.Add(new InlineHint( @@ -187,15 +179,13 @@ static bool IsNumeric(char c) } private static bool HintMatches(HintKind kind, bool literalParameters, bool objectCreationParameters, bool otherParameters) - { - return kind switch + => kind switch { HintKind.Literal => literalParameters, HintKind.ObjectCreation => objectCreationParameters, HintKind.Other => otherParameters, _ => throw ExceptionUtilities.UnexpectedValue(kind), }; - } protected static bool MatchesMethodIntent(IParameterSymbol? parameter) { diff --git a/src/Features/Core/Portable/InlineHints/InlineHintsOptions.cs b/src/Features/Core/Portable/InlineHints/InlineHintsOptions.cs index a3867d4f3b98d..305befdb72598 100644 --- a/src/Features/Core/Portable/InlineHints/InlineHintsOptions.cs +++ b/src/Features/Core/Portable/InlineHints/InlineHintsOptions.cs @@ -57,12 +57,6 @@ internal static class InlineHintsOptions defaultValue: false, storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.InlineParameterNameHints.ForOtherParameters")); - public static readonly PerLanguageOption2 ForIndexerParameters = - new(nameof(InlineHintsOptions), - nameof(ForIndexerParameters), - defaultValue: true, - storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.InlineParameterNameHints.ForArrayIndexers")); - public static readonly PerLanguageOption2 SuppressForParametersThatDifferOnlyBySuffix = new(nameof(InlineHintsOptions), nameof(SuppressForParametersThatDifferOnlyBySuffix), @@ -114,7 +108,6 @@ public InlineHintsOptionsProvider() InlineHintsOptions.ColorHints, InlineHintsOptions.EnabledForParameters, InlineHintsOptions.ForLiteralParameters, - InlineHintsOptions.ForIndexerParameters, InlineHintsOptions.ForObjectCreationParameters, InlineHintsOptions.ForOtherParameters, InlineHintsOptions.EnabledForTypes, diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index 20cb031acb37c..7f77e87431778 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -68,9 +68,8 @@ private static async Task SearchFullyLoadedDocumentAsync( if (client != null) { var callback = new NavigateToSearchServiceCallback(onItemFound); - // Don't need to sync the full solution when searching a particular project. await client.TryInvokeAsync( - document.Project, + solution, (service, solutionInfo, callbackId, cancellationToken) => service.SearchFullyLoadedDocumentAsync(solutionInfo, document.Id, searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); @@ -112,10 +111,8 @@ private static async Task SearchFullyLoadedProjectAsync( { var priorityDocumentIds = priorityDocuments.SelectAsArray(d => d.Id); var callback = new NavigateToSearchServiceCallback(onItemFound); - - // don't need to sync the entire solution when searching a particular project. await client.TryInvokeAsync( - project, + solution, (service, solutionInfo, callbackId, cancellationToken) => service.SearchFullyLoadedProjectAsync(solutionInfo, project.Id, priorityDocumentIds, searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/NavigationBar/AbstractNavigationBarItemService.cs b/src/Features/Core/Portable/NavigationBar/AbstractNavigationBarItemService.cs index a6daf33113137..6ab315a7b763f 100644 --- a/src/Features/Core/Portable/NavigationBar/AbstractNavigationBarItemService.cs +++ b/src/Features/Core/Portable/NavigationBar/AbstractNavigationBarItemService.cs @@ -23,10 +23,10 @@ public async Task> GetItemsAsync(Documen var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false); if (client != null) { - // Call the project overload. We don't need the full solution synchronized over to the OOP - // in order to get accurate navbar contents for this document. + var solution = document.Project.Solution; + var result = await client.TryInvokeAsync>( - document.Project, + solution, (service, solutionInfo, cancellationToken) => service.GetItemsAsync(solutionInfo, document.Id, supportsCodeGeneration, cancellationToken), cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs index 0a3a83b8c8615..430d5e1f23180 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs @@ -178,10 +178,13 @@ private void ReanalyzeOnOptionChange(object? sender, OptionChangedEventArgs e) // getting analyzer can be slow for the very first time since it is lazily initialized _eventProcessingQueue.ScheduleTask(nameof(ReanalyzeOnOptionChange), () => { + // Force analyze all analyzers if background analysis scope has changed. + var forceAnalyze = e.Option == SolutionCrawlerOptions.BackgroundAnalysisScopeOption; + // let each analyzer decide what they want on option change foreach (var analyzer in _documentAndProjectWorkerProcessor.Analyzers) { - if (analyzer.NeedsReanalysisOnOptionChanged(sender, e)) + if (forceAnalyze || analyzer.NeedsReanalysisOnOptionChanged(sender, e)) { var scope = new ReanalyzeScope(_registration.GetSolutionToAnalyze().Id); Reanalyze(analyzer, scope); diff --git a/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs b/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs index 304bd7e15bc61..1a78e4d6fa0b6 100644 --- a/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs +++ b/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs @@ -2,9 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#nullable disable + using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -20,8 +21,10 @@ internal sealed class BackgroundCompiler : IDisposable private readonly IDocumentTrackingService _documentTrackingService; private readonly TaskQueue _taskQueue; - [SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used to keep a strong reference to the built compilations so they are not GC'd")] - private Compilation?[]? _mostRecentCompilations; +#pragma warning disable IDE0052 // Remove unread private members + // Used to keep a strong reference to the built compilations so they are not GC'd + private Compilation[] _mostRecentCompilations; +#pragma warning restore IDE0052 // Remove unread private members private readonly object _buildGate = new(); private CancellationTokenSource _cancellationSource; @@ -51,17 +54,17 @@ public void Dispose() _workspace.DocumentOpened -= OnDocumentOpened; _workspace.WorkspaceChanged -= OnWorkspaceChanged; - _workspace = null!; + _workspace = null; } } - private void OnDocumentOpened(object? sender, DocumentEventArgs args) + private void OnDocumentOpened(object sender, DocumentEventArgs args) => Rebuild(args.Document.Project.Solution, args.Document.Project.Id); - private void OnDocumentClosed(object? sender, DocumentEventArgs args) + private void OnDocumentClosed(object sender, DocumentEventArgs args) => Rebuild(args.Document.Project.Solution, args.Document.Project.Id); - private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs args) + private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs args) { switch (args.Kind) { @@ -92,7 +95,7 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs args) } } - private void Rebuild(Solution solution, ProjectId? initialProject = null) + private void Rebuild(Solution solution, ProjectId initialProject = null) { lock (_buildGate) { @@ -126,9 +129,9 @@ private void CancelBuild(bool releasePreviousCompilations) private Task BuildCompilationsAsync( Solution solution, - ProjectId? initialProject, + ProjectId initialProject, ISet allOpenProjects, - ProjectId? activeProject) + ProjectId activeProject) { var cancellationToken = _cancellationSource.Token; return _taskQueue.ScheduleTask( @@ -139,9 +142,9 @@ private Task BuildCompilationsAsync( private Task BuildCompilationsAsync( Solution solution, - ProjectId? initialProject, + ProjectId initialProject, ISet projectsToBuild, - ProjectId? activeProject, + ProjectId activeProject, CancellationToken cancellationToken) { var allProjectIds = new List(); @@ -158,21 +161,21 @@ private Task BuildCompilationsAsync( // set the background analysis scope to only analyze active files. var compilationTasks = allProjectIds .Select(solution.GetProject) - .Select(async p => + .Where(p => { if (p is null) - return null; + return false; - if (SolutionCrawlerOptions.GetBackgroundAnalysisScope(p) == BackgroundAnalysisScope.ActiveFile - && p.Id != activeProject) + if (SolutionCrawlerOptions.GetBackgroundAnalysisScope(p) == BackgroundAnalysisScope.ActiveFile) { // For open files with Active File analysis scope, only build the compilation if the project is // active. - return null; + return p.Id == activeProject; } - return await p.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + return true; }) + .Select(p => p.GetCompilationAsync(cancellationToken)) .ToArray(); return Task.WhenAll(compilationTasks).SafeContinueWith(t => { diff --git a/src/Features/Core/Portable/Workspace/BackgroundParser.cs b/src/Features/Core/Portable/Workspace/BackgroundParser.cs index f39ab27ce372d..9f70d143d7a9c 100644 --- a/src/Features/Core/Portable/Workspace/BackgroundParser.cs +++ b/src/Features/Core/Portable/Workspace/BackgroundParser.cs @@ -167,6 +167,17 @@ public void Parse(Document document) { CancelParse(document.Id); + if (SolutionCrawlerOptions.GetBackgroundAnalysisScope(document.Project) == BackgroundAnalysisScope.ActiveFile && + _documentTrackingService.TryGetActiveDocument() != document.Id) + { + // Avoid performing any background parsing for non-active files + // if the user has explicitly set the background analysis scope + // to only analyze active files. + // Note that we bail out after executing CancelParse to ensure + // all the current background parsing tasks are cancelled. + return; + } + if (IsStarted) { _ = ParseDocumentAsync(document); @@ -202,38 +213,26 @@ private Task ParseDocumentAsync(Document document) // By not cancelling, we can reuse the useful results of previous tasks when performing later steps in the chain. // // we still cancel whole task if the task didn't start yet. we just don't cancel if task is started but not finished yet. - return _taskQueue.ScheduleTask( + var task = _taskQueue.ScheduleTask( "BackgroundParser.ParseDocumentAsync", - async () => - { - try - { - if (SolutionCrawlerOptions.GetBackgroundAnalysisScope(document.Project) == BackgroundAnalysisScope.ActiveFile - && _documentTrackingService?.TryGetActiveDocument() != document.Id) - { - // Active file analysis is enabled, but the document for parsing is not the current - // document. Return immediately without parsing. - return; - } + () => document.GetSyntaxTreeAsync(CancellationToken.None), + cancellationToken); - await document.GetSyntaxTreeAsync(CancellationToken.None).ConfigureAwait(false); - } - finally + // Always ensure that we mark this work as done from the workmap. + return task.SafeContinueWith( + _ => + { + using (_stateLock.DisposableWrite()) { - // Always ensure that we mark this work as done from the workmap. - using (_stateLock.DisposableWrite()) + // Check that we are still the active parse in the workmap before we remove it. + // Concievably if this continuation got delayed and another parse was put in, we might + // end up removing the tracking for another in-flight task. + if (_workMap.TryGetValue(document.Id, out var sourceInMap) && sourceInMap == cancellationTokenSource) { - // Check that we are still the active parse in the workmap before we remove it. - // Concievably if this continuation got delayed and another parse was put in, we might - // end up removing the tracking for another in-flight task. - if (_workMap.TryGetValue(document.Id, out var sourceInMap) && sourceInMap == cancellationTokenSource) - { - _workMap = _workMap.Remove(document.Id); - } + _workMap = _workMap.Remove(document.Id); } } - }, - cancellationToken); + }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default); } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index 425868b04704d..51025b1631c48 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -103,7 +103,7 @@ void AddDocumentsFromProject(Project? project, ImmutableArray supportedL // solution analysis on. if (!isOpen) { - var analysisScope = SolutionCrawlerOptions.GetBackgroundAnalysisScope(solution.Workspace.Options, project.Language); + var analysisScope = solution.Workspace.Options.GetOption(SolutionCrawlerOptions.BackgroundAnalysisScopeOption, project.Language); if (analysisScope != BackgroundAnalysisScope.FullSolution) { context.TraceInformation($"Skipping project '{project.Name}' as it has no open document and Full Solution Analysis is off"); diff --git a/src/Features/VisualBasic/Portable/InlineHints/VisualBasicInlineParameterNameHintsService.vb b/src/Features/VisualBasic/Portable/InlineHints/VisualBasicInlineParameterNameHintsService.vb index d39c903d08805..f0a52f3e7853b 100644 --- a/src/Features/VisualBasic/Portable/InlineHints/VisualBasicInlineParameterNameHintsService.vb +++ b/src/Features/VisualBasic/Portable/InlineHints/VisualBasicInlineParameterNameHintsService.vb @@ -86,10 +86,5 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.InlineHints Return HintKind.Other End Function - - Protected Overrides Function IsIndexer(node As SyntaxNode, parameter As IParameterSymbol) As Boolean - Dim propertySymbol = TryCast(parameter.ContainingSymbol, IPropertySymbol) - Return propertySymbol IsNot Nothing AndAlso propertySymbol.IsDefault - End Function End Class End Namespace diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml index 521428aa817e1..e3fd55e9bbabf 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml @@ -241,23 +241,19 @@ - + - + - - - + - + diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs index 5d5241e25a15a..4a35b89f1ce1e 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs @@ -137,7 +137,6 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon BindToOption(ShowHintsForLiterals, InlineHintsOptions.ForLiteralParameters, LanguageNames.CSharp); BindToOption(ShowHintsForNewExpressions, InlineHintsOptions.ForObjectCreationParameters, LanguageNames.CSharp); BindToOption(ShowHintsForEverythingElse, InlineHintsOptions.ForOtherParameters, LanguageNames.CSharp); - BindToOption(ShowHintsForIndexers, InlineHintsOptions.ForIndexerParameters, LanguageNames.CSharp); BindToOption(SuppressHintsWhenParameterNameMatchesTheMethodsIntent, InlineHintsOptions.SuppressForParametersThatMatchMethodIntent, LanguageNames.CSharp); BindToOption(SuppressHintsWhenParameterNamesDifferOnlyBySuffix, InlineHintsOptions.SuppressForParametersThatDifferOnlyBySuffix, LanguageNames.CSharp); @@ -239,7 +238,6 @@ private void UpdateInlineHintsOptions() ShowHintsForLiterals.IsEnabled = enabledForParameters; ShowHintsForNewExpressions.IsEnabled = enabledForParameters; ShowHintsForEverythingElse.IsEnabled = enabledForParameters; - ShowHintsForIndexers.IsEnabled = enabledForParameters; SuppressHintsWhenParameterNameMatchesTheMethodsIntent.IsEnabled = enabledForParameters; SuppressHintsWhenParameterNamesDifferOnlyBySuffix.IsEnabled = enabledForParameters; diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs index a1678aaf8a7ce..406f70a7beb20 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs @@ -58,9 +58,6 @@ public static string Option_Show_hints_for_new_expressions public static string Option_Show_hints_for_everything_else => ServicesVSResources.Show_hints_for_everything_else; - public static string Option_Show_hints_for_indexers - => ServicesVSResources.Show_hints_for_indexers; - public static string Option_Suppress_hints_when_parameter_name_matches_the_method_s_intent => ServicesVSResources.Suppress_hints_when_parameter_name_matches_the_method_s_intent; diff --git a/src/VisualStudio/Core/Def/Commands.vsct b/src/VisualStudio/Core/Def/Commands.vsct index 1f5a13b8eb7eb..e550f0dbe12d5 100644 --- a/src/VisualStudio/Core/Def/Commands.vsct +++ b/src/VisualStudio/Core/Def/Commands.vsct @@ -52,10 +52,6 @@ - - - - @@ -280,48 +276,6 @@ ErrorListSetSeverityNone - - - - -