private BoundExpression MakeAsOperator( BoundAsOperator oldNode, CSharpSyntaxNode syntax, BoundExpression rewrittenOperand, BoundTypeExpression rewrittenTargetType, Conversion conversion, TypeSymbol rewrittenType) { // TODO: Handle dynamic operand type and target type Debug.Assert(rewrittenTargetType.Type.Equals(rewrittenType)); // target type cannot be a non-nullable value type Debug.Assert(!rewrittenType.IsValueType || rewrittenType.IsNullableType()); if (!_inExpressionLambda) { ConstantValue constantValue = Binder.GetAsOperatorConstantResult(rewrittenOperand.Type, rewrittenType, conversion.Kind, rewrittenOperand.ConstantValue); if (constantValue != null) { Debug.Assert(constantValue.IsNull); BoundExpression result = rewrittenType.IsNullableType() ? new BoundDefaultOperator(syntax, rewrittenType) : MakeLiteral(syntax, constantValue, rewrittenType); if (rewrittenOperand.ConstantValue != null) { // No need to preserve any side-effects from the operand. // We also can keep the "constant" notion of the result, which // enables some optimizations down the road. return result; } return new BoundSequence( syntax: syntax, locals: ImmutableArray<LocalSymbol>.Empty, sideEffects: ImmutableArray.Create<BoundExpression>(rewrittenOperand), value: result, type: rewrittenType); } if (conversion.IsImplicit) { // Operand with bound implicit conversion to target type. // We don't need a runtime check, generate a conversion for the operand instead. return MakeConversionNode(syntax, rewrittenOperand, conversion, rewrittenType, @checked: false); } } return oldNode.Update(rewrittenOperand, rewrittenTargetType, conversion, rewrittenType); }
private BoundExpression VisitExpressionImpl(BoundExpression node) { ConstantValue constantValue = node.ConstantValue; if (constantValue != null) { TypeSymbol type = node.Type; if (type?.IsNullableType() != true) { return(MakeLiteral(node.Syntax, constantValue, type)); } } var visited = VisitExpressionWithStackGuard(node); // If you *really* need to change the type, consider using an indirect method // like compound assignment does (extra flag only passed when it is an expression // statement means that this constraint is not violated). // Dynamic type will be erased in emit phase. It is considered equivalent to Object in lowered bound trees. // Unused deconstructions are lowered to produce a return value that isn't a tuple type. Debug.Assert(visited == null || visited.HasErrors || ReferenceEquals(visited.Type, node.Type) || visited.Type.Equals(node.Type, TypeCompareKind.IgnoreDynamicAndTupleNames) || IsUnusedDeconstruction(node)); return(visited); }
private TypeWithAnnotations(TypeSymbol defaultType, NullableAnnotation nullableAnnotation, Extensions extensions) { Debug.Assert(defaultType?.IsNullableType() != true || (nullableAnnotation != NullableAnnotation.Oblivious && nullableAnnotation != NullableAnnotation.NotAnnotated)); Debug.Assert(extensions != null); _defaultType = defaultType; NullableAnnotation = nullableAnnotation; _extensions = extensions; }
private TypeWithAnnotations(TypeSymbol defaultType, NullableAnnotation nullableAnnotation, Extensions extensions) { Debug.Assert(defaultType?.IsNullableType() != true || nullableAnnotation == NullableAnnotation.Annotated); Debug.Assert(extensions != null); DefaultType = defaultType; NullableAnnotation = nullableAnnotation; _extensions = extensions; }
private TypeSymbolWithAnnotations(TypeSymbol defaultType, INonNullTypesContext nonNullTypesContext, bool isAnnotated, bool treatPossiblyNullableReferenceTypeTypeParameterAsNullable, Extensions extensions) { Debug.Assert((object)defaultType != null); Debug.Assert(!defaultType.IsNullableType() || isAnnotated); Debug.Assert(nonNullTypesContext != null); Debug.Assert(extensions != null); _defaultType = defaultType; IsAnnotated = isAnnotated; _treatPossiblyNullableReferenceTypeTypeParameterAsNullable = treatPossiblyNullableReferenceTypeTypeParameterAsNullable; NonNullTypesContext = nonNullTypesContext; _extensions = extensions; }
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)); }
private static bool SatisfiesConstraintType( ConversionsBase conversions, TypeSymbol typeArgument, TypeSymbol constraintType, ref HashSet <DiagnosticInfo> useSiteDiagnostics) { if (constraintType.IsErrorType()) { return(false); } // Spec 4.4.4 describes the valid conversions from // type argument A to constraint type C: // "An identity conversion (6.1.1). // An implicit reference conversion (6.1.6). ..." if (conversions.HasIdentityOrImplicitReferenceConversion(typeArgument, constraintType, ref useSiteDiagnostics)) { return(true); } // "... A boxing conversion (6.1.7), provided that type A is a non-nullable value type. ..." // NOTE: we extend this to allow, for example, a conversion from Nullable<T> to object. if (typeArgument.IsValueType && conversions.HasBoxingConversion(typeArgument.IsNullableType() ? ((NamedTypeSymbol)typeArgument).ConstructedFrom : typeArgument, constraintType, ref useSiteDiagnostics)) { return(true); } if (typeArgument.TypeKind == TypeKind.TypeParameter) { var typeParameter = (TypeParameterSymbol)typeArgument; // "... An implicit reference, boxing, or type parameter conversion // from type parameter A to C." if (conversions.HasImplicitTypeParameterConversion(typeParameter, constraintType, ref useSiteDiagnostics)) { return(true); } // TypeBind::SatisfiesBound allows cases where one of the // type parameter constraints satisfies the constraint. foreach (var typeArgumentConstraint in typeParameter.ConstraintTypesWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics)) { if (SatisfiesConstraintType(conversions, typeArgumentConstraint, constraintType, ref useSiteDiagnostics)) { return(true); } } } return(false); }
private BoundExpression ConvertCaseExpression(TypeSymbol switchGoverningType, CSharpSyntaxNode node, BoundExpression caseExpression, 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 = 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 = 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 = this.CreateConversion(caseExpression, conversion, switchGoverningType, diagnostics); } if (switchGoverningType.IsNullableType() && convertedCaseExpression.Kind == BoundKind.Conversion) { constantValueOpt = ((BoundConversion)convertedCaseExpression).Operand.ConstantValue; } else { constantValueOpt = convertedCaseExpression.ConstantValue; } return(convertedCaseExpression); }
public static bool IsNullableTypeOrTypeParameter(this TypeSymbol type) { if (type.TypeKind == TypeKind.TypeParameter) { var constraintTypes = ((TypeParameterSymbol)type).ConstraintTypesNoUseSiteDiagnostics; foreach (var constraintType in constraintTypes) { if (IsNullableTypeOrTypeParameter(constraintType)) { return(true); } } return(false); } return(type.IsNullableType()); }
private void GetUserDefinedUnaryOperatorsFromType( TypeSymbol constrainedToTypeOpt, NamedTypeSymbol type, UnaryOperatorKind kind, string name, ArrayBuilder <UnaryOperatorSignature> operators) { foreach (MethodSymbol op in type.GetOperators(name)) { // If we're in error recovery, we might have bad operators. Just ignore it. if (op.ParameterCount != 1 || op.ReturnsVoid) { continue; } TypeSymbol operandType = op.GetParameterType(0); TypeSymbol resultType = op.ReturnType; operators.Add(new UnaryOperatorSignature(UnaryOperatorKind.UserDefined | kind, operandType, resultType, op, constrainedToTypeOpt)); // SPEC: For the unary operators + ++ - -- ! ~ a lifted form of an operator exists // SPEC: if the operand and its result types are both non-nullable value types. // SPEC: The lifted form is constructed by adding a single ? modifier to the // SPEC: operator and result types. switch (kind) { case UnaryOperatorKind.UnaryPlus: case UnaryOperatorKind.PrefixDecrement: case UnaryOperatorKind.PrefixIncrement: case UnaryOperatorKind.UnaryMinus: case UnaryOperatorKind.PostfixDecrement: case UnaryOperatorKind.PostfixIncrement: case UnaryOperatorKind.LogicalNegation: case UnaryOperatorKind.BitwiseComplement: if (operandType.IsValueType && !operandType.IsNullableType() && resultType.IsValueType && !resultType.IsNullableType()) { operators.Add(new UnaryOperatorSignature( UnaryOperatorKind.Lifted | UnaryOperatorKind.UserDefined | kind, MakeNullable(operandType), MakeNullable(resultType), op, constrainedToTypeOpt)); } break; } } }
/// <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); }
internal static TypeWithAnnotations Create(TypeSymbol typeSymbol, NullableAnnotation nullableAnnotation = NullableAnnotation.Oblivious, ImmutableArray<CustomModifier> customModifiers = default) { if (typeSymbol is null && nullableAnnotation == 0) { return default; } switch (nullableAnnotation) { case NullableAnnotation.Oblivious: case NullableAnnotation.NotAnnotated: if (typeSymbol?.IsNullableType() == true) { // int?, T? where T : struct (add annotation) nullableAnnotation = NullableAnnotation.Annotated; } break; } return CreateNonLazyType(typeSymbol, nullableAnnotation, customModifiers.NullToEmpty()); }
private static ConversionKind ClassifyNullLiteralConversion(BoundExpression source, TypeSymbol destination) { Debug.Assert((object)source != null); Debug.Assert((object)destination != null); if (!source.IsLiteralNull()) { return(ConversionKind.NoConversion); } // SPEC: An implicit conversion exists from the null literal to any nullable type. if (destination.IsNullableType()) { // The spec defines a "null literal conversion" specifically as a conversion from // null to nullable type. return(ConversionKind.NullLiteral); } // SPEC: An implicit conversion exists from the null literal to any reference type. // SPEC: An implicit conversion exists from the null literal to type parameter T, // SPEC: provided T is known to be a reference type. [...] The conversion [is] classified // SPEC: as implicit reference conversion. if (destination.IsReferenceType) { return(ConversionKind.ImplicitReference); } // SPEC: The set of implicit conversions is extended to include... // SPEC: ... from the null literal to any pointer type. if (destination is PointerTypeSymbol) { return(ConversionKind.NullToPointer); } return(ConversionKind.NoConversion); }
private BoundExpression MakeAsOperator( BoundAsOperator oldNode, CSharpSyntaxNode syntax, BoundExpression rewrittenOperand, BoundTypeExpression rewrittenTargetType, Conversion conversion, TypeSymbol rewrittenType) { // TODO: Handle dynamic operand type and target type Debug.Assert(rewrittenTargetType.Type.Equals(rewrittenType)); // target type cannot be a non-nullable value type Debug.Assert(!rewrittenType.IsValueType || rewrittenType.IsNullableType()); if (!inExpressionLambda) { ConstantValue constantValue = Binder.GetAsOperatorConstantResult(rewrittenOperand.Type, rewrittenType, conversion.Kind, rewrittenOperand.ConstantValue); Debug.Assert(constantValue == null || constantValue.IsNull); if (conversion.IsImplicit) { // Operand with bound implicit conversion to target type. // We don't need a runtime check, generate a conversion for the operand instead. return(MakeConversion(syntax, rewrittenOperand, conversion, rewrittenType, @checked: false, constantValueOpt: constantValue)); } else if (constantValue != null) { return(new BoundSequence( syntax: syntax, locals: ImmutableArray <LocalSymbol> .Empty, sideEffects: ImmutableArray.Create <BoundExpression>(rewrittenOperand), value: MakeLiteral(syntax, constantValue, rewrittenType), type: rewrittenType)); } } return(oldNode.Update(rewrittenOperand, rewrittenTargetType, conversion, rewrittenType)); }
private BoundExpression MakeAsOperator( BoundAsOperator oldNode, CSharpSyntaxNode syntax, BoundExpression rewrittenOperand, BoundTypeExpression rewrittenTargetType, Conversion conversion, TypeSymbol rewrittenType) { // TODO: Handle dynamic operand type and target type Debug.Assert(rewrittenTargetType.Type.Equals(rewrittenType)); // target type cannot be a non-nullable value type Debug.Assert(!rewrittenType.IsValueType || rewrittenType.IsNullableType()); if (!inExpressionLambda) { ConstantValue constantValue = Binder.GetAsOperatorConstantResult(rewrittenOperand.Type, rewrittenType, conversion.Kind, rewrittenOperand.ConstantValue); Debug.Assert(constantValue == null || constantValue.IsNull); if (conversion.IsImplicit) { // Operand with bound implicit conversion to target type. // We don't need a runtime check, generate a conversion for the operand instead. return MakeConversion(syntax, rewrittenOperand, conversion, rewrittenType, @checked: false, constantValueOpt: constantValue); } else if (constantValue != null) { return new BoundSequence( syntax: syntax, locals: ImmutableArray<LocalSymbol>.Empty, sideEffects: ImmutableArray.Create<BoundExpression>(rewrittenOperand), value: MakeLiteral(syntax, constantValue, rewrittenType), type: rewrittenType); } } return oldNode.Update(rewrittenOperand, rewrittenTargetType, conversion, rewrittenType); }
private BoundExpression VisitExpressionImpl(BoundExpression node) { ConstantValue constantValue = node.ConstantValue; if (constantValue != null) { TypeSymbol type = node.Type; if (((object)type == null || !type.IsNullableType())) { return(MakeLiteral(node.Syntax, constantValue, type)); } } var visited = (BoundExpression)base.Visit(node); // If you *really* need to change the type, consider using an indirect method // like compound assignment does (extra flag only passed when it is an expression // statement means that this constraint is not violated). // Dynamic type will be erased in emit phase. It is considered equivalent to Object in lowered bound trees. Debug.Assert(visited == null || visited.HasErrors || ReferenceEquals(visited.Type, node.Type) || visited.Type.Equals(node.Type, ignoreDynamic: true)); return(visited); }
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; } else if (conversion.ConversionKind == ConversionKind.NoConversion && convertedExpression.Type?.IsErrorType() == true) { convertedExpression = operand; } } constantValue = convertedExpression.ConstantValue; return(convertedExpression); }
public TypeSymbol EmitConvertToPhpValue(TypeSymbol from, TypeRefMask fromHint) { // Nullable<T> -> HasValue ? T : NULL if (from.IsNullableType()) { from = EmitNullableCastToNull(from, false); // (HasValue ? Value : NULL) } var conv = DeclaringCompilation.ClassifyCommonConversion(from, CoreTypes.PhpValue.Symbol); if (conv.IsImplicit) { this.EmitConversion(conv, from, CoreTypes.PhpValue.Symbol); } else { // some conversion we did not implement as operator yet: if (from.IsReferenceType) { EmitCall(ILOpCode.Call, CoreMethods.PhpValue.FromClass_Object).Expect(CoreTypes.PhpValue); } else if (from.SpecialType == SpecialType.System_Void) { // PhpValue.Void Emit_PhpValue_Void(); } else { // box & wrap to PhpValue.Object // Template: PhpValue.FromStruct<T>( STACK ) EmitCall(ILOpCode.Call, CoreMethods.PhpValue.FromStruct_T.Symbol.Construct(from)).Expect(CoreTypes.PhpValue); } } // return(CoreTypes.PhpValue); }
public TypeSymbol EmitConvertToPhpValue(TypeSymbol from, TypeRefMask fromHint) { // Nullable<T> -> HasValue ? T : NULL if (from.IsNullableType()) { from = EmitNullableCastToNull(from, false); // (HasValue ? Value : NULL) } var conv = DeclaringCompilation.ClassifyCommonConversion(from, CoreTypes.PhpValue.Symbol); if (conv.IsImplicit) { this.EmitConversion(conv, from, CoreTypes.PhpValue.Symbol); } else { // some conversion we did not implement as operator yet: if (from.IsReferenceType) { EmitCall(ILOpCode.Call, CoreMethods.PhpValue.FromClass_Object) .Expect(CoreTypes.PhpValue); } else if (from.SpecialType == SpecialType.System_Void) { // PhpValue.Void Emit_PhpValue_Void(); } else { throw ExceptionUtilities.NotImplementedException(this, $"{from.Name} -> PhpValue"); } } // return(CoreTypes.PhpValue); }
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 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 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); } }
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) { 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; }
/// <summary> /// This method find the set of applicable user-defined and lifted conversion operators, u. /// The set consists of the user-defined and lifted implicit conversion operators declared by /// the classes and structs in d that convert from a type encompassing source to a type encompassed by target. /// However if allowAnyTarget is true, then it considers all operators that convert from a type encompassing source /// to any target. This flag must be set only if we are computing user defined conversions from a given source /// type to any target type. /// </summary> /// <remarks> /// Currently allowAnyTarget flag is only set to true by <see cref="AnalyzeImplicitUserDefinedConversionForV6SwitchGoverningType"/>, /// where we must consider user defined implicit conversions from the type of the switch expression to /// any of the possible switch governing types. /// </remarks> private void ComputeApplicableUserDefinedImplicitConversionSet( BoundExpression sourceExpression, TypeSymbol source, TypeSymbol target, ArrayBuilder <NamedTypeSymbol> d, ArrayBuilder <UserDefinedConversionAnalysis> u, ref HashSet <DiagnosticInfo> useSiteDiagnostics, bool allowAnyTarget = false) { Debug.Assert(sourceExpression != null || (object)source != null); Debug.Assert(((object)target != null) == !allowAnyTarget); Debug.Assert(d != null); Debug.Assert(u != 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 conversion operators // SPEC: declared by the classes and structs in D that convert from a type encompassing // SPEC: E to a type encompassed by T. If U is empty, the conversion is undefined and // SPEC: a compile-time error occurs. // SPEC: Give a user-defined conversion operator that converts from a non-nullable // SPEC: value type S to a non-nullable value type T, a lifted conversion operator // SPEC: exists that converts from S? to 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 bug 17021) // The specification defines a type U as "encompassing" a type V // if there is a standard implicit conversion from U to V, and // neither are interface types. // // The intention of this language is to ensure that we do not allow user-defined // conversions that involve interfaces. We have a reasonable expectation that a // conversion that involves an interface is one that preserves referential identity, // and user-defined conversions usually do not. // // Now, suppose we have a standard conversion from Alpha to Beta, a user-defined // conversion from Beta to Gamma, and a standard conversion from Gamma to Delta. // The specification allows the implicit conversion from Alpha to Delta only if // Beta encompasses Alpha and Delta encompasses Gamma. And therefore, none of them // can be interface types, de jure. // // However, the dev10 compiler only checks Alpha and Delta to see if they are interfaces, // and allows Beta and Gamma to be interfaces. // // So what's the big deal there? It's not legal to define a user-defined conversion where // the input or output types are interfaces, right? // // It is not legal to define such a conversion, no, but it is legal to create one via generic // construction. If we have a conversion from T to C<T>, then C<I> has a conversion from I to C<I>. // // The dev10 compiler fails to check for this situation. This means that, // you can convert from int to C<IComparable> because int implements IComparable, but cannot // convert from IComparable to C<IComparable>! // // Unfortunately, we know of several real programs that rely upon this bug, so we are going // to reproduce it here. if ((object)source != null && source.IsInterfaceType() || (object)target != null && target.IsInterfaceType()) { return; } foreach (NamedTypeSymbol declaringType in d) { foreach (MethodSymbol op in declaringType.GetOperators(WellKnownMemberNames.ImplicitConversionName)) { // We might have a bad operator and be in an error recovery situation. Ignore it. if (op.ReturnsVoid || op.ParameterCount != 1) { continue; } TypeSymbol convertsFrom = op.ParameterTypes[0]; TypeSymbol convertsTo = op.ReturnType; Conversion fromConversion = EncompassingImplicitConversion(sourceExpression, source, convertsFrom, ref useSiteDiagnostics); Conversion toConversion = allowAnyTarget ? Conversion.Identity : EncompassingImplicitConversion(null, convertsTo, target, ref useSiteDiagnostics); if (fromConversion.Exists && toConversion.Exists) { // 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 an operator. // // We perpetuate this fiction here. if ((object)target != null && target.IsNullableType() && convertsTo.IsNonNullableValueType()) { convertsTo = MakeNullableType(convertsTo); toConversion = allowAnyTarget ? Conversion.Identity : EncompassingImplicitConversion(null, convertsTo, target, ref useSiteDiagnostics); } u.Add(UserDefinedConversionAnalysis.Normal(op, fromConversion, toConversion, convertsFrom, convertsTo)); } else if ((object)source != null && source.IsNullableType() && convertsFrom.IsNonNullableValueType() && (allowAnyTarget || target.CanBeAssignedNull())) { // As mentioned above, here we diverge from the specification, in two ways. // First, we only check for the lifted form if the normal form was inapplicable. // Second, we are supposed to apply lifting semantics only if the conversion // parameter and return types are *both* non-nullable value types. // // In fact the native compiler determines whether to check for a lifted form on // the basis of: // // * Is the type we are ultimately converting from a nullable value type? // * Is the parameter type of the conversion a non-nullable value type? // * Is the type we are ultimately converting to a nullable value type, // pointer type, or reference type? // // If the answer to all those questions is "yes" then we lift to nullable // and see if the resulting operator is applicable. TypeSymbol nullableFrom = MakeNullableType(convertsFrom); TypeSymbol nullableTo = convertsTo.IsNonNullableValueType() ? MakeNullableType(convertsTo) : convertsTo; Conversion liftedFromConversion = EncompassingImplicitConversion(sourceExpression, source, nullableFrom, ref useSiteDiagnostics); Conversion liftedToConversion = !allowAnyTarget? EncompassingImplicitConversion(null, nullableTo, target, ref useSiteDiagnostics) : Conversion.Identity; if (liftedFromConversion.Exists && liftedToConversion.Exists) { u.Add(UserDefinedConversionAnalysis.Lifted(op, liftedFromConversion, liftedToConversion, nullableFrom, nullableTo)); } } } } }
private static ConversionKind ClassifyNullLiteralConversion(BoundExpression source, TypeSymbol destination) { Debug.Assert((object)source != null); Debug.Assert((object)destination != null); if (!source.IsLiteralNull()) { return ConversionKind.NoConversion; } // SPEC: An implicit conversion exists from the null literal to any nullable type. if (destination.IsNullableType()) { // The spec defines a "null literal conversion" specifically as a conversion from // null to nullable type. return ConversionKind.NullLiteral; } // SPEC: An implicit conversion exists from the null literal to any reference type. // SPEC: An implicit conversion exists from the null literal to type parameter T, // SPEC: provided T is known to be a reference type. [...] The conversion [is] classified // SPEC: as implicit reference conversion. if (destination.IsReferenceType) { return ConversionKind.ImplicitReference; } // SPEC: The set of implicit conversions is extended to include... // SPEC: ... from the null literal to any pointer type. if (destination is PointerTypeSymbol) { return ConversionKind.NullToPointer; } return ConversionKind.NoConversion; }
private static bool SatisfiesConstraintType( ConversionsBase conversions, TypeSymbol typeArgument, TypeSymbol constraintType, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { if (constraintType.IsErrorType()) { return false; } // Spec 4.4.4 describes the valid conversions from // type argument A to constraint type C: // "An identity conversion (6.1.1). // An implicit reference conversion (6.1.6). ..." if (conversions.HasIdentityOrImplicitReferenceConversion(typeArgument, constraintType, ref useSiteDiagnostics)) { return true; } // "... A boxing conversion (6.1.7), provided that type A is a non-nullable value type. ..." // NOTE: we extend this to allow, for example, a conversion from Nullable<T> to object. if (typeArgument.IsValueType && conversions.HasBoxingConversion(typeArgument.IsNullableType() ? ((NamedTypeSymbol)typeArgument).ConstructedFrom : typeArgument, constraintType, ref useSiteDiagnostics)) { return true; } if (typeArgument.TypeKind == TypeKind.TypeParameter) { var typeParameter = (TypeParameterSymbol)typeArgument; // "... An implicit reference, boxing, or type parameter conversion // from type parameter A to C." if (conversions.HasImplicitTypeParameterConversion(typeParameter, constraintType, ref useSiteDiagnostics)) { return true; } // TypeBind::SatisfiesBound allows cases where one of the // type parameter constraints satisfies the constraint. foreach (var typeArgumentConstraint in typeParameter.ConstraintTypesWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics)) { if (SatisfiesConstraintType(conversions, typeArgumentConstraint, constraintType, ref useSiteDiagnostics)) { return true; } } } return false; }
/// <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); }
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 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; }
// 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); }
/// <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); }
public static TypeSymbol StrippedType(this TypeSymbol type) { return(type.IsNullableType() ? type.GetNullableUnderlyingType() : type); }
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(); }
// See TypeBind::CheckSingleConstraint. private static bool CheckConstraints( Symbol containingSymbol, ConversionsBase conversions, TypeMap substitution, TypeParameterSymbol typeParameter, TypeSymbol typeArgument, Compilation currentCompilation, ArrayBuilder<TypeParameterDiagnosticInfo> diagnosticsBuilder, ref ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder, HashSet<TypeParameterSymbol> ignoreTypeConstraintsDependentOnTypeParametersOpt) { Debug.Assert(substitution != null); // The type parameters must be original definitions of type parameters from the containing symbol. Debug.Assert(ReferenceEquals(typeParameter.ContainingSymbol, containingSymbol.OriginalDefinition)); if (typeArgument.IsErrorType()) { return true; } if (typeArgument.IsPointerType() || typeArgument.IsRestrictedType() || typeArgument.SpecialType == SpecialType.System_Void) { // "The type '{0}' may not be used as a type argument" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new CSDiagnosticInfo(ErrorCode.ERR_BadTypeArgument, typeArgument))); return false; } if (typeArgument.IsStatic) { // "'{0}': static types cannot be used as type arguments" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new CSDiagnosticInfo(ErrorCode.ERR_GenericArgIsStaticClass, typeArgument))); return false; } if (typeParameter.HasReferenceTypeConstraint && !typeArgument.IsReferenceType) { // "The type '{2}' must be a reference type in order to use it as parameter '{1}' in the generic type or method '{0}'" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new CSDiagnosticInfo(ErrorCode.ERR_RefConstraintNotSatisfied, containingSymbol.ConstructedFrom(), typeParameter, typeArgument))); return false; } if (typeParameter.HasValueTypeConstraint && !typeArgument.IsNonNullableValueType()) { // "The type '{2}' must be a non-nullable value type in order to use it as parameter '{1}' in the generic type or method '{0}'" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new CSDiagnosticInfo(ErrorCode.ERR_ValConstraintNotSatisfied, containingSymbol.ConstructedFrom(), typeParameter, typeArgument))); return false; } // The type parameters for a constructed type/method are the type parameters of // the ConstructedFrom type/method, so the constraint types are not substituted. // For instance with "class C<T, U> where T : U", the type parameter for T in "C<object, int>" // has constraint "U", not "int". We need to substitute the constraints from the // original definition of the type parameters using the map from the constructed symbol. var constraintTypes = ArrayBuilder<TypeSymbol>.GetInstance(); HashSet<DiagnosticInfo> useSiteDiagnostics = null; substitution.SubstituteTypesDistinctWithoutModifiers(typeParameter.ConstraintTypesWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics), constraintTypes, ignoreTypeConstraintsDependentOnTypeParametersOpt); bool hasError = false; foreach (var constraintType in constraintTypes) { if (SatisfiesConstraintType(conversions, typeArgument, constraintType, ref useSiteDiagnostics)) { continue; } ErrorCode errorCode; if (typeArgument.IsReferenceType) { errorCode = ErrorCode.ERR_GenericConstraintNotSatisfiedRefType; } else if (typeArgument.IsNullableType()) { errorCode = constraintType.IsInterfaceType() ? ErrorCode.ERR_GenericConstraintNotSatisfiedNullableInterface : ErrorCode.ERR_GenericConstraintNotSatisfiedNullableEnum; } else if (typeArgument.TypeKind == TypeKind.TypeParameter) { errorCode = ErrorCode.ERR_GenericConstraintNotSatisfiedTyVar; } else { errorCode = ErrorCode.ERR_GenericConstraintNotSatisfiedValType; } SymbolDistinguisher distinguisher = new SymbolDistinguisher(currentCompilation, constraintType, typeArgument); diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new CSDiagnosticInfo(errorCode, containingSymbol.ConstructedFrom(), distinguisher.First, typeParameter, distinguisher.Second))); hasError = true; } if (AppendUseSiteDiagnostics(useSiteDiagnostics, typeParameter, ref useSiteDiagnosticsBuilder)) { hasError = true; } constraintTypes.Free(); // Check the constructor constraint. if (typeParameter.HasConstructorConstraint && !SatisfiesConstructorConstraint(typeArgument)) { // "'{2}' must be a non-abstract type with a public parameterless constructor in order to use it as parameter '{1}' in the generic type or method '{0}'" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new CSDiagnosticInfo(ErrorCode.ERR_NewConstraintNotSatisfied, containingSymbol.ConstructedFrom(), typeParameter, typeArgument))); return false; } return !hasError; }
/// <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); }
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 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; } }
public ConstantValue FoldConstantConversion( CSharpSyntaxNode syntax, BoundExpression source, Conversion conversion, TypeSymbol destination, DiagnosticBag diagnostics) { Debug.Assert(source != null); Debug.Assert((object)destination != null); // The diagnostics bag can be null in cases where we know ahead of time that the // conversion will succeed without error or warning. (For example, if we have a valid // implicit numeric conversion on a constant of numeric type.) // SPEC: A constant expression must be the null literal or a value with one of // SPEC: the following types: sbyte, byte, short, ushort, int, uint, long, // SPEC: ulong, char, float, double, decimal, bool, string, or any enumeration type. // SPEC: The following conversions are permitted in constant expressions: // SPEC: Identity conversions // SPEC: Numeric conversions // SPEC: Enumeration conversions // SPEC: Constant expression conversions // SPEC: Implicit and explicit reference conversions, provided that the source of the conversions // SPEC: is a constant expression that evaluates to the null value. // SPEC VIOLATION: C# has always allowed the following, even though this does violate the rule that // SPEC VIOLATION: a constant expression must be either the null literal, or an expression of one // SPEC VIOLATION: of the given types. // SPEC VIOLATION: const C c = (C)null; // TODO: Some conversions can produce errors or warnings depending on checked/unchecked. // TODO: Fold conversions on enums and strings too. if (source.HasAnyErrors) { return null; } var sourceConstantValue = source.ConstantValue; if (sourceConstantValue == null || sourceConstantValue.IsBad) { return sourceConstantValue; } switch (conversion.Kind) { case ConversionKind.Identity: case ConversionKind.NullLiteral: return sourceConstantValue; case ConversionKind.ImplicitConstant: return FoldConstantNumericConversion(syntax, sourceConstantValue, destination, diagnostics); case ConversionKind.ExplicitNumeric: case ConversionKind.ImplicitNumeric: case ConversionKind.ExplicitEnumeration: case ConversionKind.ImplicitEnumeration: // The C# specification categorizes conversion from literal zero to nullable enum as // an Implicit Enumeration Conversion. Such a thing should not be constant folded // because nullable enums are never constants. if (destination.IsNullableType()) { return null; } return FoldConstantNumericConversion(syntax, sourceConstantValue, destination, diagnostics); case ConversionKind.ExplicitReference: case ConversionKind.ImplicitReference: return sourceConstantValue.IsNull ? sourceConstantValue : null; } return null; }
private TypeSymbol TypeOrUnderlyingType(TypeSymbol type) { if (type.IsNullableType()) { return type.GetNullableUnderlyingType(); } return type; }
private static LiftingResult UserDefinedBinaryOperatorCanBeLifted(TypeSymbol left, TypeSymbol right, TypeSymbol result, BinaryOperatorKind kind) { // SPEC: For the binary operators + - * / % & | ^ << >> a lifted form of the // SPEC: operator exists if the operand and result types are all non-nullable // SPEC: value types. The lifted form is constructed by adding a single ? // SPEC: modifier to each operand and result type. // // SPEC: For the equality operators == != a lifted form of the operator exists // SPEC: if the operand types are both non-nullable value types and if the // SPEC: result type is bool. The lifted form is constructed by adding // SPEC: a single ? modifier to each operand type. // // SPEC: For the relational operators > < >= <= a lifted form of the // SPEC: operator exists if the operand types are both non-nullable value // SPEC: types and if the result type is bool. The lifted form is // SPEC: constructed by adding a single ? modifier to each operand type. if (!left.IsValueType || left.IsNullableType() || !right.IsValueType || right.IsNullableType()) { return LiftingResult.NotLifted; } switch (kind) { case BinaryOperatorKind.Equal: case BinaryOperatorKind.NotEqual: // Spec violation: can't lift unless the types match. // The spec doesn't require this, but dev11 does and it reduces ambiguity in some cases. if (left != right) return LiftingResult.NotLifted; goto case BinaryOperatorKind.GreaterThan; case BinaryOperatorKind.GreaterThan: case BinaryOperatorKind.GreaterThanOrEqual: case BinaryOperatorKind.LessThan: case BinaryOperatorKind.LessThanOrEqual: return result.SpecialType == SpecialType.System_Boolean ? LiftingResult.LiftOperandsButNotResult : LiftingResult.NotLifted; default: return result.IsValueType && !result.IsNullableType() ? LiftingResult.LiftOperandsAndResult : LiftingResult.NotLifted; } }
// See TypeBind::CheckSingleConstraint. private static bool CheckConstraints( Symbol containingSymbol, ConversionsBase conversions, TypeMap substitution, TypeParameterSymbol typeParameter, TypeSymbol typeArgument, Compilation currentCompilation, ArrayBuilder <TypeParameterDiagnosticInfo> diagnosticsBuilder, ref ArrayBuilder <TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder) { Debug.Assert(substitution != null); // The type parameters must be original definitions of type parameters from the containing symbol. Debug.Assert(ReferenceEquals(typeParameter.ContainingSymbol, containingSymbol.OriginalDefinition)); if (typeArgument.IsErrorType()) { return(true); } if (typeArgument.IsPointerType() || typeArgument.IsRestrictedType() || typeArgument.SpecialType == SpecialType.System_Void) { // "The type '{0}' may not be used as a type argument" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new CSDiagnosticInfo(ErrorCode.ERR_BadTypeArgument, typeArgument))); return(false); } if (typeArgument.IsStatic) { // "'{0}': static types cannot be used as type arguments" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new CSDiagnosticInfo(ErrorCode.ERR_GenericArgIsStaticClass, typeArgument))); return(false); } if (typeParameter.HasReferenceTypeConstraint && !typeArgument.IsReferenceType) { // "The type '{2}' must be a reference type in order to use it as parameter '{1}' in the generic type or method '{0}'" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new CSDiagnosticInfo(ErrorCode.ERR_RefConstraintNotSatisfied, containingSymbol.ConstructedFrom(), typeParameter, typeArgument))); return(false); } if (typeParameter.HasValueTypeConstraint && !typeArgument.IsNonNullableValueType()) { // "The type '{2}' must be a non-nullable value type in order to use it as parameter '{1}' in the generic type or method '{0}'" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new CSDiagnosticInfo(ErrorCode.ERR_ValConstraintNotSatisfied, containingSymbol.ConstructedFrom(), typeParameter, typeArgument))); return(false); } // The type parameters for a constructed type/method are the type parameters of // the ConstructedFrom type/method, so the constraint types are not substituted. // For instance with "class C<T, U> where T : U", the type parameter for T in "C<object, int>" // has constraint "U", not "int". We need to substitute the constraints from the // original definition of the type parameters using the map from the constructed symbol. var constraintTypes = ArrayBuilder <TypeSymbol> .GetInstance(); HashSet <DiagnosticInfo> useSiteDiagnostics = null; substitution.SubstituteTypesDistinctWithoutModifiers(typeParameter.ConstraintTypesWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics), constraintTypes); bool hasError = false; foreach (var constraintType in constraintTypes) { if (SatisfiesConstraintType(conversions, typeArgument, constraintType, ref useSiteDiagnostics)) { continue; } ErrorCode errorCode; if (typeArgument.IsReferenceType) { errorCode = ErrorCode.ERR_GenericConstraintNotSatisfiedRefType; } else if (typeArgument.IsNullableType()) { errorCode = constraintType.IsInterfaceType() ? ErrorCode.ERR_GenericConstraintNotSatisfiedNullableInterface : ErrorCode.ERR_GenericConstraintNotSatisfiedNullableEnum; } else if (typeArgument.TypeKind == TypeKind.TypeParameter) { errorCode = ErrorCode.ERR_GenericConstraintNotSatisfiedTyVar; } else { errorCode = ErrorCode.ERR_GenericConstraintNotSatisfiedValType; } SymbolDistinguisher distinguisher = new SymbolDistinguisher(currentCompilation, constraintType, typeArgument); diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new CSDiagnosticInfo(errorCode, containingSymbol.ConstructedFrom(), distinguisher.First, typeParameter, distinguisher.Second))); hasError = true; } if (AppendUseSiteDiagnostics(useSiteDiagnostics, typeParameter, ref useSiteDiagnosticsBuilder)) { hasError = true; } constraintTypes.Free(); // Check the constructor constraint. if (typeParameter.HasConstructorConstraint && !SatisfiesConstructorConstraint(typeArgument)) { // "'{2}' must be a non-abstract type with a public parameterless constructor in order to use it as parameter '{1}' in the generic type or method '{0}'" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new CSDiagnosticInfo(ErrorCode.ERR_NewConstraintNotSatisfied, containingSymbol.ConstructedFrom(), typeParameter, typeArgument))); return(false); } return(!hasError); }
// 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); }
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; }
// 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); }
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 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 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)); } } } }
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); } }
public static bool CanBeAssignedNull(this TypeSymbol type) { return(type.IsReferenceType || type.IsPointerType() || type.IsNullableType()); }
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); }
private BoundExpression RewriteLiftedConversionInExpressionTree( CSharpSyntaxNode syntax, BoundExpression rewrittenOperand, ConversionKind conversionKind, bool @checked, bool explicitCastInCode, TypeSymbol rewrittenType) { Debug.Assert((object)rewrittenType != null); TypeSymbol rewrittenOperandType = rewrittenOperand.Type; Debug.Assert(rewrittenType.IsNullableType() || rewrittenOperandType.IsNullableType()); TypeSymbol typeFrom = rewrittenOperandType.StrippedType(); TypeSymbol typeTo = rewrittenType.StrippedType(); if (typeFrom != typeTo && (typeFrom.SpecialType == SpecialType.System_Decimal || typeTo.SpecialType == SpecialType.System_Decimal)) { // take special care if the underlying conversion is a decimal conversion TypeSymbol typeFromUnderlying = typeFrom; TypeSymbol typeToUnderlying = typeTo; // They can't both be enums, since one of them is decimal. if (typeFrom.IsEnumType()) { typeFromUnderlying = typeFrom.GetEnumUnderlyingType(); // NOTE: Dev10 converts enum? to underlying?, rather than directly to underlying. rewrittenOperandType = rewrittenOperandType.IsNullableType() ? ((NamedTypeSymbol)rewrittenOperandType.OriginalDefinition).Construct(typeFromUnderlying) : typeFromUnderlying; rewrittenOperand = BoundConversion.SynthesizedNonUserDefined(syntax, rewrittenOperand, ConversionKind.ImplicitEnumeration, rewrittenOperandType); } else if (typeTo.IsEnumType()) { typeToUnderlying = typeTo.GetEnumUnderlyingType(); } var method = (MethodSymbol)this.compilation.Assembly.GetSpecialTypeMember(DecimalConversionMethod(typeFromUnderlying, typeToUnderlying)); conversionKind = conversionKind.IsImplicitConversion() ? ConversionKind.ImplicitUserDefined : ConversionKind.ExplicitUserDefined; var result = new BoundConversion(syntax, rewrittenOperand, new Conversion(conversionKind, method, false), @checked, explicitCastInCode, default(ConstantValue), rewrittenType); return result; } else { return new BoundConversion(syntax, rewrittenOperand, new Conversion(conversionKind), @checked, explicitCastInCode, default(ConstantValue), rewrittenType); } }
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 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; }