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/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/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)); 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()); + } +} diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index d53998496fb48..f5b92a40eb028 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -13,7 +13,6 @@ 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 +13350,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