提交 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 ...@@ -585,7 +585,7 @@ private BoundExpression BindExpressionInternal(ExpressionSyntax node, Diagnostic
Debug.Assert(false, "Unexpected SyntaxKind " + node.Kind()); Debug.Assert(false, "Unexpected SyntaxKind " + node.Kind());
return BadExpression(node); return BadExpression(node);
} }
} }
private BoundExpression BindRefValue(RefValueExpressionSyntax node, DiagnosticBag diagnostics) private BoundExpression BindRefValue(RefValueExpressionSyntax node, DiagnosticBag diagnostics)
{ {
...@@ -1279,34 +1279,34 @@ private BoundExpression SynthesizeReceiver(SimpleNameSyntax node, Symbol member, ...@@ -1279,34 +1279,34 @@ private BoundExpression SynthesizeReceiver(SimpleNameSyntax node, Symbol member,
bool hasErrors = false; bool hasErrors = false;
if (!IsNameofArgument(node)) if (!IsNameofArgument(node))
{ {
if (InFieldInitializer && !currentType.IsScriptClass) if (InFieldInitializer && !currentType.IsScriptClass)
{ {
//can't access "this" in field initializers //can't access "this" in field initializers
Error(diagnostics, ErrorCode.ERR_FieldInitRefNonstatic, node, member); Error(diagnostics, ErrorCode.ERR_FieldInitRefNonstatic, node, member);
hasErrors = true; hasErrors = true;
} }
else if (InConstructorInitializer || InAttributeArgument) 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)
{ {
// 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); Error(diagnostics, ErrorCode.ERR_ObjectRequired, node, member);
hasErrors = true; 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); return ThisReference(node, currentType, hasErrors, wasCompilerGenerated: true);
...@@ -1518,6 +1518,20 @@ private BoundExpression BindCast(CastExpressionSyntax node, DiagnosticBag diagno ...@@ -1518,6 +1518,20 @@ private BoundExpression BindCast(CastExpressionSyntax node, DiagnosticBag diagno
BoundExpression operand = this.BindValue(node.Expression, diagnostics, BindValueKind.RValue); BoundExpression operand = this.BindValue(node.Expression, diagnostics, BindValueKind.RValue);
TypeSymbol targetType = this.BindType(node.Type, diagnostics); 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; HashSet<DiagnosticInfo> useSiteDiagnostics = null;
Conversion conversion = this.Conversions.ClassifyConversionForCast(operand, targetType, ref useSiteDiagnostics); Conversion conversion = this.Conversions.ClassifyConversionForCast(operand, targetType, ref useSiteDiagnostics);
diagnostics.Add(node, useSiteDiagnostics); diagnostics.Add(node, useSiteDiagnostics);
...@@ -1590,6 +1604,66 @@ private BoundExpression BindCast(CastExpressionSyntax node, DiagnosticBag diagno ...@@ -1590,6 +1604,66 @@ private BoundExpression BindCast(CastExpressionSyntax node, DiagnosticBag diagno
return CreateConversion(node, operand, conversion, isCast: true, destination: targetType, diagnostics: diagnostics); 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) private static NameSyntax GetNameSyntax(CSharpSyntaxNode syntax)
{ {
string nameString; string nameString;
...@@ -1852,7 +1926,7 @@ private void BindArgumentsAndNames(BracketedArgumentListSyntax argumentListOpt, ...@@ -1852,7 +1926,7 @@ private void BindArgumentsAndNames(BracketedArgumentListSyntax argumentListOpt,
arguments[arg] = CreateConversion(argument.Syntax, argument, kind, false, type, diagnostics); arguments[arg] = CreateConversion(argument.Syntax, argument, kind, false, type, diagnostics);
} }
} }
} }
private static TypeSymbol GetCorrespondingParameterType(ref MemberAnalysisResult result, ImmutableArray<ParameterSymbol> parameters, int arg) private static TypeSymbol GetCorrespondingParameterType(ref MemberAnalysisResult result, ImmutableArray<ParameterSymbol> parameters, int arg)
...@@ -2561,7 +2635,7 @@ private static bool IsNegativeConstantForArraySize(BoundExpression expression) ...@@ -2561,7 +2635,7 @@ private static bool IsNegativeConstantForArraySize(BoundExpression expression)
if (initializerArgumentListOpt != null && analyzedArguments.HasDynamicArgument) if (initializerArgumentListOpt != null && analyzedArguments.HasDynamicArgument)
{ {
diagnostics.Add(ErrorCode.ERR_NoDynamicPhantomOnBaseCtor, diagnostics.Add(ErrorCode.ERR_NoDynamicPhantomOnBaseCtor,
((ConstructorInitializerSyntax)initializerArgumentListOpt.Parent).ThisOrBaseKeyword.GetLocation()); ((ConstructorInitializerSyntax)initializerArgumentListOpt.Parent).ThisOrBaseKeyword.GetLocation());
return new BoundBadExpression( return new BoundBadExpression(
...@@ -3188,15 +3262,15 @@ private BoundExpression BindClassCreationExpression(ObjectCreationExpressionSynt ...@@ -3188,15 +3262,15 @@ private BoundExpression BindClassCreationExpression(ObjectCreationExpressionSynt
} }
return new BoundObjectInitializerMember( return new BoundObjectInitializerMember(
namedAssignment.Left, namedAssignment.Left,
boundMember.ExpressionSymbol, boundMember.ExpressionSymbol,
arguments, arguments,
argumentNamesOpt, argumentNamesOpt,
argumentRefKindsOpt, argumentRefKindsOpt,
expanded, expanded,
argsToParamsOpt, argsToParamsOpt,
resultKind, resultKind,
boundMember.Type, boundMember.Type,
hasErrors); hasErrors);
} }
...@@ -4294,7 +4368,7 @@ private BoundExpression BindLeftOfPotentialColorColorMemberAccess(ExpressionSynt ...@@ -4294,7 +4368,7 @@ private BoundExpression BindLeftOfPotentialColorColorMemberAccess(ExpressionSynt
} }
} }
notColorColor: notColorColor:
// NOTE: it is up to the caller to call CheckValue on the result. // NOTE: it is up to the caller to call CheckValue on the result.
diagnostics.AddRangeAndFree(valueDiagnostics); diagnostics.AddRangeAndFree(valueDiagnostics);
return boundValue; return boundValue;
......
...@@ -40,7 +40,6 @@ public static Conversion FastClassifyConversion(TypeSymbol source, TypeSymbol ta ...@@ -40,7 +40,6 @@ public static Conversion FastClassifyConversion(TypeSymbol source, TypeSymbol ta
return new Conversion(convKind); return new Conversion(convKind);
} }
/// <summary> /// <summary>
/// IsBaseInterface returns true if baseType is on the base interface list of derivedType or /// 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 /// any base class of derivedType. It may be on the base interface list either directly or
......
...@@ -749,5 +749,42 @@ static void Main() ...@@ -749,5 +749,42 @@ static void Main()
// var x = ICloneable.Clone is object; // var x = ICloneable.Clone is object;
Diagnostic(ErrorCode.ERR_LambdaInIsAs, "ICloneable.Clone is object").WithLocation(8, 17)); 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) ...@@ -1612,10 +1612,9 @@ public static void Eval(object obj1, object obj2)
compilation.VerifyIL("NullableTest.EqualEqual", compilation.VerifyIL("NullableTest.EqualEqual",
@" @"
{ {
// Code size 124 (0x7c) // Code size 120 (0x78)
.maxstack 2 .maxstack 2
.locals init (decimal? V_0, .locals init (decimal? V_0)
decimal V_1)
IL_0000: ldc.i4.0 IL_0000: ldc.i4.0
IL_0001: box ""bool"" IL_0001: box ""bool""
IL_0006: ldc.i4.0 IL_0006: ldc.i4.0
...@@ -1636,24 +1635,22 @@ .maxstack 2 ...@@ -1636,24 +1635,22 @@ .maxstack 2
IL_0039: ldc.i4.0 IL_0039: ldc.i4.0
IL_003a: box ""bool"" IL_003a: box ""bool""
IL_003f: call ""void Test.Eval(object, object)"" IL_003f: call ""void Test.Eval(object, object)""
IL_0044: ldloca.s V_1 IL_0044: ldsfld ""decimal decimal.Zero""
IL_0046: initobj ""decimal"" IL_0049: ldsfld ""decimal? NullableTest.NULL""
IL_004c: ldloc.1 IL_004e: stloc.0
IL_004d: ldsfld ""decimal? NullableTest.NULL"" IL_004f: ldloca.s V_0
IL_0052: stloc.0 IL_0051: call ""decimal decimal?.GetValueOrDefault()""
IL_0053: ldloca.s V_0 IL_0056: call ""bool decimal.op_Equality(decimal, decimal)""
IL_0055: call ""decimal decimal?.GetValueOrDefault()"" IL_005b: brtrue.s IL_0060
IL_005a: call ""bool decimal.op_Equality(decimal, decimal)"" IL_005d: ldc.i4.0
IL_005f: brtrue.s IL_0064 IL_005e: br.s IL_0067
IL_0061: ldc.i4.0 IL_0060: ldloca.s V_0
IL_0062: br.s IL_006b IL_0062: call ""bool decimal?.HasValue.get""
IL_0064: ldloca.s V_0 IL_0067: box ""bool""
IL_0066: call ""bool decimal?.HasValue.get"" IL_006c: ldc.i4.0
IL_006b: box ""bool"" IL_006d: box ""bool""
IL_0070: ldc.i4.0 IL_0072: call ""void Test.Eval(object, object)""
IL_0071: box ""bool"" IL_0077: ret
IL_0076: call ""void Test.Eval(object, object)""
IL_007b: ret
} }
"); ");
} }
......
...@@ -2741,7 +2741,7 @@ public IEnumerator GetEnumerator() ...@@ -2741,7 +2741,7 @@ public IEnumerator GetEnumerator()
var compVerifier = CompileAndVerify(source, emitOptions: TestEmitters.CCI, expectedOutput: expectedOutput); var compVerifier = CompileAndVerify(source, emitOptions: TestEmitters.CCI, expectedOutput: expectedOutput);
compVerifier.VerifyIL("Test.Main", @" compVerifier.VerifyIL("Test.Main", @"
{ {
// Code size 71 (0x47) // Code size 66 (0x42)
.maxstack 4 .maxstack 4
.locals init (float? V_0) .locals init (float? V_0)
IL_0000: newobj ""D..ctor()"" IL_0000: newobj ""D..ctor()""
...@@ -2759,14 +2759,13 @@ .locals init (float? V_0) ...@@ -2759,14 +2759,13 @@ .locals init (float? V_0)
IL_0020: callvirt ""void D.Add(int, float?)"" IL_0020: callvirt ""void D.Add(int, float?)""
IL_0025: dup IL_0025: dup
IL_0026: ldc.i4.3 IL_0026: ldc.i4.3
IL_0027: ldc.r8 4.4 IL_0027: ldc.r4 4.4
IL_0030: conv.r4 IL_002c: newobj ""float?..ctor(float)""
IL_0031: newobj ""float?..ctor(float)"" IL_0031: callvirt ""void D.Add(int, float?)""
IL_0036: callvirt ""void D.Add(int, float?)"" IL_0036: callvirt ""System.Collections.IEnumerator D.GetEnumerator()""
IL_003b: callvirt ""System.Collections.IEnumerator D.GetEnumerator()"" IL_003b: call ""void Test.DisplayCollection(System.Collections.IEnumerator)""
IL_0040: call ""void Test.DisplayCollection(System.Collections.IEnumerator)"" IL_0040: ldc.i4.0
IL_0045: ldc.i4.0 IL_0041: ret
IL_0046: ret
}"); }");
} }
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities; using Roslyn.Test.Utilities;
using Xunit; using Xunit;
using System.Collections.Generic;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{ {
...@@ -660,5 +661,84 @@ static void TestAdditions() ...@@ -660,5 +661,84 @@ static void TestAdditions()
CompileAndVerify(compilation, expectedOutput: expected); 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.
先完成此消息的编辑!
想要评论请 注册