From 17e14f25436a6497ae3fa68f2a23318d6d62adc8 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sun, 16 Nov 2025 11:40:25 +0300 Subject: [PATCH 1/6] Improve error recovery for target-typed switch expressions --- .../Portable/Binder/Binder.ValueChecks.cs | 3 +- .../TargetTypedSwitchExpressionTests.cs | 264 ++++++++++++++++++ 2 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 src/Compilers/CSharp/Test/Semantic/Semantics/TargetTypedSwitchExpressionTests.cs diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 6acdb1f87cb48..59400a9b604ac 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -3,12 +3,10 @@ // 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.Diagnostics.CodeAnalysis; using System.Linq; -using Microsoft.CodeAnalysis.CSharp.CodeGen; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -806,6 +804,7 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind case BoundKind.UnconvertedObjectCreationExpression: case BoundKind.UnconvertedCollectionExpression: case BoundKind.UnconvertedConditionalOperator: + case BoundKind.UnconvertedSwitchExpression: case BoundKind.TupleLiteral: if (valueKind == BindValueKind.RValue) { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/TargetTypedSwitchExpressionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/TargetTypedSwitchExpressionTests.cs new file mode 100644 index 0000000000000..ddd5593e555b0 --- /dev/null +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/TargetTypedSwitchExpressionTests.cs @@ -0,0 +1,264 @@ +// 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.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests; + +public class TargetTypedSwitchExpressionTests : CSharpTestBase +{ + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81022")] + public void ErrorRecovery_Return() + { + var source = """ + class C + { + C M(int i) + { + return i switch + { + 1 => new(a), + _ => default, + }; + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,22): error CS0103: The name 'a' does not exist in the current context + // 1 => new(a), + Diagnostic(ErrorCode.ERR_NameNotInContext, "a").WithArguments("a").WithLocation(7, 22)); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var switchExpression = tree.GetCompilationUnitRoot().DescendantNodes().OfType().Single(); + var typeInfo = model.GetTypeInfo(switchExpression); + Assert.Null(typeInfo.Type); + Assert.Equal("C", typeInfo.ConvertedType.ToTestDisplayString()); + + var objectCreationExpression = switchExpression.DescendantNodes().OfType().Single(); + var objectCreationExpressionTypeInfo = model.GetTypeInfo(objectCreationExpression); + Assert.Equal("C", objectCreationExpressionTypeInfo.Type.ToTestDisplayString()); + Assert.Equal("C", objectCreationExpressionTypeInfo.ConvertedType.ToTestDisplayString()); + var objectCreationExpressionSymbolInfo = model.GetSymbolInfo(objectCreationExpression); + Assert.Null(objectCreationExpressionSymbolInfo.Symbol); + Assert.Equal(CandidateReason.OverloadResolutionFailure, objectCreationExpressionSymbolInfo.CandidateReason); + AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolInfo.CandidateSymbols.ToTestDisplayStrings()); + var objectCreationExpressionSymbolGroup = model.GetMemberGroup(objectCreationExpression); + AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolGroup.ToTestDisplayStrings()); + + var defaultLiteralExpression = switchExpression.DescendantNodes().OfType().Single(l => l.IsKind(SyntaxKind.DefaultLiteralExpression)); + var defaultLiteralExpressionTypeInfo = model.GetTypeInfo(defaultLiteralExpression); + Assert.Equal("C", defaultLiteralExpressionTypeInfo.Type.ToTestDisplayString()); + Assert.Equal("C", defaultLiteralExpressionTypeInfo.ConvertedType.ToTestDisplayString()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81022")] + public void ErrorRecovery_VariableDeclaration() + { + var source = """ + class C + { + void M(int i) + { + C c = i switch + { + 1 => new(a), + _ => default, + }; + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,22): error CS0103: The name 'a' does not exist in the current context + // 1 => new(a), + Diagnostic(ErrorCode.ERR_NameNotInContext, "a").WithArguments("a").WithLocation(7, 22)); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var switchExpression = tree.GetCompilationUnitRoot().DescendantNodes().OfType().Single(); + var typeInfo = model.GetTypeInfo(switchExpression); + Assert.Null(typeInfo.Type); + Assert.Equal("C", typeInfo.ConvertedType.ToTestDisplayString()); + + var objectCreationExpression = switchExpression.DescendantNodes().OfType().Single(); + var objectCreationExpressionTypeInfo = model.GetTypeInfo(objectCreationExpression); + Assert.Equal("C", objectCreationExpressionTypeInfo.Type.ToTestDisplayString()); + Assert.Equal("C", objectCreationExpressionTypeInfo.ConvertedType.ToTestDisplayString()); + var objectCreationExpressionSymbolInfo = model.GetSymbolInfo(objectCreationExpression); + Assert.Null(objectCreationExpressionSymbolInfo.Symbol); + Assert.Equal(CandidateReason.OverloadResolutionFailure, objectCreationExpressionSymbolInfo.CandidateReason); + AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolInfo.CandidateSymbols.ToTestDisplayStrings()); + var objectCreationExpressionSymbolGroup = model.GetMemberGroup(objectCreationExpression); + AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolGroup.ToTestDisplayStrings()); + + var defaultLiteralExpression = switchExpression.DescendantNodes().OfType().Single(l => l.IsKind(SyntaxKind.DefaultLiteralExpression)); + var defaultLiteralExpressionTypeInfo = model.GetTypeInfo(defaultLiteralExpression); + Assert.Equal("C", defaultLiteralExpressionTypeInfo.Type.ToTestDisplayString()); + Assert.Equal("C", defaultLiteralExpressionTypeInfo.ConvertedType.ToTestDisplayString()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81022")] + public void ErrorRecovery_Assignment() + { + var source = """ + class C + { + void M(int i) + { + C c; + c = i switch + { + 1 => new(a), + _ => default, + }; + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (8,22): error CS0103: The name 'a' does not exist in the current context + // 1 => new(a), + Diagnostic(ErrorCode.ERR_NameNotInContext, "a").WithArguments("a").WithLocation(8, 22)); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var switchExpression = tree.GetCompilationUnitRoot().DescendantNodes().OfType().Single(); + var typeInfo = model.GetTypeInfo(switchExpression); + Assert.Null(typeInfo.Type); + Assert.Equal("C", typeInfo.ConvertedType.ToTestDisplayString()); + + var objectCreationExpression = switchExpression.DescendantNodes().OfType().Single(); + var objectCreationExpressionTypeInfo = model.GetTypeInfo(objectCreationExpression); + Assert.Equal("C", objectCreationExpressionTypeInfo.Type.ToTestDisplayString()); + Assert.Equal("C", objectCreationExpressionTypeInfo.ConvertedType.ToTestDisplayString()); + var objectCreationExpressionSymbolInfo = model.GetSymbolInfo(objectCreationExpression); + Assert.Null(objectCreationExpressionSymbolInfo.Symbol); + Assert.Equal(CandidateReason.OverloadResolutionFailure, objectCreationExpressionSymbolInfo.CandidateReason); + AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolInfo.CandidateSymbols.ToTestDisplayStrings()); + var objectCreationExpressionSymbolGroup = model.GetMemberGroup(objectCreationExpression); + AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolGroup.ToTestDisplayStrings()); + + var defaultLiteralExpression = switchExpression.DescendantNodes().OfType().Single(l => l.IsKind(SyntaxKind.DefaultLiteralExpression)); + var defaultLiteralExpressionTypeInfo = model.GetTypeInfo(defaultLiteralExpression); + Assert.Equal("C", defaultLiteralExpressionTypeInfo.Type.ToTestDisplayString()); + Assert.Equal("C", defaultLiteralExpressionTypeInfo.ConvertedType.ToTestDisplayString()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81022")] + public void ErrorRecovery_Call() + { + var source = """ + class C + { + void M(int i) + { + N(i switch + { + 1 => new(a), + _ => default, + }); + } + + void N(C c) { } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,18): error CS1729: 'C' does not contain a constructor that takes 1 arguments + // 1 => new(a), + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "new(a)").WithArguments("C", "1").WithLocation(7, 18), + // (7,22): error CS0103: The name 'a' does not exist in the current context + // 1 => new(a), + Diagnostic(ErrorCode.ERR_NameNotInContext, "a").WithArguments("a").WithLocation(7, 22)); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var switchExpression = tree.GetCompilationUnitRoot().DescendantNodes().OfType().Single(); + var typeInfo = model.GetTypeInfo(switchExpression); + Assert.Null(typeInfo.Type); + Assert.Equal("C", typeInfo.ConvertedType.ToTestDisplayString()); + + var objectCreationExpression = switchExpression.DescendantNodes().OfType().Single(); + var objectCreationExpressionTypeInfo = model.GetTypeInfo(objectCreationExpression); + Assert.Equal("C", objectCreationExpressionTypeInfo.Type.ToTestDisplayString()); + Assert.Equal("C", objectCreationExpressionTypeInfo.ConvertedType.ToTestDisplayString()); + var objectCreationExpressionSymbolInfo = model.GetSymbolInfo(objectCreationExpression); + Assert.Null(objectCreationExpressionSymbolInfo.Symbol); + Assert.Equal(CandidateReason.OverloadResolutionFailure, objectCreationExpressionSymbolInfo.CandidateReason); + AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolInfo.CandidateSymbols.ToTestDisplayStrings()); + var objectCreationExpressionSymbolGroup = model.GetMemberGroup(objectCreationExpression); + AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolGroup.ToTestDisplayStrings()); + + var defaultLiteralExpression = switchExpression.DescendantNodes().OfType().Single(l => l.IsKind(SyntaxKind.DefaultLiteralExpression)); + var defaultLiteralExpressionTypeInfo = model.GetTypeInfo(defaultLiteralExpression); + Assert.Equal("C", defaultLiteralExpressionTypeInfo.Type.ToTestDisplayString()); + Assert.Equal("C", defaultLiteralExpressionTypeInfo.ConvertedType.ToTestDisplayString()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81022")] + public void ErrorRecovery_Cast() + { + var source = """ + class C + { + void M(int i) + { + var c = (C)(i switch + { + 1 => new(a), + _ => default, + }); + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,18): error CS1729: 'C' does not contain a constructor that takes 1 arguments + // 1 => new(a), + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "new(a)").WithArguments("C", "1").WithLocation(7, 18), + // (7,22): error CS0103: The name 'a' does not exist in the current context + // 1 => new(a), + Diagnostic(ErrorCode.ERR_NameNotInContext, "a").WithArguments("a").WithLocation(7, 22)); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var switchExpression = tree.GetCompilationUnitRoot().DescendantNodes().OfType().Single(); + var typeInfo = model.GetTypeInfo(switchExpression); + Assert.Null(typeInfo.Type); + Assert.Null(typeInfo.ConvertedType); + + var objectCreationExpression = switchExpression.DescendantNodes().OfType().Single(); + var objectCreationExpressionTypeInfo = model.GetTypeInfo(objectCreationExpression); + Assert.Equal("C", objectCreationExpressionTypeInfo.Type.ToTestDisplayString()); + Assert.Equal("C", objectCreationExpressionTypeInfo.ConvertedType.ToTestDisplayString()); + var objectCreationExpressionSymbolInfo = model.GetSymbolInfo(objectCreationExpression); + Assert.Null(objectCreationExpressionSymbolInfo.Symbol); + Assert.Equal(CandidateReason.OverloadResolutionFailure, objectCreationExpressionSymbolInfo.CandidateReason); + AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolInfo.CandidateSymbols.ToTestDisplayStrings()); + var objectCreationExpressionSymbolGroup = model.GetMemberGroup(objectCreationExpression); + AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolGroup.ToTestDisplayStrings()); + + var defaultLiteralExpression = switchExpression.DescendantNodes().OfType().Single(l => l.IsKind(SyntaxKind.DefaultLiteralExpression)); + var defaultLiteralExpressionTypeInfo = model.GetTypeInfo(defaultLiteralExpression); + Assert.Equal("C", defaultLiteralExpressionTypeInfo.Type.ToTestDisplayString()); + Assert.Equal("C", defaultLiteralExpressionTypeInfo.ConvertedType.ToTestDisplayString()); + } +} From bc0550b5edb15a2af1d6ce40b34f459c7fdd6dd5 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sun, 16 Nov 2025 12:28:34 +0300 Subject: [PATCH 2/6] Add IDE test --- .../CSharpCompletionCommandHandlerTests.vb | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index d53998496fb48..cf708a00ea805 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -11,9 +11,7 @@ Imports Microsoft.CodeAnalysis.Collections Imports Microsoft.CodeAnalysis.Completion Imports Microsoft.CodeAnalysis.Completion.Providers Imports Microsoft.CodeAnalysis.CSharp -Imports Microsoft.CodeAnalysis.CSharp.ExternalAccess.Pythia.Api Imports Microsoft.CodeAnalysis.CSharp.Formatting -Imports Microsoft.CodeAnalysis.CSharp.Shared.Extensions Imports Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion Imports Microsoft.CodeAnalysis.Editor.Shared.Options Imports Microsoft.CodeAnalysis.Editor.[Shared].Utilities @@ -13351,5 +13349,31 @@ class C Await state.AssertSelectedCompletionItem("ticks:") End Using End Function + + + + Public Async Function TestStartTypingInsideTargetTypedSwitchExpression(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + new($$), + _ => default, + }; + } +} + ]]>, + showCompletionInArgumentLists:=showCompletionInArgumentLists) + + state.SendTypeChars("tick") + Await state.AssertSelectedCompletionItem("ticks:") + End Using + End Function End Class End Namespace From 477b74408b45799f66044dc26b34e40fecbfd1a9 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sun, 16 Nov 2025 13:15:19 +0300 Subject: [PATCH 3/6] Bring back necessary using --- .../Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index cf708a00ea805..f5b92a40eb028 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -11,6 +11,7 @@ Imports Microsoft.CodeAnalysis.Collections Imports Microsoft.CodeAnalysis.Completion Imports Microsoft.CodeAnalysis.Completion.Providers Imports Microsoft.CodeAnalysis.CSharp +Imports Microsoft.CodeAnalysis.CSharp.ExternalAccess.Pythia.Api Imports Microsoft.CodeAnalysis.CSharp.Formatting Imports Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion Imports Microsoft.CodeAnalysis.Editor.Shared.Options From 9bed28510ce99f2d1f916b1f21a2ba76ecf3556f Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sun, 16 Nov 2025 17:31:18 +0300 Subject: [PATCH 4/6] Fix existing tests --- .../Emit3/Semantics/PatternMatchingTests_NullableTypes.cs | 6 ++++++ .../Test/Semantic/Semantics/NullableReferenceTypesTests.cs | 5 +---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests_NullableTypes.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests_NullableTypes.cs index 6625ea79ed5c8..02644a0eb2be0 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests_NullableTypes.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests_NullableTypes.cs @@ -351,6 +351,9 @@ void M(object obj) var comp = CreateCompilation(source); comp.VerifyDiagnostics( + // (10,17): error CS8506: No best type was found for the switch expression. + // _ = obj switch + Diagnostic(ErrorCode.ERR_SwitchExpressionNoBestType, "switch").WithLocation(10, 17), // (12,13): error CS8116: It is not legal to use nullable type 'int?' in a pattern; use the underlying type 'int' instead. // int? => 1, Diagnostic(ErrorCode.ERR_PatternNullableType, "int?").WithArguments("int").WithLocation(12, 13), @@ -433,6 +436,9 @@ void M(object obj) var comp = CreateCompilation(source); comp.VerifyDiagnostics( + // (10,17): error CS8506: No best type was found for the switch expression. + // _ = obj switch + Diagnostic(ErrorCode.ERR_SwitchExpressionNoBestType, "switch").WithLocation(10, 17), // (12,13): error CS8116: It is not legal to use nullable type 'int[]?' in a pattern; use the underlying type 'int[]' instead. // int[]? => 1, Diagnostic(ErrorCode.ERR_PatternNullableType, "int[]?").WithArguments("int[]").WithLocation(12, 13), diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 811154e2a64a7..cb3b773f2ee2e 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -9,6 +9,7 @@ using System.Collections.Immutable; using System.Linq; using System.Text; +using Basic.Reference.Assemblies; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Symbols.Retargeting; @@ -18,7 +19,6 @@ using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; -using Basic.Reference.Assemblies; using static Microsoft.CodeAnalysis.CSharp.Symbols.FlowAnalysisAnnotations; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics @@ -78430,9 +78430,6 @@ void F2(int i) => // (5,8): error CS0103: The name 'ERROR' does not exist in the current context // F1(ERROR, i switch { 1 => 1, 2 => null, ERROR => 2, _ => 3 }); Diagnostic(ErrorCode.ERR_NameNotInContext, "ERROR").WithArguments("ERROR").WithLocation(5, 8), - // (5,39): warning CS8619: Nullability of reference types in value of type '' doesn't match target type 'int'. - // F1(ERROR, i switch { 1 => 1, 2 => null, ERROR => 2, _ => 3 }); - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "null").WithArguments("", "int").WithLocation(5, 39), // (5,45): error CS0103: The name 'ERROR' does not exist in the current context // F1(ERROR, i switch { 1 => 1, 2 => null, ERROR => 2, _ => 3 }); Diagnostic(ErrorCode.ERR_NameNotInContext, "ERROR").WithArguments("ERROR").WithLocation(5, 45)); From 756e44b89bfa867ee0cddeb9a61c8a95ccfdbbf0 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sun, 16 Nov 2025 19:56:18 +0300 Subject: [PATCH 5/6] More fixes --- src/Scripting/CSharpTest/ScriptTests.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Scripting/CSharpTest/ScriptTests.cs b/src/Scripting/CSharpTest/ScriptTests.cs index 75996d293c6d6..f540982c2b32d 100644 --- a/src/Scripting/CSharpTest/ScriptTests.cs +++ b/src/Scripting/CSharpTest/ScriptTests.cs @@ -1009,7 +1009,10 @@ public async Task SwitchPatternWithVar_WhenNonExistentVariable_ShouldReturnNameN ex.Diagnostics.Verify( // (1,12): error CS0103: The name 'notExistentVariable' does not exist in the current context // var data = notExistentVariable switch { _ => null }; - Diagnostic(ErrorCode.ERR_NameNotInContext, "notExistentVariable").WithArguments("notExistentVariable").WithLocation(1, 12)); + Diagnostic(ErrorCode.ERR_NameNotInContext, "notExistentVariable").WithArguments("notExistentVariable").WithLocation(1, 12), + // (1,32): error CS8506: No best type was found for the switch expression. + // var data = notExistentVariable switch { _ => null }; + Diagnostic(ErrorCode.ERR_SwitchExpressionNoBestType, "switch").WithLocation(1, 32)); } Assert.True(exceptionThrown); @@ -1029,6 +1032,9 @@ public async Task SwitchPatternWithVar_WhenInvalidPattern_ShouldReturnUnsupporte exceptionThrown = true; // Verify that it produces a single NameNotInContext error. ex.Diagnostics.Verify( + // (1,19): error CS8506: No best type was found for the switch expression. + // var data = "data" switch { < 5 => null }; + Diagnostic(ErrorCode.ERR_SwitchExpressionNoBestType, "switch").WithLocation(1, 19), // (1,28): error CS8781: Relational patterns may not be used for a value of type 'string'. // var data = "data" switch { < 5 => null }; Diagnostic(ErrorCode.ERR_UnsupportedTypeForRelationalPattern, "< 5").WithArguments("string").WithLocation(1, 28), From b9cac1a6668d1a27b70eeb3a0c15444c8e486d0b Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 17 Nov 2025 19:18:09 +0300 Subject: [PATCH 6/6] Avoid more errors when there are already any --- .../CSharp/Portable/Binder/Binder_Expressions.cs | 6 +++++- .../Emit3/Semantics/PatternMatchingTests_NullableTypes.cs | 6 ------ src/Scripting/CSharpTest/ScriptTests.cs | 8 +------- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 84cfd015ae578..aa6ea3f8100ec 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -305,7 +305,11 @@ internal BoundExpression BindToNaturalType(BoundExpression expression, BindingDi bool hasErrors = expression.HasErrors; if (commonType is null) { - diagnostics.Add(ErrorCode.ERR_SwitchExpressionNoBestType, exprSyntax.SwitchKeyword.GetLocation()); + if (!expr.HasAnyErrors) + { + diagnostics.Add(ErrorCode.ERR_SwitchExpressionNoBestType, exprSyntax.SwitchKeyword.GetLocation()); + } + commonType = CreateErrorType(); hasErrors = true; } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests_NullableTypes.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests_NullableTypes.cs index 02644a0eb2be0..6625ea79ed5c8 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests_NullableTypes.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests_NullableTypes.cs @@ -351,9 +351,6 @@ void M(object obj) var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (10,17): error CS8506: No best type was found for the switch expression. - // _ = obj switch - Diagnostic(ErrorCode.ERR_SwitchExpressionNoBestType, "switch").WithLocation(10, 17), // (12,13): error CS8116: It is not legal to use nullable type 'int?' in a pattern; use the underlying type 'int' instead. // int? => 1, Diagnostic(ErrorCode.ERR_PatternNullableType, "int?").WithArguments("int").WithLocation(12, 13), @@ -436,9 +433,6 @@ void M(object obj) var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (10,17): error CS8506: No best type was found for the switch expression. - // _ = obj switch - Diagnostic(ErrorCode.ERR_SwitchExpressionNoBestType, "switch").WithLocation(10, 17), // (12,13): error CS8116: It is not legal to use nullable type 'int[]?' in a pattern; use the underlying type 'int[]' instead. // int[]? => 1, Diagnostic(ErrorCode.ERR_PatternNullableType, "int[]?").WithArguments("int[]").WithLocation(12, 13), diff --git a/src/Scripting/CSharpTest/ScriptTests.cs b/src/Scripting/CSharpTest/ScriptTests.cs index f540982c2b32d..75996d293c6d6 100644 --- a/src/Scripting/CSharpTest/ScriptTests.cs +++ b/src/Scripting/CSharpTest/ScriptTests.cs @@ -1009,10 +1009,7 @@ public async Task SwitchPatternWithVar_WhenNonExistentVariable_ShouldReturnNameN ex.Diagnostics.Verify( // (1,12): error CS0103: The name 'notExistentVariable' does not exist in the current context // var data = notExistentVariable switch { _ => null }; - Diagnostic(ErrorCode.ERR_NameNotInContext, "notExistentVariable").WithArguments("notExistentVariable").WithLocation(1, 12), - // (1,32): error CS8506: No best type was found for the switch expression. - // var data = notExistentVariable switch { _ => null }; - Diagnostic(ErrorCode.ERR_SwitchExpressionNoBestType, "switch").WithLocation(1, 32)); + Diagnostic(ErrorCode.ERR_NameNotInContext, "notExistentVariable").WithArguments("notExistentVariable").WithLocation(1, 12)); } Assert.True(exceptionThrown); @@ -1032,9 +1029,6 @@ public async Task SwitchPatternWithVar_WhenInvalidPattern_ShouldReturnUnsupporte exceptionThrown = true; // Verify that it produces a single NameNotInContext error. ex.Diagnostics.Verify( - // (1,19): error CS8506: No best type was found for the switch expression. - // var data = "data" switch { < 5 => null }; - Diagnostic(ErrorCode.ERR_SwitchExpressionNoBestType, "switch").WithLocation(1, 19), // (1,28): error CS8781: Relational patterns may not be used for a value of type 'string'. // var data = "data" switch { < 5 => null }; Diagnostic(ErrorCode.ERR_UnsupportedTypeForRelationalPattern, "< 5").WithArguments("string").WithLocation(1, 28),