static void addFromClassOrStruct(ArrayBuilder <TypeSymbol> result, bool excludeExisting, TypeSymbol type, bool includeBaseTypes, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { if (type.IsClassType() || type.IsStructType()) { if (!excludeExisting || !HasIdentityConversionToAny(type, result)) { result.Add(type); } } if (!includeBaseTypes) { return; } NamedTypeSymbol t = type.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo); while ((object)t != null) { if (!excludeExisting || !HasIdentityConversionToAny(t, result)) { result.Add(t); } t = t.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo); } }
public static void AddTypesParticipatingInUserDefinedConversion( ArrayBuilder <NamedTypeSymbol> result, TypeSymbol type, bool includeBaseTypes, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo ) { if ((object)type == null) { return; } // CONSIDER: These sets are usually small; if they are large then this is an O(n^2) // CONSIDER: algorithm. We could use a hash table instead to build up the set. Debug.Assert(!type.IsTypeParameter()); // optimization: bool excludeExisting = result.Count > 0; if (type.IsClassType() || type.IsStructType()) { var namedType = (NamedTypeSymbol)type; if (!excludeExisting || !HasIdentityConversionToAny(namedType, result)) { result.Add(namedType); } } if (!includeBaseTypes) { return; } NamedTypeSymbol t = type.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo); while ((object)t != null) { if (!excludeExisting || !HasIdentityConversionToAny(t, result)) { result.Add(t); } t = t.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo); } }
public static void AddTypesParticipatingInUserDefinedConversion(ArrayBuilder <NamedTypeSymbol> result, TypeSymbol type, bool includeBaseTypes, ref HashSet <DiagnosticInfo> useSiteDiagnostics) { if ((object)type == null) { return; } // CONSIDER: These sets are usually small; if they are large then this is an O(n^2) // CONSIDER: algorithm. We could use a hash table instead to build up the set. Debug.Assert(!type.IsTypeParameter()); // optimization: bool excludeExisting = result.Count > 0; // The decimal type does not contribute its user-defined conversions to the mix; though its // conversions are actually implemented via user-defined operators, we logically treat it as // though those conversions were built-in. if (type.IsClassType() || type.IsStructType() && type.SpecialType != SpecialType.System_Decimal) { var namedType = (NamedTypeSymbol)type; if (!excludeExisting || !HasIdentityConversionToAny(namedType, result)) { result.Add(namedType); } } if (!includeBaseTypes) { return; } NamedTypeSymbol t = type.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics); while ((object)t != null) { if (!excludeExisting || !HasIdentityConversionToAny(t, result)) { result.Add(t); } t = t.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics); } }
public static void AddTypesParticipatingInUserDefinedConversion(ArrayBuilder<NamedTypeSymbol> result, TypeSymbol type, bool includeBaseTypes, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { if ((object)type == null) { return; } // CONSIDER: These sets are usually small; if they are large then this is an O(n^2) // CONSIDER: algorithm. We could use a hash table instead to build up the set. Debug.Assert(!type.IsTypeParameter()); // optimization: bool excludeExisting = result.Count > 0; // The decimal type does not contribute its user-defined conversions to the mix; though its // conversions are actually implemented via user-defined operators, we logically treat it as // though those conversions were built-in. if (type.IsClassType() || type.IsStructType() && type.SpecialType != SpecialType.System_Decimal) { var namedType = (NamedTypeSymbol)type; if (!excludeExisting || !HasIdentityConversionToAny(namedType, result)) { result.Add(namedType); } } if (!includeBaseTypes) { return; } NamedTypeSymbol t = type.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics); while ((object)t != null) { if (!excludeExisting || !HasIdentityConversionToAny(t, result)) { result.Add(t); } t = t.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics); } }
public static void AddTypesParticipatingInUserDefinedConversion(ArrayBuilder<NamedTypeSymbol> result, TypeSymbol type, bool includeBaseTypes, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { if ((object)type == null) { return; } // CONSIDER: These sets are usually small; if they are large then this is an O(n^2) // CONSIDER: algorithm. We could use a hash table instead to build up the set. Debug.Assert(!type.IsTypeParameter()); // optimization: bool excludeExisting = result.Count > 0; if (type.IsClassType() || type.IsStructType()) { var namedType = (NamedTypeSymbol)type; if (!excludeExisting || !HasIdentityConversionToAny(namedType, result)) { result.Add(namedType); } } if (!includeBaseTypes) { return; } NamedTypeSymbol t = type.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics); while ((object)t != null) { if (!excludeExisting || !HasIdentityConversionToAny(t, result)) { result.Add(t); } t = t.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics); } }
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); } }
internal static ConstantValue GetAsOperatorConstantResult(TypeSymbol operandType, TypeSymbol targetType, ConversionKind conversionKind, ConstantValue operandConstantValue) { // 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. // Native compiler port: // // check for case we know is always false // if (arg->isNull() || !canCast(arg, type2, NOUDC) && type1->IsValType() && type2->isClassType() && (!type1->IsTypeParameterType() || !type2->isPredefType(PT_ENUM))) // { // GetErrorContext()->Error(tree, WRN_AlwaysNull, type2); // return rval; // } if (operandConstantValue == ConstantValue.Null || (conversionKind == ConversionKind.NoConversion && (operandType.IsValueType && targetType.IsClassType() && (!operandType.IsTypeParameter() || targetType.SpecialType != SpecialType.System_Enum)))) { return ConstantValue.Null; } else { return null; } }