提交 393f4521 编写于 作者: N Neal Gafter

Add support for switching on float, double, and decimal.

The code is a sequence of if-then-else tests using value.Equals(input).
Someday we might do better using divide-and-conquer.
上级 7bd28046
......@@ -236,44 +236,13 @@ private void LowerDecisionTree(DecisionTree.ByValue byValue)
return;
}
switch (byValue.Type.SpecialType)
if (byValue.Type.SpecialType == SpecialType.System_Boolean)
{
case SpecialType.System_Byte:
case SpecialType.System_Char:
case SpecialType.System_Int16:
case SpecialType.System_Int32:
case SpecialType.System_Int64:
case SpecialType.System_SByte:
case SpecialType.System_UInt16:
case SpecialType.System_UInt32:
case SpecialType.System_UInt64:
case SpecialType.System_String: // switch on a string
// switch on an integral or string type
LowerBasicSwitch(byValue);
return;
case SpecialType.System_Boolean: // switch on a boolean
LowerBooleanSwitch(byValue);
return;
// switch on a type requiring sequential comparisons. Note that we use constant.Equals(value), depending if
// possible on the one from IEquatable<T>. If that does not exist, we use instance method object.Equals(object)
// with the (now boxed) constant on the left.
case SpecialType.System_Decimal:
case SpecialType.System_Double:
case SpecialType.System_Single:
LowerOtherSwitch(byValue);
return;
default:
if (byValue.Type.TypeKind == TypeKind.Enum)
{
LowerBasicSwitch(byValue);
return;
}
// There are no other types of constants that could be used as patterns.
throw ExceptionUtilities.UnexpectedValue(byValue.Type);
LowerBooleanSwitch(byValue);
}
else
{
LowerBasicSwitch(byValue);
}
}
......@@ -391,24 +360,101 @@ private void LowerBasicSwitch(DecisionTree.ByValue byValue)
}
var rewrittenSections = switchSections.ToImmutableAndFree();
MethodSymbol stringEquality = null;
if (byValue.Type.SpecialType == SpecialType.System_String)
switch (byValue.Type.SpecialType)
{
LocalRewriter.EnsureStringHashFunction(rewrittenSections, _factory.Syntax);
stringEquality = LocalRewriter.GetSpecialTypeMethod(_factory.Syntax, SpecialMember.System_String__op_Equality);
case SpecialType.System_String:
{
LocalRewriter.EnsureStringHashFunction(rewrittenSections, _factory.Syntax);
stringEquality = LocalRewriter.GetSpecialTypeMethod(_factory.Syntax, SpecialMember.System_String__op_Equality);
goto case SpecialType.System_UInt32;
}
case SpecialType.System_Byte:
case SpecialType.System_Char:
case SpecialType.System_Int16:
case SpecialType.System_Int32:
case SpecialType.System_Int64:
case SpecialType.System_SByte:
case SpecialType.System_UInt16:
case SpecialType.System_UInt32:
case SpecialType.System_UInt64:
{
// Emit requires a constant target when there are no sections, so we accomodate that here.
// CONSIDER: can we get better code generated by giving a constant target more often here,
// e.g. when the switch expression is a constant?
var constantTarget = rewrittenSections.IsEmpty ? noValueMatches : null;
var switchStatement = new BoundSwitchStatement(
_factory.Syntax, null, _factory.Convert(underlyingSwitchType, byValue.Expression),
constantTarget,
ImmutableArray<LocalSymbol>.Empty, ImmutableArray<LocalFunctionSymbol>.Empty,
rewrittenSections, noValueMatches, stringEquality);
_loweredDecisionTree.Add(switchStatement);
// The bound switch statement implicitly defines the label noValueMatches at the end, so we do not add it explicitly.
break;
}
default:
{
if (byValue.Type.TypeKind == TypeKind.Enum)
{
goto case SpecialType.System_Int32;
}
// Here we handle "other" types, such as float, double, and decimal.
// We compare the constant values using value.Equals(input), using ordinary
// overload resolution. Note that we cannot and do not rely on switching
// on the hash code, as it may not be consistent with the behavior of Equals;
// see https://github.com/dotnet/coreclr/issues/6237. Also, the hash code is
// not guaranteed to be the same on the compilation platform as the runtime
// platform.
// CONSIDER: can we improve the quality of the code using comparisons, like
// we do for other numeric types, by generating a series of tests
// that use divide-and-conquer to efficiently find a matching value?
// If so, we should use the BoundSwitchStatement and do that in emit.
LabelSymbol nextLabel = null;
foreach (var section in rewrittenSections)
{
foreach (var boundSwitchLabel in section.SwitchLabels)
{
if (nextLabel != null)
{
_loweredDecisionTree.Add(_factory.Label(nextLabel));
}
nextLabel = _factory.GenerateLabel("failcase+" + section.SwitchLabels[0].ConstantValueOpt.Value);
Debug.Assert(boundSwitchLabel.ConstantValueOpt != null);
// generate (if (value.Equals(input)) goto label;
var literal = LocalRewriter.MakeLiteral(_factory.Syntax, boundSwitchLabel.ConstantValueOpt, byValue.Type);
var condition = _factory.InstanceCall(literal, "Equals", byValue.Expression);
if (!condition.HasErrors && condition.Type.SpecialType != SpecialType.System_Boolean)
{
var call = (BoundCall)condition;
// '{1} {0}' has the wrong return type
_factory.Diagnostics.Add(ErrorCode.ERR_BadRetType, boundSwitchLabel.Syntax.GetLocation(), call.Method, call.Type);
}
_loweredDecisionTree.Add(_factory.ConditionalGoto(condition, boundSwitchLabel.Label, true));
_loweredDecisionTree.Add(_factory.Goto(nextLabel));
}
foreach (var boundSwitchLabel in section.SwitchLabels)
{
_loweredDecisionTree.Add(_factory.Label(boundSwitchLabel.Label));
}
_loweredDecisionTree.Add(_factory.Block(section.Statements));
// this location should not be reachable.
}
Debug.Assert(nextLabel != null);
_loweredDecisionTree.Add(_factory.Label(nextLabel));
_loweredDecisionTree.Add(_factory.Label(noValueMatches));
break;
}
}
// Emit requires a constant target when there are no sections, so we accomodate that here.
// CONSIDER: can we get better code generated by giving a constant target more often here,
// e.g. when the switch expression is a constant?
var constantTarget = rewrittenSections.IsEmpty ? noValueMatches : null;
var switchStatement = new BoundSwitchStatement(
_factory.Syntax, null, _factory.Convert(underlyingSwitchType, byValue.Expression),
constantTarget,
ImmutableArray<LocalSymbol>.Empty, ImmutableArray<LocalFunctionSymbol>.Empty,
rewrittenSections, noValueMatches, stringEquality);
_loweredDecisionTree.Add(switchStatement);
// The bound switch statement implicitly defines the label noValueMatches at the end, so we do not add it explicitly.
LowerDecisionTree(byValue.Expression, byValue.Default);
}
......@@ -462,16 +508,6 @@ private void LowerBooleanSwitch(DecisionTree.ByValue byValue)
throw ExceptionUtilities.UnexpectedValue(byValue.ValueAndDecision.Count);
}
}
/// <summary>
/// We handle "other" types, such as float, double, and decimal here. We compare the constant values using IEquatable.
/// For other value types, since there is no literal notation, there will be no constants to test.
/// </summary>
private void LowerOtherSwitch(DecisionTree.ByValue byValue)
{
this.LocalRewriter._diagnostics.Add(ErrorCode.ERR_FeatureIsUnimplemented, _factory.Syntax.GetLocation(), "switch on float, double, or decimal");
throw new NotImplementedException();
}
}
}
}
......@@ -566,6 +566,11 @@ public BoundObjectCreationExpression New(MethodSymbol ctor, params BoundExpressi
return new BoundObjectCreationExpression(Syntax, ctor, args) { WasCompilerGenerated = true };
}
public BoundExpression InstanceCall(BoundExpression receiver, string name, BoundExpression arg)
{
return MakeInvocationExpression(BinderFlags.None, this.Syntax, receiver, name, ImmutableArray.Create(arg), this.Diagnostics);
}
public BoundExpression StaticCall(TypeSymbol receiver, string name, params BoundExpression[] args)
{
return MakeInvocationExpression(BinderFlags.None, this.Syntax, this.Type(receiver), name, args.ToImmutableArray(), this.Diagnostics);
......@@ -851,6 +856,11 @@ public BoundLiteral Literal(string value)
return StringLiteral(stringConst);
}
public BoundLiteral Literal(ConstantValue value, TypeSymbol type)
{
return new CSharp.BoundLiteral(Syntax, value, type) { WasCompilerGenerated = true };
}
public BoundLiteral StringLiteral(ConstantValue stringConst)
{
Debug.Assert(stringConst.IsString || stringConst.IsNull);
......
......@@ -865,5 +865,111 @@ public static void M(object o)
sasquatch";
var comp = CompileAndVerify(compilation, expectedOutput: expectedOutput);
}
[Fact]
public void WhenClause02()
{
var source =
@"using System;
class Program
{
public static void Main()
{
M(0.0);
M(-0.0);
M(2.1);
M(1.0);
M(double.NaN);
M(-double.NaN);
M(0.0f);
M(-0.0f);
M(2.1f);
M(1.0f);
M(float.NaN);
M(-float.NaN);
M(0.0m);
M(0m);
M(2.1m);
M(1.0m);
M(null);
}
public static void M(object o)
{
switch (o)
{
case 0.0f:
Console.WriteLine(""0.0f !"");
break;
case 0.0d:
Console.WriteLine(""0.0d !"");
break;
case 0.0m:
Console.WriteLine(""0.0m !"");
break;
case 1.0f:
Console.WriteLine(""1.0f !"");
break;
case 1.0d:
Console.WriteLine(""1.0d !"");
break;
case 1.0m:
Console.WriteLine(""1.0m !"");
break;
case 2.0f:
Console.WriteLine(""2.0f !"");
break;
case 2.0d:
Console.WriteLine(""2.0d !"");
break;
case 2.0m:
Console.WriteLine(""2.0m !"");
break;
case float.NaN:
Console.WriteLine(""float.NaN !"");
break;
case double.NaN:
Console.WriteLine(""double.NaN !"");
break;
case float f when f is float g:
Console.WriteLine(""float "" + g);
break;
case double d when d is double e:
Console.WriteLine(""double "" + e);
break;
case decimal d when d is decimal e:
Console.WriteLine(""decimal "" + e);
break;
case null:
Console.WriteLine(""null"");
break;
case object k:
Console.WriteLine(k.GetType() + "" + "" + k);
break;
}
}
}";
var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: s_patternParseOptions);
compilation.VerifyDiagnostics();
var expectedOutput =
@"0.0d !
0.0d !
double 2.1
1.0d !
double.NaN !
double.NaN !
0.0f !
0.0f !
float 2.1
1.0f !
float.NaN !
float.NaN !
0.0m !
0.0m !
decimal 2.1
1.0m !
null";
var comp = CompileAndVerify(compilation, expectedOutput: expectedOutput);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册