private bool bindImplicitConversionToBase(AggregateType pSource) { // 13.1.4 Implicit reference conversions // // * From any reference-type to object. // * From any class-type S to any class-type T, provided S is derived from T. // * From any class-type S to any interface-type T, provided S implements T. // * From any interface-type S to any interface-type T, provided S is derived from T. // * From any delegate-type to System.Delegate. // * From any delegate-type to System.ICloneable. if (!(_typeDest is AggregateType) || !SymbolLoader.HasBaseConversion(pSource, _typeDest)) { return(false); } EXPRFLAG flags = 0x00; if (pSource.OwningAggregate.IsStruct() && _typeDest.FundamentalType == FUNDTYPE.FT_REF) { flags = EXPRFLAG.EXF_BOX | EXPRFLAG.EXF_CANTBENULL; } else if (_exprSrc != null) { flags = _exprSrc.Flags & EXPRFLAG.EXF_CANTBENULL; } if (_needsExprDest) { _binder.bindSimpleCast(_exprSrc, _typeDest, out _exprDest, flags); } return(true); }
private AggCastResult bindExplicitConversionBetweenAggregates(AggregateType aggTypeDest) { // 13.2.3 // // The explicit reference conversions are: // // * From object to any reference-type. // * From any class-type S to any class-type T, provided S is a base class of T. // * From any class-type S to any interface-type T, provided S is not sealed and // provided S does not implement T. // * From any interface-type S to any class-type T, provided T is not sealed or provided // T implements S. // * From any interface-type S to any interface-type T, provided S is not derived from T. Debug.Assert(_typeSrc != null); Debug.Assert(aggTypeDest != null); if (!(_typeSrc is AggregateType atSrc)) { return(AggCastResult.Failure); } AggregateSymbol aggSrc = atSrc.OwningAggregate; AggregateSymbol aggDest = aggTypeDest.OwningAggregate; if (SymbolLoader.HasBaseConversion(aggTypeDest, atSrc)) { if (_needsExprDest) { _binder.bindSimpleCast( _exprSrc, _typeDest, out _exprDest, aggDest.IsValueType() && aggSrc.getThisType().FundamentalType == FUNDTYPE.FT_REF ? EXPRFLAG.EXF_UNBOX : EXPRFLAG.EXF_REFCHECK | (_exprSrc?.Flags & EXPRFLAG.EXF_CANTBENULL ?? 0)); } return(AggCastResult.Success); } if ((aggSrc.IsClass() && !aggSrc.IsSealed() && aggDest.IsInterface()) || (aggSrc.IsInterface() && aggDest.IsClass() && !aggDest.IsSealed()) || (aggSrc.IsInterface() && aggDest.IsInterface()) || CConversions.HasGenericDelegateExplicitReferenceConversion(_typeSrc, aggTypeDest)) { if (_needsExprDest) { _binder.bindSimpleCast(_exprSrc, _typeDest, out _exprDest, EXPRFLAG.EXF_REFCHECK | (_exprSrc?.Flags & EXPRFLAG.EXF_CANTBENULL ?? 0)); } return(AggCastResult.Success); } return(AggCastResult.Failure); }
private bool bindImplicitConversionFromArray() { // 13.1.4 // // The implicit reference conversions are: // // * From an array-type S with an element type SE to an array-type T with an element // type TE, provided all of the following are true: // * S and T differ only in element type. In other words, S and T have the same number of dimensions. // * An implicit reference conversion exists from SE to TE. // * From a one-dimensional array-type S[] to System.Collections.Generic.IList<S>, // System.Collections.Generic.IReadOnlyList<S> and their base interfaces // * From a one-dimensional array-type S[] to System.Collections.Generic.IList<T>, System.Collections.Generic.IReadOnlyList<T> // and their base interfaces, provided there is an implicit reference conversion from S to T. // * From any array-type to System.Array. // * From any array-type to any interface implemented by System.Array. if (!SymbolLoader.HasBaseConversion(_typeSrc, _typeDest)) { return(false); } EXPRFLAG grfex = 0; // The above if checks for dest==Array, object or an interface the array implements, // including IList<T>, ICollection<T>, IEnumerable<T>, IReadOnlyList<T>, IReadOnlyCollection<T> // and the non-generic versions. if ((_typeDest is ArrayType || (_typeDest is AggregateType aggDest && aggDest.IsInterfaceType && aggDest.TypeArgsAll.Count == 1 && (aggDest.TypeArgsAll[0] != ((ArrayType)_typeSrc).ElementType || 0 != (_flags & CONVERTTYPE.FORCECAST)))) && (0 != (_flags & CONVERTTYPE.FORCECAST) || TypeManager.TypeContainsTyVars(_typeSrc, null) || TypeManager.TypeContainsTyVars(_typeDest, null))) { grfex = EXPRFLAG.EXF_REFCHECK; } if (_needsExprDest) { _binder.bindSimpleCast(_exprSrc, _typeDest, out _exprDest, grfex); } return(true); }
private bool bindImplicitConversionFromNullable(NullableType nubSrc) { // We can convert T? using a boxing conversion, we can convert it to ValueType, and // we can convert it to any interface implemented by T. // // 13.1.5 Boxing Conversions // // A nullable-type has a boxing conversion to the same set of types to which the nullable-type's // underlying type has boxing conversions. A boxing conversion applied to a value of a nullable-type // proceeds as follows: // // * If the HasValue property of the nullable value evaluates to false, then the result of the // boxing conversion is the null reference of the appropriate type. // // Otherwise, the result is obtained by boxing the result of evaluating the Value property on // the nullable value. AggregateType atsNub = nubSrc.GetAts(); if (atsNub == _typeDest) { if (_needsExprDest) { _exprDest = _exprSrc; } return(true); } if (SymbolLoader.HasBaseConversion(nubSrc.UnderlyingType, _typeDest) && !CConversions.FUnwrappingConv(nubSrc, _typeDest)) { if (_needsExprDest) { _binder.bindSimpleCast(_exprSrc, _typeDest, out _exprDest, EXPRFLAG.EXF_BOX); if (!_typeDest.IsPredefType(PredefinedType.PT_OBJECT)) { // The base type of a nullable is always a non-nullable value type, // therefore so is typeDest unless typeDest is PT_OBJECT. In this case the conversion // needs to be unboxed. We only need this if we actually will use the result. _binder.bindSimpleCast(_exprDest, _typeDest, out _exprDest, EXPRFLAG.EXF_FORCE_UNBOX); } } return(true); } return(0 == (_flags & CONVERTTYPE.NOUDC) && _binder.bindUserDefinedConversion(_exprSrc, nubSrc, _typeDest, _needsExprDest, out _exprDest, true)); }
private bool bindImplicitConversionFromEnum(AggregateType aggTypeSrc) { // 13.1.5 Boxing conversions // // A boxing conversion permits any non-nullable-value-type to be implicitly converted to the type // object or System.ValueType or to any interface-type implemented by the value-type, and any enum // type to be implicitly converted to System.Enum as well. Boxing a value of a // non-nullable-value-type consists of allocating an object instance and copying the value-type // value into that instance. An enum can be boxed to the type System.Enum, since that is the direct // base class for all enums (21.4). A struct or enum can be boxed to the type System.ValueType, // since that is the direct base class for all structs (18.3.2) and a base class for all enums. if (_typeDest is AggregateType aggDest && SymbolLoader.HasBaseConversion(aggTypeSrc, aggDest)) { if (_needsExprDest) _binder.bindSimpleCast(_exprSrc, _typeDest, out _exprDest, EXPRFLAG.EXF_BOX | EXPRFLAG.EXF_CANTBENULL); return true; } return false; }
private static bool CheckSingleConstraint(Symbol symErr, TypeParameterType var, CType arg, TypeArray typeArgsCls, TypeArray typeArgsMeth, CheckConstraintsFlags flags) { Debug.Assert(!(arg is PointerType)); Debug.Assert(!arg.IsStaticClass); bool fReportErrors = 0 == (flags & CheckConstraintsFlags.NoErrors); if (var.HasRefConstraint && !arg.IsReferenceType) { if (fReportErrors) { throw ErrorHandling.Error(ErrorCode.ERR_RefConstraintNotSatisfied, symErr, new ErrArgNoRef(var), arg); } return(false); } TypeArray bnds = TypeManager.SubstTypeArray(var.Bounds, typeArgsCls, typeArgsMeth); int itypeMin = 0; if (var.HasValConstraint) { // If we have a type variable that is constrained to a value type, then we // want to check if its a nullable type, so that we can report the // constraint error below. In order to do this however, we need to check // that either the type arg is not a value type, or it is a nullable type. // // To check whether or not its a nullable type, we need to get the resolved // bound from the type argument and check against that. if (!arg.IsNonNullableValueType) { if (fReportErrors) { throw ErrorHandling.Error(ErrorCode.ERR_ValConstraintNotSatisfied, symErr, new ErrArgNoRef(var), arg); } return(false); } // Since FValCon() is set it is redundant to check System.ValueType as well. if (bnds.Count != 0 && bnds[0].IsPredefType(PredefinedType.PT_VALUE)) { itypeMin = 1; } } for (int j = itypeMin; j < bnds.Count; j++) { CType typeBnd = bnds[j]; if (!SatisfiesBound(arg, typeBnd)) { if (fReportErrors) { // The bound isn't satisfied because of a constraint type. Explain to the user why not. // There are 4 main cases, based on the type of the supplied type argument: // - reference type, or type parameter known to be a reference type // - nullable type, from which there is a boxing conversion to the constraint type(see below for details) // - type variable // - value type // These cases are broken out because: a) The sets of conversions which can be used // for constraint satisfaction is different based on the type argument supplied, // and b) Nullable is one funky type, and user's can use all the help they can get // when using it. ErrorCode error; if (arg.IsReferenceType) { // A reference type can only satisfy bounds to types // to which they have an implicit reference conversion error = ErrorCode.ERR_GenericConstraintNotSatisfiedRefType; } else if (arg is NullableType nubArg && SymbolLoader.HasBaseConversion(nubArg.UnderlyingType, typeBnd)) // This is inlining FBoxingConv { // nullable types do not satisfy bounds to every type that they are boxable to // They only satisfy bounds of object and ValueType if (typeBnd.IsPredefType(PredefinedType.PT_ENUM) || nubArg.UnderlyingType == typeBnd) { // Nullable types don't satisfy bounds of EnumType, or the underlying type of the enum // even though the conversion from Nullable to these types is a boxing conversion // This is a rare case, because these bounds can never be directly stated ... // These bounds can only occur when one type paramter is constrained to a second type parameter // and the second type parameter is instantiated with Enum or the underlying type of the first type // parameter error = ErrorCode.ERR_GenericConstraintNotSatisfiedNullableEnum; } else { // Nullable types don't satisfy the bounds of any interface type // even when there is a boxing conversion from the Nullable type to // the interface type. This will be a relatively common scenario // so we cal it out separately from the previous case. Debug.Assert(typeBnd.IsInterfaceType); error = ErrorCode.ERR_GenericConstraintNotSatisfiedNullableInterface; } } else { // Value types can only satisfy bounds through boxing conversions. // Note that the exceptional case of Nullable types and boxing is handled above. error = ErrorCode.ERR_GenericConstraintNotSatisfiedValType; } throw ErrorHandling.Error(error, new ErrArg(symErr), new ErrArg(typeBnd, ErrArgFlags.Unique), var, new ErrArg(arg, ErrArgFlags.Unique)); } return(false); } }
/*************************************************************************************************** * Called by BindImplicitConversion when the destination type is Nullable<T>. The following * conversions are handled by this method: * * For S in { object, ValueType, interfaces implemented by underlying type} there is an explicit * unboxing conversion S => T? * System.Enum => T? there is an unboxing conversion if T is an enum type * null => T? implemented as default(T?) * * Implicit T?* => T?+ implemented by either wrapping or calling GetValueOrDefault the * appropriate number of times. * If imp/exp S => T then imp/exp S => T?+ implemented by converting to T then wrapping the * appropriate number of times. * If imp/exp S => T then imp/exp S?+ => T?+ implemented by calling GetValueOrDefault (m-1) times * then calling HasValue, producing a null if it returns false, otherwise calling Value, * converting to T then wrapping the appropriate number of times. * * The 3 rules above can be summarized with the following recursive rules: * * If imp/exp S => T? then imp/exp S? => T? implemented as * qs.HasValue ? (T?)(qs.Value) : default(T?) * If imp/exp S => T then imp/exp S => T? implemented as new T?((T)s) * * This method also handles calling bindUserDefinedConverion. This method does NOT handle * the following conversions: * * Implicit boxing conversion from S? to { object, ValueType, Enum, ifaces implemented by S }. (Handled by BindImplicitConversion.) * If imp/exp S => T then explicit S?+ => T implemented by calling Value the appropriate number * of times. (Handled by BindExplicitConversion.) * * The recursive equivalent is: * * If imp/exp S => T and T is not nullable then explicit S? => T implemented as qs.Value * * Some nullable conversion are NOT standard conversions. In particular, if S => T is implicit * then S? => T is not standard. Similarly if S => T is not implicit then S => T? is not standard. ***************************************************************************************************/ private bool BindNubConversion(NullableType nubDst) { // This code assumes that STANDARD and ISEXPLICIT are never both set. // bindUserDefinedConversion should ensure this! Debug.Assert(0 != (~_flags & (CONVERTTYPE.STANDARD | CONVERTTYPE.ISEXPLICIT))); Debug.Assert(_exprSrc == null || _exprSrc.Type == _typeSrc); Debug.Assert(!_needsExprDest || _exprSrc != null); Debug.Assert(_typeSrc != nubDst); // BindImplicitConversion should have taken care of this already. AggregateType atsDst = nubDst.GetAts(); // Check for the unboxing conversion. This takes precedence over the wrapping conversions. if (SymbolLoader.HasBaseConversion(nubDst.UnderlyingType, _typeSrc) && !CConversions.FWrappingConv(_typeSrc, nubDst)) { // These should be different! Fix the caller if typeSrc is an AggregateType of Nullable. Debug.Assert(atsDst != _typeSrc); // typeSrc is a base type of the destination nullable type so there is an explicit // unboxing conversion. if (0 == (_flags & CONVERTTYPE.ISEXPLICIT)) { return(false); } if (_needsExprDest) { _binder.bindSimpleCast(_exprSrc, _typeDest, out _exprDest, EXPRFLAG.EXF_UNBOX); } return(true); } bool dstWasNullable; bool srcWasNullable; CType typeDstBase = nubDst.StripNubs(out dstWasNullable); CType typeSrcBase = _typeSrc.StripNubs(out srcWasNullable); ConversionFunc pfn = (_flags & CONVERTTYPE.ISEXPLICIT) != 0 ? (ConversionFunc)_binder.BindExplicitConversion : (ConversionFunc)_binder.BindImplicitConversion; if (!srcWasNullable) { Debug.Assert(_typeSrc == typeSrcBase); // The null type can be implicitly converted to T? as the default value. if (_typeSrc is NullType) { // If we have the constant null, generate it as a default value of T?. If we have // some wacky expression which has been determined to be always null, like (null??null) // keep it in its expression form and transform it in the nullable rewrite pass. if (_needsExprDest) { _exprDest = _exprSrc is ExprConstant ? ExprFactory.CreateZeroInit(nubDst) : ExprFactory.CreateCast(_typeDest, _exprSrc); } return(true); } Expr exprTmp = _exprSrc; // If there is an implicit/explicit S => T then there is an implicit/explicit S => T? if (_typeSrc == typeDstBase || pfn(_exprSrc, _typeSrc, typeDstBase, _needsExprDest, out exprTmp, _flags | CONVERTTYPE.NOUDC)) { if (_needsExprDest) { ExprUserDefinedConversion exprUDC = exprTmp as ExprUserDefinedConversion; if (exprUDC != null) { exprTmp = exprUDC.UserDefinedCall; } if (dstWasNullable) { ExprCall call = BindNubNew(exprTmp); exprTmp = call; call.NullableCallLiftKind = NullableCallLiftKind.NullableConversionConstructor; } if (exprUDC != null) { exprUDC.UserDefinedCall = exprTmp; exprTmp = exprUDC; } Debug.Assert(exprTmp.Type == nubDst); _exprDest = exprTmp; } return(true); } // No builtin conversion. Maybe there is a user defined conversion.... return(0 == (_flags & CONVERTTYPE.NOUDC) && _binder.bindUserDefinedConversion(_exprSrc, _typeSrc, nubDst, _needsExprDest, out _exprDest, 0 == (_flags & CONVERTTYPE.ISEXPLICIT))); } // Both are Nullable so there is only a conversion if there is a conversion between the base types. // That is, if there is an implicit/explicit S => T then there is an implicit/explicit S?+ => T?+. if (typeSrcBase != typeDstBase && !pfn(null, typeSrcBase, typeDstBase, false, out _exprDest, _flags | CONVERTTYPE.NOUDC)) { // No builtin conversion. Maybe there is a user defined conversion.... return(0 == (_flags & CONVERTTYPE.NOUDC) && _binder.bindUserDefinedConversion(_exprSrc, _typeSrc, nubDst, _needsExprDest, out _exprDest, 0 == (_flags & CONVERTTYPE.ISEXPLICIT))); } if (_needsExprDest) { MethWithInst mwi = new MethWithInst(null, null); ExprMemberGroup pMemGroup = ExprFactory.CreateMemGroup(null, mwi); ExprCall exprDst = ExprFactory.CreateCall(0, nubDst, _exprSrc, pMemGroup, null); // Here we want to first check whether or not the conversions work on the base types. Expr arg1 = _binder.mustCast(_exprSrc, typeSrcBase); bool convertible = (_flags & CONVERTTYPE.ISEXPLICIT) != 0 ? _binder.BindExplicitConversion( arg1, arg1.Type, typeDstBase, out arg1, _flags | CONVERTTYPE.NOUDC) : _binder.BindImplicitConversion( arg1, arg1.Type, typeDstBase, out arg1, _flags | CONVERTTYPE.NOUDC); if (!convertible) { Debug.Fail("bind(Im|Ex)plicitConversion failed unexpectedly"); return(false); } exprDst.CastOfNonLiftedResultToLiftedType = _binder.mustCast(arg1, nubDst, 0); exprDst.NullableCallLiftKind = NullableCallLiftKind.NullableConversion; exprDst.PConversions = exprDst.CastOfNonLiftedResultToLiftedType; _exprDest = exprDst; } return(true); }