diff --git a/BeefLibs/corlib/src/Attribute.bf b/BeefLibs/corlib/src/Attribute.bf index d80e506c..d6714c9d 100644 --- a/BeefLibs/corlib/src/Attribute.bf +++ b/BeefLibs/corlib/src/Attribute.bf @@ -319,7 +319,6 @@ namespace System { } - /// Generally used as a per-method optimization, [DisableObjectAccessChecks] will avoid the runtime per-object-access /// checks which by default are only applied in debug builds anyway. [AttributeUsage(AttributeTargets.Method/*, AlwaysIncludeTarget=true*/)] @@ -327,6 +326,38 @@ namespace System { } + [AttributeUsage(.Method | .Constructor)] + public struct ObsoleteAttribute : Attribute + { + public this(bool isError) + { + + } + + public this(String error, bool isError) + { + + } + } + + [AttributeUsage(.Method | .Constructor)] + public struct ErrorAttribute : Attribute + { + public this(String error) + { + + } + } + + [AttributeUsage(.Method | .Constructor)] + public struct WarnAttribute : Attribute + { + public this(String error) + { + + } + } + /// If [NoDiscard] is used on a method, the the compiler will show a warning if the result is discarded. /// If used on a type, the compiler will show an warning if any method returns that type and the caller discards the result. [AttributeUsage(.Method | .Class | .Struct)] diff --git a/IDE/mintest/minlib/src/System/Attribute.bf b/IDE/mintest/minlib/src/System/Attribute.bf index f631b1af..f53a43e9 100644 --- a/IDE/mintest/minlib/src/System/Attribute.bf +++ b/IDE/mintest/minlib/src/System/Attribute.bf @@ -332,6 +332,38 @@ namespace System { } + [AttributeUsage(.Method | .Constructor)] + public struct ObsoleteAttribute : Attribute + { + public this(bool isError) + { + + } + + public this(String error, bool isError) + { + + } + } + + [AttributeUsage(.Method | .Constructor)] + public struct ErrorAttribute : Attribute + { + public this(String error) + { + + } + } + + [AttributeUsage(.Method | .Constructor)] + public struct WarnAttribute : Attribute + { + public this(String error) + { + + } + } + [AttributeUsage(.Method | .Class | .Struct)] public struct NoDiscardAttribute : Attribute { diff --git a/IDEHelper/Compiler/BfExprEvaluator.cpp b/IDEHelper/Compiler/BfExprEvaluator.cpp index e625618e..6316d82b 100644 --- a/IDEHelper/Compiler/BfExprEvaluator.cpp +++ b/IDEHelper/Compiler/BfExprEvaluator.cpp @@ -1660,7 +1660,7 @@ bool BfMethodMatcher::CheckType(BfTypeInstance* typeInstance, BfTypedValue targe return mBestMethodDef != NULL; } -void BfMethodMatcher::TryDevirtualizeCall(BfTypedValue target) +void BfMethodMatcher::TryDevirtualizeCall(BfTypedValue target, BfTypedValue* origTarget, BfTypedValue* staticResult) { if ((mBestMethodDef == NULL) || (target.mType == NULL)) return; @@ -1684,14 +1684,25 @@ void BfMethodMatcher::TryDevirtualizeCall(BfTypedValue target) if (checkType->IsWrappableType()) checkType = mModule->GetWrappedStructType(checkType); if ((checkType != NULL) && (checkType->IsTypeInstance()) && (!checkType->IsInterface())) - { + { BfTypeInterfaceEntry* bestIFaceEntry = NULL; auto checkTypeInst = checkType->ToTypeInstance(); + + if (mBestMethodTypeInstance->mTypeDef == mModule->mCompiler->mIHashableTypeDef) + { + if ((origTarget != NULL) && (origTarget->mType->IsPointer()) && (staticResult != NULL)) + { + BfTypedValue ptrVal = mModule->LoadValue(*origTarget); + *staticResult = BfTypedValue(mModule->mBfIRBuilder->CreatePtrToInt(ptrVal.mValue, BfTypeCode_IntPtr), mModule->GetPrimitiveType(BfTypeCode_IntPtr)); + return; + } + } + while (checkTypeInst != NULL) { mModule->PopulateType(checkTypeInst, BfPopulateType_DataAndMethods); for (auto&& iface : checkTypeInst->mInterfaces) - { + { //TODO: Why did we have this check? This caused Dictionary to not be able to devirtualize // calls to TKey GetHashCode when TKey was from a user's project... /*if (!checkTypeInst->IsTypeMemberAccessible(iface.mDeclaringType, activeTypeDef)) @@ -1723,6 +1734,13 @@ void BfMethodMatcher::TryDevirtualizeCall(BfTypedValue target) if (bestIFaceEntry != NULL) break; checkTypeInst = checkTypeInst->mBaseType; + + if ((checkTypeInst == NULL) && (checkType->HasWrappedRepresentation())) + { + auto underlyingType = checkType->GetUnderlyingType(); + if ((underlyingType != NULL) && (underlyingType->IsWrappableType())) + checkTypeInst = mModule->GetWrappedStructType(underlyingType); + } } if (bestIFaceEntry != NULL) @@ -3689,6 +3707,82 @@ void BfExprEvaluator::ResolveArgValues(BfResolvedArgs& resolvedArgs, BfResolveAr autoComplete->mIgnoreFixits = hadIgnoredFixits; } +void BfExprEvaluator::PerformCallChecks(BfMethodInstance* methodInstance, BfAstNode* targetSrc) +{ + BfCustomAttributes* customAttributes = methodInstance->GetCustomAttributes(); + if (customAttributes != NULL) + { + auto _AddMethodDeclarationMoreInfo = [&]() + { + if (methodInstance->mMethodDef->mMethodDeclaration != NULL) + mModule->mCompiler->mPassInstance->MoreInfo( + StrFormat("See method declaration '%s'", mModule->MethodToString(methodInstance).c_str()), + methodInstance->mMethodDef->GetRefNode()); + }; + + BfIRConstHolder* constHolder = methodInstance->GetOwner()->mConstHolder; + auto customAttribute = customAttributes->Get(mModule->mCompiler->mObsoleteAttributeTypeDef); + if ((customAttribute != NULL) && (!customAttribute->mCtorArgs.IsEmpty())) + { + String err; + err = StrFormat("'%s' is obsolete", mModule->MethodToString(methodInstance).c_str()); + + bool isError = false; + + auto constant = constHolder->GetConstant(customAttribute->mCtorArgs[0]); + if (constant->mTypeCode == BfTypeCode_Boolean) + { + isError = constant->mBool; + } + else if (customAttribute->mCtorArgs.size() >= 2) + { + String* str = mModule->GetStringPoolString(customAttribute->mCtorArgs[0], constHolder); + if (str != NULL) + { + err += ":\n '"; + err += *str; + err += "'"; + } + + constant = constHolder->GetConstant(customAttribute->mCtorArgs[1]); + isError = constant->mBool; + } + + BfError* error = NULL; + if (isError) + error = mModule->Fail(err, targetSrc); + else + error = mModule->Warn(0, err, targetSrc); + if (error != NULL) + _AddMethodDeclarationMoreInfo(); + } + + customAttribute = customAttributes->Get(mModule->mCompiler->mErrorAttributeTypeDef); + if ((customAttribute != NULL) && (!customAttribute->mCtorArgs.IsEmpty())) + { + String err = StrFormat("Method error: '", mModule->MethodToString(methodInstance).c_str()); + String* str = mModule->GetStringPoolString(customAttribute->mCtorArgs[0], constHolder); + if (str != NULL) + err += *str; + err += "'"; + if (mModule->Fail(err, targetSrc) != NULL) + _AddMethodDeclarationMoreInfo(); + } + + customAttribute = customAttributes->Get(mModule->mCompiler->mWarnAttributeTypeDef); + if ((customAttribute != NULL) && (!customAttribute->mCtorArgs.IsEmpty())) + { + String err = StrFormat("Method warning: '", mModule->MethodToString(methodInstance).c_str()); + String* str = mModule->GetStringPoolString(customAttribute->mCtorArgs[0], constHolder); + if (str != NULL) + err += *str; + err += "'"; + if (mModule->Warn(0, err, targetSrc) != NULL) + _AddMethodDeclarationMoreInfo(); + } + } +} + BfTypedValue BfExprEvaluator::CreateCall(BfMethodInstance* methodInstance, BfIRValue func, bool bypassVirtual, SizedArrayImpl& irArgs, BfTypedValue* sret, bool isTailCall) { // static int sCallIdx = 0; @@ -5926,7 +6020,10 @@ BfTypedValue BfExprEvaluator::MatchMethod(BfAstNode* targetSrc, BfMethodBoundExp methodMatcher.CheckType(lookupTypeInst, target, true); } - methodMatcher.TryDevirtualizeCall(target); + BfTypedValue staticResult; + methodMatcher.TryDevirtualizeCall(target, &origTarget, &staticResult); + if (staticResult) + return staticResult; bypassVirtual |= methodMatcher.mBypassVirtual; if (methodMatcher.mBestMethodDef != NULL) @@ -6660,6 +6757,8 @@ BfTypedValue BfExprEvaluator::MatchMethod(BfAstNode* targetSrc, BfMethodBoundExp } } + PerformCallChecks(moduleMethodInstance.mMethodInstance, targetSrc); + if (result) { bool discardedReturnValue = mUsedAsStatement; @@ -6672,16 +6771,12 @@ BfTypedValue BfExprEvaluator::MatchMethod(BfAstNode* targetSrc, BfMethodBoundExp auto customAttribute = customAttributes->Get(mModule->mCompiler->mNoDiscardAttributeTypeDef); if (!customAttribute->mCtorArgs.IsEmpty()) { - int strId = mModule->GetStringPoolIdx(customAttribute->mCtorArgs[0], constHolder); - if (strId != -1) + String* str = mModule->GetStringPoolString(customAttribute->mCtorArgs[0], constHolder); + if ((str != NULL) && (!str->IsEmpty())) { - auto& entry = mModule->mContext->mStringObjectIdMap[strId]; - if (!entry.mString.IsEmpty()) - { - mModule->Warn(0, text + ": " + entry.mString, targetSrc); - return; - } - } + mModule->Warn(0, text + ": " + *str, targetSrc); + return; + } } } @@ -7505,7 +7600,7 @@ bool BfExprEvaluator::LookupTypeProp(BfTypeOfExpression* typeOfExpr, BfIdentifie mModule->AddDependency(type, mModule->mCurTypeInstance, BfDependencyMap::DependencyFlag_ExprTypeReference); mModule->PopulateType(type); - auto typeInstance = type->ToTypeInstance(); + auto typeInstance = type->ToTypeInstance(); auto _BoolResult = [&](bool val) { @@ -7562,6 +7657,70 @@ bool BfExprEvaluator::LookupTypeProp(BfTypeOfExpression* typeOfExpr, BfIdentifie _Int32Result((typeInstance != NULL) ? typeInstance->mInstAlign : type->mSize); else if (memberName == "InstanceStride") _Int32Result((typeInstance != NULL) ? typeInstance->GetInstStride() : type->GetStride()); + else if ((memberName == "MinValue") || (memberName == "MaxValue")) + { + bool isMin = memberName == "MinValue"; + + BfType* checkType = typeInstance; + if (checkType->IsTypedPrimitive()) + checkType = checkType->GetUnderlyingType(); + + if (checkType->IsPrimitiveType()) + { + auto primType = (BfPrimitiveType*)checkType; + + if (typeInstance->IsEnum()) + { + if (typeInstance->mTypeInfoEx != NULL) + { + mResult = BfTypedValue(mModule->mBfIRBuilder->CreateConst(primType->mTypeDef->mTypeCode, isMin ? (uint64)typeInstance->mTypeInfoEx->mMinValue : (uint64)typeInstance->mTypeInfoEx->mMaxValue), typeInstance); + return true; + } + } + else + { + switch (primType->mTypeDef->mTypeCode) + { + case BfTypeCode_Int8: + mResult = BfTypedValue(mModule->mBfIRBuilder->CreateConst(primType->mTypeDef->mTypeCode, isMin ? -0x80 : 0x7F), typeInstance); + return true; + case BfTypeCode_Int16: + mResult = BfTypedValue(mModule->mBfIRBuilder->CreateConst(primType->mTypeDef->mTypeCode, isMin ? -0x8000 : 0x7FFF), typeInstance); + return true; + case BfTypeCode_Int32: + mResult = BfTypedValue(mModule->mBfIRBuilder->CreateConst(primType->mTypeDef->mTypeCode, isMin ? (uint64)-0x80000000LL : 0x7FFFFFFF), typeInstance); + return true; + case BfTypeCode_Int64: + mResult = BfTypedValue(mModule->mBfIRBuilder->CreateConst(primType->mTypeDef->mTypeCode, isMin ? (uint64)-0x8000000000000000LL : (uint64)0x7FFFFFFFFFFFFFFFLL), typeInstance); + return true; + case BfTypeCode_UInt8: + case BfTypeCode_Char8: + mResult = BfTypedValue(mModule->mBfIRBuilder->CreateConst(primType->mTypeDef->mTypeCode, isMin ? 0 : 0xFF), typeInstance); + return true; + case BfTypeCode_UInt16: + case BfTypeCode_Char16: + mResult = BfTypedValue(mModule->mBfIRBuilder->CreateConst(primType->mTypeDef->mTypeCode, isMin ? 0 : 0xFFFF), typeInstance); + return true; + case BfTypeCode_UInt32: + case BfTypeCode_Char32: + mResult = BfTypedValue(mModule->mBfIRBuilder->CreateConst(primType->mTypeDef->mTypeCode, isMin ? 0 : (uint64)0xFFFFFFFFLL), typeInstance); + return true; + case BfTypeCode_UInt64: + mResult = BfTypedValue(mModule->mBfIRBuilder->CreateConst(primType->mTypeDef->mTypeCode, isMin ? 0 : (uint64)0xFFFFFFFFFFFFFFFFLL), typeInstance); + return true; + } + } + } + + if (typeInstance->IsEnum()) + { + mModule->Fail("'MinValue' cannot be used on enums with payloads", propName); + } + else + { + mModule->Fail(StrFormat("'%s' cannot be used on type '%s'", memberName.c_str(), mModule->TypeToString(typeInstance).c_str()), propName); + } + } else return false; @@ -11287,10 +11446,24 @@ BfTypedValue BfExprEvaluator::MakeCallableTarget(BfAstNode* targetSrc, BfTypedVa } if (target.mType->IsWrappableType()) - { + { auto primStructType = mModule->GetWrappedStructType(target.mType); if (primStructType != NULL) + { + mModule->PopulateType(primStructType); target.mType = primStructType; + if ((primStructType->IsSplattable()) && (!primStructType->IsTypedPrimitive())) + { + if (target.IsAddr()) + { + auto ptrType = mModule->CreatePointerType(primStructType); + target = BfTypedValue(mModule->mBfIRBuilder->CreateBitCast(target.mValue, mModule->mBfIRBuilder->MapType(ptrType)), primStructType, true); + } + else + target.mKind = BfTypedValueKind_SplatHead; + } + } + return target; } @@ -12981,7 +13154,7 @@ void BfExprEvaluator::Visit(BfInvocationExpression* invocationExpr) checkTarget = indexerExpr->mTarget; if (indexerExpr->mCommas.size() != 0) - mModule->Fail("Only one value expected. Consider adding an allocation specifier such as 'new' if construction of a dynamic multidimentional was intended.", indexerExpr->mCommas[0]); + mModule->Fail("Only one value expected. Consider adding an allocation specifier such as 'new' if construction of a dynamic multidimensional was intended.", indexerExpr->mCommas[0]); int arrSize = 0; @@ -13161,10 +13334,11 @@ BfModuleMethodInstance BfExprEvaluator::GetPropertyMethodInstance(BfMethodDef* m if (!checkedUnderlying) { checkedUnderlying = true; - if (checkType->IsTypedPrimitive()) + if (checkType->HasWrappedRepresentation()) { auto underlyingType = checkType->GetUnderlyingType(); - checkTypeInst = mModule->GetWrappedStructType(underlyingType); + if (underlyingType != NULL) + checkTypeInst = mModule->GetWrappedStructType(underlyingType); } } } @@ -13251,12 +13425,13 @@ BfTypedValue BfExprEvaluator::GetResult(bool clearResult, bool resolveGenericTyp if (!mModule->mBfIRBuilder->mIgnoreWrites) { BF_ASSERT(!methodInstance.mFunc.IsFake()); - } - + } + if (mPropSrc != NULL) mModule->UpdateExprSrcPos(mPropSrc); CheckPropFail(matchedMethod, methodInstance.mMethodInstance); + PerformCallChecks(methodInstance.mMethodInstance, mPropSrc); if (methodInstance.mMethodInstance->IsSkipCall()) { diff --git a/IDEHelper/Compiler/BfExprEvaluator.h b/IDEHelper/Compiler/BfExprEvaluator.h index 41a91e9c..947c0ca6 100644 --- a/IDEHelper/Compiler/BfExprEvaluator.h +++ b/IDEHelper/Compiler/BfExprEvaluator.h @@ -167,7 +167,7 @@ public: void CheckOuterTypeStaticMethods(BfTypeInstance* typeInstance, bool isFailurePass); bool WantsCheckMethod(BfProtectionCheckFlags& flags, BfTypeInstance* startTypeInstance, BfTypeInstance* checkTypeInstance, BfMethodDef* methodDef); bool CheckMethod(BfTypeInstance* typeInstance, BfMethodDef* checkMethod, bool isFailurePass); - void TryDevirtualizeCall(BfTypedValue target); + void TryDevirtualizeCall(BfTypedValue target, BfTypedValue* origTarget = NULL, BfTypedValue* staticResult = NULL); }; @@ -345,6 +345,7 @@ public: BfTypedValue LookupIdentifier(BfAstNode* identifierNode, const StringImpl& findName, bool ignoreInitialError = false, bool* hadError = NULL); BfTypedValue LookupIdentifier(BfIdentifierNode* identifierNode, bool ignoreInitialError = false, bool* hadError = NULL); void AddCallDependencies(BfMethodInstance* methodInstance); + void PerformCallChecks(BfMethodInstance* methodInstance, BfAstNode* targetSrc); BfTypedValue CreateCall(BfMethodInstance* methodInstance, BfIRValue func, bool bypassVirtual, SizedArrayImpl& irArgs, BfTypedValue* sret = NULL, bool isTailCall = false); BfTypedValue CreateCall(BfAstNode* targetSrc, const BfTypedValue& target, const BfTypedValue& origTarget, BfMethodDef* methodDef, BfModuleMethodInstance methodInstance, bool bypassVirtual, SizedArrayImpl& argValues, bool skipThis = false); BfTypedValue CreateCall(BfMethodMatcher* methodMatcher, BfTypedValue target);