[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