[clang] [llvm] WIP POC for clang-tidy attributes for methods / classes (PR #192683)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Apr 28 07:44:16 PDT 2026
https://github.com/KHicketts updated https://github.com/llvm/llvm-project/pull/192683
>From 3781e6fa8e233cf80e23eaeb6969462a860157cf Mon Sep 17 00:00:00 2001
From: Kay Hicketts <khicketts at bloomberg.net>
Date: Fri, 17 Apr 2026 14:30:14 +0000
Subject: [PATCH 1/6] add some test files for playing with
---
hicketts_test/clang-tidy-overview.md | 58 ++++++++++++++++++
hicketts_test/hicketts_optional.h | 75 ++++++++++++++++++++++++
hicketts_test/run_instructions.md | 19 ++++++
hicketts_test/test_hicketts_optional.cpp | 73 +++++++++++++++++++++++
hicketts_test/test_makevalue_fix.cpp | 68 +++++++++++++++++++++
5 files changed, 293 insertions(+)
create mode 100644 hicketts_test/clang-tidy-overview.md
create mode 100644 hicketts_test/hicketts_optional.h
create mode 100644 hicketts_test/run_instructions.md
create mode 100644 hicketts_test/test_hicketts_optional.cpp
create mode 100644 hicketts_test/test_makevalue_fix.cpp
diff --git a/hicketts_test/clang-tidy-overview.md b/hicketts_test/clang-tidy-overview.md
new file mode 100644
index 0000000000000..8bb42792f16e2
--- /dev/null
+++ b/hicketts_test/clang-tidy-overview.md
@@ -0,0 +1,58 @@
+# How clang-tidy Works
+
+## Entry Point
+
+`clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp` — parses CLI args (`--checks`, `--fix`, etc.), builds an options provider, and calls `runClangTidy()`.
+
+## Module & Check Registration
+
+Uses LLVM's static registry pattern:
+
+1. **Modules** (e.g. `BugproneModule`, `ModernizeModule`) subclass `ClangTidyModule` and register themselves statically via `ClangTidyModuleRegistry::Add<T>`.
+2. Each module's `addCheckFactories()` registers individual checks by name:
+ ```cpp
+ Factories.registerCheck<UseNullptrCheck>("modernize-use-nullptr");
+ ```
+3. `ClangTidyForceLinker.h` has linker anchors ensuring all modules get linked in.
+
+## Per-TU Processing
+
+For each translation unit, `ClangTidyASTConsumerFactory::createASTConsumer()`:
+
+1. Loads merged options for the file (CLI + `.clang-tidy` hierarchy)
+2. Instantiates only the **enabled** checks
+3. Each check calls `registerMatchers(MatchFinder*)` to register AST matchers
+4. Optionally registers preprocessor callbacks (for `#include`/`#define` analysis)
+5. `MatchFinder` traverses the AST, calling `check(MatchResult&)` on matches
+
+## Check Lifecycle
+
+Every check extends `ClangTidyCheck` (`ClangTidyCheck.h`):
+
+- **`registerMatchers()`** — declare what AST patterns to match
+- **`check()`** — called on each match; emit diagnostics via `diag()` with optional `FixItHint`s
+- **`storeOptions()`** — serialize check-specific options (read via `Options.get()`)
+- **`isLanguageVersionSupported()`** — skip checks for wrong language modes
+
+## Configuration
+
+`ClangTidyOptions.h/cpp` handles `.clang-tidy` YAML files. Options merge with priority: defaults < parent directory configs < local config < CLI flags. Each option carries a `Priority` value to resolve conflicts.
+
+## Diagnostic Flow
+
+```
+check->diag(loc, "message")
+ -> ClangTidyContext::diag()
+ -> DiagnosticsEngine
+ -> ClangTidyDiagnosticConsumer
+ -> Creates ClangTidyError (SourceManager-independent)
+ -> Converts FixItHints to tooling::Replacement
+ -> Filters by header regex, NOLINT comments
+ -> Applies fixes if --fix was passed
+```
+
+## Key Design
+
+- **Pluggable**: new checks just subclass `ClangTidyCheck` and register in a module — no core changes needed
+- **AST Matcher-based**: most checks are declarative pattern matches over the Clang AST
+- **Hierarchical config**: `.clang-tidy` files cascade up the directory tree, merged by priority
diff --git a/hicketts_test/hicketts_optional.h b/hicketts_test/hicketts_optional.h
new file mode 100644
index 0000000000000..050fd2467a0d3
--- /dev/null
+++ b/hicketts_test/hicketts_optional.h
@@ -0,0 +1,75 @@
+#ifndef HICKETTS_OPTIONAL_H_
+#define HICKETTS_OPTIONAL_H_
+
+/// A custom optional-like type with differently named functions.
+/// Mirrors std::optional semantics but uses its own vocabulary
+/// In order to test implementation of attributes for clang-tidy
+namespace mylib {
+
+struct nothing_t {
+ constexpr explicit nothing_t() {}
+};
+
+constexpr nothing_t nothing;
+
+template <typename T>
+class [[clang::analyse_as_class("std::optional")]] HickettsOptional {
+ T *storage_ = nullptr;
+
+public:
+ constexpr HickettsOptional() noexcept {}
+
+ constexpr HickettsOptional(nothing_t) noexcept {}
+
+ HickettsOptional(const HickettsOptional &) = default;
+
+ HickettsOptional(HickettsOptional &&) = default;
+
+ // Equivalent to std::optional::value()
+ [[clang_analyse_as_method(std::optional::value)]]
+ const T &unwrap() const & { return *storage_; }
+ T &unwrap() & { return *storage_; }
+ const T &&unwrap() const && { return static_cast<const T &&>(*storage_); }
+ T &&unwrap() && { return static_cast<T &&>(*storage_); }
+
+ // Equivalent to std::optional::operator*()
+ const T &deref() const & { return *storage_; }
+ T &deref() & { return *storage_; }
+
+ // Equivalent to std::optional::operator->()
+ const T *arrow() const { return storage_; }
+ T *arrow() { return storage_; }
+
+ // Equivalent to std::optional::operator bool / has_value()
+ constexpr explicit operator bool() const noexcept { return storage_ != nullptr; }
+ constexpr bool isPresent() const noexcept { return storage_ != nullptr; }
+ constexpr bool isEmpty() const noexcept { return storage_ == nullptr; }
+
+ // Equivalent to std::optional::value_or()
+ template <typename U>
+ constexpr T unwrapOr(U &&fallback) const & {
+ return storage_ ? *storage_ : static_cast<T>(fallback);
+ }
+
+ // Equivalent to std::optional::emplace()
+ template <typename... Args>
+ T &construct(Args &&...args) { return *storage_; }
+
+ // Equivalent to std::optional::reset()
+ void clear() noexcept { storage_ = nullptr; }
+
+ // Equivalent to std::optional::swap()
+ void exchange(HickettsOptional &other) noexcept {
+ T *tmp = storage_;
+ storage_ = other.storage_;
+ other.storage_ = tmp;
+ }
+
+ // Assignment
+ template <typename U>
+ HickettsOptional &operator=(const U &u) { return *this; }
+};
+
+} // namespace mylib
+
+#endif // HICKETTS_OPTIONAL_H_
diff --git a/hicketts_test/run_instructions.md b/hicketts_test/run_instructions.md
new file mode 100644
index 0000000000000..8eea4dddd823a
--- /dev/null
+++ b/hicketts_test/run_instructions.md
@@ -0,0 +1,19 @@
+# Run Instructions
+
+## Test the PR #144313 fix (makeValue/makeValueInplace)
+```bash
+clang-tidy -checks='bugprone-unchecked-optional-access' \
+ test_makevalue_fix.cpp -- \
+ -I ../clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access \
+ -Wno-undefined-inline
+```
+
+## Test HickettsOptional (custom optional-like type)
+```bash
+clang-tidy -checks='bugprone-unchecked-optional-access' \
+ test_hicketts_optional.cpp -- \
+ -I . \
+ -Wno-undefined-inline
+```
+
+All commands assume you are running from the `hicketts_test/` directory.
diff --git a/hicketts_test/test_hicketts_optional.cpp b/hicketts_test/test_hicketts_optional.cpp
new file mode 100644
index 0000000000000..9cf229440ddcf
--- /dev/null
+++ b/hicketts_test/test_hicketts_optional.cpp
@@ -0,0 +1,73 @@
+// Test cases for mylib::HickettsOptional — a custom optional-like type
+// with differently named functions.
+//
+// Run from hicketts_test/ with:
+// clang-tidy -checks='bugprone-unchecked-optional-access' \
+// test_hicketts_optional.cpp -- -I . -Wno-undefined-inline
+
+#include "hicketts_optional.h"
+
+// --- Unchecked access (should warn if the checker recognises HickettsOptional) ---
+
+void unchecked_unwrap(mylib::HickettsOptional<int> &val) {
+ val.unwrap(); // unchecked access — may be empty
+}
+
+void unchecked_deref(mylib::HickettsOptional<int> &val) {
+ val.deref(); // unchecked access — may be empty
+}
+
+// --- Checked access (should NOT warn) ---
+
+void checked_with_bool(mylib::HickettsOptional<int> &val) {
+ if (val) {
+ val.unwrap(); // safe — checked via operator bool
+ }
+}
+
+void checked_with_isPresent(mylib::HickettsOptional<int> &val) {
+ if (val.isPresent()) {
+ val.unwrap(); // safe — checked via isPresent()
+ }
+}
+
+void checked_with_isEmpty(mylib::HickettsOptional<int> &val) {
+ if (!val.isEmpty()) {
+ val.unwrap(); // safe — checked via !isEmpty()
+ }
+}
+
+// --- State changes ---
+
+void safe_after_construct(mylib::HickettsOptional<int> &val) {
+ val.construct(42);
+ val.unwrap(); // safe — just constructed a value
+}
+
+void unsafe_after_clear(mylib::HickettsOptional<int> &val) {
+ val.construct(42);
+ val.clear();
+ val.unwrap(); // unsafe — value was cleared
+}
+
+void unsafe_after_exchange(mylib::HickettsOptional<int> &a,
+ mylib::HickettsOptional<int> &b) {
+ if (a) {
+ a.exchange(b);
+ a.unwrap(); // unsafe — a's state is now unknown
+ }
+}
+
+// --- Guarded paths ---
+
+void construct_covers_empty_branch(mylib::HickettsOptional<int> &val) {
+ if (val.isEmpty()) {
+ val.construct(99);
+ }
+ val.unwrap(); // safe — either was present, or construct filled it
+}
+
+void unwrapOr_is_always_safe(mylib::HickettsOptional<int> &val) {
+ int x = val.unwrapOr(0); // safe — fallback provided
+ (void)x;
+}
diff --git a/hicketts_test/test_makevalue_fix.cpp b/hicketts_test/test_makevalue_fix.cpp
new file mode 100644
index 0000000000000..d12129f52baf6
--- /dev/null
+++ b/hicketts_test/test_makevalue_fix.cpp
@@ -0,0 +1,68 @@
+// Test cases for the fix in llvm/llvm-project#144313
+//
+// This exercises the bugprone-unchecked-optional-access check's handling
+// of BloombergLP::bdlb::NullableValue::makeValue and makeValueInplace.
+//
+// Before the fix, cases marked "OK" below would produce false-positive
+// warnings because the checker didn't know makeValue/makeValueInplace
+// establish a valid value.
+//
+// Run with:
+// clang-tidy -checks='bugprone-unchecked-optional-access' \
+// test_makevalue_fix.cpp -- \
+// -I <llvm-project>/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access
+
+#include "bde/types/bdlb_nullablevalue.h"
+
+// -- SHOULD NOT WARN (the fix) --
+
+// makeValue(val) on null branch guarantees value is present on all paths.
+void makeValue_covers_null_branch(BloombergLP::bdlb::NullableValue<int> &opt) {
+ if (opt.isNull()) {
+ opt.makeValue(42);
+ }
+ opt.value(); // OK — either was non-null already, or makeValue filled it
+}
+
+// makeValueInplace does the same thing via in-place construction.
+void makeValueInplace_covers_null_branch(BloombergLP::bdlb::NullableValue<int> &opt) {
+ if (opt.isNull()) {
+ opt.makeValueInplace(42);
+ }
+ opt.value(); // OK
+}
+
+// Unconditional makeValue — always safe to access afterwards.
+void unconditional_makeValue(BloombergLP::bdlb::NullableValue<int> &opt) {
+ opt.makeValue(100);
+ opt.value(); // OK
+}
+
+// Zero-arg makeValue — default-constructs the value.
+void makeValue_no_args(BloombergLP::bdlb::NullableValue<int> &opt) {
+ opt.makeValue();
+ opt.value(); // OK
+}
+
+// -- SHOULD WARN (not fixed by makeValue) --
+
+// Accessing without any check or makeValue is still unsafe.
+void no_check_no_makeValue(BloombergLP::bdlb::NullableValue<int> &opt) {
+ opt.value(); // WARNING: unchecked access to optional value
+}
+
+// reset() after makeValue invalidates the value.
+void makeValue_then_reset(BloombergLP::bdlb::NullableValue<int> &opt) {
+ opt.makeValue(42);
+ opt.reset();
+ opt.value(); // WARNING: value was reset
+}
+
+// makeValue on a *different* object doesn't help.
+void makeValue_wrong_object(BloombergLP::bdlb::NullableValue<int> &a,
+ BloombergLP::bdlb::NullableValue<int> &b) {
+ if (a.isNull()) {
+ b.makeValue(42);
+ }
+ a.value(); // WARNING: a may still be null
+}
>From d06dcb3fcd8917604f2c0b6ff4626f11cb1c9371 Mon Sep 17 00:00:00 2001
From: Kay Hicketts <khicketts at bloomberg.net>
Date: Fri, 17 Apr 2026 15:36:59 +0000
Subject: [PATCH 2/6] .
---
hicketts_test/clang-tidy-overview.md | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/hicketts_test/clang-tidy-overview.md b/hicketts_test/clang-tidy-overview.md
index 8bb42792f16e2..a952a760496cd 100644
--- a/hicketts_test/clang-tidy-overview.md
+++ b/hicketts_test/clang-tidy-overview.md
@@ -56,3 +56,11 @@ check->diag(loc, "message")
- **Pluggable**: new checks just subclass `ClangTidyCheck` and register in a module — no core changes needed
- **AST Matcher-based**: most checks are declarative pattern matches over the Clang AST
- **Hierarchical config**: `.clang-tidy` files cascade up the directory tree, merged by priority
+
+
+Kay TODO next
+- To implement new attributes
+SemaDeclAttr.cpp - define the attribute so clang can parse it [clang/lib/Sema/]
+every FlowSensitive class would need updating to check if that module is meant to be use for a partuclar pass (we can just do this for UncheckedOptional for now) [clang/lib/Analysis/FlowSens
+ itive/Models/]
+Have a look at attributes that are currently implemented in Attr.td [clang/include/Basic]
\ No newline at end of file
>From ea8ccea956218103bab6adca62fde5cb5734bf4c Mon Sep 17 00:00:00 2001
From: khickett <khicketts at bloomberg.net>
Date: Fri, 24 Apr 2026 15:30:56 +0100
Subject: [PATCH 3/6] Add build instructions for clang and clang-tools-extra
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
hicketts_test/BUILD_INSTRUCTIONS.txt | 60 ++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
create mode 100644 hicketts_test/BUILD_INSTRUCTIONS.txt
diff --git a/hicketts_test/BUILD_INSTRUCTIONS.txt b/hicketts_test/BUILD_INSTRUCTIONS.txt
new file mode 100644
index 0000000000000..dd92ebd42f938
--- /dev/null
+++ b/hicketts_test/BUILD_INSTRUCTIONS.txt
@@ -0,0 +1,60 @@
+LLVM Build Instructions (clang + clang-tools-extra)
+====================================================
+
+Prerequisites:
+- CMake 3.20+
+- C/C++ compiler (Apple Clang or GCC)
+- Python 3.8+
+- Make or Ninja
+
+All commands should be run from the llvm-project root directory.
+
+
+Using Ninja
+-----------
+
+Configure:
+
+ cmake -G "Ninja" -S llvm -B build-llvm \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DLLVM_ENABLE_ASSERTIONS=ON \
+ -DLLVM_UNREACHABLE_OPTIMIZE=ON \
+ -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" \
+ -DLLVM_FORCE_VC_REPOSITORY=https://github.com/KHicketts/llvm-project
+
+Build:
+
+ ninja -C build-llvm
+
+Install (optional):
+
+ ninja -C build-llvm install
+
+
+Using Make
+----------
+
+Configure:
+
+ cmake -G "Unix Makefiles" -S llvm -B build-llvm \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DLLVM_ENABLE_ASSERTIONS=ON \
+ -DLLVM_UNREACHABLE_OPTIMIZE=ON \
+ -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" \
+ -DLLVM_FORCE_VC_REPOSITORY=https://github.com/KHicketts/llvm-project
+
+Build (using all available cores):
+
+ make -C build-llvm -j$(sysctl -n hw.ncpu)
+
+Install (optional):
+
+ make -C build-llvm install
+
+
+Notes
+-----
+
+- clang-tidy binary will be at: build-llvm/bin/clang-tidy
+- To do a clean rebuild, remove the build directory: rm -rf build-llvm
+- Ninja is recommended over Make as it is faster and handles parallelism automatically.
>From e53b28a6da5be95d6ed6092c1e230b668f87aac8 Mon Sep 17 00:00:00 2001
From: khickett <khicketts at bloomberg.net>
Date: Fri, 24 Apr 2026 18:09:00 +0100
Subject: [PATCH 4/6] first micro change...
---
clang/include/clang/Basic/Attr.td | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 04b7f420e1b32..d7d6276d05973 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1003,6 +1003,22 @@ def TLSModel : InheritableAttr {
let Documentation = [TLSModelDocs];
}
+def AnalyseAsClass : InheritableAttr {
+ let Spellings = [Clang<"analyse_as_class">];
+ let Args = [StringArgument<"ClassName">];
+ let Subjects = SubjectList<[Record],
+ ErrorDiag>;
+ let Documentation = [Undocumented];
+}
+
+def AnalyseAsMethod : InheritableAttr {
+ let Spellings = [Clang<"analyse_as_method">];
+ let Args = [StringArgument<"MethodName">];
+ let Subjects = SubjectList<[CXXMethod],
+ ErrorDiag>;
+ let Documentation = [Undocumented];
+}
+
def AnalyzerNoReturn : InheritableAttr {
// TODO: should this attribute be exposed with a [[]] spelling under the clang
// vendor namespace, or should it use a vendor namespace specific to the
>From 9761daea521a8029b25fbbcae64aacee92dfd6fb Mon Sep 17 00:00:00 2001
From: khickett <khicketts at bloomberg.net>
Date: Tue, 28 Apr 2026 14:37:52 +0100
Subject: [PATCH 5/6] add sema changes for class attribute
---
clang/lib/Sema/SemaDeclAttr.cpp | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 76073ed80175f..6a38800004f0a 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6480,6 +6480,29 @@ static void handleAbiTagAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
AbiTagAttr(S.Context, AL, Tags.data(), Tags.size()));
}
+// for now this only handles std::optional (POC)
+static bool isValidAnalyseAsClassAttr(Decl *D, StringRef Tag) {
+ if (Tag == "std::optional")
+ return true;
+ return false;
+}
+
+static void handleAnalyseAsClass(Sema &S, Decl *D, const ParsedAttr &AL) {
+ StringRef Str;
+ if (!S.checkStringLiteralArgumentAttr(AL, 0, Str))
+ return;
+ if (D->hasAttr<AnalyseAsClassAttr>()) {
+ S.Diag(AL.getLoc(), diag::err_duplicate_attribute) << AL;
+ return;
+ }
+ if (!isValidAnalyseAsClassAttr(D, Str)) {
+ S.Diag(AL.getLoc(), diag::warn_attribute_type_not_supported) << AL;
+ return;
+ }
+
+ D->addAttr(::new (S.Context) AnalyseAsClassAttr(S.Context, AL, Str));
+}
+
static bool hasBTFDeclTagAttr(Decl *D, StringRef Tag) {
for (const auto *I : D->specific_attrs<BTFDeclTagAttr>()) {
if (I->getBTFDeclTag() == Tag)
@@ -7569,6 +7592,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_BPFPreserveStaticOffset:
handleSimpleAttribute<BPFPreserveStaticOffsetAttr>(S, D, AL);
break;
+ case ParsedAttr::AT_AnalyseAsClass:
+ handleAnalyseAsClass(S, D, AL);
+ break;
case ParsedAttr::AT_BTFDeclTag:
handleBTFDeclTagAttr(S, D, AL);
break;
>From d966e14eef28a22047c5aafcd300b78e4fe03f18 Mon Sep 17 00:00:00 2001
From: khickett <khicketts at bloomberg.net>
Date: Tue, 28 Apr 2026 15:43:52 +0100
Subject: [PATCH 6/6] analyse_as_class works ok
---
.../Models/UncheckedOptionalAccessModel.cpp | 4 +
hicketts_test/hicketts_optional.h | 10 ++-
hicketts_test/test_hicketts_optional.cpp | 74 +++++++++++--------
3 files changed, 55 insertions(+), 33 deletions(-)
diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index 568564fb361f4..317f5034b0145 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -12,6 +12,7 @@
//===----------------------------------------------------------------------===//
#include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h"
+#include "clang/AST/Attr.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
@@ -89,6 +90,9 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) {
isFullyQualifiedNamespaceEqualTo(*N, "bdlb", "BloombergLP");
}
+ if (RD.hasAttr<AnalyseAsClassAttr>())
+ return true;
+
return false;
}
diff --git a/hicketts_test/hicketts_optional.h b/hicketts_test/hicketts_optional.h
index 050fd2467a0d3..b915b01f70290 100644
--- a/hicketts_test/hicketts_optional.h
+++ b/hicketts_test/hicketts_optional.h
@@ -26,21 +26,29 @@ class [[clang::analyse_as_class("std::optional")]] HickettsOptional {
HickettsOptional(HickettsOptional &&) = default;
// Equivalent to std::optional::value()
- [[clang_analyse_as_method(std::optional::value)]]
+ //[[clang_analyse_as_method(std::optional::value)]]
const T &unwrap() const & { return *storage_; }
T &unwrap() & { return *storage_; }
const T &&unwrap() const && { return static_cast<const T &&>(*storage_); }
T &&unwrap() && { return static_cast<T &&>(*storage_); }
+ const T &value() const & { return *storage_; }
+ T &value() & { return *storage_; }
+ const T &&value() const && { return static_cast<const T &&>(*storage_); }
+ T &&value() && { return static_cast<T &&>(*storage_); }
+
// Equivalent to std::optional::operator*()
const T &deref() const & { return *storage_; }
T &deref() & { return *storage_; }
// Equivalent to std::optional::operator->()
+ const T* operator ->() const { return storage_; }
+ T* operator ->() { return storage_; }
const T *arrow() const { return storage_; }
T *arrow() { return storage_; }
// Equivalent to std::optional::operator bool / has_value()
+ constexpr bool hasValue() const noexcept { return storage_ != nullptr; }
constexpr explicit operator bool() const noexcept { return storage_ != nullptr; }
constexpr bool isPresent() const noexcept { return storage_ != nullptr; }
constexpr bool isEmpty() const noexcept { return storage_ == nullptr; }
diff --git a/hicketts_test/test_hicketts_optional.cpp b/hicketts_test/test_hicketts_optional.cpp
index 9cf229440ddcf..fa0153a79f341 100644
--- a/hicketts_test/test_hicketts_optional.cpp
+++ b/hicketts_test/test_hicketts_optional.cpp
@@ -9,65 +9,75 @@
// --- Unchecked access (should warn if the checker recognises HickettsOptional) ---
-void unchecked_unwrap(mylib::HickettsOptional<int> &val) {
- val.unwrap(); // unchecked access — may be empty
+static void uncheckedUnwrap(mylib::HickettsOptional<int> &Val) {
+ Val.unwrap(); // unchecked access — may be empty
}
-void unchecked_deref(mylib::HickettsOptional<int> &val) {
- val.deref(); // unchecked access — may be empty
+static void uncheckedValue(mylib::HickettsOptional<int> &Val) {
+ Val.value(); // unchecked access — may be empty
+}
+
+static void uncheckedDeref(mylib::HickettsOptional<int> &Val) {
+ Val.deref(); // unchecked access — may be empty
}
// --- Checked access (should NOT warn) ---
-void checked_with_bool(mylib::HickettsOptional<int> &val) {
- if (val) {
- val.unwrap(); // safe — checked via operator bool
+static void checkedWithBool(mylib::HickettsOptional<int> &Val) {
+ if (Val) {
+ Val.unwrap(); // safe — checked via operator bool
+ }
+}
+
+static void checkedValueWithBool(mylib::HickettsOptional<int> &Val) {
+ if (Val.hasValue()) {
+ Val.value(); // safe — checked via operator bool
}
}
-void checked_with_isPresent(mylib::HickettsOptional<int> &val) {
- if (val.isPresent()) {
- val.unwrap(); // safe — checked via isPresent()
+static void checkedWithIsPresent(mylib::HickettsOptional<int> &Val) {
+ if (Val.isPresent()) {
+ Val.unwrap(); // safe — checked via isPresent()
}
}
-void checked_with_isEmpty(mylib::HickettsOptional<int> &val) {
- if (!val.isEmpty()) {
- val.unwrap(); // safe — checked via !isEmpty()
+static void checkedWithIsEmpty(mylib::HickettsOptional<int> &Val) {
+ if (!Val.isEmpty()) {
+ Val.unwrap(); // safe — checked via !isEmpty()
}
}
// --- State changes ---
-void safe_after_construct(mylib::HickettsOptional<int> &val) {
- val.construct(42);
- val.unwrap(); // safe — just constructed a value
+static void safeAfterConstruct(mylib::HickettsOptional<int> &Val) {
+ Val.construct(42);
+ Val.unwrap(); // safe — just constructed a value
}
-void unsafe_after_clear(mylib::HickettsOptional<int> &val) {
- val.construct(42);
- val.clear();
- val.unwrap(); // unsafe — value was cleared
+static void unsafeAfterClear(mylib::HickettsOptional<int> &Val) {
+ Val.construct(42);
+ Val.clear();
+ Val.unwrap(); // unsafe — value was cleared
}
-void unsafe_after_exchange(mylib::HickettsOptional<int> &a,
- mylib::HickettsOptional<int> &b) {
- if (a) {
- a.exchange(b);
- a.unwrap(); // unsafe — a's state is now unknown
+static void unsafeAfterExchange(mylib::HickettsOptional<int> &A,
+ mylib::HickettsOptional<int> &B) {
+ if (A) {
+ A.exchange(B);
+ A.unwrap(); // unsafe — a's state is now unknown
}
}
// --- Guarded paths ---
-void construct_covers_empty_branch(mylib::HickettsOptional<int> &val) {
- if (val.isEmpty()) {
- val.construct(99);
+static void constructCoversEmptyBranch(mylib::HickettsOptional<int> &Val) {
+ if (Val.isEmpty()) {
+ Val.construct(99);
}
- val.unwrap(); // safe — either was present, or construct filled it
+ Val.unwrap(); // safe — either was present, or construct filled it
}
-void unwrapOr_is_always_safe(mylib::HickettsOptional<int> &val) {
- int x = val.unwrapOr(0); // safe — fallback provided
- (void)x;
+static void unwrapOrIsAlwaysSafe(mylib::HickettsOptional<int> &Val) {
+ int X = Val.unwrapOr(0); // safe — fallback provided
+ (void)X;
}
More information about the cfe-commits
mailing list