Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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 '<null>' doesn't match target type 'int'.
// F1(ERROR, i switch { 1 => 1, 2 => null, ERROR => 2, _ => 3 });
Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "null").WithArguments("<null>", "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));
Expand Down
Original file line number Diff line number Diff line change
@@ -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<SwitchExpressionSyntax>().Single();
var typeInfo = model.GetTypeInfo(switchExpression);
Assert.Null(typeInfo.Type);
Assert.Equal("C", typeInfo.ConvertedType.ToTestDisplayString());

var objectCreationExpression = switchExpression.DescendantNodes().OfType<ImplicitObjectCreationExpressionSyntax>().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<LiteralExpressionSyntax>().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<SwitchExpressionSyntax>().Single();
var typeInfo = model.GetTypeInfo(switchExpression);
Assert.Null(typeInfo.Type);
Assert.Equal("C", typeInfo.ConvertedType.ToTestDisplayString());

var objectCreationExpression = switchExpression.DescendantNodes().OfType<ImplicitObjectCreationExpressionSyntax>().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<LiteralExpressionSyntax>().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<SwitchExpressionSyntax>().Single();
var typeInfo = model.GetTypeInfo(switchExpression);
Assert.Null(typeInfo.Type);
Assert.Equal("C", typeInfo.ConvertedType.ToTestDisplayString());

var objectCreationExpression = switchExpression.DescendantNodes().OfType<ImplicitObjectCreationExpressionSyntax>().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<LiteralExpressionSyntax>().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<SwitchExpressionSyntax>().Single();
var typeInfo = model.GetTypeInfo(switchExpression);
Assert.Null(typeInfo.Type);
Assert.Equal("C", typeInfo.ConvertedType.ToTestDisplayString());

var objectCreationExpression = switchExpression.DescendantNodes().OfType<ImplicitObjectCreationExpressionSyntax>().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<LiteralExpressionSyntax>().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<SwitchExpressionSyntax>().Single();
var typeInfo = model.GetTypeInfo(switchExpression);
Assert.Null(typeInfo.Type);
Assert.Null(typeInfo.ConvertedType);

var objectCreationExpression = switchExpression.DescendantNodes().OfType<ImplicitObjectCreationExpressionSyntax>().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<LiteralExpressionSyntax>().Single(l => l.IsKind(SyntaxKind.DefaultLiteralExpression));
var defaultLiteralExpressionTypeInfo = model.GetTypeInfo(defaultLiteralExpression);
Assert.Equal("C", defaultLiteralExpressionTypeInfo.Type.ToTestDisplayString());
Assert.Equal("C", defaultLiteralExpressionTypeInfo.ConvertedType.ToTestDisplayString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -13351,5 +13350,31 @@ class C
Await state.AssertSelectedCompletionItem("ticks:")
End Using
End Function

<WpfTheory, CombinatorialData>
<WorkItem("https://github.com/dotnet/roslyn/issues/81022")>
Public Async Function TestStartTypingInsideTargetTypedSwitchExpression(showCompletionInArgumentLists As Boolean) As Task
Using state = TestStateFactory.CreateCSharpTestState(
<Document><![CDATA[
using System;

class C
{
public DateTime M(int i)
{
return i switch
{
1 => new($$),
_ => default,
};
}
}
]]></Document>,
showCompletionInArgumentLists:=showCompletionInArgumentLists)

state.SendTypeChars("tick")
Await state.AssertSelectedCompletionItem("ticks:")
End Using
End Function
End Class
End Namespace