提交 cad10413 编写于 作者: J jaredpar

This change implements the following rule from section 6.2.3 (Explicit...

This change implements the following rule from section 6.2.3 (Explicit Nullable Conversions).  In particular the following section.

     - If the nullable conversion is from S to T?, the conversion is evaluated as the underlying conversion from S to T followed by a wrapping from T to T?

     This rule is what is responsible for keeping expressions like the following an error:

     (int?)double.MaxValue

     The code generation changes in the Emit suites occurred because the compiler is retains the constants produced from the S -> T conversion (this mirrors the native compiler behavior).
 (changeset 1389914)
上级 948716ce
......@@ -585,7 +585,7 @@ private BoundExpression BindExpressionInternal(ExpressionSyntax node, Diagnostic
Debug.Assert(false, "Unexpected SyntaxKind " + node.Kind());
return BadExpression(node);
}
}
}
private BoundExpression BindRefValue(RefValueExpressionSyntax node, DiagnosticBag diagnostics)
{
......@@ -1279,34 +1279,34 @@ private BoundExpression SynthesizeReceiver(SimpleNameSyntax node, Symbol member,
bool hasErrors = false;
if (!IsNameofArgument(node))
{
if (InFieldInitializer && !currentType.IsScriptClass)
{
//can't access "this" in field initializers
Error(diagnostics, ErrorCode.ERR_FieldInitRefNonstatic, node, member);
hasErrors = true;
}
else if (InConstructorInitializer || InAttributeArgument)
{
//can't access "this" in constructor initializers or attribute arguments
Error(diagnostics, ErrorCode.ERR_ObjectRequired, node, member);
hasErrors = true;
}
else
{
// not an instance member if the container is a type, like when binding default parameter values.
var containingMember = ContainingMember();
bool locationIsInstanceMember = !containingMember.IsStatic &&
(containingMember.Kind != SymbolKind.NamedType || currentType.IsScriptClass);
if (!locationIsInstanceMember)
if (InFieldInitializer && !currentType.IsScriptClass)
{
//can't access "this" in field initializers
Error(diagnostics, ErrorCode.ERR_FieldInitRefNonstatic, node, member);
hasErrors = true;
}
else if (InConstructorInitializer || InAttributeArgument)
{
// error CS0120: An object reference is required for the non-static field, method, or property '{0}'
//can't access "this" in constructor initializers or attribute arguments
Error(diagnostics, ErrorCode.ERR_ObjectRequired, node, member);
hasErrors = true;
}
}
else
{
// not an instance member if the container is a type, like when binding default parameter values.
var containingMember = ContainingMember();
bool locationIsInstanceMember = !containingMember.IsStatic &&
(containingMember.Kind != SymbolKind.NamedType || currentType.IsScriptClass);
if (!locationIsInstanceMember)
{
// error CS0120: An object reference is required for the non-static field, method, or property '{0}'
Error(diagnostics, ErrorCode.ERR_ObjectRequired, node, member);
hasErrors = true;
}
}
hasErrors = hasErrors || IsRefOrOutThisParameterCaptured(node, diagnostics);
hasErrors = hasErrors || IsRefOrOutThisParameterCaptured(node, diagnostics);
}
return ThisReference(node, currentType, hasErrors, wasCompilerGenerated: true);
......@@ -1518,6 +1518,20 @@ private BoundExpression BindCast(CastExpressionSyntax node, DiagnosticBag diagno
BoundExpression operand = this.BindValue(node.Expression, diagnostics, BindValueKind.RValue);
TypeSymbol targetType = this.BindType(node.Type, diagnostics);
if (targetType.IsNullableType() &&
!operand.HasAnyErrors &&
operand.Type != null &&
!operand.Type.IsNullableType() &&
targetType.GetNullableUnderlyingType() != operand.Type)
{
return BindExplicitNullableCastFromNonNullable(node, operand, targetType, diagnostics);
}
return BindCastCore(node, operand, targetType, diagnostics);
}
private BoundExpression BindCastCore(ExpressionSyntax node, BoundExpression operand, TypeSymbol targetType, DiagnosticBag diagnostics)
{
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
Conversion conversion = this.Conversions.ClassifyConversionForCast(operand, targetType, ref useSiteDiagnostics);
diagnostics.Add(node, useSiteDiagnostics);
......@@ -1590,6 +1604,66 @@ private BoundExpression BindCast(CastExpressionSyntax node, DiagnosticBag diagno
return CreateConversion(node, operand, conversion, isCast: true, destination: targetType, diagnostics: diagnostics);
}
/// <summary>
/// This implements the casting behavior described in section 6.2.3 of the spec:
///
/// - If the nullable conversion is from S to T?, the conversion is evaluated as the underlying conversion
/// from S to T followed by a wrapping from T to T?.
///
/// This particular check is done in the binder because it involves conversion processing rules (like overflow
/// checking and constant folding) which are not handled by Conversions.
/// </summary>
private BoundExpression BindExplicitNullableCastFromNonNullable(ExpressionSyntax node, BoundExpression operand, TypeSymbol targetType, DiagnosticBag diagnostics)
{
Debug.Assert(targetType != null && targetType.IsNullableType());
Debug.Assert(operand.Type != null && !operand.Type.IsNullableType());
// Section 6.2.3 of the spec only applies when the non-null version of the types involved have a
// built in conversion.
HashSet<DiagnosticInfo> unused = null;
var underlyingTargetType = targetType.GetNullableUnderlyingType();
var underlyingConversion = Conversions.ClassifyConversion(operand.Type, underlyingTargetType, ref unused, builtinOnly: true);
if (!underlyingConversion.Exists)
{
return BindCastCore(node, operand, targetType, diagnostics);
}
var bag = DiagnosticBag.GetInstance();
try
{
var underlyingExpr = BindCastCore(node, operand, targetType.GetNullableUnderlyingType(), bag);
if (underlyingExpr.HasErrors || bag.HasAnyErrors())
{
Error(diagnostics, ErrorCode.ERR_NoExplicitConv, node, operand.Type, targetType);
return new BoundConversion(
node,
operand,
Conversion.NoConversion,
@checked: CheckOverflowAtRuntime,
explicitCastInCode: true,
constantValueOpt: ConstantValue.NotAvailable,
type: targetType,
hasErrors: true);
}
// It's possible for the S -> T conversion to produce a 'better' constant value. If this
// constant value is produced place it in the tree so that it gets emitted. This maintains
// parity with the native compiler which also evaluated the conversion at compile time.
if (underlyingExpr.ConstantValue != null)
{
underlyingExpr.WasCompilerGenerated = true;
return BindCastCore(node, underlyingExpr, targetType, diagnostics);
}
return BindCastCore(node, operand, targetType, diagnostics);
}
finally
{
bag.Free();
}
}
private static NameSyntax GetNameSyntax(CSharpSyntaxNode syntax)
{
string nameString;
......@@ -1852,7 +1926,7 @@ private void BindArgumentsAndNames(BracketedArgumentListSyntax argumentListOpt,
arguments[arg] = CreateConversion(argument.Syntax, argument, kind, false, type, diagnostics);
}
}
}
}
private static TypeSymbol GetCorrespondingParameterType(ref MemberAnalysisResult result, ImmutableArray<ParameterSymbol> parameters, int arg)
......@@ -2561,7 +2635,7 @@ private static bool IsNegativeConstantForArraySize(BoundExpression expression)
if (initializerArgumentListOpt != null && analyzedArguments.HasDynamicArgument)
{
diagnostics.Add(ErrorCode.ERR_NoDynamicPhantomOnBaseCtor,
diagnostics.Add(ErrorCode.ERR_NoDynamicPhantomOnBaseCtor,
((ConstructorInitializerSyntax)initializerArgumentListOpt.Parent).ThisOrBaseKeyword.GetLocation());
return new BoundBadExpression(
......@@ -3188,15 +3262,15 @@ private BoundExpression BindClassCreationExpression(ObjectCreationExpressionSynt
}
return new BoundObjectInitializerMember(
namedAssignment.Left,
boundMember.ExpressionSymbol,
arguments,
argumentNamesOpt,
namedAssignment.Left,
boundMember.ExpressionSymbol,
arguments,
argumentNamesOpt,
argumentRefKindsOpt,
expanded,
argsToParamsOpt,
resultKind,
boundMember.Type,
expanded,
argsToParamsOpt,
resultKind,
boundMember.Type,
hasErrors);
}
......@@ -4294,7 +4368,7 @@ private BoundExpression BindLeftOfPotentialColorColorMemberAccess(ExpressionSynt
}
}
notColorColor:
notColorColor:
// NOTE: it is up to the caller to call CheckValue on the result.
diagnostics.AddRangeAndFree(valueDiagnostics);
return boundValue;
......
......@@ -40,7 +40,6 @@ public static Conversion FastClassifyConversion(TypeSymbol source, TypeSymbol ta
return new Conversion(convKind);
}
/// <summary>
/// IsBaseInterface returns true if baseType is on the base interface list of derivedType or
/// any base class of derivedType. It may be on the base interface list either directly or
......
......@@ -749,5 +749,42 @@ static void Main()
// var x = ICloneable.Clone is object;
Diagnostic(ErrorCode.ERR_LambdaInIsAs, "ICloneable.Clone is object").WithLocation(8, 17));
}
[Fact]
[WorkItem(1084278, "DevDiv")]
public void NullableConversionFromConst()
{
var source =
@"
using System;
class C
{
static void Main()
{
Use((int?)3.5f);
Use((int?)3.5d);
}
static void Use(int? p) { }
}
";
var compilation = CompileAndVerify(source);
compilation.VerifyIL("C.Main()",
@"
{
// Code size 23 (0x17)
.maxstack 1
IL_0000: ldc.i4.3
IL_0001: newobj ""int?..ctor(int)""
IL_0006: call ""void C.Use(int?)""
IL_000b: ldc.i4.3
IL_000c: newobj ""int?..ctor(int)""
IL_0011: call ""void C.Use(int?)""
IL_0016: ret
}");
}
}
}
\ No newline at end of file
......@@ -1612,10 +1612,9 @@ public static void Eval(object obj1, object obj2)
compilation.VerifyIL("NullableTest.EqualEqual",
@"
{
// Code size 124 (0x7c)
// Code size 120 (0x78)
.maxstack 2
.locals init (decimal? V_0,
decimal V_1)
.locals init (decimal? V_0)
IL_0000: ldc.i4.0
IL_0001: box ""bool""
IL_0006: ldc.i4.0
......@@ -1636,24 +1635,22 @@ .maxstack 2
IL_0039: ldc.i4.0
IL_003a: box ""bool""
IL_003f: call ""void Test.Eval(object, object)""
IL_0044: ldloca.s V_1
IL_0046: initobj ""decimal""
IL_004c: ldloc.1
IL_004d: ldsfld ""decimal? NullableTest.NULL""
IL_0052: stloc.0
IL_0053: ldloca.s V_0
IL_0055: call ""decimal decimal?.GetValueOrDefault()""
IL_005a: call ""bool decimal.op_Equality(decimal, decimal)""
IL_005f: brtrue.s IL_0064
IL_0061: ldc.i4.0
IL_0062: br.s IL_006b
IL_0064: ldloca.s V_0
IL_0066: call ""bool decimal?.HasValue.get""
IL_006b: box ""bool""
IL_0070: ldc.i4.0
IL_0071: box ""bool""
IL_0076: call ""void Test.Eval(object, object)""
IL_007b: ret
IL_0044: ldsfld ""decimal decimal.Zero""
IL_0049: ldsfld ""decimal? NullableTest.NULL""
IL_004e: stloc.0
IL_004f: ldloca.s V_0
IL_0051: call ""decimal decimal?.GetValueOrDefault()""
IL_0056: call ""bool decimal.op_Equality(decimal, decimal)""
IL_005b: brtrue.s IL_0060
IL_005d: ldc.i4.0
IL_005e: br.s IL_0067
IL_0060: ldloca.s V_0
IL_0062: call ""bool decimal?.HasValue.get""
IL_0067: box ""bool""
IL_006c: ldc.i4.0
IL_006d: box ""bool""
IL_0072: call ""void Test.Eval(object, object)""
IL_0077: ret
}
");
}
......
......@@ -2741,7 +2741,7 @@ public IEnumerator GetEnumerator()
var compVerifier = CompileAndVerify(source, emitOptions: TestEmitters.CCI, expectedOutput: expectedOutput);
compVerifier.VerifyIL("Test.Main", @"
{
// Code size 71 (0x47)
// Code size 66 (0x42)
.maxstack 4
.locals init (float? V_0)
IL_0000: newobj ""D..ctor()""
......@@ -2759,14 +2759,13 @@ .locals init (float? V_0)
IL_0020: callvirt ""void D.Add(int, float?)""
IL_0025: dup
IL_0026: ldc.i4.3
IL_0027: ldc.r8 4.4
IL_0030: conv.r4
IL_0031: newobj ""float?..ctor(float)""
IL_0036: callvirt ""void D.Add(int, float?)""
IL_003b: callvirt ""System.Collections.IEnumerator D.GetEnumerator()""
IL_0040: call ""void Test.DisplayCollection(System.Collections.IEnumerator)""
IL_0045: ldc.i4.0
IL_0046: ret
IL_0027: ldc.r4 4.4
IL_002c: newobj ""float?..ctor(float)""
IL_0031: callvirt ""void D.Add(int, float?)""
IL_0036: callvirt ""System.Collections.IEnumerator D.GetEnumerator()""
IL_003b: call ""void Test.DisplayCollection(System.Collections.IEnumerator)""
IL_0040: ldc.i4.0
IL_0041: ret
}");
}
......
......@@ -7,6 +7,7 @@
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;
using System.Collections.Generic;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
......@@ -660,5 +661,84 @@ static void TestAdditions()
CompileAndVerify(compilation, expectedOutput: expected);
}
[Fact]
[WorkItem(1084278, "DevDiv")]
public void NullableConversionFromFloatingPointConst()
{
var source = @"
class C
{
void Use(int? p)
{
}
void Test()
{
int? i;
// double checks
i = (int?)3.5d;
i = (int?)double.MaxValue;
i = (int?)double.NaN;
i = (int?)double.NegativeInfinity;
i = (int?)double.PositiveInfinity;
// float checks
i = (int?)3.5d;
i = (int?)float.MaxValue;
i = (int?)float.NaN;
i = (int?)float.NegativeInfinity;
i = (int?)float.PositiveInfinity;
Use(i);
}
}
";
var compilation = CreateCompilationWithMscorlib(source);
compilation.VerifyDiagnostics(
// (15,13): error CS0030: Cannot convert type 'double' to 'int?'
// i = (int?)double.MaxValue;
Diagnostic(ErrorCode.ERR_NoExplicitConv, "(int?)double.MaxValue").WithArguments("double", "int?").WithLocation(15, 13),
// (16,13): error CS0030: Cannot convert type 'double' to 'int?'
// i = (int?)double.NaN;
Diagnostic(ErrorCode.ERR_NoExplicitConv, "(int?)double.NaN").WithArguments("double", "int?").WithLocation(16, 13),
// (17,13): error CS0030: Cannot convert type 'double' to 'int?'
// i = (int?)double.NegativeInfinity;
Diagnostic(ErrorCode.ERR_NoExplicitConv, "(int?)double.NegativeInfinity").WithArguments("double", "int?").WithLocation(17, 13),
// (18,13): error CS0030: Cannot convert type 'double' to 'int?'
// i = (int?)double.PositiveInfinity;
Diagnostic(ErrorCode.ERR_NoExplicitConv, "(int?)double.PositiveInfinity").WithArguments("double", "int?").WithLocation(18, 13),
// (22,13): error CS0030: Cannot convert type 'float' to 'int?'
// i = (int?)float.MaxValue;
Diagnostic(ErrorCode.ERR_NoExplicitConv, "(int?)float.MaxValue").WithArguments("float", "int?").WithLocation(22, 13),
// (23,13): error CS0030: Cannot convert type 'float' to 'int?'
// i = (int?)float.NaN;
Diagnostic(ErrorCode.ERR_NoExplicitConv, "(int?)float.NaN").WithArguments("float", "int?").WithLocation(23, 13),
// (24,13): error CS0030: Cannot convert type 'float' to 'int?'
// i = (int?)float.NegativeInfinity;
Diagnostic(ErrorCode.ERR_NoExplicitConv, "(int?)float.NegativeInfinity").WithArguments("float", "int?").WithLocation(24, 13),
// (25,13): error CS0030: Cannot convert type 'float' to 'int?'
// i = (int?)float.PositiveInfinity;
Diagnostic(ErrorCode.ERR_NoExplicitConv, "(int?)float.PositiveInfinity").WithArguments("float", "int?").WithLocation(25, 13));
var syntaxTree = compilation.SyntaxTrees.First();
var target = syntaxTree.GetRoot().DescendantNodes().OfType<CastExpressionSyntax>().ToList()[2];
var operand = target.Expression;
Assert.Equal("double.NaN", operand.ToFullString());
// Note: there is a valid conversion here at the type level. It's the process of evaluating the conversion, which for
// constants happens at compile time, that triggers the error.
HashSet<DiagnosticInfo> unused = null;
var bag = DiagnosticBag.GetInstance();
var nullableIntType = compilation.GetSpecialType(SpecialType.System_Nullable_T).Construct(compilation.GetSpecialType(SpecialType.System_Int32));
var conversion = compilation.Conversions.ClassifyConversionFromExpression(
compilation.GetBinder(target).BindExpression(operand, bag),
nullableIntType,
ref unused);
Assert.True(conversion.IsExplicit && conversion.IsNullable);
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册