private BoundExpression ConvertCaseExpression(TypeSymbol switchGoverningType, CSharpSyntaxNode node, BoundExpression caseExpression, Binder sectionBinder, ref ConstantValue constantValueOpt, DiagnosticBag diagnostics, bool isGotoCaseExpr = false) { BoundExpression convertedCaseExpression; if (!isGotoCaseExpr) { // NOTE: This will allow user-defined conversions, even though they're not allowed here. This is acceptable // because the result of a user-defined conversion does not have a ConstantValue and we'll report a diagnostic // to that effect below (same error code as Dev10). convertedCaseExpression = sectionBinder.GenerateConversionForAssignment(switchGoverningType, caseExpression, diagnostics); } else { // SPEC VIOLATION for Dev10 COMPATIBILITY: // Dev10 compiler violates the SPEC comment below: // "if the constant-expression is not implicitly convertible (§6.1) to // the governing type of the nearest enclosing switch statement, // a compile-time error occurs" // If there is no implicit conversion from gotoCaseExpression to switchGoverningType, // but there exists an explicit conversion, Dev10 compiler generates a warning "WRN_GotoCaseShouldConvert" // instead of an error. See test "CS0469_NoImplicitConversionWarning". // CONSIDER: Should we introduce a breaking change and violate Dev10 compatibility and follow the spec? HashSet <DiagnosticInfo> useSiteDiagnostics = null; Conversion conversion = sectionBinder.Conversions.ClassifyConversionFromExpression(caseExpression, switchGoverningType, ref useSiteDiagnostics); diagnostics.Add(node, useSiteDiagnostics); if (!conversion.IsValid) { GenerateImplicitConversionError(diagnostics, node, conversion, caseExpression, switchGoverningType); } else if (!conversion.IsImplicit) { diagnostics.Add(ErrorCode.WRN_GotoCaseShouldConvert, node.Location, switchGoverningType); } convertedCaseExpression = sectionBinder.CreateConversion(caseExpression, conversion, switchGoverningType, diagnostics); } if (switchGoverningType.IsNullableType() && convertedCaseExpression.Kind == BoundKind.Conversion // Null is a special case here because we want to compare null to the Nullable<T> itself, not to the underlying type. && (convertedCaseExpression.ConstantValue == null || !convertedCaseExpression.ConstantValue.IsNull)) { var operand = ((BoundConversion)convertedCaseExpression).Operand; // We are not intested in the diagnostic that get created here var diagnosticBag = DiagnosticBag.GetInstance(); constantValueOpt = sectionBinder.CreateConversion(operand, switchGoverningType.GetNullableUnderlyingType(), diagnosticBag).ConstantValue; diagnosticBag.Free(); } else { constantValueOpt = convertedCaseExpression.ConstantValue; } return(convertedCaseExpression); }
private void UnwrapCollectionExpressionIfNullable(ref BoundExpression collectionExpr, DiagnosticBag diagnostics) { TypeSymbol collectionExprType = collectionExpr.Type; // If collectionExprType is a nullable type, then use the underlying type and take the value (i.e. .Value) of collectionExpr. // This behavior is not spec'd, but it's what Dev10 does. if ((object)collectionExprType != null && collectionExprType.IsNullableType()) { CSharpSyntaxNode exprSyntax = collectionExpr.Syntax; MethodSymbol nullableValueGetter = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_Nullable_T_get_Value, diagnostics, exprSyntax); if ((object)nullableValueGetter != null) { nullableValueGetter = nullableValueGetter.AsMember((NamedTypeSymbol)collectionExprType); // Synthesized call, because we don't want to modify the type in the SemanticModel. collectionExpr = BoundCall.Synthesized( syntax: exprSyntax, receiverOpt: collectionExpr, method: nullableValueGetter); } else { collectionExpr = new BoundBadExpression( exprSyntax, LookupResultKind.Empty, ImmutableArray <Symbol> .Empty, ImmutableArray.Create <BoundNode>(collectionExpr), collectionExprType.GetNullableUnderlyingType()) { WasCompilerGenerated = true }; // Don't affect the type in the SemanticModel. } } }
internal BoundExpression ConvertPatternExpression(TypeSymbol inputType, CSharpSyntaxNode node, BoundExpression expression, ref ConstantValue constantValue, DiagnosticBag diagnostics) { // NOTE: This will allow user-defined conversions, even though they're not allowed here. This is acceptable // because the result of a user-defined conversion does not have a ConstantValue and we'll report a diagnostic // to that effect later. BoundExpression convertedExpression = GenerateConversionForAssignment(inputType, expression, diagnostics); if (convertedExpression.Kind == BoundKind.Conversion) { var conversion = (BoundConversion)convertedExpression; var operand = conversion.Operand; if (inputType.IsNullableType() && (convertedExpression.ConstantValue == null || !convertedExpression.ConstantValue.IsNull)) { // Null is a special case here because we want to compare null to the Nullable<T> itself, not to the underlying type. var discardedDiagnostics = DiagnosticBag.GetInstance(); // We are not intested in the diagnostic that get created here convertedExpression = CreateConversion(operand, inputType.GetNullableUnderlyingType(), discardedDiagnostics); discardedDiagnostics.Free(); } else if ((conversion.ConversionKind == ConversionKind.Boxing || conversion.ConversionKind == ConversionKind.ImplicitReference) && operand.ConstantValue != null && convertedExpression.ConstantValue == null) { // A boxed constant (or string converted to object) is a special case because we prefer // to compare to the pre-converted value by casting the input value to the type of the constant // (that is, unboxing or downcasting it) and then testing the resulting value using primitives. // That is much more efficient than calling object.Equals(x, y), and we can share the downcasted // input value among many constant tests. convertedExpression = operand; } } constantValue = convertedExpression.ConstantValue; return(convertedExpression); }
private BoundExpression GetConvertedLeftForNullCoalescingOperator(BoundExpression rewrittenLeft, Conversion leftConversion, TypeSymbol rewrittenResultType) { Debug.Assert(rewrittenLeft != null); Debug.Assert((object)rewrittenLeft.Type != null); Debug.Assert((object)rewrittenResultType != null); Debug.Assert(leftConversion.IsValid); TypeSymbol rewrittenLeftType = rewrittenLeft.Type; Debug.Assert(rewrittenLeftType.IsNullableType() || !rewrittenLeftType.IsValueType); // Native compiler violates the specification for the case where result type is right operand type and left operand is nullable. // For this case, we need to insert an extra explicit nullable conversion from the left operand to its underlying nullable type // before performing the leftConversion. // See comments in Binder.BindNullCoalescingOperator referring to GetConvertedLeftForNullCoalescingOperator for more details. if (!TypeSymbol.Equals(rewrittenLeftType, rewrittenResultType, TypeCompareKind.ConsiderEverything2) && rewrittenLeftType.IsNullableType()) { TypeSymbol strippedLeftType = rewrittenLeftType.GetNullableUnderlyingType(); MethodSymbol getValueOrDefault = UnsafeGetNullableMethod(rewrittenLeft.Syntax, rewrittenLeftType, SpecialMember.System_Nullable_T_GetValueOrDefault); rewrittenLeft = BoundCall.Synthesized(rewrittenLeft.Syntax, rewrittenLeft, getValueOrDefault); if (TypeSymbol.Equals(strippedLeftType, rewrittenResultType, TypeCompareKind.ConsiderEverything2)) { return(rewrittenLeft); } } return(MakeConversionNode(rewrittenLeft.Syntax, rewrittenLeft, leftConversion, rewrittenResultType, @checked: false)); }
private static bool HasImplicitEnumerationConversion(BoundExpression source, TypeSymbol destination) { Debug.Assert((object)source != null); Debug.Assert((object)destination != null); // SPEC: An implicit enumeration conversion permits the decimal-integer-literal 0 to be converted to any enum-type // SPEC: and to any nullable-type whose underlying type is an enum-type. // // For historical reasons we actually allow a conversion from any *numeric constant // zero* to be converted to any enum type, not just the literal integer zero. bool validType = destination.IsEnumType() || destination.IsNullableType() && destination.GetNullableUnderlyingType().IsEnumType(); if (!validType) { return(false); } var sourceConstantValue = source.ConstantValue; return(sourceConstantValue != null && IsNumericType(source.Type.GetSpecialTypeSafe()) && IsConstantNumericZero(sourceConstantValue)); }
private BoundExpression ConvertToNullable(SyntaxNode syntax, TypeSymbol targetNullableType, BoundExpression underlyingValue) { Debug.Assert(targetNullableType.IsNullableType()); Debug.Assert(TypeSymbol.Equals(targetNullableType.GetNullableUnderlyingType(), underlyingValue.Type, TypeCompareKind.AllIgnoreOptions)); if (!TryGetNullableMethod(syntax, targetNullableType, SpecialMember.System_Nullable_T__ctor, out MethodSymbol nullableCtor)) { return(BadExpression(syntax, targetNullableType, underlyingValue)); } return(new BoundObjectCreationExpression(syntax, nullableCtor, underlyingValue)); }
/// <summary> /// Returns true if the type is a valid switch expression type. /// </summary> internal static bool IsValidSwitchGoverningType(this TypeSymbol type, bool isTargetTypeOfUserDefinedOp = false) { // SPEC: The governing type of a switch statement is established by the switch expression. // SPEC: 1) If the type of the switch expression is sbyte, byte, short, ushort, int, uint, // SPEC: long, ulong, bool, char, string, or an enum-type, or if it is the nullable type // SPEC: corresponding to one of these types, then that is the governing type of the switch statement. // SPEC: 2) Otherwise, exactly one user-defined implicit conversion (§6.4) must exist from the // SPEC: type of the switch expression to one of the following possible governing types: // SPEC: sbyte, byte, short, ushort, int, uint, long, ulong, char, string, or, a nullable type // SPEC: corresponding to one of those types Debug.Assert((object)type != null); if (type.IsNullableType()) { type = type.GetNullableUnderlyingType(); } // User-defined implicit conversion with target type as Enum type is not valid. if (!isTargetTypeOfUserDefinedOp && type.IsEnumType()) { type = type.GetEnumUnderlyingType(); } switch (type.SpecialType) { case SpecialType.System_SByte: case SpecialType.System_Byte: case SpecialType.System_Int16: case SpecialType.System_UInt16: case SpecialType.System_Int32: case SpecialType.System_UInt32: case SpecialType.System_Int64: case SpecialType.System_UInt64: case SpecialType.System_Char: case SpecialType.System_String: return(true); case SpecialType.System_Boolean: // User-defined implicit conversion with target type as bool type is not valid. return(!isTargetTypeOfUserDefinedOp); } return(false); }
private BoundExpression GetLiftedUnaryOperatorConsequence(UnaryOperatorKind kind, SyntaxNode syntax, MethodSymbol method, TypeSymbol type, BoundExpression nonNullOperand) { MethodSymbol ctor = UnsafeGetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor); // OP(temp.GetValueOrDefault()) BoundExpression unliftedOp = MakeUnaryOperator( oldNode: null, kind: kind.Unlifted(), syntax: syntax, method: method, loweredOperand: nonNullOperand, type: type.GetNullableUnderlyingType()); // new R?(OP(temp.GetValueOrDefault())) BoundExpression consequence = new BoundObjectCreationExpression( syntax, ctor, unliftedOp); return(consequence); }
// IL gen can generate more compact code for certain conditional accesses // by utilizing stack dup/pop instructions internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, bool used) { var loweredReceiver = this.VisitExpression(node.Receiver); var receiverType = loweredReceiver.Type; // Check trivial case if (loweredReceiver.IsDefaultValue() && receiverType.IsReferenceType) { return(_factory.Default(node.Type)); } ConditionalAccessLoweringKind loweringKind; // trivial cases are directly supported in IL gen loweringKind = ConditionalAccessLoweringKind.LoweredConditionalAccess; var previousConditionalAccessTarget = _currentConditionalAccessTarget; var currentConditionalAccessID = ++_currentConditionalAccessID; LocalSymbol temp = null; switch (loweringKind) { case ConditionalAccessLoweringKind.LoweredConditionalAccess: _currentConditionalAccessTarget = new BoundConditionalReceiver( loweredReceiver.Syntax, currentConditionalAccessID, receiverType); break; case ConditionalAccessLoweringKind.Ternary: _currentConditionalAccessTarget = loweredReceiver; break; case ConditionalAccessLoweringKind.TernaryCaptureReceiverByVal: temp = _factory.SynthesizedLocal(receiverType); _currentConditionalAccessTarget = _factory.Local(temp); break; default: throw ExceptionUtilities.UnexpectedValue(loweringKind); } BoundExpression loweredAccessExpression; if (used) { loweredAccessExpression = this.VisitExpression(node.AccessExpression); } else { loweredAccessExpression = this.VisitUnusedExpression(node.AccessExpression); if (loweredAccessExpression == null) { return(null); } } Debug.Assert(loweredAccessExpression != null); _currentConditionalAccessTarget = previousConditionalAccessTarget; TypeSymbol type = this.VisitType(node.Type); TypeSymbol nodeType = node.Type; TypeSymbol accessExpressionType = loweredAccessExpression.Type; if (accessExpressionType.SpecialType == SpecialType.System_Void) { type = nodeType = accessExpressionType; } if (!TypeSymbol.Equals(accessExpressionType, nodeType, TypeCompareKind.ConsiderEverything2) && nodeType.IsNullableType()) { Debug.Assert(TypeSymbol.Equals(accessExpressionType, nodeType.GetNullableUnderlyingType(), TypeCompareKind.ConsiderEverything2)); loweredAccessExpression = _factory.New((NamedTypeSymbol)nodeType, loweredAccessExpression); } else { Debug.Assert(TypeSymbol.Equals(accessExpressionType, nodeType, TypeCompareKind.ConsiderEverything2) || (nodeType.SpecialType == SpecialType.System_Void && !used)); } BoundExpression result; var objectType = _compilation.GetSpecialType(SpecialType.System_Object); switch (loweringKind) { case ConditionalAccessLoweringKind.LoweredConditionalAccess: result = new BoundLoweredConditionalAccess( node.Syntax, loweredReceiver, receiverType.IsNullableType() ? UnsafeGetNullableMethod(node.Syntax, loweredReceiver.Type, SpecialMember.core_Option_T_get_has_value) : null, loweredAccessExpression, null, currentConditionalAccessID, type); break; case ConditionalAccessLoweringKind.TernaryCaptureReceiverByVal: // capture the receiver into a temp loweredReceiver = _factory.MakeSequence( _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver), _factory.Local(temp)); goto case ConditionalAccessLoweringKind.Ternary; case ConditionalAccessLoweringKind.Ternary: { // (object)r != null ? access : default(T) var condition = _factory.ObjectNotEqual( _factory.Convert(objectType, loweredReceiver), _factory.Null(objectType)); var consequence = loweredAccessExpression; result = RewriteConditionalOperator(node.Syntax, condition, consequence, _factory.Default(nodeType), null, nodeType, isRef: false); if (temp != null) { result = _factory.MakeSequence(temp, result); } } break; default: throw ExceptionUtilities.UnexpectedValue(loweringKind); } return(result); }
internal static ConstantValue GetIsOperatorConstantResult(TypeSymbol operandType, TypeSymbol targetType, ConversionKind conversionKind, ConstantValue operandConstantValue) { Debug.Assert((object)targetType != null); // SPEC: The result of the operation depends on D and T as follows: // SPEC: 1) If T is a reference type, the result is true if D and T are the same type, if D is a reference type and // SPEC: an implicit reference conversion from D to T exists, or if D is a value type and a boxing conversion from D to T exists. // SPEC: 2) If T is a nullable type, the result is true if D is the underlying type of T. // SPEC: 3) If T is a non-nullable value type, the result is true if D and T are the same type. // SPEC: 4) Otherwise, the result is false. // NOTE: The language specification talks about the runtime evaluation of the is operation. // NOTE: However, we are interested in computing the compile time constant value for the expression. // NOTE: Even though BoundIsOperator and BoundAsOperator will always have no ConstantValue // NOTE: (they are non-constant expressions according to Section 7.19 of the specification), // NOTE: we want to perform constant analysis of is/as expressions during binding to generate warnings (always true/false/null) // NOTE: and during rewriting for optimized codegen. // NOTE: // NOTE: Because the heuristic presented here is used to change codegen, it must be conservative. It is acceptable // NOTE: for us to fail to report a warning in cases where humans could logically deduce that the operator will // NOTE: always return false. It is not acceptable to inaccurately warn that the operator will always return false // NOTE: if there are cases where it might succeed. // // To begin our heuristic: if the operand is literal null then we automatically return that the // result is false. You might think that we can simply check to see if the conversion is // ConversionKind.NullConversion, but "null is T" for a type parameter T is actually classified // as an implicit reference conversion if T is constrained to reference types. Rather // than deal with all those special cases we can simply bail out here. if (operandConstantValue == ConstantValue.Null) { return ConstantValue.False; } Debug.Assert((object)operandType != null); switch (conversionKind) { case ConversionKind.NoConversion: // Oddly enough, "x is T" can be true even if there is no conversion from x to T! // // Scenario 1: Type parameter compared to System.Enum. // // bool M1<X>(X x) where X : struct { return x is Enum; } // // There is no conversion from X to Enum, not even an explicit conversion. But // nevertheless, X could be constructed as an enumerated type. // However, we can sometimes know that the result will be false. // // Scenario 2: Constrained type parameter compared to reference type. // // bool M2<X>(X x) where X : struct { return x is string; } // // We know that X, constrained to struct, will never be string. // // Scenario 3: Value type compared to type parameter. // // bool M3<T>(int x) { return x is T; } // // There is no conversion from int to T, but T could nevertheless be int. // // Scenario 4: Constructed type compared to open type // // bool M4<T>(C<int> x) { return x is C<T>; } // // There is no conversion from C<int> to C<T>, but nevertheless, T might be int. // // Scenario 5: Open type compared to constructed type: // // bool M5<X>(C<X> x) { return x is C<int>); // // Again, X could be int. // // We could then go on to get more complicated. For example, // // bool M6<X>(C<X> x) where X : struct { return x is C<string>; } // // We know that C<X> is never convertible to C<string> no matter what // X is. Or: // // bool M7<T>(Dictionary<int, int> x) { return x is List<T>; } // // We know that no matter what T is, the conversion will never succeed. // // As noted above, we must be conservative. We follow the lead of the native compiler, // which uses the following algorithm: // // * If neither type is open and there is no conversion then the result is always false: if (!operandType.ContainsTypeParameter() && !targetType.ContainsTypeParameter()) { return ConstantValue.False; } // * Otherwise, at least one of them is of an open type. If the operand is of value type // and the target is a class type other than System.Enum, then we are in scenario 2, // not scenario 1, and can correctly deduce that the result is false. if (operandType.IsValueType && targetType.IsClassType() && targetType.SpecialType != SpecialType.System_Enum) { return ConstantValue.False; } // * Otherwise, we give up. Though there are other situations in which we can deduce that // the result will always be false, such as scenarios 6 and 7, but we do not attempt // to deduce this. // CONSIDER: we could use TypeUnification.CanUnify to do additional compile-time checking. return null; case ConversionKind.ImplicitNumeric: case ConversionKind.ExplicitNumeric: case ConversionKind.ImplicitEnumeration: // case ConversionKind.ExplicitEnumeration: // Handled separately below. case ConversionKind.ImplicitConstant: case ConversionKind.ImplicitUserDefined: case ConversionKind.ExplicitUserDefined: case ConversionKind.IntPtr: // Consider all the cases where we know that "x is T" must be false just from // the conversion classification. // // If we have "x is T" and the conversion from x to T is numeric or enum then the result must be false. // // If we have "null is T" then obviously that must be false. // // If we have "1 is long" then that must be false. (If we have "1 is int" then it is an identity conversion, // not an implicit constant conversion. // // User-defined and IntPtr conversions are always false for "is". return ConstantValue.False; case ConversionKind.ExplicitEnumeration: // Enum-to-enum conversions should be treated the same as unsuccessful struct-to-struct // conversions (i.e. make allowances for type unification, etc) if (operandType.IsEnumType() && targetType.IsEnumType()) { goto case ConversionKind.NoConversion; } return ConstantValue.False; case ConversionKind.ExplicitNullable: // An explicit nullable conversion is a conversion of one of the following forms: // // 1) X? --> Y?, where X --> Y is an explicit conversion. (If X --> Y is an implicit // conversion then X? --> Y? is an implicit nullable conversion.) In this case we // know that "X? is Y?" must be false because either X? is null, or we have an // explicit conversion from struct type X to struct type Y, and so X is never of type Y.) // // 2) X --> Y?, where again, X --> Y is an explicit conversion. By the same reasoning // as in case 1, this must be false. if (targetType.IsNullableType()) { return ConstantValue.False; } Debug.Assert(operandType.IsNullableType()); // 3) X? --> X. In this case, this is just a different way of writing "x != null". // We do not know what the result will be. // CONSIDER: If we know statically that the operand is going to be null or non-null // CONSIDER: then we could give a better result here. if (Conversions.HasIdentityConversion(operandType.GetNullableUnderlyingType(), targetType)) { return null; } // 4) X? --> Y where the conversion X --> Y is an implicit or explicit value type conversion. // "X? is Y" again must be false. return ConstantValue.False; case ConversionKind.ImplicitReference: case ConversionKind.ExplicitReference: case ConversionKind.Unboxing: // In these three cases, the expression type must be a reference type. Therefore, // the result cannot be determined. The expression could be null, resulting // in false, or it could be a non-null reference to the appropriate type, // resulting in true. return null; case ConversionKind.Identity: // The result of "x is T" can be statically determined to be true if x is an expression // of non-nullable value type T. If x is of reference or nullable value type then // we cannot know, because again, the expression value could be null or it could be good. // If it is of pointer type then we have already given an error. return (operandType.IsValueType && !operandType.IsNullableType()) ? ConstantValue.True : null; case ConversionKind.Boxing: // A boxing conversion might be a conversion: // // * From a non-nullable value type to a reference type // * From a nullable value type to a reference type // * From a type parameter that *could* be a value type under construction // to a reference type // // In the first case we know that the conversion will always succeed and that the // operand is never null, and therefore "is" will always result in true. // // In the second two cases we do not know; either the nullable value type could be // null, or the type parameter could be constructed with a reference type, and it // could be null. return operandType.IsValueType && !operandType.IsNullableType() ? ConstantValue.True : null; case ConversionKind.ImplicitNullable: // We have "x is T" in one of the following situations: // 1) x is of type X and T is X?. The value is always true. // 2) x is of type X and T is Y? where X is convertible to Y via an implicit numeric conversion. Eg, // x is of type int and T is decimal?. The value is always false. // 3) x is of type X? and T is Y? where X is convertible to Y via an implicit numeric conversion. // The value is always false. Debug.Assert(targetType.IsNullableType()); return (operandType == targetType.GetNullableUnderlyingType()) ? ConstantValue.True : ConstantValue.False; default: case ConversionKind.ImplicitDynamic: case ConversionKind.ExplicitDynamic: case ConversionKind.PointerToInteger: case ConversionKind.PointerToPointer: case ConversionKind.PointerToVoid: case ConversionKind.IntegerToPointer: case ConversionKind.NullToPointer: case ConversionKind.AnonymousFunction: case ConversionKind.NullLiteral: case ConversionKind.MethodGroup: // We've either replaced Dynamic with Object, or already bailed out with an error. throw ExceptionUtilities.UnexpectedValue(conversionKind); } }
private bool CheckValidPatternType( CSharpSyntaxNode typeSyntax, BoundExpression operand, TypeSymbol operandType, TypeSymbol patternType, bool patternTypeWasInSource, bool isVar, DiagnosticBag diagnostics) { if (operandType?.IsErrorType() == true || patternType?.IsErrorType() == true) { return false; } else if (patternType.IsNullableType() && !isVar && patternTypeWasInSource) { // It is an error to use pattern-matching with a nullable type, because you'll never get null. Use the underlying type. Error(diagnostics, ErrorCode.ERR_PatternNullableType, typeSyntax, patternType, patternType.GetNullableUnderlyingType()); return true; } else if (patternType.IsStatic) { Error(diagnostics, ErrorCode.ERR_VarDeclIsStaticClass, typeSyntax, patternType); return true; } else if (operand != null && operandType == (object)null && !operand.HasAnyErrors) { // It is an error to use pattern-matching with a null, method group, or lambda Error(diagnostics, ErrorCode.ERR_BadIsPatternExpression, operand.Syntax); return true; } else if (!isVar) { HashSet<DiagnosticInfo> useSiteDiagnostics = null; Conversion conversion = operand != null ? this.Conversions.ClassifyConversionFromExpression(operand, patternType, ref useSiteDiagnostics, forCast: true) : this.Conversions.ClassifyConversionFromType(operandType, patternType, ref useSiteDiagnostics, forCast: true); diagnostics.Add(typeSyntax, useSiteDiagnostics); switch (conversion.Kind) { case ConversionKind.Boxing: case ConversionKind.ExplicitNullable: case ConversionKind.ExplicitReference: case ConversionKind.Identity: case ConversionKind.ImplicitReference: case ConversionKind.Unboxing: case ConversionKind.NullLiteral: case ConversionKind.ImplicitNullable: // these are the conversions allowed by a pattern match break; //case ConversionKind.ExplicitNumeric: // we do not perform numeric conversions of the operand //case ConversionKind.ImplicitConstant: //case ConversionKind.ImplicitNumeric: default: Error(diagnostics, ErrorCode.ERR_PatternWrongType, typeSyntax, operandType, patternType); return true; } } return false; }
private static int TypeToIndex(TypeSymbol type) { switch (type.GetSpecialTypeSafe()) { case SpecialType.System_Object: return(0); case SpecialType.System_String: return(1); case SpecialType.System_Boolean: return(2); case SpecialType.System_Char: return(3); case SpecialType.System_SByte: return(4); case SpecialType.System_Int16: return(5); case SpecialType.System_Int32: return(6); case SpecialType.System_Int64: return(7); case SpecialType.System_Byte: return(8); case SpecialType.System_UInt16: return(9); case SpecialType.System_UInt32: return(10); case SpecialType.System_UInt64: return(11); case SpecialType.System_Single: return(12); case SpecialType.System_Double: return(13); case SpecialType.System_Decimal: return(14); case SpecialType.None: if ((object)type != null && type.IsNullableType()) { TypeSymbol underlyingType = type.GetNullableUnderlyingType(); switch (underlyingType.GetSpecialTypeSafe()) { case SpecialType.System_Boolean: return(15); case SpecialType.System_Char: return(16); case SpecialType.System_SByte: return(17); case SpecialType.System_Int16: return(18); case SpecialType.System_Int32: return(19); case SpecialType.System_Int64: return(20); case SpecialType.System_Byte: return(21); case SpecialType.System_UInt16: return(22); case SpecialType.System_UInt32: return(23); case SpecialType.System_UInt64: return(24); case SpecialType.System_Single: return(25); case SpecialType.System_Double: return(26); case SpecialType.System_Decimal: return(27); } } // fall through goto default; default: return(-1); } }
// in simple cases could be left unlowered. // IL gen can generate more compact code for unlowered conditional accesses // by utilizing stack dup/pop instructions internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, bool used, BoundExpression rewrittenWhenNull = null) { Debug.Assert(!_inExpressionLambda); var loweredReceiver = this.VisitExpression(node.Receiver); var receiverType = loweredReceiver.Type; //TODO: if AccessExpression does not contain awaits, the node could be left unlowered (saves a temp), // but there seem to be no way of knowing that without walking AccessExpression. // For now we will just check that we are in an async method, but it would be nice // to have something more precise. var isAsync = _factory.CurrentMethod.IsAsync; ConditionalAccessLoweringKind loweringKind; // CONSIDER: If we knew that loweredReceiver is not a captured local // we could pass "false" for localsMayBeAssignedOrCaptured // otherwise not capturing receiver into a temp // could introduce additional races into the code if receiver is captured // into a closure and is modified between null check of the receiver // and the actual access. // // Nullable is special since we are not going to read any part of it twice // we will read "HasValue" and then, conditionally will read "ValueOrDefault" // that is no different than just reading both values unconditionally. // As a result in the case of nullable, not reading captured local through a temp // does not introduce any additional races so it is irrelevant whether // the local is captured or not. var localsMayBeAssignedOrCaptured = !receiverType.IsNullableType(); var needTemp = IntroducingReadCanBeObservable(loweredReceiver, localsMayBeAssignedOrCaptured); if (!isAsync && !node.AccessExpression.Type.IsDynamic() && rewrittenWhenNull == null && (receiverType.IsReferenceType || receiverType.IsTypeParameter() && needTemp)) { // trivial cases can be handled more efficiently in IL gen loweringKind = ConditionalAccessLoweringKind.None; } else if (needTemp) { if (receiverType.IsReferenceType || receiverType.IsNullableType()) { loweringKind = ConditionalAccessLoweringKind.CaptureReceiverByVal; } else { loweringKind = isAsync ? ConditionalAccessLoweringKind.DuplicateCode : ConditionalAccessLoweringKind.CaptureReceiverByRef; } } else { // locals do not need to be captured loweringKind = ConditionalAccessLoweringKind.NoCapture; } var previousConditionalAccesTarget = _currentConditionalAccessTarget; LocalSymbol temp = null; BoundExpression unconditionalAccess = null; switch (loweringKind) { case ConditionalAccessLoweringKind.None: _currentConditionalAccessTarget = null; break; case ConditionalAccessLoweringKind.NoCapture: _currentConditionalAccessTarget = loweredReceiver; break; case ConditionalAccessLoweringKind.DuplicateCode: _currentConditionalAccessTarget = loweredReceiver; unconditionalAccess = used ? this.VisitExpression(node.AccessExpression) : this.VisitUnusedExpression(node.AccessExpression); goto case ConditionalAccessLoweringKind.CaptureReceiverByVal; case ConditionalAccessLoweringKind.CaptureReceiverByVal: temp = _factory.SynthesizedLocal(receiverType); _currentConditionalAccessTarget = _factory.Local(temp); break; case ConditionalAccessLoweringKind.CaptureReceiverByRef: temp = _factory.SynthesizedLocal(receiverType, refKind: RefKind.Ref); _currentConditionalAccessTarget = _factory.Local(temp); break; default: throw ExceptionUtilities.UnexpectedValue(loweringKind); } BoundExpression loweredAccessExpression = used ? this.VisitExpression(node.AccessExpression) : this.VisitUnusedExpression(node.AccessExpression); _currentConditionalAccessTarget = previousConditionalAccesTarget; TypeSymbol type = this.VisitType(node.Type); TypeSymbol nodeType = node.Type; TypeSymbol accessExpressionType = loweredAccessExpression.Type; if (accessExpressionType.SpecialType == SpecialType.System_Void) { type = nodeType = accessExpressionType; } if (accessExpressionType != nodeType && nodeType.IsNullableType()) { Debug.Assert(accessExpressionType == nodeType.GetNullableUnderlyingType()); loweredAccessExpression = _factory.New((NamedTypeSymbol)nodeType, loweredAccessExpression); if (unconditionalAccess != null) { unconditionalAccess = _factory.New((NamedTypeSymbol)nodeType, unconditionalAccess); } } else { Debug.Assert(accessExpressionType == nodeType || (nodeType.SpecialType == SpecialType.System_Void && !used)); } BoundExpression result; var objectType = _compilation.GetSpecialType(SpecialType.System_Object); rewrittenWhenNull = rewrittenWhenNull ?? _factory.Default(nodeType); switch (loweringKind) { case ConditionalAccessLoweringKind.None: Debug.Assert(!receiverType.IsValueType); result = node.Update(loweredReceiver, loweredAccessExpression, type); break; case ConditionalAccessLoweringKind.CaptureReceiverByVal: // capture the receiver into a temp loweredReceiver = _factory.Sequence( _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver), _factory.Local(temp)); goto case ConditionalAccessLoweringKind.NoCapture; case ConditionalAccessLoweringKind.NoCapture: { // (object)r != null ? access : default(T) var condition = receiverType.IsNullableType() ? MakeOptimizedHasValue(loweredReceiver.Syntax, loweredReceiver) : _factory.ObjectNotEqual( _factory.Convert(objectType, loweredReceiver), _factory.Null(objectType)); var consequence = loweredAccessExpression; result = RewriteConditionalOperator(node.Syntax, condition, consequence, rewrittenWhenNull, null, nodeType); if (temp != null) { result = _factory.Sequence(temp, result); } } break; case ConditionalAccessLoweringKind.CaptureReceiverByRef: // {ref T r; T v; // r = ref receiver; // (isClass && { v = r; r = ref v; v == null } ) ? // null; // r.Foo()} { var v = _factory.SynthesizedLocal(receiverType); BoundExpression captureRef = _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver, refKind: RefKind.Ref); BoundExpression isNull = _factory.LogicalAnd( IsClass(receiverType, objectType), _factory.Sequence( _factory.AssignmentExpression(_factory.Local(v), _factory.Local(temp)), _factory.AssignmentExpression(_factory.Local(temp), _factory.Local(v), RefKind.Ref), _factory.ObjectEqual(_factory.Convert(objectType, _factory.Local(v)), _factory.Null(objectType))) ); result = RewriteConditionalOperator(node.Syntax, isNull, rewrittenWhenNull, loweredAccessExpression, null, nodeType); result = _factory.Sequence( ImmutableArray.Create(temp, v), captureRef, result ); } break; case ConditionalAccessLoweringKind.DuplicateCode: { Debug.Assert(!receiverType.IsNullableType()); // if we have a class, do regular conditional access via a val temp loweredReceiver = _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver); BoundExpression ifClass = RewriteConditionalOperator(node.Syntax, _factory.ObjectNotEqual( _factory.Convert(objectType, loweredReceiver), _factory.Null(objectType)), loweredAccessExpression, rewrittenWhenNull, null, nodeType); if (temp != null) { ifClass = _factory.Sequence(temp, ifClass); } // if we have a struct, then just access unconditionally BoundExpression ifStruct = unconditionalAccess; // (object)(default(T)) != null ? ifStruct: ifClass result = RewriteConditionalOperator(node.Syntax, IsClass(receiverType, objectType), ifClass, ifStruct, null, nodeType); } break; default: throw ExceptionUtilities.Unreachable; } return(result); }
private void AddUserDefinedConversionsToExplicitCandidateSet( BoundExpression sourceExpression, TypeSymbol source, TypeSymbol target, ArrayBuilder <UserDefinedConversionAnalysis> u, NamedTypeSymbol declaringType, string operatorName, ref HashSet <DiagnosticInfo> useSiteDiagnostics) { Debug.Assert(sourceExpression != null || (object)source != null); Debug.Assert((object)target != null); Debug.Assert(u != null); Debug.Assert((object)declaringType != null); Debug.Assert(operatorName != null); // SPEC: Find the set of applicable user-defined and lifted conversion operators, U. // SPEC: The set consists of the user-defined and lifted implicit or explicit // SPEC: conversion operators declared by the classes and structs in D that convert // SPEC: from a type encompassing E or encompassed by S (if it exists) to a type // SPEC: encompassing or encompassed by T. // DELIBERATE SPEC VIOLATION: // // The spec here essentially says that we add an applicable "regular" conversion and // an applicable lifted conversion, if there is one, to the candidate set, and then // let them duke it out to determine which one is "best". // // This is not at all what the native compiler does, and attempting to implement // the specification, or slight variations on it, produces too many backwards-compatibility // breaking changes. // // The native compiler deviates from the specification in two major ways here. // First, it does not add *both* the regular and lifted forms to the candidate set. // Second, the way it characterizes a "lifted" form is very, very different from // how the specification characterizes a lifted form. // // An operation, in this case, X-->Y, is properly said to be "lifted" to X?-->Y? via // the rule that X?-->Y? matches the behavior of X-->Y for non-null X, and converts // null X to null Y otherwise. // // The native compiler, by contrast, takes the existing operator and "lifts" either // the operator's parameter type or the operator's return type to nullable. For // example, a conversion from X?-->Y would be "lifted" to X?-->Y? by making the // conversion from X? to Y, and then from Y to Y?. No "lifting" semantics // are imposed; we do not check to see if the X? is null. This operator is not // actually "lifted" at all; rather, an implicit conversion is applied to the // output. **The native compiler considers the result type Y? of that standard implicit // conversion to be the result type of the "lifted" conversion**, rather than // properly considering Y to be the result type of the conversion for the purposes // of computing the best output type. // // Moreover: the native compiler actually *does* implement nullable lifting semantics // in the case where the input type of the user-defined conversion is a non-nullable // value type and the output type is a nullable value type **or pointer type, or // reference type**. This is an enormous departure from the specification; the // native compiler will take a user-defined conversion from X-->Y? or X-->C and "lift" // it to a conversion from X?-->Y? or X?-->C that has nullable semantics. // // This is quite confusing. In this code we will classify the conversion as either // "normal" or "lifted" on the basis of *whether or not special lifting semantics // are to be applied*. That is, whether or not a later rewriting pass is going to // need to insert a check to see if the source expression is null, and decide // whether or not to call the underlying unlifted conversion or produce a null // value without calling the unlifted conversion. // DELIBERATE SPEC VIOLATION: See the comment regarding bug 17021 in // UserDefinedImplicitConversions.cs. if ((object)source != null && source.IsInterfaceType() || target.IsInterfaceType()) { return; } foreach (MethodSymbol op in declaringType.GetOperators(operatorName)) { // We might have a bad operator and be in an error recovery situation. Ignore it. if (op.ReturnsVoid || op.ParameterCount != 1 || op.ReturnType.TypeKind == TypeKind.Error) { continue; } TypeSymbol convertsFrom = op.GetParameterType(0); TypeSymbol convertsTo = op.ReturnType; Conversion fromConversion = EncompassingExplicitConversion(sourceExpression, source, convertsFrom, ref useSiteDiagnostics); Conversion toConversion = EncompassingExplicitConversion(null, convertsTo, target, ref useSiteDiagnostics); // We accept candidates for which the parameter type encompasses the *underlying* source type. if (!fromConversion.Exists && (object)source != null && source.IsNullableType() && EncompassingExplicitConversion(null, source.GetNullableUnderlyingType(), convertsFrom, ref useSiteDiagnostics).Exists) { fromConversion = ClassifyBuiltInConversion(source, convertsFrom, ref useSiteDiagnostics); } // As in dev11 (and the revised spec), we also accept candidates for which the return type is encompassed by the *stripped* target type. if (!toConversion.Exists && (object)target != null && target.IsNullableType() && EncompassingExplicitConversion(null, convertsTo, target.GetNullableUnderlyingType(), ref useSiteDiagnostics).Exists) { toConversion = ClassifyBuiltInConversion(convertsTo, target, ref useSiteDiagnostics); } // In the corresponding implicit conversion code we can get away with first // checking to see if standard implicit conversions exist from the source type // to the parameter type, and from the return type to the target type. If not, // then we can check for a lifted operator. // // That's not going to cut it in the explicit conversion code. Suppose we have // a conversion X-->Y and have source type X? and target type Y?. There *are* // standard explicit conversions from X?-->X and Y?-->Y, but we do not want // to bind this as an *unlifted* conversion from X? to Y?; we want such a thing // to be a *lifted* conversion from X? to Y?, that checks for null on the source // and decides to not call the underlying user-defined conversion if it is null. // // We therefore cannot do what we do in the implicit conversions, where we check // to see if the unlifted conversion works, and if it does, then don't add the lifted // conversion at all. Rather, we have to see if what we're building here is a // lifted conversion or not. // // Under what circumstances is this conversion a lifted conversion? (In the // "spec" sense of a lifted conversion; that is, that we check for null // and skip the user-defined conversion if necessary). // // * The source type must be a nullable value type. // * The parameter type must be a non-nullable value type. // * The target type must be able to take on a null value. if (fromConversion.Exists && toConversion.Exists) { if ((object)source != null && source.IsNullableType() && convertsFrom.IsNonNullableValueType() && target.CanBeAssignedNull()) { TypeSymbol nullableFrom = MakeNullableType(convertsFrom); TypeSymbol nullableTo = convertsTo.IsNonNullableValueType() ? MakeNullableType(convertsTo) : convertsTo; Conversion liftedFromConversion = EncompassingExplicitConversion(sourceExpression, source, nullableFrom, ref useSiteDiagnostics); Conversion liftedToConversion = EncompassingExplicitConversion(null, nullableTo, target, ref useSiteDiagnostics); Debug.Assert(liftedFromConversion.Exists); Debug.Assert(liftedToConversion.Exists); u.Add(UserDefinedConversionAnalysis.Lifted(op, liftedFromConversion, liftedToConversion, nullableFrom, nullableTo)); } else { // There is an additional spec violation in the native compiler. Suppose // we have a conversion from X-->Y and are asked to do "Y? y = new X();" Clearly // the intention is to convert from X-->Y via the implicit conversion, and then // stick a standard implicit conversion from Y-->Y? on the back end. **In this // situation, the native compiler treats the conversion as though it were // actually X-->Y? in source for the purposes of determining the best target // type of a set of operators. // // Similarly, if we have a conversion from X-->Y and are asked to do // an explicit conversion from X? to Y then we treat the conversion as // though it really were X?-->Y for the purposes of determining the best // source type of a set of operators. // // We perpetuate these fictions here. if (target.IsNullableType() && convertsTo.IsNonNullableValueType()) { convertsTo = MakeNullableType(convertsTo); toConversion = EncompassingExplicitConversion(null, convertsTo, target, ref useSiteDiagnostics); } if ((object)source != null && source.IsNullableType() && convertsFrom.IsNonNullableValueType()) { convertsFrom = MakeNullableType(convertsFrom); fromConversion = EncompassingExplicitConversion(null, convertsFrom, source, ref useSiteDiagnostics); } u.Add(UserDefinedConversionAnalysis.Normal(op, fromConversion, toConversion, convertsFrom, convertsTo)); } } } }
/// <summary> /// Check that the pattern type is valid for the operand. Return true if an error was reported. /// </summary> private bool CheckValidPatternType( CSharpSyntaxNode typeSyntax, BoundExpression operand, TypeSymbol operandType, TypeSymbol patternType, bool patternTypeWasInSource, bool isVar, DiagnosticBag diagnostics) { Debug.Assert((object)operandType != null); Debug.Assert((object)patternType != null); // Because we do not support recursive patterns, we always have an operand Debug.Assert((object)operand != null); // Once we support recursive patterns that will be relaxed. if (operandType.IsErrorType() || patternType.IsErrorType()) { return(false); } else if (patternType.IsNullableType() && !isVar && patternTypeWasInSource) { // It is an error to use pattern-matching with a nullable type, because you'll never get null. Use the underlying type. Error(diagnostics, ErrorCode.ERR_PatternNullableType, typeSyntax, patternType, patternType.GetNullableUnderlyingType()); return(true); } else if (patternType.IsStatic) { Error(diagnostics, ErrorCode.ERR_VarDeclIsStaticClass, typeSyntax, patternType); return(true); } else if (!isVar) { if (patternType.IsDynamic()) { Error(diagnostics, ErrorCode.ERR_PatternDynamicType, typeSyntax); return(true); } HashSet <DiagnosticInfo> useSiteDiagnostics = null; Conversion conversion = operand != null ? this.Conversions.ClassifyConversionFromExpression(operand, patternType, ref useSiteDiagnostics, forCast : true) : this.Conversions.ClassifyConversionFromType(operandType, patternType, ref useSiteDiagnostics, forCast: true); diagnostics.Add(typeSyntax, useSiteDiagnostics); switch (conversion.Kind) { case ConversionKind.ExplicitDynamic: case ConversionKind.ImplicitDynamic: // Since the input was `dynamic`, which is equivalent to `object`, there must also // exist some unboxing, identity, or reference conversion as well, making the conversion legal. case ConversionKind.Boxing: case ConversionKind.ExplicitNullable: case ConversionKind.ExplicitReference: case ConversionKind.Identity: case ConversionKind.ImplicitReference: case ConversionKind.Unboxing: case ConversionKind.ImplicitNullable: // these are the conversions allowed by a pattern match break; case ConversionKind.DefaultOrNullLiteral: throw ExceptionUtilities.UnexpectedValue(conversion.Kind); //case ConversionKind.ExplicitNumeric: // we do not perform numeric conversions of the operand //case ConversionKind.ImplicitConstant: //case ConversionKind.ImplicitNumeric: default: Error(diagnostics, ErrorCode.ERR_PatternWrongType, typeSyntax, operandType, patternType); return(true); } } return(false); }
/// <summary> /// Helper method to generate a lowered conversion. /// </summary> private BoundExpression MakeConversion( BoundConversion oldNode, CSharpSyntaxNode syntax, BoundExpression rewrittenOperand, ConversionKind conversionKind, MethodSymbol symbolOpt, bool @checked, bool explicitCastInCode, bool isExtensionMethod, bool isArrayIndex, ConstantValue constantValueOpt, TypeSymbol rewrittenType) { Debug.Assert(oldNode == null || oldNode.Syntax == syntax); Debug.Assert((object)rewrittenType != null); @checked = @checked && (inExpressionLambda && (explicitCastInCode || DistinctSpecialTypes(rewrittenOperand.Type, rewrittenType)) || NeedsChecked(rewrittenOperand.Type, rewrittenType)); switch (conversionKind) { case ConversionKind.Identity: // Spec 6.1.1: // An identity conversion converts from any type to the same type. // This conversion exists such that an entity that already has a required type can be said to be convertible to that type. // Because object and dynamic are considered equivalent there is an identity conversion between object and dynamic, // and between constructed types that are the same when replacing all occurrences of dynamic with object. // Why ignoreDynamic: false? // Lowering phase treats object and dynamic as equivalent types. So we don't need to produce any conversion here, // but we need to change the Type property on the resulting BoundExpression to match the rewrittenType. // This is necessary so that subsequent lowering transformations see that the expression is dynamic. if (inExpressionLambda || !rewrittenOperand.Type.Equals(rewrittenType, ignoreCustomModifiers: false, ignoreDynamic: false)) { break; } // 4.1.6 C# spec: To force a value of a floating point type to the exact precision of its type, an explicit cast can be used. // If this is not an identity conversion of a float with unknown precision, strip away the identity conversion. if (!(explicitCastInCode && IsFloatPointExpressionOfUnknownPrecision(rewrittenOperand))) { return rewrittenOperand; } break; case ConversionKind.ExplicitUserDefined: case ConversionKind.ImplicitUserDefined: return RewriteUserDefinedConversion( syntax: syntax, rewrittenOperand: rewrittenOperand, method: symbolOpt, rewrittenType: rewrittenType, conversionKind: conversionKind); case ConversionKind.IntPtr: return RewriteIntPtrConversion(oldNode, syntax, rewrittenOperand, conversionKind, symbolOpt, @checked, explicitCastInCode, isExtensionMethod, isArrayIndex, constantValueOpt, rewrittenType); case ConversionKind.ImplicitNullable: case ConversionKind.ExplicitNullable: return RewriteNullableConversion( syntax: syntax, rewrittenOperand: rewrittenOperand, conversionKind: conversionKind, @checked: @checked, explicitCastInCode: explicitCastInCode, rewrittenType: rewrittenType); case ConversionKind.Boxing: if (!inExpressionLambda) { // We can perform some optimizations if we have a nullable value type // as the operand and we know its nullability: // * (object)new int?() is the same as (object)null // * (object)new int?(123) is the same as (object)123 if (NullableNeverHasValue(rewrittenOperand)) { return new BoundDefaultOperator(syntax, rewrittenType); } BoundExpression nullableValue = NullableAlwaysHasValue(rewrittenOperand); if (nullableValue != null) { // Recurse, eliminating the unnecessary ctor. return MakeConversion(oldNode, syntax, nullableValue, conversionKind, symbolOpt, @checked, explicitCastInCode, isExtensionMethod, isArrayIndex, constantValueOpt, rewrittenType); } } break; case ConversionKind.NullLiteral: if (!inExpressionLambda || !explicitCastInCode) { return new BoundDefaultOperator(syntax, rewrittenType); } break; case ConversionKind.ImplicitReference: case ConversionKind.ExplicitReference: if (rewrittenOperand.IsDefaultValue() && (!inExpressionLambda || !explicitCastInCode)) { return new BoundDefaultOperator(syntax, rewrittenType); } break; case ConversionKind.ImplicitNumeric: case ConversionKind.ExplicitNumeric: if (rewrittenOperand.IsDefaultValue() && (!inExpressionLambda || !explicitCastInCode)) { return new BoundDefaultOperator(syntax, rewrittenType); } if (rewrittenType.SpecialType == SpecialType.System_Decimal || rewrittenOperand.Type.SpecialType == SpecialType.System_Decimal) { return RewriteDecimalConversion(oldNode, syntax, rewrittenOperand, rewrittenOperand.Type, rewrittenType); } break; case ConversionKind.ImplicitEnumeration: // A conversion from constant zero to nullable is actually classified as an // implicit enumeration conversion, not an implicit nullable conversion. // Lower it to (E?)(E)0. if (rewrittenType.IsNullableType()) { var operand = MakeConversion( oldNode, syntax, rewrittenOperand, ConversionKind.ImplicitEnumeration, symbolOpt, @checked, explicitCastInCode, isExtensionMethod, false, constantValueOpt, rewrittenType.GetNullableUnderlyingType()); return MakeConversion( oldNode, syntax, operand, ConversionKind.ImplicitNullable, symbolOpt, @checked, explicitCastInCode, isExtensionMethod, isArrayIndex, constantValueOpt, rewrittenType); } goto case ConversionKind.ExplicitEnumeration; case ConversionKind.ExplicitEnumeration: if (!rewrittenType.IsNullableType() && rewrittenOperand.IsDefaultValue() && (!inExpressionLambda || !explicitCastInCode)) { return new BoundDefaultOperator(syntax, rewrittenType); } if (rewrittenType.SpecialType == SpecialType.System_Decimal) { Debug.Assert(rewrittenOperand.Type.IsEnumType()); var underlyingTypeFrom = rewrittenOperand.Type.GetEnumUnderlyingType(); rewrittenOperand = MakeConversion(rewrittenOperand, underlyingTypeFrom, false); return RewriteDecimalConversion(oldNode, syntax, rewrittenOperand, underlyingTypeFrom, rewrittenType); } else if (rewrittenOperand.Type.SpecialType == SpecialType.System_Decimal) { // This is where we handle conversion from Decimal to Enum: e.g., E e = (E) d; // where 'e' is of type Enum E and 'd' is of type Decimal. // Conversion can be simply done by applying its underlying numeric type to RewriteDecimalConversion(). Debug.Assert(rewrittenType.IsEnumType()); var underlyingTypeTo = rewrittenType.GetEnumUnderlyingType(); var rewrittenNode = RewriteDecimalConversion(oldNode, syntax, rewrittenOperand, rewrittenOperand.Type, underlyingTypeTo); // However, the type of the rewritten node becomes underlying numeric type, not Enum type, // which violates the overall constraint saying the type cannot be changed during rewriting (see LocalRewriter.cs). // Instead of loosening this constraint, we return BoundConversion from underlying numeric type to Enum type, // which will be eliminated during emitting (see EmitEnumConversion): e.g., E e = (E)(int) d; return new BoundConversion( syntax, rewrittenNode, conversionKind, LookupResultKind.Viable, isBaseConversion: false, symbolOpt: symbolOpt, @checked: false, explicitCastInCode: explicitCastInCode, isExtensionMethod: isExtensionMethod, isArrayIndex: false, constantValueOpt: constantValueOpt, type: rewrittenType); } break; case ConversionKind.ImplicitDynamic: case ConversionKind.ExplicitDynamic: Debug.Assert((object)symbolOpt == null); Debug.Assert(!isExtensionMethod); Debug.Assert(constantValueOpt == null); return dynamicFactory.MakeDynamicConversion(rewrittenOperand, explicitCastInCode || conversionKind == ConversionKind.ExplicitDynamic, isArrayIndex, @checked, rewrittenType).ToExpression(); default: break; } return oldNode != null ? oldNode.Update( rewrittenOperand, conversionKind, oldNode.ResultKind, isBaseConversion: oldNode.IsBaseConversion, symbolOpt: symbolOpt, @checked: @checked, explicitCastInCode: explicitCastInCode, isExtensionMethod: isExtensionMethod, isArrayIndex: isArrayIndex, constantValueOpt: constantValueOpt, type: rewrittenType) : new BoundConversion( syntax, rewrittenOperand, conversionKind, LookupResultKind.Viable, isBaseConversion: false, symbolOpt: symbolOpt, @checked: @checked, explicitCastInCode: explicitCastInCode, isExtensionMethod: isExtensionMethod, isArrayIndex: isArrayIndex, constantValueOpt: constantValueOpt, type: rewrittenType); }
protected BoundExpression LowerEvaluation(BoundDagEvaluation evaluation) { BoundExpression input = _tempAllocator.GetTemp(evaluation.Input); switch (evaluation) { case BoundDagFieldEvaluation f: { FieldSymbol field = f.Field; var outputTemp = new BoundDagTemp(f.Syntax, field.Type, f); BoundExpression output = _tempAllocator.GetTemp(outputTemp); BoundExpression access = _localRewriter.MakeFieldAccess(f.Syntax, input, field, null, LookupResultKind.Viable, field.Type); access.WasCompilerGenerated = true; return(_factory.AssignmentExpression(output, access)); } case BoundDagPropertyEvaluation p: { PropertySymbol property = p.Property; var outputTemp = new BoundDagTemp(p.Syntax, property.Type, p); BoundExpression output = _tempAllocator.GetTemp(outputTemp); return(_factory.AssignmentExpression(output, _localRewriter.MakePropertyAccess(_factory.Syntax, input, property, LookupResultKind.Viable, property.Type, isLeftOfAssignment: false))); } case BoundDagDeconstructEvaluation d: { MethodSymbol method = d.DeconstructMethod; var refKindBuilder = ArrayBuilder <RefKind> .GetInstance(); var argBuilder = ArrayBuilder <BoundExpression> .GetInstance(); BoundExpression receiver; void addArg(RefKind refKind, BoundExpression expression) { refKindBuilder.Add(refKind); argBuilder.Add(expression); } Debug.Assert(method.Name == WellKnownMemberNames.DeconstructMethodName); int extensionExtra; if (method.IsStatic) { Debug.Assert(method.IsExtensionMethod); receiver = _factory.Type(method.ContainingType); addArg(method.ParameterRefKinds[0], input); extensionExtra = 1; } else { receiver = input; extensionExtra = 0; } for (int i = extensionExtra; i < method.ParameterCount; i++) { ParameterSymbol parameter = method.Parameters[i]; Debug.Assert(parameter.RefKind == RefKind.Out); var outputTemp = new BoundDagTemp(d.Syntax, parameter.Type, d, i - extensionExtra); addArg(RefKind.Out, _tempAllocator.GetTemp(outputTemp)); } return(_factory.Call(receiver, method, refKindBuilder.ToImmutableAndFree(), argBuilder.ToImmutableAndFree())); } case BoundDagTypeEvaluation t: { TypeSymbol inputType = input.Type; Debug.Assert(inputType is { }); if (inputType.IsDynamic()) { // Avoid using dynamic conversions for pattern-matching. inputType = _factory.SpecialType(SpecialType.System_Object); input = _factory.Convert(inputType, input); } TypeSymbol type = t.Type; var outputTemp = new BoundDagTemp(t.Syntax, type, t); BoundExpression output = _tempAllocator.GetTemp(outputTemp); CompoundUseSiteInfo <AssemblySymbol> useSiteInfo = _localRewriter.GetNewCompoundUseSiteInfo(); Conversion conversion = _factory.Compilation.Conversions.ClassifyBuiltInConversion(inputType, output.Type, isChecked: false, ref useSiteInfo); Debug.Assert(!conversion.IsUserDefined); _localRewriter._diagnostics.Add(t.Syntax, useSiteInfo); BoundExpression evaluated; if (conversion.Exists) { if (conversion.Kind == ConversionKind.ExplicitNullable && inputType.GetNullableUnderlyingType().Equals(output.Type, TypeCompareKind.AllIgnoreOptions) && _localRewriter.TryGetNullableMethod(t.Syntax, inputType, SpecialMember.System_Nullable_T_GetValueOrDefault, out MethodSymbol getValueOrDefault)) { // As a special case, since the null test has already been done we can use Nullable<T>.GetValueOrDefault evaluated = _factory.Call(input, getValueOrDefault); } else { evaluated = _factory.Convert(type, input, conversion); } } else { evaluated = _factory.As(input, type); } return(_factory.AssignmentExpression(output, evaluated)); }
private bool LowerBoundNullableInference(TypeSymbol source, TypeSymbol target, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { Debug.Assert((object)source != null); Debug.Assert((object)target != null); if (!source.IsNullableType() || !target.IsNullableType()) { return false; } LowerBoundInference(source.GetNullableUnderlyingType(), target.GetNullableUnderlyingType(), ref useSiteDiagnostics); return true; }
private bool LowerBoundNullableInference(TypeSymbol source, TypeSymbol target, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { Debug.Assert((object)source != null); Debug.Assert((object)target != null); // SPEC ISSUE: As noted above, the spec does not clearly call out how // SPEC ISSUE: to do type inference to a nullable target. I propose the // SPEC ISSUE: following: // SPEC ISSUE: // SPEC ISSUE: * Otherwise, if V is nullable type V1? and U is a // SPEC ISSUE: non-nullable struct type then an exact inference is made from U to V1. if (!target.IsNullableType() || !source.IsValueType || source.IsNullableType()) { return false; } ExactInference(source, target.GetNullableUnderlyingType(), ref useSiteDiagnostics); return true; }
private BoundExpression CreateTupleLiteralConversion(CSharpSyntaxNode syntax, BoundTupleLiteral sourceTuple, Conversion conversion, bool isCast, TypeSymbol destination, DiagnosticBag diagnostics) { // We have a successful tuple conversion; rather than producing a separate conversion node // which is a conversion on top of a tuple literal, tuple conversion is an element-wise conversion of arguments. Debug.Assert(conversion.Kind == ConversionKind.ImplicitTupleLiteral || conversion.Kind == ConversionKind.ImplicitNullable); Debug.Assert((conversion.Kind == ConversionKind.ImplicitNullable) == destination.IsNullableType()); TypeSymbol destinationWithoutNullable = conversion.Kind == ConversionKind.ImplicitNullable ? destinationWithoutNullable = destination.GetNullableUnderlyingType() : destination; NamedTypeSymbol targetType = (NamedTypeSymbol)destinationWithoutNullable; if (targetType.IsTupleType) { var destTupleType = (TupleTypeSymbol)targetType; // do not lose the original element names in the literal if different from names in the target // Come back to this, what about locations? (https://github.com/dotnet/roslyn/issues/11013) targetType = destTupleType.WithElementNames(sourceTuple.ArgumentNamesOpt); } var arguments = sourceTuple.Arguments; var convertedArguments = ArrayBuilder<BoundExpression>.GetInstance(arguments.Length); ImmutableArray<TypeSymbol> targetElementTypes = targetType.GetElementTypesOfTupleOrCompatible(); Debug.Assert(targetElementTypes.Length == arguments.Length, "converting a tuple literal to incompatible type?"); for (int i = 0; i < arguments.Length; i++) { var argument = arguments[i]; var destType = targetElementTypes[i]; HashSet<DiagnosticInfo> useSiteDiagnostics = null; Conversion elementConversion; if (isCast) { elementConversion = this.Conversions.ClassifyConversionForCast(argument, destType, ref useSiteDiagnostics); } else { elementConversion = this.Conversions.ClassifyConversionFromExpression(argument, destType, ref useSiteDiagnostics); } diagnostics.Add(syntax, useSiteDiagnostics); convertedArguments.Add(CreateConversion(argument.Syntax, argument, elementConversion, isCast, destType, diagnostics)); } BoundExpression result = new BoundConvertedTupleLiteral( sourceTuple.Syntax, sourceTuple.Type, convertedArguments.ToImmutableAndFree(), targetType); // We need to preserve any conversion that changes the type (even identity conversions), // or that was explicitly written in code (so that GetSemanticInfo can find the syntax in the bound tree). if (!isCast && targetType == destination) { return result; } // if we have a nullable cast combined with a name/dynamic cast // name/dynamic cast must happen before converting to nullable if (conversion.Kind == ConversionKind.ImplicitNullable && destinationWithoutNullable != targetType) { Debug.Assert(destinationWithoutNullable.Equals(targetType, ignoreDynamic: true)); result = new BoundConversion( syntax, result, Conversion.Identity, @checked: false, explicitCastInCode: isCast, constantValueOpt: ConstantValue.NotAvailable, type: destinationWithoutNullable) { WasCompilerGenerated = sourceTuple.WasCompilerGenerated }; } return new BoundConversion( syntax, result, conversion, @checked: false, explicitCastInCode: isCast, constantValueOpt: ConstantValue.NotAvailable, type: destination) { WasCompilerGenerated = sourceTuple.WasCompilerGenerated }; }
private bool CheckValidPatternType( CSharpSyntaxNode typeSyntax, BoundExpression operand, TypeSymbol operandType, TypeSymbol patternType, bool patternTypeWasInSource, bool isVar, DiagnosticBag diagnostics) { Debug.Assert((object)operandType != null); Debug.Assert((object)patternType != null); if (operandType.IsErrorType() || patternType.IsErrorType()) { return(false); } else if (patternType.IsNullableType() && !isVar && patternTypeWasInSource) { // It is an error to use pattern-matching with a nullable type, because you'll never get null. Use the underlying type. Error(diagnostics, ErrorCode.ERR_PatternNullableType, typeSyntax, patternType, patternType.GetNullableUnderlyingType()); return(true); } else if (patternType.IsStatic) { Error(diagnostics, ErrorCode.ERR_VarDeclIsStaticClass, typeSyntax, patternType); return(true); } else if (!isVar) { HashSet <DiagnosticInfo> useSiteDiagnostics = null; Conversion conversion = operand != null ? this.Conversions.ClassifyConversionFromExpression(operand, patternType, ref useSiteDiagnostics, forCast : true) : this.Conversions.ClassifyConversionFromType(operandType, patternType, ref useSiteDiagnostics, forCast: true); diagnostics.Add(typeSyntax, useSiteDiagnostics); switch (conversion.Kind) { case ConversionKind.Boxing: case ConversionKind.ExplicitNullable: case ConversionKind.ExplicitReference: case ConversionKind.Identity: case ConversionKind.ImplicitReference: case ConversionKind.Unboxing: case ConversionKind.NullLiteral: case ConversionKind.ImplicitNullable: // these are the conversions allowed by a pattern match break; //case ConversionKind.ExplicitNumeric: // we do not perform numeric conversions of the operand //case ConversionKind.ImplicitConstant: //case ConversionKind.ImplicitNumeric: default: Error(diagnostics, ErrorCode.ERR_PatternWrongType, typeSyntax, operandType, patternType); return(true); } } return(false); }
private bool CheckValidPatternType( CSharpSyntaxNode typeSyntax, BoundExpression operand, TypeSymbol operandType, TypeSymbol patternType, bool patternTypeWasInSource, bool isVar, DiagnosticBag diagnostics) { if (operandType?.IsErrorType() == true || patternType?.IsErrorType() == true) { return(false); } else if (patternType.IsNullableType() && !isVar && patternTypeWasInSource) { // It is an error to use pattern-matching with a nullable type, because you'll never get null. Use the underlying type. Error(diagnostics, ErrorCode.ERR_PatternNullableType, typeSyntax, patternType, patternType.GetNullableUnderlyingType()); return(true); } else if (operand != null && operandType == (object)null && !operand.HasAnyErrors) { // It is an error to use pattern-matching with a null, method group, or lambda Error(diagnostics, ErrorCode.ERR_BadIsPatternExpression, operand.Syntax); return(true); } else if (!isVar) { HashSet <DiagnosticInfo> useSiteDiagnostics = null; Conversion conversion = operand != null ? this.Conversions.ClassifyConversionForCast(operand, patternType, ref useSiteDiagnostics) : this.Conversions.ClassifyConversionForCast(operandType, patternType, ref useSiteDiagnostics); diagnostics.Add(typeSyntax, useSiteDiagnostics); switch (conversion.Kind) { case ConversionKind.Boxing: case ConversionKind.ExplicitNullable: case ConversionKind.ExplicitReference: case ConversionKind.Identity: case ConversionKind.ImplicitReference: case ConversionKind.Unboxing: case ConversionKind.NullLiteral: case ConversionKind.ImplicitNullable: // these are the conversions allowed by a pattern match break; //case ConversionKind.ExplicitNumeric: // we do not perform numeric conversions of the operand //case ConversionKind.ImplicitConstant: //case ConversionKind.ImplicitNumeric: default: Error(diagnostics, ErrorCode.ERR_NoExplicitConv, typeSyntax, operandType, patternType); return(true); } } return(false); }
private TypeSymbol TypeOrUnderlyingType(TypeSymbol type) { if (type.IsNullableType()) { return type.GetNullableUnderlyingType(); } return type; }
private BoundExpression MakeLiftedUserDefinedConversionConsequence(BoundCall call, TypeSymbol resultType) { if (call.Method.ReturnType.IsNonNullableValueType()) { Debug.Assert(resultType.IsNullableType() && resultType.GetNullableUnderlyingType() == call.Method.ReturnType); MethodSymbol ctor = GetNullableMethod(call.Syntax, resultType, SpecialMember.System_Nullable_T__ctor); return new BoundObjectCreationExpression(call.Syntax, ctor, call); } return call; }
private BoundExpression GetLiftedUnaryOperatorConsequence(UnaryOperatorKind kind, CSharpSyntaxNode syntax, MethodSymbol method, TypeSymbol type, BoundExpression nonNullOperand) { MethodSymbol ctor = GetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor); // OP(temp.GetValueOrDefault()) BoundExpression unliftedOp = MakeUnaryOperator( oldNode: null, kind: kind.Unlifted(), syntax: syntax, method: method, loweredOperand: nonNullOperand, type: type.GetNullableUnderlyingType()); // new R?(OP(temp.GetValueOrDefault())) BoundExpression consequence = new BoundObjectCreationExpression( syntax, ctor, unliftedOp); return consequence; }
private static int? TypeToIndex(TypeSymbol type) { switch (type.GetSpecialTypeSafe()) { case SpecialType.System_Object: return 0; case SpecialType.System_String: return 1; case SpecialType.System_Boolean: return 2; case SpecialType.System_Char: return 3; case SpecialType.System_SByte: return 4; case SpecialType.System_Int16: return 5; case SpecialType.System_Int32: return 6; case SpecialType.System_Int64: return 7; case SpecialType.System_Byte: return 8; case SpecialType.System_UInt16: return 9; case SpecialType.System_UInt32: return 10; case SpecialType.System_UInt64: return 11; case SpecialType.System_Single: return 12; case SpecialType.System_Double: return 13; case SpecialType.System_Decimal: return 14; case SpecialType.None: if (type != null && type.IsNullableType()) { TypeSymbol underlyingType = type.GetNullableUnderlyingType(); switch (underlyingType.GetSpecialTypeSafe()) { case SpecialType.System_Boolean: return 15; case SpecialType.System_Char: return 16; case SpecialType.System_SByte: return 17; case SpecialType.System_Int16: return 18; case SpecialType.System_Int32: return 19; case SpecialType.System_Int64: return 20; case SpecialType.System_Byte: return 21; case SpecialType.System_UInt16: return 22; case SpecialType.System_UInt32: return 23; case SpecialType.System_UInt64: return 24; case SpecialType.System_Single: return 25; case SpecialType.System_Double: return 26; case SpecialType.System_Decimal: return 27; } } // fall through goto default; default: return null; } }
private BoundExpression CreateTupleLiteralConversion(SyntaxNode syntax, BoundTupleLiteral sourceTuple, Conversion conversion, bool isCast, TypeSymbol destination, DiagnosticBag diagnostics) { // We have a successful tuple conversion; rather than producing a separate conversion node // which is a conversion on top of a tuple literal, tuple conversion is an element-wise conversion of arguments. Debug.Assert((conversion.Kind == ConversionKind.ImplicitNullable) == destination.IsNullableType()); var destinationWithoutNullable = destination; var conversionWithoutNullable = conversion; if (conversion.Kind == ConversionKind.ImplicitNullable) { destinationWithoutNullable = destination.GetNullableUnderlyingType(); conversionWithoutNullable = conversion.UnderlyingConversions[0]; } Debug.Assert(conversionWithoutNullable.IsTupleLiteralConversion); NamedTypeSymbol targetType = (NamedTypeSymbol)destinationWithoutNullable; if (targetType.IsTupleType) { var destTupleType = (TupleTypeSymbol)targetType; // do not lose the original element names in the literal if different from names in the target TupleTypeSymbol.ReportNamesMismatchesIfAny(targetType, sourceTuple, diagnostics); // Come back to this, what about locations? (https://github.com/dotnet/roslyn/issues/11013) targetType = destTupleType.WithElementNames(sourceTuple.ArgumentNamesOpt); } var arguments = sourceTuple.Arguments; var convertedArguments = ArrayBuilder<BoundExpression>.GetInstance(arguments.Length); ImmutableArray<TypeSymbol> targetElementTypes = targetType.GetElementTypesOfTupleOrCompatible(); Debug.Assert(targetElementTypes.Length == arguments.Length, "converting a tuple literal to incompatible type?"); var underlyingConversions = conversionWithoutNullable.UnderlyingConversions; for (int i = 0; i < arguments.Length; i++) { var argument = arguments[i]; var destType = targetElementTypes[i]; var elementConversion = underlyingConversions[i]; convertedArguments.Add(CreateConversion(argument.Syntax, argument, elementConversion, isCast, destType, diagnostics)); } BoundExpression result = new BoundConvertedTupleLiteral( sourceTuple.Syntax, sourceTuple.Type, convertedArguments.ToImmutableAndFree(), targetType); if (sourceTuple.Type != destination) { // literal cast is applied to the literal result = new BoundConversion( sourceTuple.Syntax, result, conversion, @checked: false, explicitCastInCode: isCast, constantValueOpt: ConstantValue.NotAvailable, type: destination); } // If we had a cast in the code, keep conversion in the tree. // even though the literal is already converted to the target type. if (isCast) { result = new BoundConversion( syntax, result, Conversion.Identity, @checked: false, explicitCastInCode: isCast, constantValueOpt: ConstantValue.NotAvailable, type: destination); } return result; }
private BoundExpression GetDefaultParameterValue(CSharpSyntaxNode syntax, ParameterSymbol parameter) { TypeSymbol parameterType = parameter.Type; ConstantValue defaultConstantValue = parameter.ExplicitDefaultConstantValue; BoundExpression defaultValue; SourceLocation callerSourceLocation; if (parameter.IsCallerLineNumber && ((callerSourceLocation = GetCallerLocation(syntax)) != null)) { int line = callerSourceLocation.SourceTree.GetDisplayLineNumber(callerSourceLocation.SourceSpan); BoundExpression lineLiteral = MakeLiteral(syntax, ConstantValue.Create(line), compilation.GetSpecialType(SpecialType.System_Int32)); if (parameterType.IsNullableType()) { defaultValue = MakeConversion(lineLiteral, parameterType.GetNullableUnderlyingType(), false); // wrap it in a nullable ctor. defaultValue = new BoundObjectCreationExpression( syntax, GetNullableMethod(syntax, parameterType, SpecialMember.System_Nullable_T__ctor), defaultValue); } else { defaultValue = MakeConversion(lineLiteral, parameterType, false); } } else if (parameter.IsCallerFilePath && ((callerSourceLocation = GetCallerLocation(syntax)) != null)) { string path = callerSourceLocation.SourceTree.GetDisplayPath(callerSourceLocation.SourceSpan, compilation.Options.SourceReferenceResolver); BoundExpression memberNameLiteral = MakeLiteral(syntax, ConstantValue.Create(path), compilation.GetSpecialType(SpecialType.System_String)); defaultValue = MakeConversion(memberNameLiteral, parameterType, false); } else if (parameter.IsCallerMemberName && ((callerSourceLocation = GetCallerLocation(syntax)) != null)) { string memberName = this.factory.TopLevelMethod.GetMemberCallerName(); BoundExpression memberNameLiteral = MakeLiteral(syntax, ConstantValue.Create(memberName), compilation.GetSpecialType(SpecialType.System_String)); defaultValue = MakeConversion(memberNameLiteral, parameterType, false); } else if (defaultConstantValue == ConstantValue.NotAvailable) { // There is no constant value given for the parameter in source/metadata. if (parameterType.IsDynamic() || parameterType.SpecialType == SpecialType.System_Object) { // We have something like M([Optional] object x). We have special handling for such situations. defaultValue = GetDefaultParameterSpecial(syntax, parameter); } else { // The argument to M([Optional] int x) becomes default(int) defaultValue = new BoundDefaultOperator(syntax, parameterType); } } else if (defaultConstantValue.IsNull && parameterType.IsValueType) { // We have something like M(int? x = null) or M(S x = default(S)), // so replace the argument with default(int?). defaultValue = new BoundDefaultOperator(syntax, parameterType); } else if (parameterType.IsNullableType()) { // We have something like M(double? x = 1.23), so replace the argument // with new double?(1.23). TypeSymbol constantType = compilation.GetSpecialType(defaultConstantValue.SpecialType); defaultValue = MakeLiteral(syntax, defaultConstantValue, constantType); // The parameter's underlying type might not match the constant type. For example, we might have // a default value of 5 (an integer) but a parameter type of decimal?. defaultValue = MakeConversion(defaultValue, parameterType.GetNullableUnderlyingType(), @checked: false, acceptFailingConversion: true); // Finally, wrap it in a nullable ctor. defaultValue = new BoundObjectCreationExpression( syntax, GetNullableMethod(syntax, parameterType, SpecialMember.System_Nullable_T__ctor), defaultValue); } else if (defaultConstantValue.IsNull || defaultConstantValue.IsBad) { defaultValue = MakeLiteral(syntax, defaultConstantValue, parameterType); } else { // We have something like M(double = 1.23), so replace the argument with 1.23. TypeSymbol constantType = compilation.GetSpecialType(defaultConstantValue.SpecialType); defaultValue = MakeLiteral(syntax, defaultConstantValue, constantType); // The parameter type might not match the constant type. defaultValue = MakeConversion(defaultValue, parameterType, @checked: false, acceptFailingConversion: true); } return(defaultValue); }
public static TypeSymbol StrippedType(this TypeSymbol type) { return(type.IsNullableType() ? type.GetNullableUnderlyingType() : type); }
private BoundExpression ConvertCaseExpression(TypeSymbol switchGoverningType, CSharpSyntaxNode node, BoundExpression caseExpression, Binder sectionBinder, ref ConstantValue constantValueOpt, DiagnosticBag diagnostics, bool isGotoCaseExpr = false) { BoundExpression convertedCaseExpression; if (!isGotoCaseExpr) { // NOTE: This will allow user-defined conversions, even though they're not allowed here. This is acceptable // because the result of a user-defined conversion does not have a ConstantValue and we'll report a diagnostic // to that effect below (same error code as Dev10). convertedCaseExpression = sectionBinder.GenerateConversionForAssignment(switchGoverningType, caseExpression, diagnostics); } else { // SPEC VIOLATION for Dev10 COMPATIBILITY: // Dev10 compiler violates the SPEC comment below: // "if the constant-expression is not implicitly convertible (§6.1) to // the governing type of the nearest enclosing switch statement, // a compile-time error occurs" // If there is no implicit conversion from gotoCaseExpression to switchGoverningType, // but there exists an explicit conversion, Dev10 compiler generates a warning "WRN_GotoCaseShouldConvert" // instead of an error. See test "CS0469_NoImplicitConversionWarning". // CONSIDER: Should we introduce a breaking change and violate Dev10 compatibility and follow the spec? HashSet<DiagnosticInfo> useSiteDiagnostics = null; Conversion conversion = sectionBinder.Conversions.ClassifyConversionFromExpression(caseExpression, switchGoverningType, ref useSiteDiagnostics); diagnostics.Add(node, useSiteDiagnostics); if (!conversion.IsValid) { GenerateImplicitConversionError(diagnostics, node, conversion, caseExpression, switchGoverningType); } else if (!conversion.IsImplicit) { diagnostics.Add(ErrorCode.WRN_GotoCaseShouldConvert, node.Location, switchGoverningType); } convertedCaseExpression = sectionBinder.CreateConversion(caseExpression, conversion, switchGoverningType, diagnostics); } if (switchGoverningType.IsNullableType() && convertedCaseExpression.Kind == BoundKind.Conversion // Null is a special case here because we want to compare null to the Nullable<T> itself, not to the underlying type. && (convertedCaseExpression.ConstantValue == null || !convertedCaseExpression.ConstantValue.IsNull)) { var operand = ((BoundConversion)convertedCaseExpression).Operand; // We are not intested in the diagnostic that get created here var diagnosticBag = DiagnosticBag.GetInstance(); constantValueOpt = sectionBinder.CreateConversion(operand, switchGoverningType.GetNullableUnderlyingType(), diagnosticBag).ConstantValue; diagnosticBag.Free(); } else { constantValueOpt = convertedCaseExpression.ConstantValue; } return convertedCaseExpression; }
// in simple cases could be left unlowered. // IL gen can generate more compact code for unlowered conditional accesses // by utilizing stack dup/pop instructions public override BoundNode VisitConditionalAccess(BoundConditionalAccess node) { BoundExpression loweredReceiver = (BoundExpression)this.Visit(node.Receiver); var receiverType = loweredReceiver.Type; //TODO: if AccessExpression does not contain awaits, the node could be left unlowered (saves a temp), // but there seem to be no way of knowing that without walking AccessExpression. var needToLower = receiverType.IsNullableType() || this.inExpressionLambda || this.factory.CurrentMethod.IsAsync; var previousConditionalAccesTarget = currentConditionalAccessTarget; LocalSymbol temp = null; if (needToLower) { if (NeedsTemp(loweredReceiver, localsMayBeAssigned: false)) { temp = factory.SynthesizedLocal(receiverType); currentConditionalAccessTarget = factory.Local(temp); loweredReceiver = factory.AssignmentExpression(factory.Local(temp), loweredReceiver); } else { currentConditionalAccessTarget = loweredReceiver; } } else { currentConditionalAccessTarget = null; } BoundExpression loweredAccessExpression = (BoundExpression)this.Visit(node.AccessExpression); currentConditionalAccessTarget = previousConditionalAccesTarget; TypeSymbol type = this.VisitType(node.Type); TypeSymbol nodeType = node.Type; TypeSymbol accessExpressionType = loweredAccessExpression.Type; if (accessExpressionType != nodeType) { Debug.Assert(nodeType.IsNullableType() && accessExpressionType == nodeType.GetNullableUnderlyingType()); loweredAccessExpression = factory.New((NamedTypeSymbol)nodeType, loweredAccessExpression); } BoundExpression result; if (!needToLower) { Debug.Assert(receiverType.IsReferenceType); result = node.Update(loweredReceiver, loweredAccessExpression, type); } else { var condition = receiverType.IsReferenceType ? factory.ObjectNotEqual(loweredReceiver, factory.Null(receiverType)) : MakeOptimizedHasValue(loweredReceiver.Syntax, loweredReceiver); var consequence = loweredAccessExpression; var alternative = factory.Default(nodeType); result = RewriteConditionalOperator(node.Syntax, condition, consequence, alternative, null, alternative.Type); if (temp != null) { result = factory.Sequence(temp, result); } } return(result); }
internal BoundExpression ConvertPatternExpression(TypeSymbol inputType, CSharpSyntaxNode node, BoundExpression expression, ref ConstantValue constantValue, DiagnosticBag diagnostics) { // NOTE: This will allow user-defined conversions, even though they're not allowed here. This is acceptable // because the result of a user-defined conversion does not have a ConstantValue and we'll report a diagnostic // to that effect later. BoundExpression convertedExpression = GenerateConversionForAssignment(inputType, expression, diagnostics); if (convertedExpression.Kind == BoundKind.Conversion) { var conversion = (BoundConversion)convertedExpression; var operand = conversion.Operand; if (inputType.IsNullableType() && (convertedExpression.ConstantValue == null || !convertedExpression.ConstantValue.IsNull)) { // Null is a special case here because we want to compare null to the Nullable<T> itself, not to the underlying type. var discardedDiagnostics = DiagnosticBag.GetInstance(); // We are not intested in the diagnostic that get created here convertedExpression = CreateConversion(operand, inputType.GetNullableUnderlyingType(), discardedDiagnostics); discardedDiagnostics.Free(); } else if ((conversion.ConversionKind == ConversionKind.Boxing || conversion.ConversionKind == ConversionKind.ImplicitReference) && operand.ConstantValue != null && convertedExpression.ConstantValue == null) { // A boxed constant (or string converted to object) is a special case because we prefer // to compare to the pre-converted value by casting the input value to the type of the constant // (that is, unboxing or downcasting it) and then testing the resulting value using primitives. // That is much more efficient than calling object.Equals(x, y), and we can share the downcasted // input value among many constant tests. convertedExpression = operand; } } constantValue = convertedExpression.ConstantValue; return convertedExpression; }
/// <summary> /// Return the side-effect expression corresponding to an evaluation. /// </summary> protected BoundExpression LowerEvaluation(BoundDagEvaluation evaluation) { BoundExpression input = _tempAllocator.GetTemp(evaluation.Input); switch (evaluation) { case BoundDagFieldEvaluation f: { FieldSymbol field = f.Field; var outputTemp = new BoundDagTemp(f.Syntax, field.Type.TypeSymbol, f, index: 0); BoundExpression output = _tempAllocator.GetTemp(outputTemp); BoundExpression access = _localRewriter.MakeFieldAccess(f.Syntax, input, field, null, LookupResultKind.Viable, field.Type.TypeSymbol); access.WasCompilerGenerated = true; return(_factory.AssignmentExpression(output, access)); } case BoundDagPropertyEvaluation p: { PropertySymbol property = p.Property; var outputTemp = new BoundDagTemp(p.Syntax, property.Type.TypeSymbol, p, index: 0); BoundExpression output = _tempAllocator.GetTemp(outputTemp); return(_factory.AssignmentExpression(output, _factory.Property(input, property))); } case BoundDagDeconstructEvaluation d: { MethodSymbol method = d.DeconstructMethod; var refKindBuilder = ArrayBuilder <RefKind> .GetInstance(); var argBuilder = ArrayBuilder <BoundExpression> .GetInstance(); BoundExpression receiver; void addArg(RefKind refKind, BoundExpression expression) { refKindBuilder.Add(refKind); argBuilder.Add(expression); } Debug.Assert(method.Name == WellKnownMemberNames.DeconstructMethodName); int extensionExtra; if (method.IsStatic) { Debug.Assert(method.IsExtensionMethod); receiver = _factory.Type(method.ContainingType); addArg(method.ParameterRefKinds[0], input); extensionExtra = 1; } else { receiver = input; extensionExtra = 0; } for (int i = extensionExtra; i < method.ParameterCount; i++) { ParameterSymbol parameter = method.Parameters[i]; Debug.Assert(parameter.RefKind == RefKind.Out); var outputTemp = new BoundDagTemp(d.Syntax, parameter.Type.TypeSymbol, d, i - extensionExtra); addArg(RefKind.Out, _tempAllocator.GetTemp(outputTemp)); } return(_factory.Call(receiver, method, refKindBuilder.ToImmutableAndFree(), argBuilder.ToImmutableAndFree())); } case BoundDagTypeEvaluation t: { TypeSymbol inputType = input.Type; if (inputType.IsDynamic() || inputType.ContainsTypeParameter()) { inputType = _factory.SpecialType(SpecialType.System_Object); } TypeSymbol type = t.Type; var outputTemp = new BoundDagTemp(t.Syntax, type, t, index: 0); BoundExpression output = _tempAllocator.GetTemp(outputTemp); HashSet <DiagnosticInfo> useSiteDiagnostics = null; Conversion conversion = _factory.Compilation.Conversions.ClassifyBuiltInConversion(inputType, output.Type, ref useSiteDiagnostics); _localRewriter._diagnostics.Add(t.Syntax, useSiteDiagnostics); BoundExpression evaluated; if (conversion.Exists) { if (conversion.Kind == ConversionKind.ExplicitNullable && inputType.GetNullableUnderlyingType().Equals(output.Type, TypeCompareKind.AllIgnoreOptions) && _localRewriter.TryGetNullableMethod(t.Syntax, inputType, SpecialMember.System_Nullable_T_GetValueOrDefault, out MethodSymbol getValueOrDefault)) { // As a special case, since the null test has already been done we can use Nullable<T>.GetValueOrDefault evaluated = _factory.Call(input, getValueOrDefault); } else { evaluated = _factory.Convert(type, input, conversion); } } else { evaluated = _factory.As(input, type); } return(_factory.AssignmentExpression(output, evaluated)); } case BoundDagIndexEvaluation e: { // This is an evaluation of an indexed property with a constant int value. // The input type must be ITuple, and the property must be a property of ITuple. Debug.Assert(e.Property.ContainingSymbol.Equals(input.Type)); Debug.Assert(e.Property.GetMethod.ParameterCount == 1); Debug.Assert(e.Property.GetMethod.Parameters[0].Type.SpecialType == SpecialType.System_Int32); TypeSymbol type = e.Property.GetMethod.ReturnType.TypeSymbol; var outputTemp = new BoundDagTemp(e.Syntax, type, e, index: 0); BoundExpression output = _tempAllocator.GetTemp(outputTemp); return(_factory.AssignmentExpression(output, _factory.Call(input, e.Property.GetMethod, _factory.Literal(e.Index)))); } default: throw ExceptionUtilities.UnexpectedValue(evaluation); } }
private void AddUserDefinedConversionsToExplicitCandidateSet( BoundExpression sourceExpression, TypeSymbol source, TypeSymbol target, ArrayBuilder<UserDefinedConversionAnalysis> u, NamedTypeSymbol declaringType, string operatorName, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { Debug.Assert(sourceExpression != null || (object)source != null); Debug.Assert((object)target != null); Debug.Assert(u != null); Debug.Assert((object)declaringType != null); Debug.Assert(operatorName != null); // SPEC: Find the set of applicable user-defined and lifted conversion operators, U. // SPEC: The set consists of the user-defined and lifted implicit or explicit // SPEC: conversion operators declared by the classes and structs in D that convert // SPEC: from a type encompassing E or encompassed by S (if it exists) to a type // SPEC: encompassing or encompassed by T. // DELIBERATE SPEC VIOLATION: // // The spec here essentially says that we add an applicable "regular" conversion and // an applicable lifted conversion, if there is one, to the candidate set, and then // let them duke it out to determine which one is "best". // // This is not at all what the native compiler does, and attempting to implement // the specification, or slight variations on it, produces too many backwards-compatibility // breaking changes. // // The native compiler deviates from the specification in two major ways here. // First, it does not add *both* the regular and lifted forms to the candidate set. // Second, the way it characterizes a "lifted" form is very, very different from // how the specification characterizes a lifted form. // // An operation, in this case, X-->Y, is properly said to be "lifted" to X?-->Y? via // the rule that X?-->Y? matches the behavior of X-->Y for non-null X, and converts // null X to null Y otherwise. // // The native compiler, by contrast, takes the existing operator and "lifts" either // the operator's parameter type or the operator's return type to nullable. For // example, a conversion from X?-->Y would be "lifted" to X?-->Y? by making the // conversion from X? to Y, and then from Y to Y?. No "lifting" semantics // are imposed; we do not check to see if the X? is null. This operator is not // actually "lifted" at all; rather, an implicit conversion is applied to the // output. **The native compiler considers the result type Y? of that standard implicit // conversion to be the result type of the "lifted" conversion**, rather than // properly considering Y to be the result type of the conversion for the purposes // of computing the best output type. // // Moreover: the native compiler actually *does* implement nullable lifting semantics // in the case where the input type of the user-defined conversion is a non-nullable // value type and the output type is a nullable value type **or pointer type, or // reference type**. This is an enormous departure from the specification; the // native compiler will take a user-defined conversion from X-->Y? or X-->C and "lift" // it to a conversion from X?-->Y? or X?-->C that has nullable semantics. // // This is quite confusing. In this code we will classify the conversion as either // "normal" or "lifted" on the basis of *whether or not special lifting semantics // are to be applied*. That is, whether or not a later rewriting pass is going to // need to insert a check to see if the source expression is null, and decide // whether or not to call the underlying unlifted conversion or produce a null // value without calling the unlifted conversion. // DELIBERATE SPEC VIOLATION: See the comment regarding bug 17021 in // UserDefinedImplicitConversions.cs. if ((object)source != null && source.IsInterfaceType() || target.IsInterfaceType()) { return; } foreach (MethodSymbol op in declaringType.GetOperators(operatorName)) { // We might have a bad operator and be in an error recovery situation. Ignore it. if (op.ReturnsVoid || op.ParameterCount != 1 || op.ReturnType.TypeKind == TypeKind.Error) { continue; } TypeSymbol convertsFrom = op.ParameterTypes[0]; TypeSymbol convertsTo = op.ReturnType; Conversion fromConversion = EncompassingExplicitConversion(sourceExpression, source, convertsFrom, ref useSiteDiagnostics); Conversion toConversion = EncompassingExplicitConversion(null, convertsTo, target, ref useSiteDiagnostics); // We accept candidates for which the parameter type encompasses the *underlying* source type. if (!fromConversion.Exists && (object)source != null && source.IsNullableType() && EncompassingExplicitConversion(null, source.GetNullableUnderlyingType(), convertsFrom, ref useSiteDiagnostics).Exists) { fromConversion = ClassifyConversion(source, convertsFrom, ref useSiteDiagnostics, builtinOnly: true); } // As in dev11 (and the revised spec), we also accept candidates for which the return type is encompassed by the *stripped* target type. if (!toConversion.Exists && (object)target != null && target.IsNullableType() && EncompassingExplicitConversion(null, convertsTo, target.GetNullableUnderlyingType(), ref useSiteDiagnostics).Exists) { toConversion = ClassifyConversion(convertsTo, target, ref useSiteDiagnostics, builtinOnly: true); } // In the corresponding implicit conversion code we can get away with first // checking to see if standard implicit conversions exist from the source type // to the parameter type, and from the return type to the target type. If not, // then we can check for a lifted operator. // // That's not going to cut it in the explicit conversion code. Suppose we have // a conversion X-->Y and have source type X? and target type Y?. There *are* // standard explicit conversions from X?-->X and Y?-->Y, but we do not want // to bind this as an *unlifted* conversion from X? to Y?; we want such a thing // to be a *lifted* conversion from X? to Y?, that checks for null on the source // and decides to not call the underlying user-defined conversion if it is null. // // We therefore cannot do what we do in the implicit conversions, where we check // to see if the unlifted conversion works, and if it does, then don't add the lifted // conversion at all. Rather, we have to see if what we're building here is a // lifted conversion or not. // // Under what circumstances is this conversion a lifted conversion? (In the // "spec" sense of a lifted conversion; that is, that we check for null // and skip the user-defined conversion if necessary). // // * The source type must be a nullable value type. // * The parameter type must be a non-nullable value type. // * The target type must be able to take on a null value. if (fromConversion.Exists && toConversion.Exists) { if ((object)source != null && source.IsNullableType() && convertsFrom.IsNonNullableValueType() && target.CanBeAssignedNull()) { TypeSymbol nullableFrom = MakeNullableType(convertsFrom); TypeSymbol nullableTo = convertsTo.IsNonNullableValueType() ? MakeNullableType(convertsTo) : convertsTo; Conversion liftedFromConversion = EncompassingExplicitConversion(sourceExpression, source, nullableFrom, ref useSiteDiagnostics); Conversion liftedToConversion = EncompassingExplicitConversion(null, nullableTo, target, ref useSiteDiagnostics); Debug.Assert(liftedFromConversion.Exists); Debug.Assert(liftedToConversion.Exists); u.Add(UserDefinedConversionAnalysis.Lifted(op, liftedFromConversion, liftedToConversion, nullableFrom, nullableTo)); } else { // There is an additional spec violation in the native compiler. Suppose // we have a conversion from X-->Y and are asked to do "Y? y = new X();" Clearly // the intention is to convert from X-->Y via the implicit conversion, and then // stick a standard implicit conversion from Y-->Y? on the back end. **In this // situation, the native compiler treats the conversion as though it were // actually X-->Y? in source for the purposes of determining the best target // type of a set of operators. // // Similarly, if we have a conversion from X-->Y and are asked to do // an explicit conversion from X? to Y then we treat the conversion as // though it really were X?-->Y for the purposes of determining the best // source type of a set of operators. // // We perpetuate these fictions here. if (target.IsNullableType() && convertsTo.IsNonNullableValueType()) { convertsTo = MakeNullableType(convertsTo); toConversion = EncompassingExplicitConversion(null, convertsTo, target, ref useSiteDiagnostics); } if ((object)source != null && source.IsNullableType() && convertsFrom.IsNonNullableValueType()) { convertsFrom = MakeNullableType(convertsFrom); fromConversion = EncompassingExplicitConversion(null, convertsFrom, source, ref useSiteDiagnostics); } u.Add(UserDefinedConversionAnalysis.Normal(op, fromConversion, toConversion, convertsFrom, convertsTo)); } } } }
private static BinaryOperatorKind GetCorrespondingBinaryOperator(BoundIncrementOperator node) { // We need to create expressions that have the semantics of incrementing or decrementing: // sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal and // any enum. However, the binary addition operators we have at our disposal are just // int, uint, long, ulong, float, double and decimal. UnaryOperatorKind unaryOperatorKind = node.OperatorKind; BinaryOperatorKind result; switch (unaryOperatorKind.OperandTypes()) { case UnaryOperatorKind.Int: case UnaryOperatorKind.SByte: case UnaryOperatorKind.Short: result = BinaryOperatorKind.Int; break; case UnaryOperatorKind.Byte: case UnaryOperatorKind.UShort: case UnaryOperatorKind.Char: case UnaryOperatorKind.UInt: result = BinaryOperatorKind.UInt; break; case UnaryOperatorKind.Long: result = BinaryOperatorKind.Long; break; case UnaryOperatorKind.ULong: result = BinaryOperatorKind.ULong; break; case UnaryOperatorKind.Float: result = BinaryOperatorKind.Float; break; case UnaryOperatorKind.Double: result = BinaryOperatorKind.Double; break; case UnaryOperatorKind.Decimal: //Dev10 special cased this, but we'll let DecimalRewriter handle it result = BinaryOperatorKind.Decimal; break; case UnaryOperatorKind.Enum: { TypeSymbol underlyingType = node.Type; if (underlyingType.IsNullableType()) { underlyingType = underlyingType.GetNullableUnderlyingType(); } Debug.Assert(underlyingType.IsEnumType()); underlyingType = underlyingType.GetEnumUnderlyingType(); // Operator overload resolution will not have chosen the enumerated type // unless the operand actually is of the enumerated type (or nullable enum type.) switch (underlyingType.SpecialType) { case SpecialType.System_SByte: case SpecialType.System_Int16: case SpecialType.System_Int32: result = BinaryOperatorKind.Int; break; case SpecialType.System_Byte: case SpecialType.System_UInt16: case SpecialType.System_UInt32: result = BinaryOperatorKind.UInt; break; case SpecialType.System_Int64: result = BinaryOperatorKind.Long; break; case SpecialType.System_UInt64: result = BinaryOperatorKind.ULong; break; default: throw ExceptionUtilities.UnexpectedValue(underlyingType.SpecialType); } } break; case UnaryOperatorKind.Pointer: result = BinaryOperatorKind.PointerAndInt; break; case UnaryOperatorKind.UserDefined: case UnaryOperatorKind.Bool: default: throw ExceptionUtilities.UnexpectedValue(unaryOperatorKind.OperandTypes()); } switch (result) { case BinaryOperatorKind.UInt: case BinaryOperatorKind.Int: case BinaryOperatorKind.ULong: case BinaryOperatorKind.Long: case BinaryOperatorKind.PointerAndInt: result |= (BinaryOperatorKind)unaryOperatorKind.OverflowChecks(); break; } if (unaryOperatorKind.IsLifted()) { result |= BinaryOperatorKind.Lifted; } return(result); }
// IL gen can generate more compact code for certain conditional accesses // by utilizing stack dup/pop instructions internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, bool used) { Debug.Assert(!_inExpressionLambda); var loweredReceiver = this.VisitExpression(node.Receiver); var receiverType = loweredReceiver.Type; // Check trivial case if (loweredReceiver.IsDefaultValue() && receiverType.IsReferenceType) { return(_factory.Default(node.Type)); } ConditionalAccessLoweringKind loweringKind; // dynamic receivers are not directly supported in codegen and need to be lowered to a ternary var lowerToTernary = node.AccessExpression.Type.IsDynamic(); if (!lowerToTernary) { // trivial cases are directly supported in IL gen loweringKind = ConditionalAccessLoweringKind.LoweredConditionalAccess; } else if (CanChangeValueBetweenReads(loweredReceiver)) { // NOTE: dynamic operations historically do not propagate mutations // to the receiver if that happens to be a value type // so we can capture receiver by value in dynamic case regardless of // the type of receiver // Nullable receivers are immutable so should be captured by value as well. loweringKind = ConditionalAccessLoweringKind.TernaryCaptureReceiverByVal; } else { loweringKind = ConditionalAccessLoweringKind.Ternary; } var previousConditionalAccessTarget = _currentConditionalAccessTarget; var currentConditionalAccessID = ++_currentConditionalAccessID; LocalSymbol temp = null; switch (loweringKind) { case ConditionalAccessLoweringKind.LoweredConditionalAccess: _currentConditionalAccessTarget = new BoundConditionalReceiver( loweredReceiver.Syntax, currentConditionalAccessID, receiverType); break; case ConditionalAccessLoweringKind.Ternary: _currentConditionalAccessTarget = loweredReceiver; break; case ConditionalAccessLoweringKind.TernaryCaptureReceiverByVal: temp = _factory.SynthesizedLocal(receiverType); _currentConditionalAccessTarget = _factory.Local(temp); break; default: throw ExceptionUtilities.UnexpectedValue(loweringKind); } BoundExpression loweredAccessExpression; if (used) { loweredAccessExpression = this.VisitExpression(node.AccessExpression); } else { loweredAccessExpression = this.VisitUnusedExpression(node.AccessExpression); if (loweredAccessExpression == null) { return(null); } } Debug.Assert(loweredAccessExpression != null); _currentConditionalAccessTarget = previousConditionalAccessTarget; TypeSymbol type = this.VisitType(node.Type); TypeSymbol nodeType = node.Type; TypeSymbol accessExpressionType = loweredAccessExpression.Type; if (accessExpressionType.SpecialType == SpecialType.System_Void) { type = nodeType = accessExpressionType; } if (!TypeSymbol.Equals(accessExpressionType, nodeType, TypeCompareKind.ConsiderEverything2) && nodeType.IsNullableType()) { Debug.Assert(TypeSymbol.Equals(accessExpressionType, nodeType.GetNullableUnderlyingType(), TypeCompareKind.ConsiderEverything2)); loweredAccessExpression = _factory.New((NamedTypeSymbol)nodeType, loweredAccessExpression); } else { Debug.Assert(TypeSymbol.Equals(accessExpressionType, nodeType, TypeCompareKind.ConsiderEverything2) || (nodeType.SpecialType == SpecialType.System_Void && !used)); } BoundExpression result; var objectType = _compilation.GetSpecialType(SpecialType.System_Object); switch (loweringKind) { case ConditionalAccessLoweringKind.LoweredConditionalAccess: result = new BoundLoweredConditionalAccess( node.Syntax, loweredReceiver, receiverType.IsNullableType() ? UnsafeGetNullableMethod(node.Syntax, loweredReceiver.Type, SpecialMember.System_Nullable_T_get_HasValue) : null, loweredAccessExpression, null, currentConditionalAccessID, type); break; case ConditionalAccessLoweringKind.TernaryCaptureReceiverByVal: // capture the receiver into a temp loweredReceiver = _factory.MakeSequence( _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver), _factory.Local(temp)); goto case ConditionalAccessLoweringKind.Ternary; case ConditionalAccessLoweringKind.Ternary: { // (object)r != null ? access : default(T) var condition = _factory.ObjectNotEqual( _factory.Convert(objectType, loweredReceiver), _factory.Null(objectType)); var consequence = loweredAccessExpression; result = RewriteConditionalOperator(node.Syntax, condition, consequence, _factory.Default(nodeType), null, nodeType, isRef: false); if (temp != null) { result = _factory.MakeSequence(temp, result); } } break; default: throw ExceptionUtilities.UnexpectedValue(loweringKind); } return(result); }
internal static bool ReportDefaultParameterErrors( Binder binder, Symbol owner, ParameterSyntax parameterSyntax, SourceParameterSymbol parameter, BoundExpression defaultExpression, DiagnosticBag diagnostics) { bool hasErrors = false; // SPEC VIOLATION: The spec says that the conversion from the initializer to the // parameter type is required to be either an identity or a nullable conversion, but // that is not right: // // void M(short myShort = 10) {} // * not an identity or nullable conversion but should be legal // // void M(object obj = (dynamic)null) {} // * an identity conversion, but should be illegal // // void M(MyStruct? myStruct = default(MyStruct)) {} // * a nullable conversion, but must be illegal because we cannot generate metadata for it // // Even if the expression is thoroughly illegal, we still want to bind it and // stick it in the parameter because we want to be able to analyze it for // IntelliSense purposes. TypeSymbol parameterType = parameter.Type; HashSet <DiagnosticInfo> useSiteDiagnostics = null; Conversion conversion = binder.Conversions.ClassifyImplicitConversionFromExpression(defaultExpression, parameterType, ref useSiteDiagnostics); diagnostics.Add(defaultExpression.Syntax, useSiteDiagnostics); SyntaxToken outKeyword; SyntaxToken refKeyword; SyntaxToken paramsKeyword; SyntaxToken thisKeyword; GetModifiers(parameterSyntax.Modifiers, out outKeyword, out refKeyword, out paramsKeyword, out thisKeyword); // CONSIDER: We are inconsistent here regarding where the error is reported; is it // CONSIDER: reported on the parameter name, or on the value of the initializer? // CONSIDER: Consider making this consistent. if (outKeyword.Kind() == SyntaxKind.OutKeyword) { // error CS1741: A ref or out parameter cannot have a default value diagnostics.Add(ErrorCode.ERR_RefOutDefaultValue, outKeyword.GetLocation()); hasErrors = true; } else if (refKeyword.Kind() == SyntaxKind.RefKeyword) { // error CS1741: A ref or out parameter cannot have a default value diagnostics.Add(ErrorCode.ERR_RefOutDefaultValue, refKeyword.GetLocation()); hasErrors = true; } else if (paramsKeyword.Kind() == SyntaxKind.ParamsKeyword) { // error CS1751: Cannot specify a default value for a parameter array diagnostics.Add(ErrorCode.ERR_DefaultValueForParamsParameter, paramsKeyword.GetLocation()); hasErrors = true; } else if (thisKeyword.Kind() == SyntaxKind.ThisKeyword) { // Only need to report CS1743 for the first parameter. The caller will // have reported CS1100 if 'this' appeared on another parameter. if (parameter.Ordinal == 0) { // error CS1743: Cannot specify a default value for the 'this' parameter diagnostics.Add(ErrorCode.ERR_DefaultValueForExtensionParameter, thisKeyword.GetLocation()); hasErrors = true; } } else if (!defaultExpression.HasAnyErrors && !IsValidDefaultValue(defaultExpression)) { // error CS1736: Default parameter value for '{0}' must be a compile-time constant diagnostics.Add(ErrorCode.ERR_DefaultValueMustBeConstant, parameterSyntax.Default.Value.Location, parameterSyntax.Identifier.ValueText); hasErrors = true; } else if (!conversion.Exists || conversion.IsUserDefined || conversion.IsIdentity && parameterType.SpecialType == SpecialType.System_Object && defaultExpression.Type.IsDynamic()) { // If we had no implicit conversion, or a user-defined conversion, report an error. // // Even though "object x = (dynamic)null" is a legal identity conversion, we do not allow it. // CONSIDER: We could. Doesn't hurt anything. // error CS1750: A value of type '{0}' cannot be used as a default parameter because there are no standard conversions to type '{1}' diagnostics.Add(ErrorCode.ERR_NoConversionForDefaultParam, parameterSyntax.Identifier.GetLocation(), defaultExpression.Type ?? defaultExpression.Display, parameterType); hasErrors = true; } else if (conversion.IsReference && (parameterType.SpecialType == SpecialType.System_Object || parameterType.Kind == SymbolKind.DynamicType) && (object)defaultExpression.Type != null && defaultExpression.Type.SpecialType == SpecialType.System_String || conversion.IsBoxing) { // We don't allow object x = "hello", object x = 123, dynamic x = "hello", etc. // error CS1763: '{0}' is of type '{1}'. A default parameter value of a reference type other than string can only be initialized with null diagnostics.Add(ErrorCode.ERR_NotNullRefDefaultParameter, parameterSyntax.Identifier.GetLocation(), parameterSyntax.Identifier.ValueText, parameterType); hasErrors = true; } else if (conversion.IsNullable && !defaultExpression.Type.IsNullableType() && !(parameterType.GetNullableUnderlyingType().IsEnumType() || parameterType.GetNullableUnderlyingType().IsIntrinsicType())) { // We can do: // M(int? x = default(int)) // M(int? x = default(int?)) // M(MyEnum? e = default(enum)) // M(MyEnum? e = default(enum?)) // M(MyStruct? s = default(MyStruct?)) // // but we cannot do: // // M(MyStruct? s = default(MyStruct)) // error CS1770: // A value of type '{0}' cannot be used as default parameter for nullable parameter '{1}' because '{0}' is not a simple type diagnostics.Add(ErrorCode.ERR_NoConversionForNubDefaultParam, parameterSyntax.Identifier.GetLocation(), defaultExpression.Type, parameterSyntax.Identifier.ValueText); hasErrors = true; } // Certain contexts allow default parameter values syntactically but they are ignored during // semantic analysis. They are: // 1. Explicitly implemented interface methods; since the method will always be called // via the interface, the defaults declared on the implementation will not // be seen at the call site. // // UNDONE: 2. The "actual" side of a partial method; the default values are taken from the // UNDONE: "declaring" side of the method. // // UNDONE: 3. An indexer with only one formal parameter; it is illegal to omit every argument // UNDONE: to an indexer. // // 4. A user-defined operator; it is syntactically impossible to omit the argument. if (owner.IsExplicitInterfaceImplementation() || owner.IsPartialImplementation() || owner.IsOperator()) { // CS1066: The default value specified for parameter '{0}' will have no effect because it applies to a // member that is used in contexts that do not allow optional arguments diagnostics.Add(ErrorCode.WRN_DefaultValueForUnconsumedLocation, parameterSyntax.Identifier.GetLocation(), parameterSyntax.Identifier.ValueText); } return(hasErrors); }
// A "surprising" sign extension is: // // * a conversion with no cast in source code that goes from a smaller // signed type to a larger signed or unsigned type. // // * an conversion (with or without a cast) from a smaller // signed type to a larger unsigned type. private static ulong FindSurprisingSignExtensionBits(BoundExpression expr) { if (expr.Kind != BoundKind.Conversion) { return(0); } BoundConversion conv = (BoundConversion)expr; TypeSymbol from = conv.Operand.Type; TypeSymbol to = conv.Type; if ((object)from == null || (object)to == null) { return(0); } if (from.IsNullableType()) { from = from.GetNullableUnderlyingType(); } if (to.IsNullableType()) { to = to.GetNullableUnderlyingType(); } SpecialType fromSpecialType = from.SpecialType; SpecialType toSpecialType = to.SpecialType; if (!fromSpecialType.IsIntegralType() || !toSpecialType.IsIntegralType()) { return(0); } int fromSize = fromSpecialType.SizeInBytes(); int toSize = toSpecialType.SizeInBytes(); if (fromSize == 0 || toSize == 0) { return(0); } // The operand might itself be a conversion, and might be contributing // surprising bits. We might have more, fewer or the same surprising bits // as the operand. ulong recursive = FindSurprisingSignExtensionBits(conv.Operand); if (fromSize == toSize) { // No change. return(recursive); } if (toSize < fromSize) { // We are casting from a larger type to a smaller type, and are therefore // losing surprising bits. switch (toSize) { case 1: return(unchecked ((ulong)(byte)recursive)); case 2: return(unchecked ((ulong)(ushort)recursive)); case 4: return(unchecked ((ulong)(uint)recursive)); } Debug.Assert(false, "How did we get here?"); return(recursive); } // We are converting from a smaller type to a larger type, and therefore might // be adding surprising bits. First of all, the smaller type has got to be signed // for there to be sign extension. bool fromSigned = fromSpecialType.IsSignedIntegralType(); if (!fromSigned) { return(recursive); } // OK, we know that the "from" type is a signed integer that is smaller than the // "to" type, so we are going to have sign extension. Is it surprising? The only // time that sign extension is *not* surprising is when we have a cast operator // to a *signed* type. That is, (int)myShort is not a surprising sign extension. if (conv.ExplicitCastInCode && toSpecialType.IsSignedIntegralType()) { return(recursive); } // Note that we *could* be somewhat more clever here. Consider the following edge case: // // (ulong)(int)(uint)(ushort)mySbyte // // We could reason that the sbyte-to-ushort conversion is going to add one byte of // unexpected sign extension. The conversion from ushort to uint adds no more bytes. // The conversion from uint to int adds no more bytes. Does the conversion from int // to ulong add any more bytes of unexpected sign extension? Well, no, because we // know that the previous conversion from ushort to uint will ensure that the top bit // of the uint is off! // // But we are not going to try to be that clever. In the extremely unlikely event that // someone does this, we will record that the unexpectedly turned-on bits are // 0xFFFFFFFF0000FF00, even though we could in theory deduce that only 0x000000000000FF00 // are the unexpected bits. ulong result = recursive; for (int i = fromSize; i < toSize; ++i) { result |= (0xFFUL) << (i * 8); } return(result); }
public static string GetErrorReportingName(TypeSymbol type, RefKind refKind = RefKind.None) { string prefix = ""; switch (refKind) { case RefKind.Ref: prefix = "ref "; break; case RefKind.Out: prefix = "out "; break; } switch (type.GetSpecialTypeSafe()) { case SpecialType.System_Void: case SpecialType.System_SByte: case SpecialType.System_Int16: case SpecialType.System_Int32: case SpecialType.System_Int64: case SpecialType.System_Byte: case SpecialType.System_UInt16: case SpecialType.System_UInt32: case SpecialType.System_UInt64: case SpecialType.System_Single: case SpecialType.System_Double: case SpecialType.System_Decimal: case SpecialType.System_Char: case SpecialType.System_Boolean: case SpecialType.System_String: case SpecialType.System_Object: return prefix + SemanticFacts.GetLanguageName(type.SpecialType); case SpecialType.None: if (type != null && type.IsNullableType() && !ReferenceEquals(type, type.OriginalDefinition)) { TypeSymbol underlyingType = type.GetNullableUnderlyingType(); switch (underlyingType.GetSpecialTypeSafe()) { case SpecialType.System_Boolean: case SpecialType.System_SByte: case SpecialType.System_Int16: case SpecialType.System_Int32: case SpecialType.System_Int64: case SpecialType.System_Byte: case SpecialType.System_UInt16: case SpecialType.System_UInt32: case SpecialType.System_UInt64: case SpecialType.System_Single: case SpecialType.System_Double: case SpecialType.System_Decimal: case SpecialType.System_Char: return prefix + SemanticFacts.GetLanguageName(underlyingType.SpecialType) + "?"; } return prefix + GetErrorReportingName(underlyingType) + "?"; } break; } var dynamicType = type as DynamicTypeSymbol; if (dynamicType != null) { return prefix + "dynamic"; } var arrayType = type as ArrayTypeSymbol; if (arrayType != null) { string suffix = ""; while (true) { var elementType = arrayType.ElementType; suffix += GetSuffix(arrayType.Rank); arrayType = elementType as ArrayTypeSymbol; if (arrayType == null) { return prefix + GetErrorReportingName(elementType) + suffix; } } } var pointerType = type as PointerTypeSymbol; if (pointerType != null) { return prefix + GetErrorReportingName(pointerType.BaseType) + "*"; } var namedType = type as NamedTypeSymbol; if (namedType != null) { string result = ""; if (namedType.ContainingType != null) { result = GetErrorReportingName(namedType.ContainingType) + "."; } else if (namedType.ContainingNamespace != null && !namedType.ContainingNamespace.IsGlobalNamespace) { result = namedType.ContainingNamespace.GetFullName() + "."; } result += type.Name; if (namedType.TypeArguments.Count != 0) { result += "<"; result += namedType.TypeArguments.Select(a => GetErrorReportingName(a)).Comma(","); result += ">"; } return prefix + result; } var typeParameter = type as TypeParameterSymbol; if (typeParameter != null) { return prefix + type.Name; } Debug.Fail("What case did we miss in type name error reporter?"); return prefix + type.GetFullName(); }
/// <summary> /// Check that the pattern type is valid for the operand. Return true if an error was reported. /// </summary> private bool CheckValidPatternType( CSharpSyntaxNode typeSyntax, TypeSymbol operandType, TypeSymbol patternType, bool patternTypeWasInSource, bool isVar, DiagnosticBag diagnostics) { Debug.Assert((object)operandType != null); Debug.Assert((object)patternType != null); if (operandType.IsErrorType() || patternType.IsErrorType()) { return(false); } else if (patternType.IsNullableType() && !isVar && patternTypeWasInSource) { // It is an error to use pattern-matching with a nullable type, because you'll never get null. Use the underlying type. Error(diagnostics, ErrorCode.ERR_PatternNullableType, typeSyntax, patternType, patternType.GetNullableUnderlyingType()); return(true); } else if (patternType.IsStatic) { Error(diagnostics, ErrorCode.ERR_VarDeclIsStaticClass, typeSyntax, patternType); return(true); } else if (!isVar) { if (patternType.IsDynamic()) { Error(diagnostics, ErrorCode.ERR_PatternDynamicType, typeSyntax); return(true); } HashSet <DiagnosticInfo> useSiteDiagnostics = null; var matchPossible = ExpressionOfTypeMatchesPatternType(Conversions, operandType, patternType, ref useSiteDiagnostics, out Conversion conversion, operandConstantValue: null, operandCouldBeNull: true); diagnostics.Add(typeSyntax, useSiteDiagnostics); if (matchPossible != false) { if (!conversion.Exists && (operandType.ContainsTypeParameter() || patternType.ContainsTypeParameter())) { // permit pattern-matching when one of the types is an open type in C# 7.1. LanguageVersion requiredVersion = MessageID.IDS_FeatureGenericPatternMatching.RequiredVersion(); if (requiredVersion > Compilation.LanguageVersion) { Error(diagnostics, ErrorCode.ERR_PatternWrongGenericTypeInVersion, typeSyntax, operandType, patternType, Compilation.LanguageVersion.ToDisplayString(), new CSharpRequiredLanguageVersion(requiredVersion)); return(true); } } } else { Error(diagnostics, ErrorCode.ERR_PatternWrongType, typeSyntax, operandType, patternType); return(true); } } return(false); }
public static string GetErrorReportingName(TypeSymbol type, RefKind refKind = RefKind.None) { string prefix = ""; switch (refKind) { case RefKind.Ref: prefix = "ref "; break; case RefKind.Out: prefix = "out "; break; } switch (type.GetSpecialTypeSafe()) { case SpecialType.System_Void: case SpecialType.System_SByte: case SpecialType.System_Int16: case SpecialType.System_Int32: case SpecialType.System_Int64: case SpecialType.System_Byte: case SpecialType.System_UInt16: case SpecialType.System_UInt32: case SpecialType.System_UInt64: case SpecialType.System_Single: case SpecialType.System_Double: case SpecialType.System_Decimal: case SpecialType.System_Char: case SpecialType.System_Boolean: case SpecialType.System_String: case SpecialType.System_Object: return(prefix + SemanticFacts.GetLanguageName(type.SpecialType)); case SpecialType.None: if (type != null && type.IsNullableType() && !ReferenceEquals(type, type.OriginalDefinition)) { TypeSymbol underlyingType = type.GetNullableUnderlyingType(); switch (underlyingType.GetSpecialTypeSafe()) { case SpecialType.System_Boolean: case SpecialType.System_SByte: case SpecialType.System_Int16: case SpecialType.System_Int32: case SpecialType.System_Int64: case SpecialType.System_Byte: case SpecialType.System_UInt16: case SpecialType.System_UInt32: case SpecialType.System_UInt64: case SpecialType.System_Single: case SpecialType.System_Double: case SpecialType.System_Decimal: case SpecialType.System_Char: return(prefix + SemanticFacts.GetLanguageName(underlyingType.SpecialType) + "?"); } return(prefix + GetErrorReportingName(underlyingType) + "?"); } break; } var dynamicType = type as DynamicTypeSymbol; if (dynamicType != null) { return(prefix + "dynamic"); } var arrayType = type as ArrayTypeSymbol; if (arrayType != null) { string suffix = ""; while (true) { var elementType = arrayType.ElementType; suffix += GetSuffix(arrayType.Rank); arrayType = elementType as ArrayTypeSymbol; if (arrayType == null) { return(prefix + GetErrorReportingName(elementType) + suffix); } } } var pointerType = type as PointerTypeSymbol; if (pointerType != null) { return(prefix + GetErrorReportingName(pointerType.BaseType) + "*"); } var namedType = type as NamedTypeSymbol; if (namedType != null) { string result = ""; if (namedType.ContainingType != null) { result = GetErrorReportingName(namedType.ContainingType) + "."; } else if (namedType.ContainingNamespace != null && !namedType.ContainingNamespace.IsGlobalNamespace) { result = namedType.ContainingNamespace.GetFullName() + "."; } result += type.Name; if (namedType.TypeArguments.Count != 0) { result += "<"; result += namedType.TypeArguments.Select(a => GetErrorReportingName(a)).Comma(","); result += ">"; } return(prefix + result); } var typeParameter = type as TypeParameterSymbol; if (typeParameter != null) { return(prefix + type.Name); } Debug.Fail("What case did we miss in type name error reporter?"); return(prefix + type.GetFullName()); }
private BoundExpression MakeBuiltInIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement) { BoundExpression result; // If we have a built-in increment or decrement then things get a bit trickier. Suppose for example we have // a user-defined conversion from X to short and from short to X, but no user-defined increment operator on // X. The increment portion of "++x" is then: (X)(short)((int)(short)x + 1). That is, first x must be // converted to short via an implicit user- defined conversion, then to int via an implicit numeric // conversion, then the addition is performed in integers. The resulting integer is converted back to short, // and then the short is converted to X. // This is the input and output type of the unary increment operator we're going to call. // That is, "short" in the example above. TypeSymbol unaryOperandType = GetUnaryOperatorType(node); // This is the kind of binary operator that we're going to realize the unary operator // as. That is, "int + int --> int" in the example above. BinaryOperatorKind binaryOperatorKind = GetCorrespondingBinaryOperator(node); binaryOperatorKind |= IsIncrement(node) ? BinaryOperatorKind.Addition : BinaryOperatorKind.Subtraction; // The "1" in the example above. ConstantValue constantOne = GetConstantOneForBinOp(binaryOperatorKind); Debug.Assert(constantOne != null); Debug.Assert(constantOne.SpecialType != SpecialType.None); Debug.Assert(binaryOperatorKind.OperandTypes() != 0); // The input/output type of the binary operand. "int" in the example. TypeSymbol binaryOperandType = _compilation.GetSpecialType(constantOne.SpecialType); // 1 BoundExpression boundOne = MakeLiteral( syntax: node.Syntax, constantValue: constantOne, type: binaryOperandType); if (binaryOperatorKind.IsLifted()) { binaryOperandType = _compilation.GetSpecialType(SpecialType.System_Nullable_T).Construct(binaryOperandType); MethodSymbol ctor = UnsafeGetNullableMethod(node.Syntax, binaryOperandType, SpecialMember.System_Nullable_T__ctor); boundOne = new BoundObjectCreationExpression(node.Syntax, ctor, boundOne); } // Now we construct the other operand to the binary addition. We start with just plain "x". BoundExpression binaryOperand = rewrittenValueToIncrement; bool @checked = node.OperatorKind.IsChecked(); // If we need to make a conversion from the original operand type to the operand type of the // underlying increment operation, do it now. if (!node.OperandConversion.IsIdentity) { // (short)x binaryOperand = MakeConversionNode( syntax: node.Syntax, rewrittenOperand: binaryOperand, conversion: node.OperandConversion, rewrittenType: unaryOperandType, @checked: @checked); } // Early-out for pointer increment - we don't need to convert the operands to a common type. if (node.OperatorKind.OperandTypes() == UnaryOperatorKind.Pointer) { Debug.Assert(binaryOperatorKind.OperandTypes() == BinaryOperatorKind.PointerAndInt); Debug.Assert(binaryOperand.Type.IsPointerType()); Debug.Assert(boundOne.Type.SpecialType == SpecialType.System_Int32); return(MakeBinaryOperator(node.Syntax, binaryOperatorKind, binaryOperand, boundOne, binaryOperand.Type, method: null)); } // If we need to make a conversion from the unary operator type to the binary operator type, // do it now. // (int)(short)x binaryOperand = MakeConversionNode(binaryOperand, binaryOperandType, @checked); // Perform the addition. // (int)(short)x + 1 BoundExpression binOp; if (unaryOperandType.SpecialType == SpecialType.System_Decimal) { binOp = MakeDecimalIncDecOperator(node.Syntax, binaryOperatorKind, binaryOperand); } else if (unaryOperandType.IsNullableType() && unaryOperandType.GetNullableUnderlyingType().SpecialType == SpecialType.System_Decimal) { binOp = MakeLiftedDecimalIncDecOperator(node.Syntax, binaryOperatorKind, binaryOperand); } else { binOp = MakeBinaryOperator(node.Syntax, binaryOperatorKind, binaryOperand, boundOne, binaryOperandType, method: null); } // Generate the conversion back to the type of the unary operator. // (short)((int)(short)x + 1) result = MakeConversionNode(binOp, unaryOperandType, @checked); return(result); }
private BoundExpression RewriteNullableConversion( CSharpSyntaxNode syntax, BoundExpression rewrittenOperand, ConversionKind conversionKind, bool @checked, bool explicitCastInCode, TypeSymbol rewrittenType) { Debug.Assert((object)rewrittenType != null); if (inExpressionLambda) { return RewriteLiftedConversionInExpressionTree(syntax, rewrittenOperand, conversionKind, @checked, explicitCastInCode, rewrittenType); } TypeSymbol rewrittenOperandType = rewrittenOperand.Type; Debug.Assert(rewrittenType.IsNullableType() || rewrittenOperandType.IsNullableType()); if (rewrittenOperandType.IsNullableType() && rewrittenType.IsNullableType()) { return RewriteFullyLiftedBuiltInConversion(syntax, rewrittenOperand, conversionKind, @checked, rewrittenType); } else if (rewrittenType.IsNullableType()) { // SPEC: If the nullable conversion is from S to T?, the conversion is // SPEC: evaluated as the underlying conversion from S to T followed // SPEC: by a wrapping from T to T?. BoundExpression rewrittenConversion = MakeConversion(rewrittenOperand, rewrittenType.GetNullableUnderlyingType(), @checked); MethodSymbol ctor = GetNullableMethod(syntax, rewrittenType, SpecialMember.System_Nullable_T__ctor); return new BoundObjectCreationExpression(syntax, ctor, rewrittenConversion); } else { // SPEC: if the nullable conversion is from S? to T, the conversion is // SPEC: evaluated as an unwrapping from S? to S followed by the underlying // SPEC: conversion from S to T. // We can do a simple optimization here if we know that the source is never null: BoundExpression nonNullValue = NullableAlwaysHasValue(rewrittenOperand); if (nonNullValue != null) { return MakeConversion(nonNullValue, rewrittenType, @checked); } // (If the source is known to be null then we need to keep the call to get Value // in place so that it throws at runtime.) MethodSymbol get_Value = GetNullableMethod(syntax, rewrittenOperandType, SpecialMember.System_Nullable_T_get_Value); return MakeConversion(BoundCall.Synthesized(syntax, rewrittenOperand, get_Value), rewrittenType, @checked); } }
private static bool HasImplicitEnumerationConversion(BoundExpression source, TypeSymbol destination) { Debug.Assert((object)source != null); Debug.Assert((object)destination != null); // SPEC: An implicit enumeration conversion permits the decimal-integer-literal 0 to be converted to any enum-type // SPEC: and to any nullable-type whose underlying type is an enum-type. // // For historical reasons we actually allow a conversion from any *numeric constant // zero* to be converted to any enum type, not just the literal integer zero. bool validType = destination.IsEnumType() || destination.IsNullableType() && destination.GetNullableUnderlyingType().IsEnumType(); if (!validType) { return false; } var sourceConstantValue = source.ConstantValue; return sourceConstantValue != null && IsNumericType(source.Type.GetSpecialTypeSafe()) && IsConstantNumericZero(sourceConstantValue); }
private BoundExpression RewriteFullyLiftedBuiltInConversion( CSharpSyntaxNode syntax, BoundExpression operand, ConversionKind kind, bool @checked, TypeSymbol type) { // SPEC: If the nullable conversion is from S? to T?: // SPEC: * If the source HasValue property is false the result // SPEC: is a null value of type T?. // SPEC: * Otherwise the conversion is evaluated as an unwrapping // SPEC: from S? to S, followed by the underlying conversion from // SPEC: S to T, followed by a wrapping from T to T? BoundExpression optimized = OptimizeLiftedBuiltInConversion(syntax, operand, kind, @checked, type); if (optimized != null) { return optimized; } // We are unable to optimize the conversion. "(T?)s" is generated as: // S? temp = s; // temp.HasValue ? new T?((T)temp.GetValueOrDefault()) : default(T?) BoundAssignmentOperator tempAssignment; var boundTemp = factory.StoreToTemp(operand, out tempAssignment); MethodSymbol get_HasValue = GetNullableMethod(syntax, boundTemp.Type, SpecialMember.System_Nullable_T_get_HasValue); MethodSymbol getValueOrDefault = GetNullableMethod(syntax, boundTemp.Type, SpecialMember.System_Nullable_T_GetValueOrDefault); BoundExpression condition = BoundCall.Synthesized(syntax, boundTemp, get_HasValue); BoundExpression consequence = new BoundObjectCreationExpression( syntax, GetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor), MakeConversion( BoundCall.Synthesized(syntax, boundTemp, getValueOrDefault), type.GetNullableUnderlyingType(), @checked)); BoundExpression alternative = new BoundDefaultOperator(syntax, null, type); BoundExpression conditionalExpression = RewriteConditionalOperator( syntax: syntax, rewrittenCondition: condition, rewrittenConsequence: consequence, rewrittenAlternative: alternative, constantValueOpt: null, rewrittenType: type); return new BoundSequence( syntax: syntax, locals: ImmutableArray.Create(boundTemp.LocalSymbol), sideEffects: ImmutableArray.Create<BoundExpression>(tempAssignment), value: conditionalExpression, type: type); }
private BoundExpression MakeLiftedBinaryOperatorConsequence( CSharpSyntaxNode syntax, BinaryOperatorKind kind, BoundExpression left, BoundExpression right, TypeSymbol type, MethodSymbol method) { // tempX.GetValueOrDefault() OP tempY.GetValueOrDefault() BoundExpression unliftedOp = MakeBinaryOperator( syntax: syntax, operatorKind: kind.Unlifted(), loweredLeft: left, loweredRight: right, type: type.GetNullableUnderlyingType(), method: method); // new R?(tempX.GetValueOrDefault() OP tempY.GetValueOrDefault) return new BoundObjectCreationExpression( syntax, GetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor), unliftedOp); }
private BoundExpression OptimizeLiftedBuiltInConversion( CSharpSyntaxNode syntax, BoundExpression operand, ConversionKind kind, bool @checked, TypeSymbol type) { Debug.Assert(operand != null); Debug.Assert((object)type != null); // First, an optimization. If the source is known to always be null then // we can simply return the alternative. if (NullableNeverHasValue(operand)) { return new BoundDefaultOperator(syntax, null, type); } // Second, a trickier optimization. If the conversion is "(T?)(new S?(x))" then // we generate "new T?((T)x)" BoundExpression nonNullValue = NullableAlwaysHasValue(operand); if (nonNullValue != null) { return new BoundObjectCreationExpression( syntax, GetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor), MakeConversion( nonNullValue, type.GetNullableUnderlyingType(), @checked)); } // Third, a very tricky optimization. return DistributeLiftedConversionIntoLiftedOperand(syntax, operand, kind, @checked, null, type); }