mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-20 08:58:00 +02:00
Added extension methods
This commit is contained in:
parent
c1a2bd79e1
commit
e1c340a711
12 changed files with 507 additions and 225 deletions
|
@ -2810,26 +2810,27 @@ public:
|
|||
BF_AST_TYPE(BfMethodDeclaration, BfMemberDeclaration);
|
||||
|
||||
BfCommentNode* mDocumentation;
|
||||
ASTREF(BfAttributeDirective*) mReturnAttributes;
|
||||
ASTREF(BfTokenNode*) mExternSpecifier;
|
||||
ASTREF(BfTokenNode*) mVirtualSpecifier; // either 'virtual', 'override', or 'abstract'
|
||||
ASTREF(BfTokenNode*) mNewSpecifier;
|
||||
ASTREF(BfTokenNode*) mMixinSpecifier;
|
||||
ASTREF(BfTokenNode*) mPartialSpecifier;
|
||||
ASTREF(BfTokenNode*) mMutSpecifier;
|
||||
ASTREF(BfTypeReference*) mReturnType;
|
||||
ASTREF(BfTypeReference*) mExplicitInterface;
|
||||
ASTREF(BfTokenNode*) mExplicitInterfaceDotToken;
|
||||
ASTREF(BfIdentifierNode*) mNameNode;
|
||||
ASTREF(BfTokenNode*) mOpenParen;
|
||||
BfSizedArray<ASTREF(BfParameterDeclaration*)> mParams;
|
||||
BfSizedArray<ASTREF(BfTokenNode*)> mCommas;
|
||||
ASTREF(BfTokenNode*) mCloseParen;
|
||||
ASTREF(BfGenericParamsDeclaration*) mGenericParams;
|
||||
ASTREF(BfGenericConstraintsDeclaration*) mGenericConstraintsDeclaration;
|
||||
ASTREF(BfAstNode*) mEndSemicolon;
|
||||
ASTREF(BfTokenNode*) mFatArrowToken;
|
||||
ASTREF(BfAstNode*) mBody; // Either expression or block
|
||||
BfAttributeDirective* mReturnAttributes;
|
||||
BfTokenNode* mExternSpecifier;
|
||||
BfTokenNode* mVirtualSpecifier; // either 'virtual', 'override', or 'abstract'
|
||||
BfTokenNode* mNewSpecifier;
|
||||
BfTokenNode* mMixinSpecifier;
|
||||
BfTokenNode* mPartialSpecifier;
|
||||
BfTokenNode* mMutSpecifier;
|
||||
BfTypeReference* mReturnType;
|
||||
BfTypeReference* mExplicitInterface;
|
||||
BfTokenNode* mExplicitInterfaceDotToken;
|
||||
BfIdentifierNode* mNameNode;
|
||||
BfTokenNode* mOpenParen;
|
||||
BfTokenNode* mThisToken;
|
||||
BfSizedArray<BfParameterDeclaration*> mParams;
|
||||
BfSizedArray<BfTokenNode*> mCommas;
|
||||
BfTokenNode* mCloseParen;
|
||||
BfGenericParamsDeclaration* mGenericParams;
|
||||
BfGenericConstraintsDeclaration* mGenericConstraintsDeclaration;
|
||||
BfAstNode* mEndSemicolon;
|
||||
BfTokenNode* mFatArrowToken;
|
||||
BfAstNode* mBody; // Either expression or block
|
||||
|
||||
//BfMethodDef* mMethodDef;
|
||||
|
||||
|
|
|
@ -910,16 +910,104 @@ void BfAutoComplete::AddEnumTypeMembers(BfTypeInstance* typeInst, const StringIm
|
|||
}
|
||||
}
|
||||
|
||||
// bool BfAutoComplete::IsInExpression(BfAstNode* node)
|
||||
// {
|
||||
// if (mModule->mCurMethodInstance != NULL)
|
||||
// return true;
|
||||
// if (node == NULL)
|
||||
// return false;
|
||||
// if ((node->IsA<BfExpression>()) && (!node->IsA<BfIdentifierNode>()) && (!node->IsA<BfBlock>()))
|
||||
// return true;
|
||||
// return IsInExpression(node->mParent);
|
||||
// }
|
||||
void BfAutoComplete::AddExtensionMethods(BfTypeInstance* targetType, BfTypeInstance* extensionContainer, const StringImpl & filter, bool allowProtected, bool allowPrivate)
|
||||
{
|
||||
if (!extensionContainer->mTypeDef->mHasExtensionMethods)
|
||||
return;
|
||||
|
||||
mModule->PopulateType(extensionContainer, BfPopulateType_Data);
|
||||
|
||||
for (auto methodDef : extensionContainer->mTypeDef->mMethods)
|
||||
{
|
||||
if (methodDef->mMethodType != BfMethodType_Extension)
|
||||
continue;
|
||||
if (methodDef->mIsNoShow)
|
||||
continue;
|
||||
if (methodDef->mName.IsEmpty())
|
||||
continue;
|
||||
|
||||
bool canUseMethod = true;
|
||||
canUseMethod &= CheckProtection(methodDef->mProtection, allowProtected, allowPrivate);
|
||||
|
||||
auto methodInstance = mModule->GetRawMethodInstanceAtIdx(extensionContainer, methodDef->mIdx);
|
||||
if (methodInstance == NULL)
|
||||
continue;
|
||||
|
||||
// Do filter match first- may be cheaper than generic validation
|
||||
if (!DoesFilterMatch(methodDef->mName.c_str(), filter.c_str()))
|
||||
continue;
|
||||
|
||||
auto thisType = methodInstance->GetParamType(0);
|
||||
bool paramValidated = false;
|
||||
if (methodInstance->GetNumGenericParams() > 0)
|
||||
{
|
||||
if ((thisType->IsGenericParam()) && (methodInstance->GetNumGenericParams() == 1))
|
||||
{
|
||||
auto genericParamType = (BfGenericParamType*)thisType;
|
||||
if (genericParamType->mGenericParamKind == BfGenericParamKind_Method)
|
||||
{
|
||||
auto& genericParams = methodInstance->mMethodInfoEx->mGenericParams;
|
||||
if (!mModule->CheckGenericConstraints(BfGenericParamSource(methodInstance), targetType, NULL, genericParams[genericParamType->mGenericParamIdx], NULL, NULL))
|
||||
continue;
|
||||
paramValidated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (((thisType->IsUnspecializedTypeVariation()) || (thisType->IsGenericParam())) &&
|
||||
(!paramValidated))
|
||||
{
|
||||
BfTypeVector genericTypeVector;
|
||||
genericTypeVector.resize(methodInstance->GetNumGenericParams());
|
||||
BfGenericInferContext genericInferContext;
|
||||
genericInferContext.mCheckMethodGenericArguments = &genericTypeVector;
|
||||
genericInferContext.mModule = mModule;
|
||||
genericInferContext.mPrevArgValues.resize(methodInstance->GetNumGenericParams());
|
||||
|
||||
if (!genericInferContext.InferGenericArgument(methodInstance, targetType, thisType, BfIRValue()))
|
||||
continue;
|
||||
|
||||
thisType = mModule->ResolveGenericType(thisType, NULL, &genericTypeVector, false);
|
||||
if (thisType == NULL)
|
||||
continue;
|
||||
|
||||
auto& genericParams = methodInstance->mMethodInfoEx->mGenericParams;
|
||||
bool validateError = false;
|
||||
for (int genericIdx = 0; genericIdx < (int)genericTypeVector.size(); genericIdx++)
|
||||
{
|
||||
auto genericArg = genericTypeVector[genericIdx];
|
||||
if (genericArg == NULL)
|
||||
continue;
|
||||
if (!mModule->CheckGenericConstraints(BfGenericParamSource(methodInstance), genericArg, NULL, genericParams[genericIdx], &genericTypeVector, NULL))
|
||||
{
|
||||
validateError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (validateError)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!paramValidated)
|
||||
{
|
||||
if (!mModule->CanCast(BfTypedValue(mModule->mBfIRBuilder->GetFakeVal(), targetType), thisType))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (canUseMethod)
|
||||
{
|
||||
if (auto methodDeclaration = methodDef->GetMethodDeclaration())
|
||||
{
|
||||
String replaceName;
|
||||
AutoCompleteEntry entry("method", methodDef->mName, methodDeclaration->mDocumentation);
|
||||
if ((AddEntry(entry)) && (mIsGetDefinition))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BfAutoComplete::AddTopLevelNamespaces(BfAstNode* identifierNode)
|
||||
{
|
||||
|
@ -988,35 +1076,6 @@ void BfAutoComplete::AddTopLevelTypes(BfAstNode* identifierNode, bool onlyAttrib
|
|||
}
|
||||
|
||||
AddCurrentTypes(mModule->mCurTypeInstance, filter, true, true, onlyAttribute);
|
||||
|
||||
// Do inners
|
||||
// bool allowInnerPrivate = false;
|
||||
// auto checkTypeInst = mModule->mCurTypeInstance;
|
||||
// while (checkTypeInst != NULL)
|
||||
// {
|
||||
// auto checkTypeDef = checkTypeInst->mTypeDef;
|
||||
// for (auto nestedTypeDef : checkTypeDef->mNestedTypes)
|
||||
// {
|
||||
// if (CheckProtection(nestedTypeDef->mProtection, true, allowInnerPrivate))
|
||||
// AddTypeDef(nestedTypeDef, filter, onlyAttribute);
|
||||
// }
|
||||
//
|
||||
// checkTypeInst = mModule->GetOuterType(mModule->mCurTypeInstance);
|
||||
// if (checkTypeInst != NULL)
|
||||
// AddInnerTypes(checkTypeInst, filter, true, true);
|
||||
//
|
||||
// AddOuterTypes(checkTypeInst, filter, true, true);
|
||||
//
|
||||
// checkTypeInst = mModule->GetBaseType(checkTypeInst);
|
||||
// allowInnerPrivate = false;
|
||||
// }
|
||||
|
||||
// allowInnerPrivate = true;
|
||||
// checkTypeInst = mModule->GetOuterType(mModule->mCurTypeInstance);
|
||||
// if (checkTypeInst != NULL)
|
||||
// AddInnerTypes(checkTypeInst, filter, true, true);
|
||||
//
|
||||
// AddOuterTypes(mModule->mCurTypeInstance, filter, true, true);
|
||||
}
|
||||
|
||||
if (mModule->mCurMethodInstance != NULL)
|
||||
|
@ -1090,29 +1149,6 @@ void BfAutoComplete::AddTopLevelTypes(BfAstNode* identifierNode, bool onlyAttrib
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BfStaticSearch* staticSearch;
|
||||
// if (mModule->mCurTypeInstance->mStaticSearchMap.TryGetValue(activeTypeDef, &staticSearch))
|
||||
// {
|
||||
// for (auto typeInst : staticSearch->mStaticTypes)
|
||||
// {
|
||||
// AddTypeDef(typeInst->mTypeDef, filter, onlyAttribute);
|
||||
// }
|
||||
// }
|
||||
// else if (!activeTypeDef->mStaticSearch.IsEmpty())
|
||||
// {
|
||||
// BF_ASSERT(mModule->mCompiler->IsAutocomplete());
|
||||
// for (auto typeRef : activeTypeDef->mStaticSearch)
|
||||
// {
|
||||
// auto type = mModule->ResolveTypeRef(typeRef, NULL, BfPopulateType_Declaration);
|
||||
// if (type != NULL)
|
||||
// {
|
||||
// auto typeInst = type->ToTypeInstance();
|
||||
// if (typeInst != NULL)
|
||||
// AddTypeDef(typeInst->mTypeDef, filter, onlyAttribute);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1567,8 +1603,38 @@ bool BfAutoComplete::CheckMemberReference(BfAstNode* target, BfAstNode* dotToken
|
|||
AddInnerTypes(typeInst, filter, allowProtected, allowPrivate);
|
||||
|
||||
if (!onlyShowTypes)
|
||||
{
|
||||
AddTypeMembers(typeInst, isStatic, !isStatic, filter, typeInst, false, false);
|
||||
|
||||
if (!isStatic)
|
||||
{
|
||||
auto checkTypeInst = mModule->mCurTypeInstance;
|
||||
while (checkTypeInst != NULL)
|
||||
{
|
||||
AddExtensionMethods(typeInst, checkTypeInst, filter, allowProtected, allowPrivate);
|
||||
checkTypeInst = mModule->GetOuterType(checkTypeInst);
|
||||
}
|
||||
|
||||
if ((mModule->mContext->mCurTypeState != NULL) && (mModule->mContext->mCurTypeState->mTypeInstance != NULL))
|
||||
{
|
||||
BF_ASSERT(mModule->mCurTypeInstance == mModule->mContext->mCurTypeState->mTypeInstance);
|
||||
|
||||
BfGlobalLookup globalLookup;
|
||||
globalLookup.mKind = BfGlobalLookup::Kind_All;
|
||||
mModule->PopulateGlobalContainersList(globalLookup);
|
||||
for (auto& globalContainer : mModule->mContext->mCurTypeState->mGlobalContainers)
|
||||
AddExtensionMethods(typeInst, globalContainer.mTypeInst, filter, false, false);
|
||||
}
|
||||
|
||||
BfStaticSearch* staticSearch = mModule->GetStaticSearch();
|
||||
if (staticSearch != NULL)
|
||||
{
|
||||
for (auto staticTypeInst : staticSearch->mStaticTypes)
|
||||
AddExtensionMethods(typeInst, staticTypeInst, filter, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeInst->IsInterface())
|
||||
{
|
||||
AddTypeMembers(mModule->mContext->mBfObjectType, isStatic, !isStatic, filter, mModule->mContext->mBfObjectType, true, false);
|
||||
|
|
|
@ -210,6 +210,7 @@ public:
|
|||
void AddSelfResultTypeMembers(BfTypeInstance* typeInst, BfTypeInstance* selfType, const StringImpl& filter, bool allowPrivate);
|
||||
bool InitAutocomplete(BfAstNode* dotNode, BfAstNode* nameNode, String& filter);
|
||||
void AddEnumTypeMembers(BfTypeInstance* typeInst, const StringImpl& filter, bool allowProtected, bool allowPrivate);
|
||||
void AddExtensionMethods(BfTypeInstance* targetType, BfTypeInstance* extensionContainer, const StringImpl& filter, bool allowProtected, bool allowPrivate);
|
||||
void AddTopLevelNamespaces(BfAstNode* identifierNode);
|
||||
void AddTopLevelTypes(BfAstNode* identifierNode, bool onlyAttribute = false);
|
||||
void AddOverrides(const StringImpl& filter);
|
||||
|
|
|
@ -3666,6 +3666,10 @@ void BfCompiler::ProcessAutocompleteTempType()
|
|||
return;
|
||||
}
|
||||
|
||||
SetAndRestoreValue<BfMethodState*> prevMethodState(module->mCurMethodState, NULL);
|
||||
SetAndRestoreValue<BfTypeInstance*> prevTypeInstance(module->mCurTypeInstance, NULL);
|
||||
SetAndRestoreValue<BfMethodInstance*> prevMethodInstance(module->mCurMethodInstance, NULL);
|
||||
|
||||
// >>> VisitExteriorIdentifiers
|
||||
mResolvePassData->mAutoComplete->SetModule(module);
|
||||
{
|
||||
|
@ -3706,8 +3710,6 @@ void BfCompiler::ProcessAutocompleteTempType()
|
|||
return;
|
||||
}
|
||||
|
||||
SetAndRestoreValue<BfMethodState*> prevMethodState(module->mCurMethodState, NULL);
|
||||
|
||||
BfTypeState typeState;
|
||||
typeState.mCurTypeDef = tempTypeDef;
|
||||
SetAndRestoreValue<BfTypeState*> prevTypeState(module->mContext->mCurTypeState, &typeState);
|
||||
|
@ -4097,6 +4099,9 @@ void BfCompiler::ProcessAutocompleteTempType()
|
|||
methodInstance->mMethodInstanceGroup = &methodInstanceGroup;
|
||||
methodInstance->mIsAutocompleteMethod = true;
|
||||
|
||||
methodInstanceGroup.mDefault = methodInstance;
|
||||
defer(methodInstanceGroup.mDefault = NULL);
|
||||
|
||||
for (int genericParamIdx = 0; genericParamIdx < (int)methodDef->mGenericParams.size(); genericParamIdx++)
|
||||
{
|
||||
auto genericParamType = module->GetGenericParamType(BfGenericParamKind_Method, genericParamIdx);
|
||||
|
|
|
@ -411,6 +411,9 @@ BfMethodDef* BfDefBuilder::CreateMethodDef(BfMethodDeclaration* methodDeclaratio
|
|||
methodDef->mIsExtern = methodDeclaration->mExternSpecifier != NULL;
|
||||
methodDef->mBody = methodDeclaration->mBody;
|
||||
|
||||
if (methodDeclaration->mThisToken != NULL)
|
||||
methodDef->mMethodType = BfMethodType_Extension;
|
||||
|
||||
HashContext signatureHashCtx;
|
||||
HashNode(signatureHashCtx, methodDeclaration, methodDef->mBody);
|
||||
//methodDef->mSignatureHash = signatureHashCtx.Finish128();
|
||||
|
@ -554,14 +557,18 @@ BfMethodDef* BfDefBuilder::CreateMethodDef(BfMethodDeclaration* methodDeclaratio
|
|||
if (methodDeclaration->mNameNode != NULL)
|
||||
methodDef->mName = methodDeclaration->mNameNode->ToString();
|
||||
methodDef->mMethodType = BfMethodType_Mixin;
|
||||
/*if (!methodDef->mIsStatic)
|
||||
Fail("Mixin must be declared static", methodDeclaration->mMixinSpecifier);*/
|
||||
}
|
||||
else
|
||||
{
|
||||
if (methodDeclaration->mNameNode != NULL)
|
||||
methodDef->mName = methodDeclaration->mNameNode->ToString();
|
||||
methodDef->mMethodType = BfMethodType_Normal;
|
||||
|
||||
if (methodDeclaration->mThisToken != NULL)
|
||||
{
|
||||
methodDef->mMethodType = BfMethodType_Extension;
|
||||
mCurTypeDef->mHasExtensionMethods = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (outerMethodDef != NULL)
|
||||
|
|
|
@ -981,6 +981,7 @@ void BfElementVisitor::Visit(BfMethodDeclaration* methodDeclaration)
|
|||
VisitChild(methodDeclaration->mExplicitInterfaceDotToken);
|
||||
VisitChild(methodDeclaration->mNameNode);
|
||||
VisitChild(methodDeclaration->mOpenParen);
|
||||
VisitChild(methodDeclaration->mThisToken);
|
||||
for (auto& param : methodDeclaration->mParams)
|
||||
VisitChild(param);
|
||||
for (auto& comma : methodDeclaration->mCommas)
|
||||
|
|
|
@ -171,8 +171,8 @@ void BfMethodMatcher::Init(/*SizedArrayImpl<BfResolvedArg>& arguments, */BfSized
|
|||
mSkipImplicitParams = false;
|
||||
mAllowImplicitThis = false;
|
||||
mHadVarConflictingReturnType = false;
|
||||
mAutoFlushAmbiguityErrors = true;
|
||||
mMethodCheckCount = 0;
|
||||
mInferGenericProgressIdx = 0;
|
||||
mCheckedKind = BfCheckedKind_NotSet;
|
||||
mMatchFailKind = MatchFailKind_None;
|
||||
|
||||
|
@ -218,7 +218,7 @@ bool BfMethodMatcher::IsMemberAccessible(BfTypeInstance* typeInst, BfTypeDef* de
|
|||
return true;
|
||||
}
|
||||
|
||||
bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfType* argType, BfType* wantType, BfIRValue argValue, HashSet<BfType*>& checkedTypeSet)
|
||||
bool BfGenericInferContext::InferGenericArgument(BfMethodInstance* methodInstance, BfType* argType, BfType* wantType, BfIRValue argValue)
|
||||
{
|
||||
if (argType == NULL)
|
||||
return false;
|
||||
|
@ -249,7 +249,7 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
return;
|
||||
}
|
||||
|
||||
if (mCheckMethodGenericArguments[wantGenericParam->mGenericParamIdx] != argType)
|
||||
if ((*mCheckMethodGenericArguments)[wantGenericParam->mGenericParamIdx] != argType)
|
||||
{
|
||||
if (methodGenericTypeConstraint != NULL)
|
||||
{
|
||||
|
@ -266,7 +266,7 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
if (argGenericType->mTypeDef == wantGenericType->mTypeDef)
|
||||
{
|
||||
for (int genericArgIdx = 0; genericArgIdx < (int)argGenericType->mTypeGenericArguments.size(); genericArgIdx++)
|
||||
InferGenericArgument(methodInstance, argGenericType->mTypeGenericArguments[genericArgIdx], wantGenericType->mTypeGenericArguments[genericArgIdx], BfIRValue(), checkedTypeSet);
|
||||
InferGenericArgument(methodInstance, argGenericType->mTypeGenericArguments[genericArgIdx], wantGenericType->mTypeGenericArguments[genericArgIdx], BfIRValue());
|
||||
}
|
||||
}
|
||||
else if (checkArgType->IsSizedArray())
|
||||
|
@ -274,10 +274,10 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
auto sizedArrayType = (BfSizedArrayType*)checkArgType;
|
||||
if (wantGenericType->mTypeDef == mModule->mCompiler->mSizedArrayTypeDef)
|
||||
{
|
||||
InferGenericArgument(methodInstance, sizedArrayType->mElementType, wantGenericType->mTypeGenericArguments[0], BfIRValue(), checkedTypeSet);
|
||||
InferGenericArgument(methodInstance, sizedArrayType->mElementType, wantGenericType->mTypeGenericArguments[0], BfIRValue());
|
||||
auto intType = mModule->GetPrimitiveType(BfTypeCode_IntPtr);
|
||||
BfTypedValue arraySize = BfTypedValue(mModule->mBfIRBuilder->CreateConst(BfTypeCode_IntPtr, (uint64)sizedArrayType->mElementCount), intType);
|
||||
InferGenericArgument(methodInstance, mModule->CreateConstExprValueType(arraySize), wantGenericType->mTypeGenericArguments[1], BfIRValue(), checkedTypeSet);
|
||||
InferGenericArgument(methodInstance, mModule->CreateConstExprValueType(arraySize), wantGenericType->mTypeGenericArguments[1], BfIRValue());
|
||||
}
|
||||
}
|
||||
else if (checkArgType->IsPointer())
|
||||
|
@ -285,7 +285,7 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
auto pointerType = (BfPointerType*)checkArgType;
|
||||
if (wantGenericType->mTypeDef == mModule->mCompiler->mPointerTTypeDef)
|
||||
{
|
||||
InferGenericArgument(methodInstance, pointerType->mElementType, wantGenericType->mTypeGenericArguments[0], BfIRValue(), checkedTypeSet);
|
||||
InferGenericArgument(methodInstance, pointerType->mElementType, wantGenericType->mTypeGenericArguments[0], BfIRValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -297,10 +297,9 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
}
|
||||
}
|
||||
|
||||
mInferGenericProgressIdx++;
|
||||
mCheckMethodGenericArguments[wantGenericParam->mGenericParamIdx] = argType;
|
||||
(*mCheckMethodGenericArguments)[wantGenericParam->mGenericParamIdx] = argType;
|
||||
}
|
||||
mCheckMethodGenericArguments[wantGenericParam->mGenericParamIdx] = argType;
|
||||
(*mCheckMethodGenericArguments)[wantGenericParam->mGenericParamIdx] = argType;
|
||||
mPrevArgValues[wantGenericParam->mGenericParamIdx] = argValue;
|
||||
};
|
||||
|
||||
|
@ -334,7 +333,7 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
}
|
||||
}
|
||||
|
||||
auto prevGenericMethodArg = mCheckMethodGenericArguments[wantGenericParam->mGenericParamIdx];
|
||||
auto prevGenericMethodArg = (*mCheckMethodGenericArguments)[wantGenericParam->mGenericParamIdx];
|
||||
auto prevArgValue = mPrevArgValues[wantGenericParam->mGenericParamIdx];
|
||||
if (prevGenericMethodArg == NULL)
|
||||
{
|
||||
|
@ -382,7 +381,7 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
}
|
||||
|
||||
// No implicit conversion, FAIL!
|
||||
mCheckMethodGenericArguments[wantGenericParam->mGenericParamIdx] = NULL;
|
||||
(*mCheckMethodGenericArguments)[wantGenericParam->mGenericParamIdx] = NULL;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -396,12 +395,12 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
auto genericParam = mModule->GetGenericParamInstance((BfGenericParamType*)argType);
|
||||
if ((genericParam->mGenericParamFlags & BfGenericParamFlag_Var) != 0)
|
||||
{
|
||||
InferGenericArgument(methodInstance, mModule->GetPrimitiveType(BfTypeCode_Var), wantType, BfIRValue(), checkedTypeSet);
|
||||
InferGenericArgument(methodInstance, mModule->GetPrimitiveType(BfTypeCode_Var), wantType, BfIRValue());
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((genericParam->mTypeConstraint != NULL) && (genericParam->mTypeConstraint->IsGenericTypeInstance()))
|
||||
InferGenericArgument(methodInstance, genericParam->mTypeConstraint, wantType, BfIRValue(), checkedTypeSet);
|
||||
InferGenericArgument(methodInstance, genericParam->mTypeConstraint, wantType, BfIRValue());
|
||||
}
|
||||
|
||||
if (argType->IsVar())
|
||||
|
@ -411,7 +410,7 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
BfType* wantGenericArgument = wantGenericType->mTypeGenericArguments[genericArgIdx];
|
||||
if (!wantGenericArgument->IsUnspecializedType())
|
||||
continue;
|
||||
InferGenericArgument(methodInstance, mModule->GetPrimitiveType(BfTypeCode_Var), wantGenericArgument, BfIRValue(), checkedTypeSet);
|
||||
InferGenericArgument(methodInstance, mModule->GetPrimitiveType(BfTypeCode_Var), wantGenericArgument, BfIRValue());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -420,16 +419,16 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
return true;
|
||||
auto argGenericType = (BfGenericTypeInstance*)argType;
|
||||
if (argGenericType->mTypeDef != wantGenericType->mTypeDef)
|
||||
return true;
|
||||
return false;
|
||||
|
||||
for (int genericArgIdx = 0; genericArgIdx < (int)argGenericType->mTypeGenericArguments.size(); genericArgIdx++)
|
||||
{
|
||||
BfType* wantGenericArgument = wantGenericType->mTypeGenericArguments[genericArgIdx];
|
||||
if (!wantGenericArgument->IsUnspecializedType())
|
||||
continue;
|
||||
if (!_AddToCheckedSet(argType, checkedTypeSet, alreadyChecked))
|
||||
if (!_AddToCheckedSet(argType, mCheckedTypeSet, alreadyChecked))
|
||||
return true;
|
||||
InferGenericArgument(methodInstance, argGenericType->mTypeGenericArguments[genericArgIdx], wantGenericArgument, BfIRValue(), checkedTypeSet);
|
||||
InferGenericArgument(methodInstance, argGenericType->mTypeGenericArguments[genericArgIdx], wantGenericArgument, BfIRValue());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -440,15 +439,14 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
if (!argType->IsRef())
|
||||
{
|
||||
// Match to non-ref
|
||||
InferGenericArgument(methodInstance, argType, wantRefType->mElementType, BfIRValue(), checkedTypeSet);
|
||||
InferGenericArgument(methodInstance, argType, wantRefType->mElementType, BfIRValue());
|
||||
return true;
|
||||
}
|
||||
auto argRefType = (BfRefType*)argType;
|
||||
//TODO: We removed this check so we still infer even if we have the wrong ref kind
|
||||
//if (wantRefType->mRefKind != argRefType->mRefKind)
|
||||
//return true;
|
||||
InferGenericArgument(methodInstance, argRefType->mElementType, wantRefType->mElementType, BfIRValue(), checkedTypeSet);
|
||||
return true;
|
||||
return InferGenericArgument(methodInstance, argRefType->mElementType, wantRefType->mElementType, BfIRValue());
|
||||
}
|
||||
|
||||
if (wantType->IsPointer())
|
||||
|
@ -457,8 +455,7 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
return true;
|
||||
auto wantPointerType = (BfPointerType*) wantType;
|
||||
auto argPointerType = (BfPointerType*) argType;
|
||||
InferGenericArgument(methodInstance, argPointerType->mElementType, wantPointerType->mElementType, BfIRValue(), checkedTypeSet);
|
||||
return true;
|
||||
return InferGenericArgument(methodInstance, argPointerType->mElementType, wantPointerType->mElementType, BfIRValue());
|
||||
}
|
||||
|
||||
if (wantType->IsUnknownSizedArray())
|
||||
|
@ -467,14 +464,14 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
if (argType->IsUnknownSizedArray())
|
||||
{
|
||||
auto argArrayType = (BfUnknownSizedArrayType*)argType;
|
||||
InferGenericArgument(methodInstance, argArrayType->mElementCountSource, wantArrayType->mElementCountSource, BfIRValue(), checkedTypeSet);
|
||||
InferGenericArgument(methodInstance, argArrayType->mElementCountSource, wantArrayType->mElementCountSource, BfIRValue());
|
||||
}
|
||||
else if (argType->IsSizedArray())
|
||||
{
|
||||
auto argArrayType = (BfSizedArrayType*)argType;
|
||||
BfTypedValue sizeValue(mModule->GetConstValue(argArrayType->mElementCount), mModule->GetPrimitiveType(BfTypeCode_IntPtr));
|
||||
auto sizedType = mModule->CreateConstExprValueType(sizeValue);
|
||||
InferGenericArgument(methodInstance, sizedType, wantArrayType->mElementCountSource, BfIRValue(), checkedTypeSet);
|
||||
InferGenericArgument(methodInstance, sizedType, wantArrayType->mElementCountSource, BfIRValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -484,7 +481,7 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
{
|
||||
auto wantArrayType = (BfSizedArrayType*)wantType;
|
||||
auto argArrayType = (BfSizedArrayType*)argType;
|
||||
InferGenericArgument(methodInstance, argArrayType->mElementType, wantArrayType->mElementType, BfIRValue(), checkedTypeSet);
|
||||
InferGenericArgument(methodInstance, argArrayType->mElementType, wantArrayType->mElementType, BfIRValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -493,7 +490,7 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
if (((argType->IsDelegate()) || (argType->IsFunction())) &&
|
||||
(wantType->IsDelegate() == argType->IsDelegate()))
|
||||
{
|
||||
if (!_AddToCheckedSet(argType, checkedTypeSet, alreadyChecked))
|
||||
if (!_AddToCheckedSet(argType, mCheckedTypeSet, alreadyChecked))
|
||||
return true;
|
||||
|
||||
auto argInvokeMethod = mModule->GetRawMethodByName(argType->ToTypeInstance(), "Invoke");
|
||||
|
@ -501,9 +498,9 @@ bool BfMethodMatcher::InferGenericArgument(BfMethodInstance* methodInstance, BfT
|
|||
|
||||
if ((argInvokeMethod != NULL) && (wantInvokeMethod != NULL) && (argInvokeMethod->GetParamCount() == wantInvokeMethod->GetParamCount()))
|
||||
{
|
||||
InferGenericArgument(methodInstance, argInvokeMethod->mReturnType, wantInvokeMethod->mReturnType, BfIRValue(), checkedTypeSet);
|
||||
InferGenericArgument(methodInstance, argInvokeMethod->mReturnType, wantInvokeMethod->mReturnType, BfIRValue());
|
||||
for (int argIdx = 0; argIdx < (int)argInvokeMethod->GetParamCount(); argIdx++)
|
||||
InferGenericArgument(methodInstance, argInvokeMethod->GetParamType(argIdx), wantInvokeMethod->GetParamType(argIdx), BfIRValue(), checkedTypeSet);
|
||||
InferGenericArgument(methodInstance, argInvokeMethod->GetParamType(argIdx), wantInvokeMethod->GetParamType(argIdx), BfIRValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -515,6 +512,13 @@ void BfMethodMatcher::CompareMethods(BfMethodInstance* prevMethodInstance, BfTyp
|
|||
BfMethodInstance* newMethodInstance, BfTypeVector* genericArgumentsSubstitute,
|
||||
bool* outNewIsBetter, bool* outNewIsWorse, bool allowSpecializeFail)
|
||||
{
|
||||
if (prevMethodInstance == newMethodInstance)
|
||||
{
|
||||
*outNewIsBetter = false;
|
||||
*outNewIsWorse = true;
|
||||
return;
|
||||
}
|
||||
|
||||
#define SET_BETTER_OR_WORSE(lhs, rhs) \
|
||||
if ((!isBetter) && (lhs) && !(rhs)) isBetter = true; \
|
||||
if ((!isWorse) && !(lhs) && (rhs)) isWorse = true;
|
||||
|
@ -551,12 +555,25 @@ void BfMethodMatcher::CompareMethods(BfMethodInstance* prevMethodInstance, BfTyp
|
|||
}
|
||||
}
|
||||
|
||||
bool anyIsExtension = false;
|
||||
|
||||
int newImplicitParamCount = newMethodInstance->GetImplicitParamCount();
|
||||
if (newMethodInstance->mMethodDef->mHasAppend)
|
||||
newImplicitParamCount++;
|
||||
if (newMethodInstance->mMethodDef->mMethodType == BfMethodType_Extension)
|
||||
{
|
||||
newImplicitParamCount++;
|
||||
anyIsExtension = true;
|
||||
}
|
||||
|
||||
int prevImplicitParamCount = prevMethodInstance->GetImplicitParamCount();
|
||||
if (prevMethodInstance->mMethodDef->mHasAppend)
|
||||
prevImplicitParamCount++;
|
||||
if (prevMethodInstance->mMethodDef->mMethodType == BfMethodType_Extension)
|
||||
{
|
||||
prevImplicitParamCount++;
|
||||
anyIsExtension = true;
|
||||
}
|
||||
|
||||
bool hadEnoughArgs = newMethodInstance->GetParamCount() - newImplicitParamCount < (int)mArguments.size();
|
||||
bool prevHadEnoughArgs = prevMethodInstance->GetParamCount() - prevImplicitParamCount < (int)mArguments.size();
|
||||
|
@ -576,16 +593,26 @@ void BfMethodMatcher::CompareMethods(BfMethodInstance* prevMethodInstance, BfTyp
|
|||
bool betterByGenericParam = false;
|
||||
bool worseByGenericParam = false;
|
||||
|
||||
for (argIdx = 0; argIdx < (int)mArguments.size(); argIdx++)
|
||||
for (argIdx = anyIsExtension ? -1 : 0; argIdx < (int)mArguments.size(); argIdx++)
|
||||
{
|
||||
BfResolvedArg resolvedArg = mArguments[argIdx];;
|
||||
BfTypedValue arg = resolvedArg.mTypedValue;
|
||||
BfTypedValue arg;
|
||||
BfResolvedArg* resolvedArg = NULL;
|
||||
|
||||
if (argIdx == -1)
|
||||
{
|
||||
arg = mTarget;
|
||||
}
|
||||
else
|
||||
{
|
||||
resolvedArg = &mArguments[argIdx];
|
||||
arg = resolvedArg->mTypedValue;
|
||||
}
|
||||
|
||||
int newArgIdx = argIdx + newImplicitParamCount;
|
||||
int prevArgIdx = argIdx + prevImplicitParamCount;
|
||||
|
||||
bool wasGenericParam = newMethodInstance->WasGenericParam(newArgIdx);
|
||||
bool prevWasGenericParam = prevMethodInstance->WasGenericParam(prevArgIdx);
|
||||
bool wasGenericParam = (newArgIdx >= 0) && newMethodInstance->WasGenericParam(newArgIdx);
|
||||
bool prevWasGenericParam = (prevArgIdx >= 0) && prevMethodInstance->WasGenericParam(prevArgIdx);
|
||||
|
||||
BfType* paramType = newMethodInstance->GetParamType(newArgIdx);
|
||||
BfType* prevParamType = prevMethodInstance->GetParamType(prevArgIdx);
|
||||
|
@ -637,7 +664,7 @@ void BfMethodMatcher::CompareMethods(BfMethodInstance* prevMethodInstance, BfTyp
|
|||
{
|
||||
// The resolved argument type may actually match for both considered functions. IE:
|
||||
// Method(int8 val) and Method(int16 val) called with Method(0) will create arguments that match their param types
|
||||
if ((IsType(arg, paramType)) && (prevParamType != resolvedArg.mBestBoundType))
|
||||
if ((IsType(arg, paramType)) && ((resolvedArg == NULL) || (prevParamType != resolvedArg->mBestBoundType)))
|
||||
isBetter = true;
|
||||
else if ((IsType(arg, prevParamType)) && (!IsType(arg, paramType)))
|
||||
isWorse = true;
|
||||
|
@ -706,9 +733,9 @@ void BfMethodMatcher::CompareMethods(BfMethodInstance* prevMethodInstance, BfTyp
|
|||
isWorse = true;
|
||||
}
|
||||
|
||||
if (newMethodInstance->GetParamKind(newArgIdx) == BfParamKind_Params)
|
||||
if ((newArgIdx >= 0) && (newMethodInstance->GetParamKind(newArgIdx) == BfParamKind_Params))
|
||||
usedExtendedForm = true;
|
||||
if (prevMethodInstance->GetParamKind(prevArgIdx) == BfParamKind_Params)
|
||||
if ((prevArgIdx >= 0) && (prevMethodInstance->GetParamKind(prevArgIdx) == BfParamKind_Params))
|
||||
prevUsedExtendedForm = true;
|
||||
|
||||
if ((usedExtendedForm) || (prevUsedExtendedForm))
|
||||
|
@ -1236,6 +1263,10 @@ bool BfMethodMatcher::CheckMethod(BfTypeInstance* targetTypeInstance, BfTypeInst
|
|||
}
|
||||
}
|
||||
|
||||
BfGenericInferContext genericInferContext;
|
||||
genericInferContext.mModule = mModule;
|
||||
genericInferContext.mCheckMethodGenericArguments = &mCheckMethodGenericArguments;
|
||||
|
||||
HashSet<int> allowEmptyGenericSet;
|
||||
BfAutoComplete* autoComplete = NULL;
|
||||
if ((mModule->mCompiler->mResolvePassData != NULL) && (!isFailurePass))
|
||||
|
@ -1280,7 +1311,8 @@ bool BfMethodMatcher::CheckMethod(BfTypeInstance* targetTypeInstance, BfTypeInst
|
|||
checkGenericArgRef = NULL;
|
||||
|
||||
mCheckMethodGenericArguments.resize(checkMethod->mGenericParams.size());
|
||||
mPrevArgValues.resize(checkMethod->mGenericParams.size());
|
||||
|
||||
//mPrevArgValues.resize(checkMethod->mGenericParams.size());
|
||||
for (auto& genericArgRef : mCheckMethodGenericArguments)
|
||||
genericArgRef = NULL;
|
||||
|
||||
|
@ -1313,18 +1345,28 @@ bool BfMethodMatcher::CheckMethod(BfTypeInstance* targetTypeInstance, BfTypeInst
|
|||
|
||||
if (needInferGenericParams)
|
||||
{
|
||||
genericInferContext.mPrevArgValues.resize(checkMethod->mGenericParams.size());
|
||||
|
||||
int paramOfs = methodInstance->GetImplicitParamCount();
|
||||
int paramCount = methodInstance->GetParamCount();
|
||||
|
||||
int argIdx = 0;
|
||||
int paramIdx = 0;
|
||||
|
||||
if (checkMethod->mHasAppend)
|
||||
paramOfs++;
|
||||
for (int argIdx = 0; argIdx < (int)mArguments.size(); argIdx++)
|
||||
paramIdx++;
|
||||
|
||||
if (checkMethod->mMethodType == BfMethodType_Extension)
|
||||
{
|
||||
argIdx--;
|
||||
paramOfs++;
|
||||
}
|
||||
|
||||
for ( ; argIdx < (int)mArguments.size(); argIdx++)
|
||||
{
|
||||
if (argIdx >= (int)checkMethod->mParams.size())
|
||||
break;
|
||||
int paramIdx = argIdx + paramOfs;
|
||||
if (paramIdx >= paramCount)
|
||||
break; // Possible for delegate 'params' type methods
|
||||
auto wantType = methodInstance->GetParamType(argIdx + paramOfs);
|
||||
auto wantType = methodInstance->GetParamType(paramIdx);
|
||||
|
||||
auto checkType = wantType;
|
||||
auto origCheckType = checkType;
|
||||
|
@ -1354,17 +1396,25 @@ bool BfMethodMatcher::CheckMethod(BfTypeInstance* targetTypeInstance, BfTypeInst
|
|||
|
||||
if (wantType->IsUnspecializedType())
|
||||
{
|
||||
BfTypedValue argTypedValue = ResolveArgTypedValue(mArguments[argIdx], checkType, genericArgumentsSubstitute);
|
||||
BfTypedValue argTypedValue;
|
||||
if (argIdx == -1)
|
||||
argTypedValue = mTarget;
|
||||
else
|
||||
argTypedValue = ResolveArgTypedValue(mArguments[argIdx], checkType, genericArgumentsSubstitute);
|
||||
if (!argTypedValue.IsUntypedValue())
|
||||
{
|
||||
auto type = argTypedValue.mType;
|
||||
if (!argTypedValue)
|
||||
goto NoMatch;
|
||||
HashSet<BfType*> checkedTypeSet;
|
||||
if (!InferGenericArgument(methodInstance, type, wantType, argTypedValue.mValue, checkedTypeSet))
|
||||
//HashSet<BfType*> checkedTypeSet;
|
||||
|
||||
genericInferContext.mCheckedTypeSet.Clear();
|
||||
if (!genericInferContext.InferGenericArgument(methodInstance, type, wantType, argTypedValue.mValue))
|
||||
goto NoMatch;
|
||||
}
|
||||
}
|
||||
|
||||
paramIdx++;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -1414,6 +1464,9 @@ bool BfMethodMatcher::CheckMethod(BfTypeInstance* targetTypeInstance, BfTypeInst
|
|||
}
|
||||
}
|
||||
|
||||
if (checkMethod->mMethodType == BfMethodType_Extension)
|
||||
argIdx--;
|
||||
|
||||
// Iterate through params
|
||||
while (true)
|
||||
{
|
||||
|
@ -1425,7 +1478,7 @@ bool BfMethodMatcher::CheckMethod(BfTypeInstance* targetTypeInstance, BfTypeInst
|
|||
|
||||
bool isDeferredEval = false;
|
||||
|
||||
if ((methodInstance->GetParamKind(paramIdx) == BfParamKind_Params) && (paramsElementType == NULL))
|
||||
if ((argIdx >= 0) && (methodInstance->GetParamKind(paramIdx) == BfParamKind_Params) && (paramsElementType == NULL))
|
||||
{
|
||||
if (paramIdx >= (int) mArguments.size())
|
||||
break; // No params
|
||||
|
@ -1501,12 +1554,17 @@ bool BfMethodMatcher::CheckMethod(BfTypeInstance* targetTypeInstance, BfTypeInst
|
|||
if (wantType->IsSelf())
|
||||
wantType = typeInstance;
|
||||
|
||||
if ((mArguments[argIdx].mArgFlags & BfArgFlag_ParamsExpr) != 0)
|
||||
if ((argIdx >= 0) && ((mArguments[argIdx].mArgFlags & BfArgFlag_ParamsExpr) != 0))
|
||||
{
|
||||
// We had a 'params' expression but this method didn't have a params slot in this parameter
|
||||
goto NoMatch;
|
||||
}
|
||||
BfTypedValue argTypedValue = ResolveArgTypedValue(mArguments[argIdx], wantType, genericArgumentsSubstitute);
|
||||
|
||||
BfTypedValue argTypedValue;
|
||||
if (argIdx == -1)
|
||||
argTypedValue = mTarget;
|
||||
else
|
||||
argTypedValue = ResolveArgTypedValue(mArguments[argIdx], wantType, genericArgumentsSubstitute);
|
||||
|
||||
if (!argTypedValue.IsUntypedValue())
|
||||
{
|
||||
|
@ -1775,17 +1833,13 @@ bool BfMethodMatcher::IsType(BfTypedValue& typedVal, BfType* type)
|
|||
}
|
||||
|
||||
// This method checks all base classes before checking interfaces. Is that correct?
|
||||
bool BfMethodMatcher::CheckType(BfTypeInstance* typeInstance, BfTypedValue target, bool isFailurePass)
|
||||
bool BfMethodMatcher::CheckType(BfTypeInstance* typeInstance, BfTypedValue target, bool isFailurePass, bool forceOuterCheck)
|
||||
{
|
||||
auto curTypeInst = typeInstance;
|
||||
auto curTypeDef = typeInstance->mTypeDef;
|
||||
|
||||
int checkInterfaceIdx = 0;
|
||||
|
||||
bool allowExplicitInterface = curTypeInst->IsInterface() && mBypassVirtual;
|
||||
auto activeTypeDef = mModule->GetActiveTypeDef();
|
||||
bool isDelegate = typeInstance->IsDelegate();
|
||||
|
||||
bool targetIsBase = target.IsBase();
|
||||
bool checkExtensionBase = false;
|
||||
if (targetIsBase)
|
||||
|
@ -1806,15 +1860,25 @@ bool BfMethodMatcher::CheckType(BfTypeInstance* typeInstance, BfTypedValue targe
|
|||
|
||||
while (true)
|
||||
{
|
||||
curTypeDef->PopulateMemberSets();
|
||||
bool doSearch = true;
|
||||
if ((mMethodType == BfMethodType_Extension) && (!curTypeDef->mHasExtensionMethods))
|
||||
doSearch = false;
|
||||
|
||||
BfMethodDef* nextMethodDef = NULL;
|
||||
if (doSearch)
|
||||
{
|
||||
curTypeDef->PopulateMemberSets();
|
||||
BfMemberSetEntry* entry;
|
||||
if (curTypeDef->mMethodSet.TryGetWith(mMethodName, &entry))
|
||||
nextMethodDef = (BfMethodDef*)entry->mMemberDef;
|
||||
|
||||
}
|
||||
BfProtectionCheckFlags protectionCheckFlags = BfProtectionCheckFlag_None;
|
||||
while (nextMethodDef != NULL)
|
||||
{
|
||||
bool allowExplicitInterface = curTypeInst->IsInterface() && mBypassVirtual;
|
||||
auto activeTypeDef = mModule->GetActiveTypeDef();
|
||||
bool isDelegate = typeInstance->IsDelegate();
|
||||
|
||||
auto checkMethod = nextMethodDef;
|
||||
nextMethodDef = nextMethodDef->mNextWithSameName;
|
||||
|
||||
|
@ -1914,8 +1978,9 @@ bool BfMethodMatcher::CheckType(BfTypeInstance* typeInstance, BfTypedValue targe
|
|||
mMatchFailKind = matchFailKind;
|
||||
}
|
||||
|
||||
if (mBestMethodDef != NULL)
|
||||
if ((mBestMethodDef != NULL) && (mMethodType != BfMethodType_Extension))
|
||||
{
|
||||
if (mAutoFlushAmbiguityErrors)
|
||||
FlushAmbiguityError();
|
||||
return true;
|
||||
}
|
||||
|
@ -1953,7 +2018,8 @@ bool BfMethodMatcher::CheckType(BfTypeInstance* typeInstance, BfTypedValue targe
|
|||
mBestMethodDef = mBackupMethodDef;
|
||||
}
|
||||
|
||||
if ((mBestMethodDef == NULL) && (!target) && (mAllowImplicitThis))
|
||||
if (((mBestMethodDef == NULL) && (!target) && (mAllowImplicitThis)) ||
|
||||
(forceOuterCheck))
|
||||
{
|
||||
// No explicit target - maybe this was a static call in the outer type?
|
||||
auto outerType = mModule->GetOuterType(typeInstance);
|
||||
|
@ -1961,6 +2027,7 @@ bool BfMethodMatcher::CheckType(BfTypeInstance* typeInstance, BfTypedValue targe
|
|||
CheckOuterTypeStaticMethods(outerType, isFailurePass);
|
||||
}
|
||||
|
||||
if (mAutoFlushAmbiguityErrors)
|
||||
FlushAmbiguityError();
|
||||
|
||||
return mBestMethodDef != NULL;
|
||||
|
@ -1977,11 +2044,6 @@ void BfMethodMatcher::TryDevirtualizeCall(BfTypedValue target, BfTypedValue* ori
|
|||
if (mModule->mBfIRBuilder->mIgnoreWrites)
|
||||
return;
|
||||
|
||||
if (mBestMethodDef->mName == "Quab")
|
||||
{
|
||||
NOP;
|
||||
}
|
||||
|
||||
if (mBestMethodTypeInstance->IsInterface())
|
||||
{
|
||||
mModule->PopulateType(mBestMethodTypeInstance, BfPopulateType_DataAndMethods);
|
||||
|
@ -2165,7 +2227,7 @@ void BfMethodMatcher::CheckOuterTypeStaticMethods(BfTypeInstance* typeInstance,
|
|||
// These can only be invoked when the target itself is the interface
|
||||
if (checkMethod->mExplicitInterface != NULL)
|
||||
continue;
|
||||
if ((checkMethod->mMethodType != BfMethodType_Normal) || (!checkMethod->mIsStatic))
|
||||
if ((checkMethod->mMethodType != mMethodType) || (!checkMethod->mIsStatic))
|
||||
continue;
|
||||
if (checkMethod->mName != mMethodName)
|
||||
continue;
|
||||
|
@ -5240,6 +5302,9 @@ BfTypedValue BfExprEvaluator::CreateCall(BfAstNode* targetSrc, const BfTypedValu
|
|||
skipMutCheck = true;
|
||||
}
|
||||
|
||||
if (methodDef->mMethodType == BfMethodType_Extension)
|
||||
PushArg(target, irArgs);
|
||||
else
|
||||
PushThis(targetSrc, target, moduleMethodInstance.mMethodInstance, irArgs, skipMutCheck);
|
||||
}
|
||||
}
|
||||
|
@ -5251,6 +5316,10 @@ BfTypedValue BfExprEvaluator::CreateCall(BfAstNode* targetSrc, const BfTypedValu
|
|||
auto funcPtrType = mModule->mBfIRBuilder->GetPointerTo(funcType);
|
||||
moduleMethodInstance.mFunc = mModule->mBfIRBuilder->CreateIntToPtr(target.mValue, funcPtrType);
|
||||
}
|
||||
else if (methodDef->mMethodType == BfMethodType_Extension)
|
||||
{
|
||||
// Handled in args
|
||||
}
|
||||
else
|
||||
{
|
||||
mModule->CheckStaticAccess(methodInstance->mMethodInstanceGroup->mOwner);
|
||||
|
@ -5463,14 +5532,25 @@ BfTypedValue BfExprEvaluator::CreateCall(BfAstNode* targetSrc, const BfTypedValu
|
|||
|
||||
BfAstNode* arg = NULL;
|
||||
bool hadMissingArg = false;
|
||||
if (argIdx < (int)argValues.size())
|
||||
|
||||
int argExprIdx = argIdx;
|
||||
if ((methodDef->mMethodType == BfMethodType_Extension))
|
||||
{
|
||||
arg = argValues[argIdx].mExpression;
|
||||
if ((arg == NULL) && (argValues[0].mExpression != NULL))
|
||||
argExprIdx--;
|
||||
if (argExprIdx == -1)
|
||||
arg = targetSrc;
|
||||
}
|
||||
if (argExprIdx >= 0)
|
||||
{
|
||||
if (argExprIdx < (int)argValues.size())
|
||||
{
|
||||
arg = argValues[argExprIdx].mExpression;
|
||||
if ((arg == NULL) && (argValues[argExprIdx].mExpression != NULL))
|
||||
hadMissingArg = true;
|
||||
}
|
||||
else
|
||||
hadMissingArg = true;
|
||||
}
|
||||
|
||||
BfTypedValue argValue;
|
||||
|
||||
|
@ -5589,11 +5669,14 @@ BfTypedValue BfExprEvaluator::CreateCall(BfAstNode* targetSrc, const BfTypedValu
|
|||
}
|
||||
else
|
||||
{
|
||||
argValue = argValues[argIdx].mTypedValue;
|
||||
if (argExprIdx == -1)
|
||||
argValue = target;
|
||||
else
|
||||
argValue = argValues[argExprIdx].mTypedValue;
|
||||
|
||||
if ((argValue.IsParams()) && (!isDirectPass))
|
||||
{
|
||||
BfAstNode* refNode = argValues[argIdx].mExpression;
|
||||
BfAstNode* refNode = arg;
|
||||
if (auto unaryOperatorExpr = BfNodeDynCast<BfUnaryOperatorExpression>(refNode))
|
||||
refNode = unaryOperatorExpr->mOpToken;
|
||||
mModule->Warn(0, "Unused 'params' expression", refNode);
|
||||
|
@ -5606,7 +5689,7 @@ BfTypedValue BfExprEvaluator::CreateCall(BfAstNode* targetSrc, const BfTypedValu
|
|||
SetMethodElementType(expr);
|
||||
|
||||
if (!argValue)
|
||||
argValue = mModule->CreateValueFromExpression(BfNodeDynCast<BfExpression>(argValues[argIdx].mExpression), wantType, BfEvalExprFlags_NoCast);
|
||||
argValue = mModule->CreateValueFromExpression(BfNodeDynCast<BfExpression>(arg), wantType, BfEvalExprFlags_NoCast);
|
||||
|
||||
// Add any implicit captures now
|
||||
auto methodRefType = (BfMethodRefType*)wantType;
|
||||
|
@ -5615,7 +5698,7 @@ BfTypedValue BfExprEvaluator::CreateCall(BfAstNode* targetSrc, const BfTypedValu
|
|||
for (int dataIdx = 0; dataIdx < methodRefType->GetCaptureDataCount(); dataIdx++)
|
||||
{
|
||||
int paramIdx = methodRefType->GetParamIdxFromDataIdx(dataIdx);
|
||||
auto lookupVal = DoImplicitArgCapture(argValues[argIdx].mExpression, useMethodInstance, paramIdx, failed, BfImplicitParamKind_General, argValue);
|
||||
auto lookupVal = DoImplicitArgCapture(arg, useMethodInstance, paramIdx, failed, BfImplicitParamKind_General, argValue);
|
||||
if (lookupVal)
|
||||
{
|
||||
if (methodRefType->WantsDataPassedAsSplat(dataIdx))
|
||||
|
@ -5633,14 +5716,14 @@ BfTypedValue BfExprEvaluator::CreateCall(BfAstNode* targetSrc, const BfTypedValu
|
|||
argIdx++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
else if (argExprIdx >= 0)
|
||||
{
|
||||
BfParamKind paramKind = BfParamKind_Normal;
|
||||
if (paramIdx < methodInstance->GetParamCount())
|
||||
paramKind = methodInstance->GetParamKind(paramIdx);
|
||||
|
||||
argValues[argIdx].mExpectedType = wantType;
|
||||
argValue = ResolveArgValue(argValues[argIdx], wantType, NULL, paramKind);
|
||||
argValues[argExprIdx].mExpectedType = wantType;
|
||||
argValue = ResolveArgValue(argValues[argExprIdx], wantType, NULL, paramKind);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6449,6 +6532,7 @@ BfTypedValue BfExprEvaluator::MatchMethod(BfAstNode* targetSrc, BfMethodBoundExp
|
|||
|
||||
SetAndRestoreValue<bool> prevNoBind(mNoBind, mNoBind || isUnboundCall);
|
||||
|
||||
bool wantsExtensionCheck = target;
|
||||
auto targetType = target.mType;
|
||||
BfTypeDef* curTypeDef = NULL;
|
||||
BfTypeInstance* targetTypeInst = NULL;
|
||||
|
@ -6544,10 +6628,12 @@ BfTypedValue BfExprEvaluator::MatchMethod(BfAstNode* targetSrc, BfMethodBoundExp
|
|||
BfTypeInstance* curTypeInst = targetTypeInst;
|
||||
|
||||
BfMethodMatcher methodMatcher(targetSrc, mModule, methodName, argValues.mResolvedArgs, methodGenericArguments);
|
||||
methodMatcher.mTarget = target;
|
||||
methodMatcher.mCheckedKind = checkedKind;
|
||||
methodMatcher.mAllowImplicitThis = allowImplicitThis;
|
||||
methodMatcher.mAllowStatic = !target.mValue;
|
||||
methodMatcher.mAllowNonStatic = !methodMatcher.mAllowStatic;
|
||||
methodMatcher.mAutoFlushAmbiguityErrors = !wantsExtensionCheck;
|
||||
if (allowImplicitThis)
|
||||
{
|
||||
if (mModule->mCurMethodState == NULL)
|
||||
|
@ -7053,10 +7139,25 @@ BfTypedValue BfExprEvaluator::MatchMethod(BfAstNode* targetSrc, BfMethodBoundExp
|
|||
}
|
||||
}
|
||||
|
||||
if ((methodDef == NULL) && (!target) && (mModule->mContext->mCurTypeState != NULL))
|
||||
// For for extensions in current type
|
||||
if (wantsExtensionCheck)
|
||||
{
|
||||
//BF_ASSERT(mModule->mCurTypeInstance == mModule->mContext->mCurTypeState->mTypeInstance);
|
||||
auto checkTypeInst = mModule->mCurTypeInstance;
|
||||
methodMatcher.mMethodType = BfMethodType_Extension;
|
||||
methodMatcher.CheckType(checkTypeInst, BfTypedValue(), false, true);
|
||||
if (methodMatcher.mBestMethodDef != NULL)
|
||||
{
|
||||
isFailurePass = false;
|
||||
curTypeInst = methodMatcher.mBestMethodTypeInstance;
|
||||
methodDef = methodMatcher.mBestMethodDef;
|
||||
}
|
||||
}
|
||||
|
||||
// Look in globals. Always check for extension methods.
|
||||
if ((methodDef == NULL) || (wantsExtensionCheck))
|
||||
{
|
||||
if (mModule->mContext->mCurTypeState != NULL)
|
||||
{
|
||||
BfGlobalLookup globalLookup;
|
||||
globalLookup.mKind = BfGlobalLookup::Kind_Method;
|
||||
globalLookup.mName = methodName;
|
||||
|
@ -7065,35 +7166,47 @@ BfTypedValue BfExprEvaluator::MatchMethod(BfAstNode* targetSrc, BfMethodBoundExp
|
|||
{
|
||||
if (globalContainer.mTypeInst == NULL)
|
||||
continue;
|
||||
methodMatcher.mMethodType = wantsExtensionCheck ? BfMethodType_Extension : BfMethodType_Normal;
|
||||
methodMatcher.CheckType(globalContainer.mTypeInst, BfTypedValue(), false);
|
||||
|
||||
if (methodMatcher.mBestMethodDef != NULL)
|
||||
{
|
||||
isFailurePass = false;
|
||||
curTypeInst = methodMatcher.mBestMethodTypeInstance;
|
||||
methodDef = methodMatcher.mBestMethodDef;
|
||||
// Extension check must check all possible extensions, no early bailout
|
||||
if (!wantsExtensionCheck)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (methodDef == NULL)
|
||||
// Look in static search. Always check for extension methods.
|
||||
if ((methodDef == NULL) || (wantsExtensionCheck))
|
||||
{
|
||||
BfStaticSearch* staticSearch = mModule->GetStaticSearch();
|
||||
if (staticSearch != NULL)
|
||||
{
|
||||
for (auto typeInst : staticSearch->mStaticTypes)
|
||||
{
|
||||
methodMatcher.mMethodType = wantsExtensionCheck ? BfMethodType_Extension : BfMethodType_Normal;
|
||||
methodMatcher.CheckType(typeInst, BfTypedValue(), false);
|
||||
if (methodMatcher.mBestMethodDef != NULL)
|
||||
{
|
||||
isFailurePass = false;
|
||||
curTypeInst = methodMatcher.mBestMethodTypeInstance;
|
||||
methodDef = methodMatcher.mBestMethodDef;
|
||||
// Extension check must check all possible extensions, no early bailout
|
||||
if (!wantsExtensionCheck)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This will flush out any new ambiguity errors from extension methods
|
||||
methodMatcher.FlushAmbiguityError();
|
||||
|
||||
if (methodDef == NULL)
|
||||
{
|
||||
|
@ -7205,6 +7318,14 @@ BfTypedValue BfExprEvaluator::MatchMethod(BfAstNode* targetSrc, BfMethodBoundExp
|
|||
else
|
||||
MakeBaseConcrete(target);
|
||||
|
||||
if (methodDef->mMethodType == BfMethodType_Extension)
|
||||
{
|
||||
auto thisType = moduleMethodInstance.mMethodInstance->GetParamType(0);
|
||||
curTypeInst = thisType->ToTypeInstance();
|
||||
if (curTypeInst == NULL)
|
||||
curTypeInst = mModule->mContext->mBfObjectType;
|
||||
}
|
||||
|
||||
BfTypedValue callTarget;
|
||||
if (isSkipCall)
|
||||
{
|
||||
|
@ -12741,6 +12862,9 @@ void BfExprEvaluator::CheckLocalMethods(BfAstNode* targetSrc, BfTypeInstance* ty
|
|||
|
||||
void BfExprEvaluator::InjectMixin(BfAstNode* targetSrc, BfTypedValue target, bool allowImplicitThis, const StringImpl& name, const BfSizedArray<BfExpression*>& arguments, BfSizedArray<ASTREF(BfTypeReference*)>* methodGenericArgs)
|
||||
{
|
||||
if (mModule->mCurMethodState == NULL)
|
||||
return;
|
||||
|
||||
BfAstNode* origTargetSrc = targetSrc;
|
||||
BfScopedInvocationTarget* scopedInvocationTarget = NULL;
|
||||
|
||||
|
|
|
@ -101,6 +101,18 @@ public:
|
|||
void HandleFixits(BfModule* module);
|
||||
};
|
||||
|
||||
class BfGenericInferContext
|
||||
{
|
||||
public:
|
||||
HashSet<BfType*> mCheckedTypeSet;
|
||||
BfModule* mModule;
|
||||
BfTypeVector* mCheckMethodGenericArguments;
|
||||
SizedArray<BfIRValue, 4> mPrevArgValues;
|
||||
|
||||
public:
|
||||
bool InferGenericArgument(BfMethodInstance* methodInstance, BfType* argType, BfType* wantType, BfIRValue argValue);
|
||||
};
|
||||
|
||||
class BfMethodMatcher
|
||||
{
|
||||
public:
|
||||
|
@ -119,6 +131,7 @@ public:
|
|||
|
||||
public:
|
||||
BfAstNode* mTargetSrc;
|
||||
BfTypedValue mTarget;
|
||||
BfModule* mModule;
|
||||
BfTypeDef* mActiveTypeDef;
|
||||
String mMethodName;
|
||||
|
@ -134,13 +147,12 @@ public:
|
|||
bool mAllowStatic;
|
||||
bool mAllowNonStatic;
|
||||
bool mSkipImplicitParams;
|
||||
bool mAutoFlushAmbiguityErrors;
|
||||
int mMethodCheckCount;
|
||||
int mInferGenericProgressIdx;
|
||||
BfType* mExplicitInterfaceCheck;
|
||||
MatchFailKind mMatchFailKind;
|
||||
|
||||
BfTypeVector mCheckMethodGenericArguments;
|
||||
SizedArray<BfIRValue, 4> mPrevArgValues;
|
||||
|
||||
BfType* mSelfType; // Only when matching interfaces when 'Self' needs to refer back to the implementing type
|
||||
BfMethodDef* mBackupMethodDef;
|
||||
|
@ -157,7 +169,6 @@ public:
|
|||
|
||||
public:
|
||||
BfTypedValue ResolveArgTypedValue(BfResolvedArg& resolvedArg, BfType* checkType, BfTypeVector* genericArgumentsSubstitute);
|
||||
bool InferGenericArgument(BfMethodInstance* methodInstance, BfType* argType, BfType* wantType, BfIRValue argValue, HashSet<BfType*>& checkedTypeSet);
|
||||
bool InferFromGenericConstraints(BfGenericParamInstance* genericParamInst, BfTypeVector* methodGenericArgs);
|
||||
void CompareMethods(BfMethodInstance* prevMethodInstance, BfTypeVector* prevGenericArgumentsSubstitute,
|
||||
BfMethodInstance* newMethodInstance, BfTypeVector* genericArgumentsSubstitute,
|
||||
|
@ -170,7 +181,7 @@ public:
|
|||
BfMethodMatcher(BfAstNode* targetSrc, BfModule* module, BfMethodInstance* interfaceMethodInstance, SizedArrayImpl<BfResolvedArg>& arguments, BfSizedArray<ASTREF(BfTypeReference*)>* methodGenericArguments = NULL);
|
||||
void Init(/*SizedArrayImpl<BfResolvedArg>& arguments, */BfSizedArray<ASTREF(BfTypeReference*)>* methodGenericArguments);
|
||||
bool IsMemberAccessible(BfTypeInstance* typeInst, BfTypeDef* declaringType);
|
||||
bool CheckType(BfTypeInstance* typeInstance, BfTypedValue target, bool isFailurePass);
|
||||
bool CheckType(BfTypeInstance* typeInstance, BfTypedValue target, bool isFailurePass, bool forceOuterCheck = false);
|
||||
void CheckOuterTypeStaticMethods(BfTypeInstance* typeInstance, bool isFailurePass);
|
||||
bool WantsCheckMethod(BfProtectionCheckFlags& flags, BfTypeInstance* startTypeInstance, BfTypeInstance* checkTypeInstance, BfMethodDef* methodDef);
|
||||
bool CheckMethod(BfTypeInstance* targetTypeInstance, BfTypeInstance* typeInstance, BfMethodDef* checkMethod, bool isFailurePass);
|
||||
|
|
|
@ -2290,6 +2290,7 @@ void BfPrinter::QueueMethodDeclaration(BfMethodDeclaration* methodDeclaration)
|
|||
}
|
||||
|
||||
QueueVisitChild(methodDeclaration->mOpenParen);
|
||||
QueueVisitChild(methodDeclaration->mThisToken);
|
||||
for (int i = 0; i < (int) methodDeclaration->mParams.size(); i++)
|
||||
{
|
||||
if (i > 0)
|
||||
|
|
|
@ -2618,6 +2618,7 @@ void BfSystem::InjectNewRevision(BfTypeDef* typeDef)
|
|||
typeDef->mIsStatic = nextTypeDef->mIsStatic;
|
||||
typeDef->mHasAppendCtor = nextTypeDef->mHasAppendCtor;
|
||||
typeDef->mHasOverrideMethods = nextTypeDef->mHasOverrideMethods;
|
||||
typeDef->mHasExtensionMethods = nextTypeDef->mHasExtensionMethods;
|
||||
typeDef->mIsOpaque = nextTypeDef->mIsOpaque;
|
||||
|
||||
typeDef->mDupDetectedRevision = nextTypeDef->mDupDetectedRevision;
|
||||
|
@ -2718,6 +2719,7 @@ void BfSystem::AddToCompositePartial(BfPassInstance* passInstance, BfTypeDef* co
|
|||
typeDef->mIsConcrete = partialTypeDef->mIsConcrete;
|
||||
typeDef->mIsStatic = partialTypeDef->mIsStatic;
|
||||
typeDef->mHasAppendCtor = partialTypeDef->mHasAppendCtor;
|
||||
typeDef->mHasExtensionMethods = partialTypeDef->mHasExtensionMethods;
|
||||
typeDef->mHasOverrideMethods = partialTypeDef->mHasOverrideMethods;
|
||||
|
||||
for (auto generic : partialTypeDef->mGenericParamDefs)
|
||||
|
@ -2750,6 +2752,7 @@ void BfSystem::AddToCompositePartial(BfPassInstance* passInstance, BfTypeDef* co
|
|||
typeDef->mIsConcrete |= partialTypeDef->mIsConcrete;
|
||||
typeDef->mIsStatic |= partialTypeDef->mIsStatic;
|
||||
typeDef->mHasAppendCtor |= partialTypeDef->mHasAppendCtor;
|
||||
typeDef->mHasExtensionMethods |= partialTypeDef->mHasExtensionMethods;
|
||||
typeDef->mHasOverrideMethods |= partialTypeDef->mHasOverrideMethods;
|
||||
typeDef->mProtection = BF_MIN(typeDef->mProtection, partialTypeDef->mProtection);
|
||||
|
||||
|
|
|
@ -631,7 +631,8 @@ enum BfMethodType : uint8
|
|||
BfMethodType_CtorClear,
|
||||
BfMethodType_Dtor,
|
||||
BfMethodType_Operator,
|
||||
BfMethodType_Mixin
|
||||
BfMethodType_Mixin,
|
||||
BfMethodType_Extension
|
||||
};
|
||||
|
||||
enum BfCallingConvention : uint8
|
||||
|
@ -898,6 +899,7 @@ public:
|
|||
bool mIsConcrete;
|
||||
bool mIsStatic;
|
||||
bool mHasAppendCtor;
|
||||
bool mHasExtensionMethods;
|
||||
bool mHasOverrideMethods;
|
||||
bool mIsOpaque;
|
||||
bool mIsNextRevision;
|
||||
|
@ -934,6 +936,7 @@ public:
|
|||
mIsClosure = false;
|
||||
mIsStatic = false;
|
||||
mHasAppendCtor = false;
|
||||
mHasExtensionMethods = false;
|
||||
mHasOverrideMethods = false;
|
||||
mIsOpaque = false;
|
||||
mPartialUsed = false;
|
||||
|
|
59
IDEHelper/Tests/src/ExtensionMethods.bf
Normal file
59
IDEHelper/Tests/src/ExtensionMethods.bf
Normal file
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
|
||||
using static Tests.ExtensionMethods.ClassB;
|
||||
|
||||
namespace Tests
|
||||
{
|
||||
class ExtensionMethods
|
||||
{
|
||||
public static int Remove(this String str, float val, float val2)
|
||||
{
|
||||
return 123;
|
||||
}
|
||||
|
||||
class ClassA
|
||||
{
|
||||
public static void Test()
|
||||
{
|
||||
Test.Assert("Abc".Remove(1.2f, 2.3f) == 123);
|
||||
}
|
||||
}
|
||||
|
||||
public class ClassB
|
||||
{
|
||||
public static int Flop(this String str, int a)
|
||||
{
|
||||
return a + 1000;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public static void TestBasics()
|
||||
{
|
||||
ClassA.Test();
|
||||
|
||||
Test.Assert("Test".Flop(234) == 1234);
|
||||
|
||||
List<int> iList = scope .();
|
||||
iList.Add(3);
|
||||
iList.Add(20);
|
||||
iList.Add(100);
|
||||
|
||||
Test.Assert(iList.Total() == 123);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
{
|
||||
public static T Total<T>(this List<T> list) where T : IOpAddable
|
||||
{
|
||||
T total = default;
|
||||
for (let val in list)
|
||||
total += val;
|
||||
return total;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue