Skip to content

Commit ec4f06e

Browse files
authored
Target typed switch expression recovery (#81270)
Fixes: #81022
1 parent 4800e39 commit ec4f06e

File tree

5 files changed

+297
-8
lines changed

5 files changed

+297
-8
lines changed

src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6-
using System.Collections.Generic;
76
using System.Collections.Immutable;
87
using System.Diagnostics;
98
using System.Diagnostics.CodeAnalysis;
109
using System.Linq;
11-
using Microsoft.CodeAnalysis.CSharp.CodeGen;
1210
using Microsoft.CodeAnalysis.CSharp.Symbols;
1311
using Microsoft.CodeAnalysis.PooledObjects;
1412
using Roslyn.Utilities;
@@ -806,6 +804,7 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind
806804
case BoundKind.UnconvertedObjectCreationExpression:
807805
case BoundKind.UnconvertedCollectionExpression:
808806
case BoundKind.UnconvertedConditionalOperator:
807+
case BoundKind.UnconvertedSwitchExpression:
809808
case BoundKind.TupleLiteral:
810809
if (valueKind == BindValueKind.RValue)
811810
{

src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,11 @@ internal BoundExpression BindToNaturalType(BoundExpression expression, BindingDi
305305
bool hasErrors = expression.HasErrors;
306306
if (commonType is null)
307307
{
308-
diagnostics.Add(ErrorCode.ERR_SwitchExpressionNoBestType, exprSyntax.SwitchKeyword.GetLocation());
308+
if (!expr.HasAnyErrors)
309+
{
310+
diagnostics.Add(ErrorCode.ERR_SwitchExpressionNoBestType, exprSyntax.SwitchKeyword.GetLocation());
311+
}
312+
309313
commonType = CreateErrorType();
310314
hasErrors = true;
311315
}

src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Collections.Immutable;
1010
using System.Linq;
1111
using System.Text;
12+
using Basic.Reference.Assemblies;
1213
using Microsoft.CodeAnalysis.CSharp.Symbols;
1314
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
1415
using Microsoft.CodeAnalysis.CSharp.Symbols.Retargeting;
@@ -18,7 +19,6 @@
1819
using Roslyn.Test.Utilities;
1920
using Roslyn.Utilities;
2021
using Xunit;
21-
using Basic.Reference.Assemblies;
2222
using static Microsoft.CodeAnalysis.CSharp.Symbols.FlowAnalysisAnnotations;
2323

2424
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics
@@ -78430,9 +78430,6 @@ void F2(int i) =>
7843078430
// (5,8): error CS0103: The name 'ERROR' does not exist in the current context
7843178431
// F1(ERROR, i switch { 1 => 1, 2 => null, ERROR => 2, _ => 3 });
7843278432
Diagnostic(ErrorCode.ERR_NameNotInContext, "ERROR").WithArguments("ERROR").WithLocation(5, 8),
78433-
// (5,39): warning CS8619: Nullability of reference types in value of type '<null>' doesn't match target type 'int'.
78434-
// F1(ERROR, i switch { 1 => 1, 2 => null, ERROR => 2, _ => 3 });
78435-
Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "null").WithArguments("<null>", "int").WithLocation(5, 39),
7843678433
// (5,45): error CS0103: The name 'ERROR' does not exist in the current context
7843778434
// F1(ERROR, i switch { 1 => 1, 2 => null, ERROR => 2, _ => 3 });
7843878435
Diagnostic(ErrorCode.ERR_NameNotInContext, "ERROR").WithArguments("ERROR").WithLocation(5, 45));
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Linq;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
8+
using Microsoft.CodeAnalysis.Test.Utilities;
9+
using Roslyn.Test.Utilities;
10+
using Xunit;
11+
12+
namespace Microsoft.CodeAnalysis.CSharp.UnitTests;
13+
14+
public class TargetTypedSwitchExpressionTests : CSharpTestBase
15+
{
16+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81022")]
17+
public void ErrorRecovery_Return()
18+
{
19+
var source = """
20+
class C
21+
{
22+
C M(int i)
23+
{
24+
return i switch
25+
{
26+
1 => new(a),
27+
_ => default,
28+
};
29+
}
30+
}
31+
""";
32+
33+
var comp = CreateCompilation(source);
34+
comp.VerifyDiagnostics(
35+
// (7,22): error CS0103: The name 'a' does not exist in the current context
36+
// 1 => new(a),
37+
Diagnostic(ErrorCode.ERR_NameNotInContext, "a").WithArguments("a").WithLocation(7, 22));
38+
39+
var tree = comp.SyntaxTrees.First();
40+
var model = comp.GetSemanticModel(tree);
41+
42+
var switchExpression = tree.GetCompilationUnitRoot().DescendantNodes().OfType<SwitchExpressionSyntax>().Single();
43+
var typeInfo = model.GetTypeInfo(switchExpression);
44+
Assert.Null(typeInfo.Type);
45+
Assert.Equal("C", typeInfo.ConvertedType.ToTestDisplayString());
46+
47+
var objectCreationExpression = switchExpression.DescendantNodes().OfType<ImplicitObjectCreationExpressionSyntax>().Single();
48+
var objectCreationExpressionTypeInfo = model.GetTypeInfo(objectCreationExpression);
49+
Assert.Equal("C", objectCreationExpressionTypeInfo.Type.ToTestDisplayString());
50+
Assert.Equal("C", objectCreationExpressionTypeInfo.ConvertedType.ToTestDisplayString());
51+
var objectCreationExpressionSymbolInfo = model.GetSymbolInfo(objectCreationExpression);
52+
Assert.Null(objectCreationExpressionSymbolInfo.Symbol);
53+
Assert.Equal(CandidateReason.OverloadResolutionFailure, objectCreationExpressionSymbolInfo.CandidateReason);
54+
AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolInfo.CandidateSymbols.ToTestDisplayStrings());
55+
var objectCreationExpressionSymbolGroup = model.GetMemberGroup(objectCreationExpression);
56+
AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolGroup.ToTestDisplayStrings());
57+
58+
var defaultLiteralExpression = switchExpression.DescendantNodes().OfType<LiteralExpressionSyntax>().Single(l => l.IsKind(SyntaxKind.DefaultLiteralExpression));
59+
var defaultLiteralExpressionTypeInfo = model.GetTypeInfo(defaultLiteralExpression);
60+
Assert.Equal("C", defaultLiteralExpressionTypeInfo.Type.ToTestDisplayString());
61+
Assert.Equal("C", defaultLiteralExpressionTypeInfo.ConvertedType.ToTestDisplayString());
62+
}
63+
64+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81022")]
65+
public void ErrorRecovery_VariableDeclaration()
66+
{
67+
var source = """
68+
class C
69+
{
70+
void M(int i)
71+
{
72+
C c = i switch
73+
{
74+
1 => new(a),
75+
_ => default,
76+
};
77+
}
78+
}
79+
""";
80+
81+
var comp = CreateCompilation(source);
82+
comp.VerifyDiagnostics(
83+
// (7,22): error CS0103: The name 'a' does not exist in the current context
84+
// 1 => new(a),
85+
Diagnostic(ErrorCode.ERR_NameNotInContext, "a").WithArguments("a").WithLocation(7, 22));
86+
87+
var tree = comp.SyntaxTrees.First();
88+
var model = comp.GetSemanticModel(tree);
89+
90+
var switchExpression = tree.GetCompilationUnitRoot().DescendantNodes().OfType<SwitchExpressionSyntax>().Single();
91+
var typeInfo = model.GetTypeInfo(switchExpression);
92+
Assert.Null(typeInfo.Type);
93+
Assert.Equal("C", typeInfo.ConvertedType.ToTestDisplayString());
94+
95+
var objectCreationExpression = switchExpression.DescendantNodes().OfType<ImplicitObjectCreationExpressionSyntax>().Single();
96+
var objectCreationExpressionTypeInfo = model.GetTypeInfo(objectCreationExpression);
97+
Assert.Equal("C", objectCreationExpressionTypeInfo.Type.ToTestDisplayString());
98+
Assert.Equal("C", objectCreationExpressionTypeInfo.ConvertedType.ToTestDisplayString());
99+
var objectCreationExpressionSymbolInfo = model.GetSymbolInfo(objectCreationExpression);
100+
Assert.Null(objectCreationExpressionSymbolInfo.Symbol);
101+
Assert.Equal(CandidateReason.OverloadResolutionFailure, objectCreationExpressionSymbolInfo.CandidateReason);
102+
AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolInfo.CandidateSymbols.ToTestDisplayStrings());
103+
var objectCreationExpressionSymbolGroup = model.GetMemberGroup(objectCreationExpression);
104+
AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolGroup.ToTestDisplayStrings());
105+
106+
var defaultLiteralExpression = switchExpression.DescendantNodes().OfType<LiteralExpressionSyntax>().Single(l => l.IsKind(SyntaxKind.DefaultLiteralExpression));
107+
var defaultLiteralExpressionTypeInfo = model.GetTypeInfo(defaultLiteralExpression);
108+
Assert.Equal("C", defaultLiteralExpressionTypeInfo.Type.ToTestDisplayString());
109+
Assert.Equal("C", defaultLiteralExpressionTypeInfo.ConvertedType.ToTestDisplayString());
110+
}
111+
112+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81022")]
113+
public void ErrorRecovery_Assignment()
114+
{
115+
var source = """
116+
class C
117+
{
118+
void M(int i)
119+
{
120+
C c;
121+
c = i switch
122+
{
123+
1 => new(a),
124+
_ => default,
125+
};
126+
}
127+
}
128+
""";
129+
130+
var comp = CreateCompilation(source);
131+
comp.VerifyDiagnostics(
132+
// (8,22): error CS0103: The name 'a' does not exist in the current context
133+
// 1 => new(a),
134+
Diagnostic(ErrorCode.ERR_NameNotInContext, "a").WithArguments("a").WithLocation(8, 22));
135+
136+
var tree = comp.SyntaxTrees.First();
137+
var model = comp.GetSemanticModel(tree);
138+
139+
var switchExpression = tree.GetCompilationUnitRoot().DescendantNodes().OfType<SwitchExpressionSyntax>().Single();
140+
var typeInfo = model.GetTypeInfo(switchExpression);
141+
Assert.Null(typeInfo.Type);
142+
Assert.Equal("C", typeInfo.ConvertedType.ToTestDisplayString());
143+
144+
var objectCreationExpression = switchExpression.DescendantNodes().OfType<ImplicitObjectCreationExpressionSyntax>().Single();
145+
var objectCreationExpressionTypeInfo = model.GetTypeInfo(objectCreationExpression);
146+
Assert.Equal("C", objectCreationExpressionTypeInfo.Type.ToTestDisplayString());
147+
Assert.Equal("C", objectCreationExpressionTypeInfo.ConvertedType.ToTestDisplayString());
148+
var objectCreationExpressionSymbolInfo = model.GetSymbolInfo(objectCreationExpression);
149+
Assert.Null(objectCreationExpressionSymbolInfo.Symbol);
150+
Assert.Equal(CandidateReason.OverloadResolutionFailure, objectCreationExpressionSymbolInfo.CandidateReason);
151+
AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolInfo.CandidateSymbols.ToTestDisplayStrings());
152+
var objectCreationExpressionSymbolGroup = model.GetMemberGroup(objectCreationExpression);
153+
AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolGroup.ToTestDisplayStrings());
154+
155+
var defaultLiteralExpression = switchExpression.DescendantNodes().OfType<LiteralExpressionSyntax>().Single(l => l.IsKind(SyntaxKind.DefaultLiteralExpression));
156+
var defaultLiteralExpressionTypeInfo = model.GetTypeInfo(defaultLiteralExpression);
157+
Assert.Equal("C", defaultLiteralExpressionTypeInfo.Type.ToTestDisplayString());
158+
Assert.Equal("C", defaultLiteralExpressionTypeInfo.ConvertedType.ToTestDisplayString());
159+
}
160+
161+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81022")]
162+
public void ErrorRecovery_Call()
163+
{
164+
var source = """
165+
class C
166+
{
167+
void M(int i)
168+
{
169+
N(i switch
170+
{
171+
1 => new(a),
172+
_ => default,
173+
});
174+
}
175+
176+
void N(C c) { }
177+
}
178+
""";
179+
180+
var comp = CreateCompilation(source);
181+
comp.VerifyDiagnostics(
182+
// (7,18): error CS1729: 'C' does not contain a constructor that takes 1 arguments
183+
// 1 => new(a),
184+
Diagnostic(ErrorCode.ERR_BadCtorArgCount, "new(a)").WithArguments("C", "1").WithLocation(7, 18),
185+
// (7,22): error CS0103: The name 'a' does not exist in the current context
186+
// 1 => new(a),
187+
Diagnostic(ErrorCode.ERR_NameNotInContext, "a").WithArguments("a").WithLocation(7, 22));
188+
189+
var tree = comp.SyntaxTrees.First();
190+
var model = comp.GetSemanticModel(tree);
191+
192+
var switchExpression = tree.GetCompilationUnitRoot().DescendantNodes().OfType<SwitchExpressionSyntax>().Single();
193+
var typeInfo = model.GetTypeInfo(switchExpression);
194+
Assert.Null(typeInfo.Type);
195+
Assert.Equal("C", typeInfo.ConvertedType.ToTestDisplayString());
196+
197+
var objectCreationExpression = switchExpression.DescendantNodes().OfType<ImplicitObjectCreationExpressionSyntax>().Single();
198+
var objectCreationExpressionTypeInfo = model.GetTypeInfo(objectCreationExpression);
199+
Assert.Equal("C", objectCreationExpressionTypeInfo.Type.ToTestDisplayString());
200+
Assert.Equal("C", objectCreationExpressionTypeInfo.ConvertedType.ToTestDisplayString());
201+
var objectCreationExpressionSymbolInfo = model.GetSymbolInfo(objectCreationExpression);
202+
Assert.Null(objectCreationExpressionSymbolInfo.Symbol);
203+
Assert.Equal(CandidateReason.OverloadResolutionFailure, objectCreationExpressionSymbolInfo.CandidateReason);
204+
AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolInfo.CandidateSymbols.ToTestDisplayStrings());
205+
var objectCreationExpressionSymbolGroup = model.GetMemberGroup(objectCreationExpression);
206+
AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolGroup.ToTestDisplayStrings());
207+
208+
var defaultLiteralExpression = switchExpression.DescendantNodes().OfType<LiteralExpressionSyntax>().Single(l => l.IsKind(SyntaxKind.DefaultLiteralExpression));
209+
var defaultLiteralExpressionTypeInfo = model.GetTypeInfo(defaultLiteralExpression);
210+
Assert.Equal("C", defaultLiteralExpressionTypeInfo.Type.ToTestDisplayString());
211+
Assert.Equal("C", defaultLiteralExpressionTypeInfo.ConvertedType.ToTestDisplayString());
212+
}
213+
214+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81022")]
215+
public void ErrorRecovery_Cast()
216+
{
217+
var source = """
218+
class C
219+
{
220+
void M(int i)
221+
{
222+
var c = (C)(i switch
223+
{
224+
1 => new(a),
225+
_ => default,
226+
});
227+
}
228+
}
229+
""";
230+
231+
var comp = CreateCompilation(source);
232+
comp.VerifyDiagnostics(
233+
// (7,18): error CS1729: 'C' does not contain a constructor that takes 1 arguments
234+
// 1 => new(a),
235+
Diagnostic(ErrorCode.ERR_BadCtorArgCount, "new(a)").WithArguments("C", "1").WithLocation(7, 18),
236+
// (7,22): error CS0103: The name 'a' does not exist in the current context
237+
// 1 => new(a),
238+
Diagnostic(ErrorCode.ERR_NameNotInContext, "a").WithArguments("a").WithLocation(7, 22));
239+
240+
var tree = comp.SyntaxTrees.First();
241+
var model = comp.GetSemanticModel(tree);
242+
243+
var switchExpression = tree.GetCompilationUnitRoot().DescendantNodes().OfType<SwitchExpressionSyntax>().Single();
244+
var typeInfo = model.GetTypeInfo(switchExpression);
245+
Assert.Null(typeInfo.Type);
246+
Assert.Null(typeInfo.ConvertedType);
247+
248+
var objectCreationExpression = switchExpression.DescendantNodes().OfType<ImplicitObjectCreationExpressionSyntax>().Single();
249+
var objectCreationExpressionTypeInfo = model.GetTypeInfo(objectCreationExpression);
250+
Assert.Equal("C", objectCreationExpressionTypeInfo.Type.ToTestDisplayString());
251+
Assert.Equal("C", objectCreationExpressionTypeInfo.ConvertedType.ToTestDisplayString());
252+
var objectCreationExpressionSymbolInfo = model.GetSymbolInfo(objectCreationExpression);
253+
Assert.Null(objectCreationExpressionSymbolInfo.Symbol);
254+
Assert.Equal(CandidateReason.OverloadResolutionFailure, objectCreationExpressionSymbolInfo.CandidateReason);
255+
AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolInfo.CandidateSymbols.ToTestDisplayStrings());
256+
var objectCreationExpressionSymbolGroup = model.GetMemberGroup(objectCreationExpression);
257+
AssertEx.SetEqual(["C..ctor()"], objectCreationExpressionSymbolGroup.ToTestDisplayStrings());
258+
259+
var defaultLiteralExpression = switchExpression.DescendantNodes().OfType<LiteralExpressionSyntax>().Single(l => l.IsKind(SyntaxKind.DefaultLiteralExpression));
260+
var defaultLiteralExpressionTypeInfo = model.GetTypeInfo(defaultLiteralExpression);
261+
Assert.Equal("C", defaultLiteralExpressionTypeInfo.Type.ToTestDisplayString());
262+
Assert.Equal("C", defaultLiteralExpressionTypeInfo.ConvertedType.ToTestDisplayString());
263+
}
264+
}

src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ Imports Microsoft.CodeAnalysis.Completion.Providers
1313
Imports Microsoft.CodeAnalysis.CSharp
1414
Imports Microsoft.CodeAnalysis.CSharp.ExternalAccess.Pythia.Api
1515
Imports Microsoft.CodeAnalysis.CSharp.Formatting
16-
Imports Microsoft.CodeAnalysis.CSharp.Shared.Extensions
1716
Imports Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion
1817
Imports Microsoft.CodeAnalysis.Editor.Shared.Options
1918
Imports Microsoft.CodeAnalysis.Editor.[Shared].Utilities
@@ -13351,5 +13350,31 @@ class C
1335113350
Await state.AssertSelectedCompletionItem("ticks:")
1335213351
End Using
1335313352
End Function
13353+
13354+
<WpfTheory, CombinatorialData>
13355+
<WorkItem("https://github.com/dotnet/roslyn/issues/81022")>
13356+
Public Async Function TestStartTypingInsideTargetTypedSwitchExpression(showCompletionInArgumentLists As Boolean) As Task
13357+
Using state = TestStateFactory.CreateCSharpTestState(
13358+
<Document><![CDATA[
13359+
using System;
13360+
13361+
class C
13362+
{
13363+
public DateTime M(int i)
13364+
{
13365+
return i switch
13366+
{
13367+
1 => new($$),
13368+
_ => default,
13369+
};
13370+
}
13371+
}
13372+
]]></Document>,
13373+
showCompletionInArgumentLists:=showCompletionInArgumentLists)
13374+
13375+
state.SendTypeChars("tick")
13376+
Await state.AssertSelectedCompletionItem("ticks:")
13377+
End Using
13378+
End Function
1335413379
End Class
1335513380
End Namespace

0 commit comments

Comments
 (0)