From 419e24e93d621b6024ad106983e8fb668c9200ec Mon Sep 17 00:00:00 2001 From: Jens Alfke Date: Fri, 21 Nov 2025 12:11:20 -0800 Subject: [PATCH 1/3] Ref<> enhancements - `destroy()` method releases the reference, equivalent to `= nullptr` which is otherwise disallowed. This is for cases where a non-null reference needs to be released in e.g. a `close()` method, but it's understood that it can't be accessed again. This allows it to be typed as `Ref<>`. - `isValid()` returns false if the Ref has been destroyed. - `operator bool` is disallowed for Ref<>, because it's conceptually not nullable, so testing it is usually a sign that you're doing unnecessary checks. If you really need to check, use isValid(). --- API/fleece/RefCounted.hh | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/API/fleece/RefCounted.hh b/API/fleece/RefCounted.hh index 1e8fe664..33def92e 100644 --- a/API/fleece/RefCounted.hh +++ b/API/fleece/RefCounted.hh @@ -166,8 +166,29 @@ namespace fleece { } return *this; } - - explicit operator bool () const FLPURE {return N==NonNull || (_ref != nullptr);} + +#if 1 + explicit operator bool () const noexcept FLPURE {return (_ref != nullptr);} +#else + explicit operator bool () const noexcept requires(N==MaybeNull) FLPURE {return (_ref != nullptr);} + [[deprecated("A Ref<> is non-null")]] explicit operator bool () const noexcept requires(N==NonNull) FLPURE {return (_ref != nullptr);} +#endif + + /// Releases the reference. Afterwards this Ref MUST NOT be used again. + /// @warning This method should be used with caution! + /// Its purpose is to support classes with a `close` or `finalize` method that releases all + /// references before it's destructed. Those references might always be non-null until the + /// close; they can be (accurately) declared as `Ref` instead of `Retained` by having the + /// close method use `destroy` to release them. + /// @note Calling `destroy` requires an explicit `std::move`; this is to make it clear + /// that the object's lifetime is effectively ended by the call. + void destroy() && noexcept requires(N==NonNull) { + release(_ref); + _ref = nullptr; + } + + /// Returns true if \ref destroy has been called on this Ref. + bool isDestroyed() const noexcept requires(N==NonNull) FLPURE {return _ref == nullptr;} // typical dereference operations: operator T_ptr () const & noexcept LIFETIMEBOUND FLPURE STEPOVER {return _ref;} From 3f1fbdd61c2225ef45ed9903d65682fc0fb77bb9 Mon Sep 17 00:00:00 2001 From: Jens Alfke Date: Fri, 21 Nov 2025 12:12:21 -0800 Subject: [PATCH 2/3] Use Ref<> in Fleece code Converted some `Retained` into `Ref`s. Fixed a template method in ValueSlot that didn't work with `Ref`. --- Fleece/Core/Builder.cc | 8 ++++---- Fleece/Core/Doc.cc | 4 ++-- Fleece/Core/Doc.hh | 4 ++-- Fleece/Core/Encoder.cc | 11 +++++------ Fleece/Core/Encoder.hh | 2 +- Fleece/Mutable/MutableArray.hh | 6 +++--- Fleece/Mutable/MutableDict.hh | 4 ++-- Fleece/Mutable/ValueSlot.hh | 5 ++--- 8 files changed, 21 insertions(+), 23 deletions(-) diff --git a/Fleece/Core/Builder.cc b/Fleece/Core/Builder.cc index 816fc733..05e6aaa7 100644 --- a/Fleece/Core/Builder.cc +++ b/Fleece/Core/Builder.cc @@ -48,13 +48,13 @@ namespace fleece::impl::builder { RetainedConst buildValue() { switch (peekToken()) { case '[': { - Retained array = MutableArray::newArray(); + Ref array = MutableArray::newArray(); _buildInto(array); finished(); return array.get(); } case '{': { - Retained dict = MutableDict::newDict(); + Ref dict = MutableDict::newDict(); _buildInto(dict); finished(); return dict.get(); @@ -82,13 +82,13 @@ namespace fleece::impl::builder { bool _buildValue(ValueSlot &inSlot) { switch (peekValue()) { case ValueType::array: { - Retained array = MutableArray::newArray(); + Ref array = MutableArray::newArray(); _buildInto(array); inSlot.set(array); break; } case ValueType::dict: { - Retained dict = MutableDict::newDict(); + Ref dict = MutableDict::newDict(); _buildInto(dict); inSlot.set(dict); break; diff --git a/Fleece/Core/Doc.cc b/Fleece/Core/Doc.cc index e007d743..ef0d97df 100644 --- a/Fleece/Core/Doc.cc +++ b/Fleece/Core/Doc.cc @@ -350,11 +350,11 @@ namespace fleece { namespace impl { } - Retained Doc::fromFleece(const alloc_slice &fleece, Trust trust) { + Ref Doc::fromFleece(const alloc_slice &fleece, Trust trust) { return new Doc(fleece, trust); } - Retained Doc::fromJSON(slice json, SharedKeys *sk) { + Ref Doc::fromJSON(slice json, SharedKeys *sk) { return new Doc(JSONConverter::convertJSON(json, sk), kTrusted, sk); } diff --git a/Fleece/Core/Doc.hh b/Fleece/Core/Doc.hh index 4b705421..a3c9b53e 100644 --- a/Fleece/Core/Doc.hh +++ b/Fleece/Core/Doc.hh @@ -109,8 +109,8 @@ namespace fleece { namespace impl { slice subData, Trust =kUntrusted) noexcept; - static Retained fromFleece(const alloc_slice &fleece, Trust =kUntrusted); - static Retained fromJSON(slice json, SharedKeys* =nullptr); + static Ref fromFleece(const alloc_slice &fleece, Trust =kUntrusted); + static Ref fromJSON(slice json, SharedKeys* =nullptr); static RetainedConst containing(const Value* NONNULL) noexcept; diff --git a/Fleece/Core/Encoder.cc b/Fleece/Core/Encoder.cc index 397111e5..edcf270d 100644 --- a/Fleece/Core/Encoder.cc +++ b/Fleece/Core/Encoder.cc @@ -146,12 +146,11 @@ namespace fleece { namespace impl { return out; } - Retained Encoder::finishDoc() { - Retained doc = new Doc(finish(), - Doc::kTrusted, - _sharedKeys, - (_markExternPtrs ? _base : slice())); - return doc; + Ref Encoder::finishDoc() { + return new Doc(finish(), + Doc::kTrusted, + _sharedKeys, + (_markExternPtrs ? _base : slice())); } // Returns position in the stream of the next write. Pads stream to even pos if necessary. diff --git a/Fleece/Core/Encoder.hh b/Fleece/Core/Encoder.hh index 4788b8ec..9e8b0480 100644 --- a/Fleece/Core/Encoder.hh +++ b/Fleece/Core/Encoder.hh @@ -67,7 +67,7 @@ namespace fleece { namespace impl { alloc_slice finish(); /** Returns the encoded data as a Doc. This implicitly calls end(). */ - Retained finishDoc(); + Ref finishDoc(); /** Resets the encoder so it can be used again. */ void reset(); diff --git a/Fleece/Mutable/MutableArray.hh b/Fleece/Mutable/MutableArray.hh index 5dedcc4b..eb400c05 100644 --- a/Fleece/Mutable/MutableArray.hh +++ b/Fleece/Mutable/MutableArray.hh @@ -24,20 +24,20 @@ namespace fleece { namespace impl { public: /** Creates a new array of size `initialCount` filled with null Values. */ - static Retained newArray(uint32_t initialCount =0) { + static Ref newArray(uint32_t initialCount =0) { return (new internal::HeapArray(initialCount))->asMutableArray(); } /** Creates a copy of `a`, or an empty array if `a` is null. If `deepCopy` is true, nested mutable collections will be recursively copied too. */ - static Retained newArray(const Array *a, CopyFlags flags =kDefaultCopy) { + static Ref newArray(const Array *a, CopyFlags flags =kDefaultCopy) { auto ha = retained(new internal::HeapArray(a)); if (flags) ha->copyChildren(flags); return ha->asMutableArray(); } - Retained copy(CopyFlags f = kDefaultCopy) {return newArray(this, f);} + Ref copy(CopyFlags f = kDefaultCopy) {return newArray(this, f);} const Array* source() const {return heapArray()->_source;} bool isChanged() const {return heapArray()->isChanged();} diff --git a/Fleece/Mutable/MutableDict.hh b/Fleece/Mutable/MutableDict.hh index b62e5713..5105c93d 100644 --- a/Fleece/Mutable/MutableDict.hh +++ b/Fleece/Mutable/MutableDict.hh @@ -21,11 +21,11 @@ namespace fleece { namespace impl { class MutableDict : public Dict { public: - static Retained newDict(const Dict *d =nullptr, CopyFlags flags =kDefaultCopy) { + static Ref newDict(const Dict *d =nullptr, CopyFlags flags =kDefaultCopy) { return retained(new internal::HeapDict(d, flags))->asMutableDict(); } - Retained copy(CopyFlags f =kDefaultCopy) {return newDict(this, f);} + Ref copy(CopyFlags f =kDefaultCopy) {return newDict(this, f);} const Dict* source() const {return heapDict()->_source;} bool isChanged() const {return heapDict()->isChanged();} diff --git a/Fleece/Mutable/ValueSlot.hh b/Fleece/Mutable/ValueSlot.hh index 3d2d1329..6bacdb43 100644 --- a/Fleece/Mutable/ValueSlot.hh +++ b/Fleece/Mutable/ValueSlot.hh @@ -57,9 +57,8 @@ namespace fleece { namespace impl { // These methods allow set(Value*) to be called, without allowing any other pointer type // to be used; without them, set(Foo*) would incorrectly call set(bool). - template void set(const T* t) {setValue(t);} - template void set(const Retained &t) {setValue(t);} - template void set(const RetainedConst &t) {setValue(t);} + template void set(const T* t) {setValue(t);} + template void set(const Retained &t) {setValue(t);} /** Replaces an external value with a copy of itself. */ void copyValue(CopyFlags); From a129f281080906b744c63190e5d61e95f1252af3 Mon Sep 17 00:00:00 2001 From: Jens Alfke Date: Thu, 26 Feb 2026 10:28:37 -0800 Subject: [PATCH 3/3] Fixed one more test failure due to 1000people.fleece --- Tests/EncoderTests.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/EncoderTests.cc b/Tests/EncoderTests.cc index 73c2be0e..5c9cceca 100644 --- a/Tests/EncoderTests.cc +++ b/Tests/EncoderTests.cc @@ -721,7 +721,9 @@ class EncoderTests { #if FL_HAVE_TEST_FILES TEST_CASE_METHOD(EncoderTests, "Encode To File", "[Encoder]") { - auto doc = readTestFile("1000people.fleece"); + if (!sCreated1000PeopleFile) + create100PeopleFleeceFile(); + auto doc = readTestFile("1000people.fleece"); auto root = Value::fromTrustedData(doc)->asArray(); {