mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-10 04:22:20 +02:00
Fixed a broken test
This commit is contained in:
parent
098ad1ce55
commit
503261e916
3 changed files with 10 additions and 936 deletions
|
@ -6648,11 +6648,11 @@ bool BfModule::CheckGenericConstraints(const BfGenericParamSource& genericParamS
|
||||||
bool implementsInterface = false;
|
bool implementsInterface = false;
|
||||||
if (origCheckArgType != checkArgType)
|
if (origCheckArgType != checkArgType)
|
||||||
{
|
{
|
||||||
implementsInterface = CanImplicitlyCast(origCheckArgType, convCheckConstraint);
|
implementsInterface = CanImplicitlyCast(BfTypedValue(BfIRValue::sValueless, origCheckArgType), convCheckConstraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!implementsInterface)
|
if (!implementsInterface)
|
||||||
implementsInterface = CanImplicitlyCast(checkArgType, convCheckConstraint);
|
implementsInterface = CanImplicitlyCast(BfTypedValue(BfIRValue::sValueless, checkArgType), convCheckConstraint);
|
||||||
|
|
||||||
if ((!implementsInterface) && (origCheckArgType->IsWrappableType()))
|
if ((!implementsInterface) && (origCheckArgType->IsWrappableType()))
|
||||||
{
|
{
|
||||||
|
|
|
@ -7694,941 +7694,15 @@ BfType* BfModule::ResolveTypeRef(BfAstNode* astNode, const BfSizedArray<BfTypeRe
|
||||||
return ResolveTypeRef(typeRef, populateType, resolveFlags);
|
return ResolveTypeRef(typeRef, populateType, resolveFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BfModule::DoCanImplicitlyCast(BfTypedValue typedVal, BfType* toType, BfCastFlags castFlags)
|
|
||||||
{
|
|
||||||
BfType* fromType = typedVal.mType;
|
|
||||||
|
|
||||||
if (fromType == toType)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Ref X to Ref Y, X* to Y*
|
|
||||||
{
|
|
||||||
bool checkUnderlying = false;
|
|
||||||
|
|
||||||
if (((typedVal.mType->IsRef()) && (toType->IsRef())))
|
|
||||||
{
|
|
||||||
auto fromRefType = (BfRefType*)typedVal.mType;
|
|
||||||
auto toRefType = (BfRefType*)toType;
|
|
||||||
if (fromRefType->mRefKind == toRefType->mRefKind)
|
|
||||||
checkUnderlying = true;
|
|
||||||
else if ((fromRefType->mRefKind == BfRefType::RefKind_Ref) && (toRefType->mRefKind == BfRefType::RefKind_Mut))
|
|
||||||
checkUnderlying = true; // Allow a ref-to-mut implicit conversion
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((typedVal.mType->IsPointer()) && (toType->IsPointer()))
|
|
||||||
checkUnderlying = true;
|
|
||||||
|
|
||||||
if (checkUnderlying)
|
|
||||||
{
|
|
||||||
auto fromInner = typedVal.mType->GetUnderlyingType();
|
|
||||||
auto toInner = toType->GetUnderlyingType();
|
|
||||||
|
|
||||||
if (fromInner == toInner)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// ref int <-> ref int64/int32 (of same size)
|
|
||||||
if (((fromInner->IsInteger()) && (toInner->IsInteger())) &&
|
|
||||||
(fromInner->mSize == toInner->mSize) &&
|
|
||||||
(fromInner->IsSigned() == toInner->IsSigned()))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic param -> *
|
|
||||||
if ((typedVal.mType->IsGenericParam()) && (!toType->IsGenericParam()))
|
|
||||||
{
|
|
||||||
if (toType == mContext->mBfObjectType)
|
|
||||||
{
|
|
||||||
//auto resolvedType = ResolveGenericType(typedVal.mType);
|
|
||||||
//auto resolvedType = typedVal.mType;
|
|
||||||
/*if (!resolvedType->IsGenericParam())
|
|
||||||
return CanImplicitlyCast(BfTypedValue(typedVal.mValue, resolvedType), toType);*/
|
|
||||||
// Can we never NOT do this?
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For these casts, it's just important we get *A* value to work with here,
|
|
||||||
// as this is just use for unspecialized parsing. We don't use the generated code
|
|
||||||
auto genericParamInst = GetGenericParamInstance((BfGenericParamType*)typedVal.mType);
|
|
||||||
if ((genericParamInst->mGenericParamFlags & BfGenericParamFlag_Var) != 0)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (toType->IsInterface())
|
|
||||||
{
|
|
||||||
for (auto iface : genericParamInst->mInterfaceConstraints)
|
|
||||||
if (TypeIsSubTypeOf(iface, toType->ToTypeInstance()))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (genericParamInst->mTypeConstraint != NULL)
|
|
||||||
{
|
|
||||||
auto defaultFromValue = GetDefaultTypedValue(genericParamInst->mTypeConstraint);
|
|
||||||
auto result = CanImplicitlyCast(defaultFromValue, toType);
|
|
||||||
|
|
||||||
if ((result) && (genericParamInst->mTypeConstraint->IsDelegate()) && (toType->IsDelegate()))
|
|
||||||
{
|
|
||||||
// Don't allow cast when we are constrained by a delegate type, because BfMethodRefs can match and we require an actual alloc
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic constrained with class or pointer type -> void*
|
|
||||||
if (toType->IsVoidPtr())
|
|
||||||
{
|
|
||||||
if ((genericParamInst->mGenericParamFlags & (BfGenericParamFlag_Class | BfGenericParamFlag_StructPtr)) ||
|
|
||||||
((genericParamInst->mTypeConstraint != NULL) &&
|
|
||||||
((genericParamInst->mTypeConstraint->IsPointer()) || (genericParamInst->mTypeConstraint->IsObjectOrInterface()))))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// * -> Generic param
|
|
||||||
if (toType->IsGenericParam())
|
|
||||||
{
|
|
||||||
auto genericParamInst = GetGenericParamInstance((BfGenericParamType*)toType);
|
|
||||||
if (genericParamInst->mGenericParamFlags & BfGenericParamFlag_Var)
|
|
||||||
return true;
|
|
||||||
if (typedVal.mType->IsNull())
|
|
||||||
{
|
|
||||||
bool allowCast = (genericParamInst->mGenericParamFlags & BfGenericParamFlag_Class) || (genericParamInst->mGenericParamFlags & BfGenericParamFlag_StructPtr);
|
|
||||||
if ((!allowCast) && (genericParamInst->mTypeConstraint != NULL))
|
|
||||||
allowCast = genericParamInst->mTypeConstraint->IsObject() || genericParamInst->mTypeConstraint->IsPointer();
|
|
||||||
if (allowCast)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (genericParamInst->mTypeConstraint != NULL)
|
|
||||||
{
|
|
||||||
if (CanImplicitlyCast(typedVal, genericParamInst->mTypeConstraint))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Struct truncate
|
|
||||||
if ((fromType->IsStruct()) && (toType->IsStruct()))
|
|
||||||
{
|
|
||||||
auto fromTypeInstance = fromType->ToTypeInstance();
|
|
||||||
auto toTypeInstance = toType->ToTypeInstance();
|
|
||||||
if (TypeIsSubTypeOf(fromTypeInstance, toTypeInstance))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((fromType->IsVar()) || (toType->IsVar()))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Null -> ObjectInst|IFace|ptr
|
|
||||||
if ((fromType->IsNull()) &&
|
|
||||||
((toType->IsObjectOrInterface()) || (toType->IsPointer()) || (toType->IsPointer())))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Object/struct -> object/struct|IFace
|
|
||||||
if ((fromType->IsTypeInstance()) && ((toType->IsTypeInstance() || (toType->IsInterface()))))
|
|
||||||
{
|
|
||||||
auto fromTypeInstance = fromType->ToTypeInstance();
|
|
||||||
auto toTypeInstance = toType->ToTypeInstance();
|
|
||||||
|
|
||||||
if (TypeIsSubTypeOf(fromTypeInstance, toTypeInstance))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// concrete IFace -> object|IFace
|
|
||||||
if ((fromType->IsConcreteInterfaceType()) && ((toType->IsObject() || (toType->IsInterface()))))
|
|
||||||
{
|
|
||||||
auto concreteInterfaceType = (BfConcreteInterfaceType*)fromType;
|
|
||||||
if ((toType->IsObject()) || (concreteInterfaceType->mInterface == toType))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// IFace -> object
|
|
||||||
if ((fromType->IsInterface()) && (toType == mContext->mBfObjectType))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (toType->IsPointer())
|
|
||||||
{
|
|
||||||
// Ptr -> Ptr
|
|
||||||
if (fromType->IsPointer())
|
|
||||||
{
|
|
||||||
bool allowCast = false;
|
|
||||||
auto fromPointerType = (BfPointerType*)typedVal.mType;
|
|
||||||
auto toPointerType = (BfPointerType*)toType;
|
|
||||||
|
|
||||||
auto fromUnderlying = fromPointerType->mElementType;
|
|
||||||
auto toUnderlying = toPointerType->mElementType;
|
|
||||||
|
|
||||||
// Allow cast from T[size]* to T* implicitly
|
|
||||||
// And from T* to T[size]* explicitly
|
|
||||||
if (fromUnderlying->IsSizedArray())
|
|
||||||
fromUnderlying = fromUnderlying->GetUnderlyingType();
|
|
||||||
// if ((toUnderlying->IsSizedArray()) && (explicitCast))
|
|
||||||
// toUnderlying = toUnderlying->GetUnderlyingType();
|
|
||||||
|
|
||||||
if ((fromUnderlying == toUnderlying) ||
|
|
||||||
(TypeIsSubTypeOf(fromUnderlying->ToTypeInstance(), toUnderlying->ToTypeInstance())) ||
|
|
||||||
(toUnderlying->IsVoid()))
|
|
||||||
allowCast = true;
|
|
||||||
|
|
||||||
if (allowCast)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (fromType->IsObject())
|
|
||||||
{
|
|
||||||
auto fromTypeInst = fromType->ToTypeInstance();
|
|
||||||
auto charType = GetPrimitiveType(BfTypeCode_Char8);
|
|
||||||
auto charPtrType = CreatePointerType(charType);
|
|
||||||
if ((fromTypeInst->mTypeDef == mCompiler->mStringTypeDef) && (toType == charPtrType))
|
|
||||||
{
|
|
||||||
// String Object -> char* literal
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*else if (typedVal.mType->IsSizedArray())
|
|
||||||
{
|
|
||||||
if (typedVal.IsAddr())
|
|
||||||
{
|
|
||||||
BfSizedArrayType* arrayType = (BfSizedArrayType*)typedVal.mType;
|
|
||||||
BfTypedValue returnPointer(typedVal.mValue, CreatePointerType(arrayType->mElementType));
|
|
||||||
return CanImplicitlyCast(returnPointer, toType);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boxing?
|
|
||||||
if (((fromType->IsValueType()) || (fromType->IsPointer()) || (fromType->IsValuelessType())) &&
|
|
||||||
((toType->IsInterface()) || (toType == mContext->mBfObjectType)))
|
|
||||||
{
|
|
||||||
// if (fromType->IsPointer())
|
|
||||||
// {
|
|
||||||
// if (toType == mContext->mBfObjectType)
|
|
||||||
// return true;
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (toType == mContext->mBfObjectType)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
BfTypeInstance* fromStructTypeInstance = NULL;
|
|
||||||
fromStructTypeInstance = fromType->ToTypeInstance();
|
|
||||||
if (fromStructTypeInstance == NULL)
|
|
||||||
{
|
|
||||||
if (fromType->IsPrimitiveType())
|
|
||||||
{
|
|
||||||
auto primType = (BfPrimitiveType*)fromType;
|
|
||||||
fromStructTypeInstance = GetPrimitiveStructType(primType->mTypeDef->mTypeCode);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto toTypeInstance = toType->ToTypeInstance();
|
|
||||||
// Need to box it
|
|
||||||
if (TypeIsSubTypeOf(fromStructTypeInstance, toTypeInstance))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Null -> Nullable<T>
|
|
||||||
if ((typedVal.mType->IsNull()) && (toType->IsNullable()))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nullable<A> -> Nullable<B>
|
|
||||||
if ((typedVal.mType->IsNullable()) && (toType->IsNullable()))
|
|
||||||
{
|
|
||||||
auto fromNullableType = (BfGenericTypeInstance*)typedVal.mType;
|
|
||||||
auto toNullableType = (BfGenericTypeInstance*)toType;
|
|
||||||
|
|
||||||
return CanImplicitlyCast(BfTypedValue(mBfIRBuilder->GetFakeVal(), fromNullableType->mTypeGenericArguments[0]), toNullableType->mTypeGenericArguments[0], castFlags);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tuple -> Tuple
|
|
||||||
if ((typedVal.mType->IsTuple()) && (toType->IsTuple()))
|
|
||||||
{
|
|
||||||
auto fromTupleType = (BfTupleType*)typedVal.mType;
|
|
||||||
auto toTupleType = (BfTupleType*)toType;
|
|
||||||
if (fromTupleType->mFieldInstances.size() == toTupleType->mFieldInstances.size())
|
|
||||||
{
|
|
||||||
bool canCast = true;
|
|
||||||
|
|
||||||
BfIRValue curTupleValue = mBfIRBuilder->CreateUndefValue(mBfIRBuilder->MapType(toTupleType));
|
|
||||||
for (int valueIdx = 0; valueIdx < (int)fromTupleType->mFieldInstances.size(); valueIdx++)
|
|
||||||
{
|
|
||||||
BfFieldInstance* fromFieldInstance = &fromTupleType->mFieldInstances[valueIdx];
|
|
||||||
BfFieldInstance* toFieldInstance = &toTupleType->mFieldInstances[valueIdx];
|
|
||||||
|
|
||||||
//
|
|
||||||
{
|
|
||||||
BfFieldDef* fromFieldDef = fromFieldInstance->GetFieldDef();
|
|
||||||
BfFieldDef* toFieldDef = toFieldInstance->GetFieldDef();
|
|
||||||
|
|
||||||
// Either the names have to match or one has to be unnamed
|
|
||||||
if ((!fromFieldDef->IsUnnamedTupleField()) && (!toFieldDef->IsUnnamedTupleField()) &&
|
|
||||||
(fromFieldDef->mName != toFieldDef->mName))
|
|
||||||
{
|
|
||||||
curTupleValue = BfIRValue();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto fromFieldType = fromFieldInstance->GetResolvedType();
|
|
||||||
auto toFieldType = toFieldInstance->GetResolvedType();
|
|
||||||
|
|
||||||
if (toFieldType->IsVoid())
|
|
||||||
continue; // Allow sinking to void
|
|
||||||
|
|
||||||
BfIRValue fromFieldValue;
|
|
||||||
bool canCastField = CanImplicitlyCast(BfTypedValue(mBfIRBuilder->GetFakeVal(), fromFieldType), toFieldType, (BfCastFlags)(castFlags | BfCastFlags_Explicit));
|
|
||||||
if (!canCastField)
|
|
||||||
{
|
|
||||||
canCast = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canCast)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*if (fromType->IsRef())
|
|
||||||
{
|
|
||||||
if (toType->IsRef())
|
|
||||||
{
|
|
||||||
BfTypedValue unrefValue = BfTypedValue(typedVal.mValue, fromType->GetUnderlyingType(), true);
|
|
||||||
return CanImplicitlyCast(unrefValue, toType->GetUnderlyingType());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// ref T -> T
|
|
||||||
return fromType->GetUnderlyingType() == toType;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// Int -> Enum
|
|
||||||
if ((typedVal.mType->IsIntegral()) && (toType->IsEnum()))
|
|
||||||
{
|
|
||||||
// Allow implicit cast of zero
|
|
||||||
if (typedVal.mValue)
|
|
||||||
{
|
|
||||||
auto constant = mBfIRBuilder->GetConstant(typedVal.mValue);
|
|
||||||
if ((constant != NULL) && (BfIRBuilder::IsInt(constant->mTypeCode)))
|
|
||||||
{
|
|
||||||
int64 srcVal = constant->mInt64;
|
|
||||||
if (srcVal == 0)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tuple -> Tuple
|
|
||||||
if ((typedVal.mType->IsTuple()) && (toType->IsTuple()))
|
|
||||||
{
|
|
||||||
auto fromTupleType = (BfTupleType*)typedVal.mType;
|
|
||||||
auto toTupleType = (BfTupleType*)toType;
|
|
||||||
if (fromTupleType->mFieldInstances.size() == toTupleType->mFieldInstances.size())
|
|
||||||
{
|
|
||||||
for (int valueIdx = 0; valueIdx < (int)fromTupleType->mFieldInstances.size(); valueIdx++)
|
|
||||||
{
|
|
||||||
BfFieldInstance* fromFieldInstance = &fromTupleType->mFieldInstances[valueIdx];
|
|
||||||
BfFieldInstance* toFieldInstance = &toTupleType->mFieldInstances[valueIdx];
|
|
||||||
|
|
||||||
BfFieldDef* fromFieldDef = fromFieldInstance->GetFieldDef();
|
|
||||||
BfFieldDef* toFieldDef = toFieldInstance->GetFieldDef();
|
|
||||||
|
|
||||||
auto fromFieldType = fromFieldInstance->GetResolvedType();
|
|
||||||
auto toFieldType = toFieldInstance->GetResolvedType();
|
|
||||||
|
|
||||||
// Either the names have to match or one has to be unnamed
|
|
||||||
if ((!fromFieldDef->IsUnnamedTupleField()) && (!toFieldDef->IsUnnamedTupleField()) &&
|
|
||||||
(fromFieldDef->mName != toFieldDef->mName))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (toFieldType->IsVoid())
|
|
||||||
continue; // Allow sinking to void
|
|
||||||
|
|
||||||
if (!CanImplicitlyCast(GetFakeTypedValue(fromFieldType), toFieldType))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -> const <value>
|
|
||||||
if (toType->IsConstExprValue())
|
|
||||||
{
|
|
||||||
auto constant = mBfIRBuilder->GetConstant(typedVal.mValue);
|
|
||||||
if (constant != NULL)
|
|
||||||
{
|
|
||||||
BfConstExprValueType* toConstExprValueType = (BfConstExprValueType*)toType;
|
|
||||||
|
|
||||||
auto variantVal = TypedValueToVariant(NULL, typedVal);
|
|
||||||
if ((mBfIRBuilder->IsInt(variantVal.mTypeCode)) && (mBfIRBuilder->IsInt(toConstExprValueType->mValue.mTypeCode)))
|
|
||||||
{
|
|
||||||
if (variantVal.mInt64 == toConstExprValueType->mValue.mInt64)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if ((mBfIRBuilder->IsFloat(variantVal.mTypeCode)) && (mBfIRBuilder->IsFloat(toConstExprValueType->mValue.mTypeCode)))
|
|
||||||
{
|
|
||||||
if (variantVal.ToDouble() == toConstExprValueType->mValue.ToDouble())
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((fromType->IsPrimitiveType()) && (toType->IsPrimitiveType()))
|
|
||||||
{
|
|
||||||
auto fromPrimType = (BfPrimitiveType*)fromType;
|
|
||||||
auto toPrimType = (BfPrimitiveType*)toType;
|
|
||||||
|
|
||||||
BfTypeCode fromTypeCode = fromPrimType->mTypeDef->mTypeCode;
|
|
||||||
BfTypeCode toTypeCode = toPrimType->mTypeDef->mTypeCode;
|
|
||||||
|
|
||||||
// Must be from a default int to do an implicit constant cast, not casted by user, ie: (ushort)123
|
|
||||||
if ((toType->IsIntegral()) && (typedVal.mValue))
|
|
||||||
{
|
|
||||||
// Allow constant ints to be implicitly casted to a smaller type if they fit
|
|
||||||
auto constant = mBfIRBuilder->GetConstant(typedVal.mValue);
|
|
||||||
if (constant != NULL)
|
|
||||||
{
|
|
||||||
if (BfIRBuilder::IsInt(constant->mTypeCode))
|
|
||||||
{
|
|
||||||
int64 srcVal = constant->mInt64;
|
|
||||||
|
|
||||||
if (toPrimType->IsChar())
|
|
||||||
{
|
|
||||||
if (srcVal == 0)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if ((fromPrimType->IsChar()) && (!toPrimType->IsChar()))
|
|
||||||
{
|
|
||||||
// Never allow this
|
|
||||||
}
|
|
||||||
else if ((constant->mTypeCode == BfTypeCode_UInt64) && (srcVal < 0))
|
|
||||||
{
|
|
||||||
// There's nothing that this could fit into
|
|
||||||
}
|
|
||||||
else if (toType->IsSigned())
|
|
||||||
{
|
|
||||||
if (toType->mSize == 8) // int64
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int64 minVal = -(1LL << (8 * toType->mSize - 1));
|
|
||||||
int64 maxVal = (1LL << (8 * toType->mSize - 1)) - 1;
|
|
||||||
if ((srcVal >= minVal) && (srcVal <= maxVal))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (toType->mSize == 8) // ulong
|
|
||||||
{
|
|
||||||
if (srcVal >= 0)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int64 minVal = 0;
|
|
||||||
int64 maxVal = (1LL << (8 * toType->mSize)) - 1;
|
|
||||||
if ((srcVal >= minVal) && (srcVal <= maxVal))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (constant->mConstType == BfConstType_Undef)
|
|
||||||
{
|
|
||||||
BF_ASSERT(mBfIRBuilder->mIgnoreWrites);
|
|
||||||
|
|
||||||
auto undefConst = (BfConstantUndef*)constant;
|
|
||||||
|
|
||||||
auto fakeVal = GetFakeTypedValue(GetPrimitiveType(undefConst->mTypeCode));
|
|
||||||
if (CanImplicitlyCast(fakeVal, toType))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (toTypeCode)
|
|
||||||
{
|
|
||||||
case BfTypeCode_UInt8:
|
|
||||||
switch (fromTypeCode)
|
|
||||||
{
|
|
||||||
case BfTypeCode_UInt8:
|
|
||||||
return true;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BfTypeCode_Char16:
|
|
||||||
switch (fromTypeCode)
|
|
||||||
{
|
|
||||||
case BfTypeCode_Char8:
|
|
||||||
return true;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BfTypeCode_Int16:
|
|
||||||
switch (fromTypeCode)
|
|
||||||
{
|
|
||||||
case BfTypeCode_Int8:
|
|
||||||
return true;
|
|
||||||
case BfTypeCode_UInt8:
|
|
||||||
return true;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BfTypeCode_UInt16:
|
|
||||||
switch (fromTypeCode)
|
|
||||||
{
|
|
||||||
case BfTypeCode_UInt8:
|
|
||||||
return true;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BfTypeCode_Int32:
|
|
||||||
switch (fromTypeCode)
|
|
||||||
{
|
|
||||||
case BfTypeCode_Int8:
|
|
||||||
case BfTypeCode_Int16:
|
|
||||||
return true;
|
|
||||||
case BfTypeCode_IntPtr:
|
|
||||||
if (mCompiler->mSystem->mPtrSize == 4)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
case BfTypeCode_UInt8:
|
|
||||||
case BfTypeCode_UInt16:
|
|
||||||
return true;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BfTypeCode_Char32:
|
|
||||||
switch (fromTypeCode)
|
|
||||||
{
|
|
||||||
case BfTypeCode_Char8:
|
|
||||||
case BfTypeCode_Char16:
|
|
||||||
return true;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BfTypeCode_UInt32:
|
|
||||||
switch (fromTypeCode)
|
|
||||||
{
|
|
||||||
case BfTypeCode_UInt8:
|
|
||||||
case BfTypeCode_UInt16:
|
|
||||||
case BfTypeCode_UInt32:
|
|
||||||
return true;
|
|
||||||
case BfTypeCode_UIntPtr:
|
|
||||||
if (mCompiler->mSystem->mPtrSize == 4)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BfTypeCode_Int64:
|
|
||||||
switch (fromTypeCode)
|
|
||||||
{
|
|
||||||
case BfTypeCode_Int8:
|
|
||||||
case BfTypeCode_Int16:
|
|
||||||
case BfTypeCode_Int32:
|
|
||||||
case BfTypeCode_IntPtr:
|
|
||||||
return true;
|
|
||||||
case BfTypeCode_UInt8:
|
|
||||||
case BfTypeCode_UInt16:
|
|
||||||
case BfTypeCode_UInt32:
|
|
||||||
return true;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BfTypeCode_UInt64:
|
|
||||||
switch (fromTypeCode)
|
|
||||||
{
|
|
||||||
case BfTypeCode_UInt8:
|
|
||||||
case BfTypeCode_UInt16:
|
|
||||||
case BfTypeCode_UInt32:
|
|
||||||
case BfTypeCode_UIntPtr:
|
|
||||||
return true;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BfTypeCode_IntPtr:
|
|
||||||
switch (fromTypeCode)
|
|
||||||
{
|
|
||||||
case BfTypeCode_Int8:
|
|
||||||
case BfTypeCode_Int16:
|
|
||||||
case BfTypeCode_Int32:
|
|
||||||
return true;
|
|
||||||
case BfTypeCode_UInt8:
|
|
||||||
case BfTypeCode_UInt16:
|
|
||||||
return true;
|
|
||||||
case BfTypeCode_UInt32:
|
|
||||||
case BfTypeCode_Int64:
|
|
||||||
if (mCompiler->mSystem->mPtrSize == 8)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BfTypeCode_UIntPtr:
|
|
||||||
switch (fromTypeCode)
|
|
||||||
{
|
|
||||||
case BfTypeCode_UInt8:
|
|
||||||
case BfTypeCode_UInt16:
|
|
||||||
case BfTypeCode_UInt32:
|
|
||||||
return true;
|
|
||||||
case BfTypeCode_UInt64:
|
|
||||||
if (mCompiler->mSystem->mPtrSize == 8)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BfTypeCode_Single:
|
|
||||||
switch (fromTypeCode)
|
|
||||||
{
|
|
||||||
case BfTypeCode_Int8:
|
|
||||||
case BfTypeCode_Int16:
|
|
||||||
case BfTypeCode_Int32:
|
|
||||||
case BfTypeCode_Int64:
|
|
||||||
case BfTypeCode_IntPtr:
|
|
||||||
case BfTypeCode_IntUnknown:
|
|
||||||
return true;
|
|
||||||
case BfTypeCode_UInt8:
|
|
||||||
case BfTypeCode_UInt16:
|
|
||||||
case BfTypeCode_UInt32:
|
|
||||||
case BfTypeCode_UInt64:
|
|
||||||
case BfTypeCode_UIntPtr:
|
|
||||||
case BfTypeCode_UIntUnknown:
|
|
||||||
return true;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BfTypeCode_Double:
|
|
||||||
switch (fromTypeCode)
|
|
||||||
{
|
|
||||||
case BfTypeCode_Int8:
|
|
||||||
case BfTypeCode_Int16:
|
|
||||||
case BfTypeCode_Int32:
|
|
||||||
case BfTypeCode_Int64:
|
|
||||||
case BfTypeCode_IntPtr:
|
|
||||||
case BfTypeCode_IntUnknown:
|
|
||||||
return true;
|
|
||||||
case BfTypeCode_UInt8:
|
|
||||||
case BfTypeCode_UInt16:
|
|
||||||
case BfTypeCode_UInt32:
|
|
||||||
case BfTypeCode_UInt64:
|
|
||||||
case BfTypeCode_UIntPtr:
|
|
||||||
case BfTypeCode_UIntUnknown:
|
|
||||||
return true;
|
|
||||||
case BfTypeCode_Single:
|
|
||||||
return true;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrappable -> struct
|
|
||||||
// if ((fromType->IsWrappableType()) && (toType->IsStruct()))
|
|
||||||
// {
|
|
||||||
// auto wrappableType = GetWrappedStructType(fromType);
|
|
||||||
// if (TypeIsSubTypeOf(wrappableType, toType->ToTypeInstance()))
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Check user-defined operators
|
|
||||||
// {
|
|
||||||
// auto fromTypeInstance = fromType->ToTypeInstance();
|
|
||||||
// auto toTypeInstance = toType->ToTypeInstance();
|
|
||||||
//
|
|
||||||
// int bestFromDist = INT_MAX;
|
|
||||||
// BfType* bestFromType = NULL;
|
|
||||||
// int bestNegFromDist = INT_MAX;
|
|
||||||
// BfType* bestNegFromType = NULL;
|
|
||||||
//
|
|
||||||
// int bestToDist = INT_MAX;
|
|
||||||
// BfType* bestToType = NULL;
|
|
||||||
// int bestNegToDist = INT_MAX;
|
|
||||||
// BfType* bestNegToType = NULL;
|
|
||||||
//
|
|
||||||
// for (int pass = 0; pass < 2; pass++)
|
|
||||||
// {
|
|
||||||
// BfBaseClassWalker baseClassWalker(fromType, toType, this);
|
|
||||||
// while (true)
|
|
||||||
// {
|
|
||||||
// auto entry = baseClassWalker.Next();
|
|
||||||
// auto checkInstance = entry.mTypeInstance;
|
|
||||||
// if (checkInstance == NULL)
|
|
||||||
// break;
|
|
||||||
//
|
|
||||||
// for (auto operatorDef : checkInstance->mTypeDef->mOperators)
|
|
||||||
// {
|
|
||||||
// if (operatorDef->mOperatorDeclaration->mIsConvOperator)
|
|
||||||
// {
|
|
||||||
// if ((operatorDef->mOperatorDeclaration->mExplicitToken != NULL) &&
|
|
||||||
// (operatorDef->mOperatorDeclaration->mExplicitToken->GetToken() == BfToken_Explicit))
|
|
||||||
// continue;
|
|
||||||
//
|
|
||||||
// // Get in native module so our module doesn't get a reference to it - we may not end up calling it at all!
|
|
||||||
// auto methodInst = checkInstance->mModule->GetRawMethodInstanceAtIdx(checkInstance, operatorDef->mIdx);
|
|
||||||
//
|
|
||||||
// if (methodInst->GetParamCount() != 1)
|
|
||||||
// {
|
|
||||||
// //AssertErrorState();
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// auto checkFromType = methodInst->GetParamType(0);
|
|
||||||
// if (checkFromType->IsSelf())
|
|
||||||
// checkFromType = entry.mSrcType;
|
|
||||||
//
|
|
||||||
// // Selection pass
|
|
||||||
// if (pass == 0)
|
|
||||||
// {
|
|
||||||
// int fromDist = GetTypeDistance(checkFromType, fromType);
|
|
||||||
// int toDist = GetTypeDistance(toType, methodInst->mReturnType);
|
|
||||||
//
|
|
||||||
// if ((fromDist == INT_MAX) || (toDist == INT_MAX))
|
|
||||||
// continue;
|
|
||||||
//
|
|
||||||
// if ((fromDist >= 0) && (toDist >= 0))
|
|
||||||
// {
|
|
||||||
// if ((fromDist >= 0) && (fromDist < bestFromDist))
|
|
||||||
// {
|
|
||||||
// bestFromDist = fromDist;
|
|
||||||
// bestFromType = checkFromType;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if ((fromDist >= 0) && (toDist < bestToDist))
|
|
||||||
// {
|
|
||||||
// bestToDist = toDist;
|
|
||||||
// bestToType = methodInst->mReturnType;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else // Execution Pass
|
|
||||||
// {
|
|
||||||
// auto returnType = methodInst->mReturnType;
|
|
||||||
// if (returnType->IsSelf())
|
|
||||||
// returnType = entry.mSrcType;
|
|
||||||
//
|
|
||||||
// if ((checkFromType == bestFromType) && (methodInst->mReturnType == bestToType))
|
|
||||||
// {
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (bestFromType == NULL)
|
|
||||||
// bestFromType = bestNegFromType;
|
|
||||||
// if (bestToType == NULL)
|
|
||||||
// bestToType = bestNegToType;
|
|
||||||
//
|
|
||||||
// if ((bestFromType == NULL) || (bestToType == NULL))
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Check user-defined operators
|
|
||||||
if ((castFlags & BfCastFlags_NoConversionOperator) == 0)
|
|
||||||
{
|
|
||||||
auto fromType = typedVal.mType;
|
|
||||||
auto fromTypeInstance = fromType->ToTypeInstance();
|
|
||||||
auto toTypeInstance = toType->ToTypeInstance();
|
|
||||||
|
|
||||||
auto liftedFromType = ((fromTypeInstance != NULL) && fromTypeInstance->IsNullable()) ? fromTypeInstance->GetUnderlyingType() : NULL;
|
|
||||||
auto liftedToType = ((toTypeInstance != NULL) && toTypeInstance->IsNullable()) ? toTypeInstance->GetUnderlyingType() : NULL;
|
|
||||||
|
|
||||||
int bestFromDist = INT_MAX;
|
|
||||||
BfType* bestFromType = NULL;
|
|
||||||
int bestNegFromDist = INT_MAX;
|
|
||||||
BfType* bestNegFromType = NULL;
|
|
||||||
|
|
||||||
int bestToDist = INT_MAX;
|
|
||||||
BfType* bestToType = NULL;
|
|
||||||
int bestNegToDist = INT_MAX;
|
|
||||||
BfType* bestNegToType = NULL;
|
|
||||||
bool isAmbiguousCast = false;
|
|
||||||
|
|
||||||
BfIRValue conversionResult;
|
|
||||||
BfMethodInstance* opMethodInstance = NULL;
|
|
||||||
BfType* opMethodSrcType = NULL;
|
|
||||||
|
|
||||||
// Normal, lifted, execute
|
|
||||||
for (int pass = 0; pass < 3; pass++)
|
|
||||||
{
|
|
||||||
auto checkToType = toType;
|
|
||||||
auto checkFromType = fromType;
|
|
||||||
|
|
||||||
if (pass == 1)
|
|
||||||
{
|
|
||||||
if ((bestFromType != NULL) && (bestToType != NULL))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (liftedFromType != NULL)
|
|
||||||
checkFromType = liftedFromType;
|
|
||||||
if (liftedToType != NULL)
|
|
||||||
checkToType = liftedToType;
|
|
||||||
}
|
|
||||||
else if (pass == 2)
|
|
||||||
{
|
|
||||||
if ((bestFromType == NULL) || (bestToType == NULL))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
BfBaseClassWalker baseClassWalker(fromType, toType, this);
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
auto entry = baseClassWalker.Next();
|
|
||||||
auto checkInstance = entry.mTypeInstance;
|
|
||||||
if (checkInstance == NULL)
|
|
||||||
break;
|
|
||||||
|
|
||||||
for (auto operatorDef : checkInstance->mTypeDef->mOperators)
|
|
||||||
{
|
|
||||||
if (operatorDef->mOperatorDeclaration->mIsConvOperator)
|
|
||||||
{
|
|
||||||
if ((operatorDef->mOperatorDeclaration->mExplicitToken != NULL) &&
|
|
||||||
(operatorDef->mOperatorDeclaration->mExplicitToken->GetToken() == BfToken_Explicit))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto methodInst = GetRawMethodInstanceAtIdx(checkInstance, operatorDef->mIdx);
|
|
||||||
|
|
||||||
if (methodInst->GetParamCount() != 1)
|
|
||||||
{
|
|
||||||
BF_ASSERT(mCompiler->mPassInstance->HasFailed());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto methodFromType = methodInst->GetParamType(0);
|
|
||||||
auto methodToType = methodInst->mReturnType;
|
|
||||||
|
|
||||||
if (methodFromType->IsSelf())
|
|
||||||
methodFromType = entry.mSrcType;
|
|
||||||
if (methodToType->IsSelf())
|
|
||||||
methodToType = entry.mSrcType;
|
|
||||||
|
|
||||||
// Selection pass
|
|
||||||
if (pass < 2)
|
|
||||||
{
|
|
||||||
auto methodCheckFromType = methodFromType;
|
|
||||||
auto methodCheckToType = methodToType;
|
|
||||||
if (pass == 1)
|
|
||||||
{
|
|
||||||
// Only check inner type on lifted types when we aren't checking conversions within lifted class
|
|
||||||
// This avoid some infinite conversions
|
|
||||||
if ((methodCheckFromType->IsNullable()) && (!checkInstance->IsNullable()))
|
|
||||||
methodCheckFromType = methodCheckFromType->GetUnderlyingType();
|
|
||||||
if ((methodCheckToType->IsNullable()) && (!checkInstance->IsNullable()))
|
|
||||||
methodCheckToType = methodCheckToType->GetUnderlyingType();
|
|
||||||
}
|
|
||||||
|
|
||||||
int fromDist = GetTypeDistance(methodCheckFromType, checkFromType);
|
|
||||||
if (fromDist < 0)
|
|
||||||
{
|
|
||||||
// Allow us to cast a constant int to a smaller type if it satisfies the cast operator
|
|
||||||
if ((typedVal.mValue.IsConst()) && (methodCheckFromType != toType) && (CanImplicitlyCast(typedVal, methodCheckFromType)))
|
|
||||||
{
|
|
||||||
fromDist = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int toDist = GetTypeDistance(methodCheckToType, checkToType);
|
|
||||||
|
|
||||||
if ((fromDist == INT_MAX) || (toDist == INT_MAX))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if ((fromDist >= 0) && (toDist >= 0))
|
|
||||||
{
|
|
||||||
if ((fromDist >= 0) && (fromDist < bestFromDist))
|
|
||||||
{
|
|
||||||
bestFromDist = fromDist;
|
|
||||||
bestFromType = methodFromType;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((toDist >= 0) && (toDist < bestToDist))
|
|
||||||
{
|
|
||||||
bestToDist = toDist;
|
|
||||||
bestToType = methodToType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (pass == 2) // Execution Pass
|
|
||||||
{
|
|
||||||
if ((methodFromType == bestFromType) && (methodToType == bestToType))
|
|
||||||
{
|
|
||||||
return CanImplicitlyCast(BfTypedValue(BfIRValue::sValueless, bestToType), toType, castFlags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAmbiguousCast)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bestFromType == NULL)
|
|
||||||
bestFromType = bestNegFromType;
|
|
||||||
if (bestToType == NULL)
|
|
||||||
bestToType = bestNegToType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prim -> TypedPrimitive
|
|
||||||
if ((typedVal.mType->IsPrimitiveType()) && (toType->IsTypedPrimitive()))
|
|
||||||
{
|
|
||||||
bool allowCast = false;
|
|
||||||
if (toType == mCurTypeInstance)
|
|
||||||
allowCast = true;
|
|
||||||
if ((!allowCast) && (typedVal.mType->IsIntegral()) /*&& (!toType->IsEnum())*/)
|
|
||||||
{
|
|
||||||
// Allow implicit cast of zero
|
|
||||||
auto constant = mBfIRBuilder->GetConstant(typedVal.mValue);
|
|
||||||
if ((constant != NULL) && (mBfIRBuilder->IsInt(constant->mTypeCode)))
|
|
||||||
{
|
|
||||||
allowCast = constant->mInt64 == 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (allowCast)
|
|
||||||
{
|
|
||||||
return CanImplicitlyCast(typedVal, toType->GetUnderlyingType(), castFlags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This flow should mirror CastToValue
|
// This flow should mirror CastToValue
|
||||||
bool BfModule::CanImplicitlyCast(BfTypedValue typedVal, BfType* toType, BfCastFlags castFlags)
|
bool BfModule::CanImplicitlyCast(BfTypedValue typedVal, BfType* toType, BfCastFlags castFlags)
|
||||||
{
|
{
|
||||||
BP_ZONE("BfModule::CanImplicitlyCast");
|
BP_ZONE("BfModule::CanImplicitlyCast");
|
||||||
|
|
||||||
//return DoCanImplicitlyCast(typedVal, toType, castFlags);
|
SetAndRestoreValue<bool> prevIgnoreWrites(mBfIRBuilder->mIgnoreWrites, true);
|
||||||
|
return CastToValue(NULL, typedVal, toType, (BfCastFlags)(castFlags | BfCastFlags_SilentFail));
|
||||||
bool canCastToValue;
|
|
||||||
{
|
|
||||||
SetAndRestoreValue<bool> prevIgnoreWrites(mBfIRBuilder->mIgnoreWrites, true);
|
|
||||||
canCastToValue = CastToValue(NULL, typedVal, toType, (BfCastFlags)(castFlags | BfCastFlags_SilentFail));
|
|
||||||
return canCastToValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return canCastToValue;
|
|
||||||
|
|
||||||
bool sideCanCast = DoCanImplicitlyCast(typedVal, toType, castFlags);
|
|
||||||
if (canCastToValue != sideCanCast)
|
|
||||||
{
|
|
||||||
NOP;
|
|
||||||
}
|
|
||||||
//BF_ASSERT(canCastToValue == sideCanCast);
|
|
||||||
return sideCanCast;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool BfModule::AreSplatsCompatible(BfType* fromType, BfType* toType, bool* outNeedsMemberCasting)
|
bool BfModule::AreSplatsCompatible(BfType* fromType, BfType* toType, bool* outNeedsMemberCasting)
|
||||||
{
|
{
|
||||||
if ((fromType->IsTypeInstance()) && (!fromType->IsSplattable()))
|
if ((fromType->IsTypeInstance()) && (!fromType->IsSplattable()))
|
||||||
|
|
|
@ -94,13 +94,13 @@ namespace Tests
|
||||||
IHashable ihB = valB;
|
IHashable ihB = valB;
|
||||||
IHashable ihC = valC;
|
IHashable ihC = valC;
|
||||||
|
|
||||||
Test.Assert(ihA.GetHashCode() == (int)valA);
|
Test.Assert(ihA.GetHashCode() == (int)(void*)valA);
|
||||||
Test.Assert(ihB.GetHashCode() == (int)valB);
|
Test.Assert(ihB.GetHashCode() == (int)(void*)valB);
|
||||||
Test.Assert(ihC.GetHashCode() == (int)valC);
|
Test.Assert(ihC.GetHashCode() == (int)(void*)valC);
|
||||||
|
|
||||||
Test.Assert(GetHash(ihA) == (int)valA);
|
Test.Assert(GetHash(ihA) == (int)(void*)valA);
|
||||||
Test.Assert(GetHash(ihB) == (int)valB);
|
Test.Assert(GetHash(ihB) == (int)(void*)valB);
|
||||||
Test.Assert(GetHash(ihC) == (int)valC);
|
Test.Assert(GetHash(ihC) == (int)(void*)valC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue