diff --git a/IDE/Tests/CompileFail001/src/Declarations.bf b/IDE/Tests/CompileFail001/src/Declarations.bf index 66f95615..112dd66c 100644 --- a/IDE/Tests/CompileFail001/src/Declarations.bf +++ b/IDE/Tests/CompileFail001/src/Declarations.bf @@ -1,3 +1,5 @@ +#pragma warning disable 168 + using System; namespace IDETest { @@ -49,7 +51,7 @@ namespace IDETest { parent = test; mInnerInt = parent.mA; - + mSA.mA = 123; int a = mSA.mA; int b = mSA.mB; //FAIL diff --git a/IDE/Tests/CompileFail001/src/Properties.bf b/IDE/Tests/CompileFail001/src/Properties.bf index e0857b4a..fb2ee73c 100644 --- a/IDE/Tests/CompileFail001/src/Properties.bf +++ b/IDE/Tests/CompileFail001/src/Properties.bf @@ -19,6 +19,9 @@ namespace IDETest struct StructB { public StructA B { get; } + public StructA B2 { get; set mut; } + public ref StructA B3 { get; } //FAIL + public ref StructA B4 { get mut; } int mZ = 9; @@ -29,6 +32,7 @@ namespace IDETest public void Yoop() mut { B = .(); //FAIL + B2.mA = .(); //WARN } } } diff --git a/IDE/src/ScriptManager.bf b/IDE/src/ScriptManager.bf index ea5bf61e..8cb8b0a6 100644 --- a/IDE/src/ScriptManager.bf +++ b/IDE/src/ScriptManager.bf @@ -2428,51 +2428,62 @@ namespace IDE ewc.GetLineText(lineIdx, lineText); ewc.GetLinePosition(lineIdx, var lineStart, var lineEnd); - bool hasError = false; - for (int i = lineStart; i < lineEnd; i++) + + void FindError(bool warning) { - var flags = (SourceElementFlags)ewc.mData.mText[i].mDisplayFlags; - if (flags.HasFlag(.Error)) - hasError = true; - } - - int failIdx = lineText.IndexOf("//FAIL"); - bool expectedError = failIdx != -1; - if (hasError == expectedError) - { - if (expectedError) + bool hasError = false; + for (int i = lineStart; i < lineEnd; i++) { - String wantsError = scope String(lineText, failIdx + "//FAIL".Length); - wantsError.Trim(); - if (!wantsError.IsEmpty) + var flags = (SourceElementFlags)ewc.mData.mText[i].mDisplayFlags; + if (flags.HasFlag(warning ? .Warning : .Error)) + hasError = true; + } + + String kind = warning ? "warning" : "error"; + int failIdx = lineText.IndexOf(warning ? "//WARN" : "//FAIL"); + bool expectedError = failIdx != -1; + if (hasError == expectedError) + { + if (expectedError) { - bool foundErrorText = false; - if (var error = FindError(lineIdx)) + String wantsError = scope String(lineText, failIdx + "//FAIL".Length); + wantsError.Trim(); + if (!wantsError.IsEmpty) { - if (error.mError.Contains(wantsError)) - foundErrorText = true; - if (error.mMoreInfo != null) + bool foundErrorText = false; + if (var error = FindError(lineIdx)) { - for (var moreInfo in error.mMoreInfo) - if (moreInfo.mError.Contains(wantsError)) + if (error.mIsWarning == warning) + { + if (error.mError.Contains(wantsError)) foundErrorText = true; + if (error.mMoreInfo != null) + { + for (var moreInfo in error.mMoreInfo) + if (moreInfo.mError.Contains(wantsError)) + foundErrorText = true; + } + } + } + if (!foundErrorText) + { + mScriptManager.Fail($"Line {lineIdx + 1} {kind} in {textPanel.mFilePath} did not contain {kind} text '{wantsError}'\n\t"); } - } - if (!foundErrorText) - { - mScriptManager.Fail("Error at line {0} in {1} did not contain error text '{2}'\n\t", lineIdx + 1, textPanel.mFilePath, wantsError); } } } - } - else - { - if (hasError) - mScriptManager.Fail("Unexpected error at line {0} in {1}\n\t", lineIdx + 1, textPanel.mFilePath); else - mScriptManager.Fail("Expected error but didn't encounter one at line {0} in {1}\n\t", lineIdx + 1, textPanel.mFilePath); - return; + { + if (hasError) + mScriptManager.Fail($"Unexpected {kind} at line {lineIdx + 1} in {textPanel.mFilePath}\n\t"); + else + mScriptManager.Fail($"Expected {kind} but didn't encounter one at line {lineIdx + 1} in {textPanel.mFilePath}\n\t"); + return; + } } + + FindError(false); + FindError(true); } } diff --git a/IDEHelper/Compiler/BfAst.h b/IDEHelper/Compiler/BfAst.h index c8653d59..0984bbd3 100644 --- a/IDEHelper/Compiler/BfAst.h +++ b/IDEHelper/Compiler/BfAst.h @@ -569,6 +569,7 @@ enum BfTypedValueKind { BfTypedValueKind_Addr, BfTypedValueKind_CopyOnMutateAddr, + BfTypedValueKind_CopyOnMutateAddr_Derived, BfTypedValueKind_ReadOnlyAddr, BfTypedValueKind_TempAddr, BfTypedValueKind_RestrictedTempAddr, @@ -688,7 +689,7 @@ public: bool IsCopyOnMutate() const { - return (mKind == BfTypedValueKind_CopyOnMutateAddr); + return (mKind == BfTypedValueKind_CopyOnMutateAddr) || (mKind == BfTypedValueKind_CopyOnMutateAddr_Derived); } bool IsReadOnly() const diff --git a/IDEHelper/Compiler/BfExprEvaluator.cpp b/IDEHelper/Compiler/BfExprEvaluator.cpp index f918cef2..bf9f66fe 100644 --- a/IDEHelper/Compiler/BfExprEvaluator.cpp +++ b/IDEHelper/Compiler/BfExprEvaluator.cpp @@ -4591,22 +4591,29 @@ BfTypedValue BfExprEvaluator::LoadProperty(BfAstNode* targetSrc, BfTypedValue ta { bool needsCopy = true; - if (setter == NULL) + if (BfNodeIsA(prop->mTypeRef)) { - if (((mModule->mCurMethodInstance->mMethodDef->mMethodType == BfMethodType_Ctor)) && - (target.mType == mModule->mCurTypeInstance)) - { - // Allow writing inside ctor - } - else - { - result.MakeReadOnly(); - needsCopy = false; - } + // Allow full ref } + else + { + if (setter == NULL) + { + if (((mModule->mCurMethodInstance->mMethodDef->mMethodType == BfMethodType_Ctor)) && + (target.mType == mModule->mCurTypeInstance)) + { + // Allow writing inside ctor + } + else + { + result.MakeReadOnly(); + needsCopy = false; + } + } - if (result.mKind == BfTypedValueKind_Addr) - result.mKind = BfTypedValueKind_CopyOnMutateAddr; + if (result.mKind == BfTypedValueKind_Addr) + result.mKind = BfTypedValueKind_CopyOnMutateAddr; + } mPropDef = NULL; mPropSrc = NULL; @@ -4890,10 +4897,7 @@ BfTypedValue BfExprEvaluator::LoadField(BfAstNode* targetSrc, BfTypedValue targe mModule->mBfIRBuilder->CreateStore(target.mValue, elementAddr); target = BfTypedValue(allocaInst, primStructType, true); } - - if (target.IsCopyOnMutate()) - target = mModule->CopyValue(target); - + BfTypedValue targetValue; if ((target.mType != typeInstance) && (!target.IsSplat())) { @@ -4972,6 +4976,9 @@ BfTypedValue BfExprEvaluator::LoadField(BfAstNode* targetSrc, BfTypedValue targe { if ((wantsReadOnly) && (retVal.IsAddr()) && (!retVal.IsReadOnly())) retVal.mKind = BfTypedValueKind_ReadOnlyAddr; + else if ((target.IsCopyOnMutate()) && (retVal.IsAddr())) + retVal.mKind = BfTypedValueKind_CopyOnMutateAddr_Derived; + mIsHeapReference = true; } @@ -6803,6 +6810,9 @@ void BfExprEvaluator::PushThis(BfAstNode* targetSrc, BfTypedValue argVal, BfMeth if (((argVal.mType->IsComposite()) || (argVal.mType->IsTypedPrimitive()))) { + if (argVal.IsCopyOnMutate()) + argVal = mModule->CopyValue(argVal); + if ((argVal.IsReadOnly()) || (!argVal.IsAddr())) { if (!skipMutCheck) @@ -18297,11 +18307,8 @@ bool BfExprEvaluator::CheckIsBase(BfAstNode* checkNode) return true; } -bool BfExprEvaluator::CheckModifyResult(BfTypedValue typedVal, BfAstNode* refNode, const char* modifyType, bool onlyNeedsMut, bool emitWarning, bool skipCopyOnMutate) -{ - if ((!skipCopyOnMutate) && (typedVal.IsCopyOnMutate())) - typedVal = mModule->CopyValue(typedVal); - +bool BfExprEvaluator::CheckModifyResult(BfTypedValue& typedVal, BfAstNode* refNode, const char* modifyType, bool onlyNeedsMut, bool emitWarning, bool skipCopyOnMutate) +{ BfLocalVariable* localVar = NULL; bool isCapturedLocal = false; if (mResultLocalVar != NULL) @@ -18336,6 +18343,9 @@ bool BfExprEvaluator::CheckModifyResult(BfTypedValue typedVal, BfAstNode* refNod } bool canModify = typedVal.CanModify(); + if (((typedVal.mKind == BfTypedValueKind_TempAddr) || (typedVal.mKind == BfTypedValueKind_CopyOnMutateAddr_Derived)) && + (strcmp(modifyType, "assign to") == 0)) + mModule->Warn(0, "Assigning to temporary copy of a value. Consider using 'ref' in value source declaration.", refNode); auto _Fail = [&](const StringImpl& error, BfAstNode* refNode) { @@ -18497,6 +18507,9 @@ bool BfExprEvaluator::CheckModifyResult(BfTypedValue typedVal, BfAstNode* refNod return false; } + if ((!skipCopyOnMutate) && (typedVal.IsCopyOnMutate())) + typedVal = mModule->CopyValue(typedVal); + return mModule->CheckModifyValue(typedVal, refNode, modifyType); } @@ -19250,6 +19263,11 @@ void BfExprEvaluator::PerformAssignment(BfAssignmentExpression* assignExpr, bool return; } + if (ptr.mKind == BfTypedValueKind_CopyOnMutateAddr) + ptr.mKind = BfTypedValueKind_Addr; + else if (ptr.IsCopyOnMutate()) + ptr = mModule->CopyValue(ptr); + BF_ASSERT(convVal); if ((convVal) && (convVal.mType->IsNull()) && (ptr.mType->IsNullable())) { diff --git a/IDEHelper/Compiler/BfExprEvaluator.h b/IDEHelper/Compiler/BfExprEvaluator.h index f54e9d65..e64acc21 100644 --- a/IDEHelper/Compiler/BfExprEvaluator.h +++ b/IDEHelper/Compiler/BfExprEvaluator.h @@ -449,7 +449,7 @@ public: void MarkResultAssigned(); void MakeResultAsValue(); bool CheckIsBase(BfAstNode* checkNode); - bool CheckModifyResult(BfTypedValue typeValue, BfAstNode* refNode, const char* modifyType, bool onlyNeedsMut = false, bool emitWarning = false, bool skipCopyOnMutate = false); + bool CheckModifyResult(BfTypedValue& typeValue, BfAstNode* refNode, const char* modifyType, bool onlyNeedsMut = false, bool emitWarning = false, bool skipCopyOnMutate = false); bool CheckGenericCtor(BfGenericParamType* genericParamType, BfResolvedArgs& argValues, BfAstNode* targetSrc); BfTypedValue LoadProperty(BfAstNode* targetSrc, BfTypedValue target, BfTypeInstance* typeInstance, BfPropertyDef* prop, BfLookupFieldFlags flags, BfCheckedKind checkedKind, bool isInline); BfTypedValue LoadField(BfAstNode* targetSrc, BfTypedValue target, BfTypeInstance* typeInstance, BfFieldDef* fieldDef, BfLookupFieldFlags flags); diff --git a/IDEHelper/Compiler/BfModule.cpp b/IDEHelper/Compiler/BfModule.cpp index 8c38e4d6..fac939de 100644 --- a/IDEHelper/Compiler/BfModule.cpp +++ b/IDEHelper/Compiler/BfModule.cpp @@ -20710,6 +20710,9 @@ void BfModule::ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup, } else if (methodDef->mMethodType == BfMethodType_PropertyGetter) { + if ((methodInstance->mReturnType->IsRef()) && (!methodDef->mIsMutating)) + Fail("Auto-implemented ref property getters must declare 'mut'", methodInstance->mMethodDef->GetRefNode()); + if (methodInstance->mReturnType->IsValuelessType()) { mBfIRBuilder->CreateRetVoid(); @@ -20736,8 +20739,14 @@ void BfModule::ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup, lookupValue = BfTypedValue(mBfIRBuilder->CreateInBoundsGEP(GetThis().mValue, 0, fieldInstance->mDataIdx), fieldInstance->mResolvedType, true); else lookupValue = ExtractValue(GetThis(), fieldInstance, fieldInstance->mDataIdx); - if (!methodInstance->mReturnType->IsRef()) + if (methodInstance->mReturnType->IsRef()) + { + if ((!lookupValue.IsAddr()) && (!lookupValue.mType->IsValuelessType())) + lookupValue = MakeAddressable(lookupValue); + } + else lookupValue = LoadOrAggregateValue(lookupValue); + CreateReturn(lookupValue.mValue); EmitLifetimeEnds(&mCurMethodState->mHeadScope); } diff --git a/IDEHelper/Tests/src/Properties.bf b/IDEHelper/Tests/src/Properties.bf index 6e7119b5..df120820 100644 --- a/IDEHelper/Tests/src/Properties.bf +++ b/IDEHelper/Tests/src/Properties.bf @@ -23,13 +23,20 @@ namespace Tests struct StructB { public StructA B { get; set mut; } + public ref StructA B2 { get mut; set mut; } int mZ = 9; public this() { B = .(); +#unwarn B.mA += 1000; + + StructA sa = .(); + sa.mA += 2000; + B2 = sa; + B2.mA += 3000; } } @@ -72,8 +79,6 @@ namespace Tests } } - - class ClassB { public StructA B { get; set; } @@ -118,10 +123,12 @@ namespace Tests { StructB sb = .(); StructA sa = sb.B; + StructA sa2 = sb.B2; Test.Assert(sa.mA == 111); sb.B = .(222); sa = sb.B; Test.Assert(sa.mA == 222); + Test.Assert(sa2.mA == 5111); StructC sc = default; Test.Assert(sc.C == 123);