https://github.com/KHicketts created 
https://github.com/llvm/llvm-project/pull/196272

None

>From 52d11c1082709188e7fa4d8b6995febf1bcf19fa Mon Sep 17 00:00:00 2001
From: khickett <[email protected]>
Date: Tue, 28 Apr 2026 14:37:52 +0100
Subject: [PATCH 01/11] 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 e47a30193567f..243c66b474384 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6462,6 +6462,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)
@@ -7551,6 +7574,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 fe028c7292be79ba6f753ebae1039e65aa0032e9 Mon Sep 17 00:00:00 2001
From: khickett <[email protected]>
Date: Tue, 28 Apr 2026 15:43:52 +0100
Subject: [PATCH 02/11] analyse_as_class works ok

---
 .../Models/UncheckedOptionalAccessModel.cpp   |  4 +
 hicketts_test/hicketts_optional.h             | 83 +++++++++++++++++++
 hicketts_test/test_hicketts_optional.cpp      | 83 +++++++++++++++++++
 3 files changed, 170 insertions(+)
 create mode 100644 hicketts_test/hicketts_optional.h
 create mode 100644 hicketts_test/test_hicketts_optional.cpp

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
new file mode 100644
index 0000000000000..b915b01f70290
--- /dev/null
+++ b/hicketts_test/hicketts_optional.h
@@ -0,0 +1,83 @@
+#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_); }
+
+  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; }
+
+  // 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/test_hicketts_optional.cpp 
b/hicketts_test/test_hicketts_optional.cpp
new file mode 100644
index 0000000000000..fa0153a79f341
--- /dev/null
+++ b/hicketts_test/test_hicketts_optional.cpp
@@ -0,0 +1,83 @@
+// 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) ---
+
+static void uncheckedUnwrap(mylib::HickettsOptional<int> &Val) {
+  Val.unwrap(); // 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) ---
+
+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
+  }
+}
+
+static void checkedWithIsPresent(mylib::HickettsOptional<int> &Val) {
+  if (Val.isPresent()) {
+    Val.unwrap(); // safe — checked via isPresent()
+  }
+}
+
+static void checkedWithIsEmpty(mylib::HickettsOptional<int> &Val) {
+  if (!Val.isEmpty()) {
+    Val.unwrap(); // safe — checked via !isEmpty()
+  }
+}
+
+// --- State changes ---
+
+static void safeAfterConstruct(mylib::HickettsOptional<int> &Val) {
+  Val.construct(42);
+  Val.unwrap(); // safe — just constructed a value
+}
+
+static void unsafeAfterClear(mylib::HickettsOptional<int> &Val) {
+  Val.construct(42);
+  Val.clear();
+  Val.unwrap(); // unsafe — value was cleared
+}
+
+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 ---
+
+static void constructCoversEmptyBranch(mylib::HickettsOptional<int> &Val) {
+  if (Val.isEmpty()) {
+    Val.construct(99);
+  }
+  Val.unwrap(); // safe — either was present, or construct filled it
+}
+
+static void unwrapOrIsAlwaysSafe(mylib::HickettsOptional<int> &Val) {
+  int X = Val.unwrapOr(0); // safe — fallback provided
+  (void)X;
+}

>From ec4ea5a3de75bcc71b0140a11f24298d4681fdb0 Mon Sep 17 00:00:00 2001
From: khickett <[email protected]>
Date: Tue, 28 Apr 2026 16:35:01 +0100
Subject: [PATCH 03/11] partially working for per method analysis

---
 .../Models/UncheckedOptionalAccessModel.cpp   | 19 +++++++++++--
 clang/lib/Sema/SemaDeclAttr.cpp               | 27 +++++++++++++++++++
 hicketts_test/hicketts_optional.h             | 17 ++++++------
 3 files changed, 52 insertions(+), 11 deletions(-)

diff --git 
a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp 
b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index 317f5034b0145..f2c9aa756943e 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -230,6 +230,20 @@ AST_MATCHER(CXXOperatorCallExpr, 
hasOptionalOperatorObjectType) {
   return hasReceiverTypeDesugaringToOptional(Node.getArg(0));
 }
 
+AST_MATCHER_P(NamedDecl, hasAnalyseAsMethodName, std::string, MethodName) {
+  if (const auto *MD = dyn_cast<CXXMethodDecl>(&Node)) {
+    if (const auto *Attr = MD->getAttr<AnalyseAsMethodAttr>()) {
+      StringRef AttrValue = Attr->getMethodName();
+      auto Pos = AttrValue.rfind("::");
+      StringRef AttrMethodName = (Pos != StringRef::npos)
+          ? AttrValue.substr(Pos + 2)
+          : AttrValue;
+      return AttrMethodName == MethodName;
+    }
+  }
+  return false;
+}
+
 auto isOptionalMemberCallWithNameMatcher(
     ast_matchers::internal::Matcher<NamedDecl> matcher,
     const std::optional<StatementMatcher> &Ignorable = std::nullopt) {
@@ -982,8 +996,9 @@ ignorableOptional(const UncheckedOptionalAccessModelOptions 
&Options) {
 
 StatementMatcher
 valueCall(const std::optional<StatementMatcher> &IgnorableOptional) {
-  return isOptionalMemberCallWithNameMatcher(hasName("value"),
-                                             IgnorableOptional);
+  return isOptionalMemberCallWithNameMatcher(
+      anyOf(hasName("value"), hasAnalyseAsMethodName("value")),
+      IgnorableOptional);
 }
 
 StatementMatcher
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 243c66b474384..f999c307b7111 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6485,6 +6485,30 @@ static void handleAnalyseAsClass(Sema &S, Decl *D, const 
ParsedAttr &AL) {
   D->addAttr(::new (S.Context) AnalyseAsClassAttr(S.Context, AL, Str));
 }
 
+// for now this only handles std::optional (POC)
+static bool isValidAnalyseAsMethodAttr(Decl *D, StringRef Tag) {
+  // no validation is done currently.  if someone writes something with a 
nonsense name,
+  // it simply won't be validated but also no warning will be emitted
+  // would be nice to do something smarter in the real implementation
+  return true;
+}
+
+static void handleAnalyseAsMethod(Sema &S, Decl *D, const ParsedAttr &AL) {
+  StringRef Str;
+  if (!S.checkStringLiteralArgumentAttr(AL, 0, Str))
+    return;
+  if (D->hasAttr<AnalyseAsMethodAttr>()) {
+    S.Diag(AL.getLoc(), diag::err_duplicate_attribute) << AL;
+    return;
+  }
+  if (!isValidAnalyseAsMethodAttr(D, Str)) {
+    S.Diag(AL.getLoc(), diag::warn_attribute_type_not_supported) << AL;
+    return;
+  }
+
+  D->addAttr(::new (S.Context) AnalyseAsMethodAttr(S.Context, AL, Str));
+}
+
 static bool hasBTFDeclTagAttr(Decl *D, StringRef Tag) {
   for (const auto *I : D->specific_attrs<BTFDeclTagAttr>()) {
     if (I->getBTFDeclTag() == Tag)
@@ -7577,6 +7601,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, 
const ParsedAttr &AL,
   case ParsedAttr::AT_AnalyseAsClass:
     handleAnalyseAsClass(S, D, AL);
     break;
+  case ParsedAttr::AT_AnalyseAsMethod:
+    handleAnalyseAsMethod(S, D, AL);
+    break;
   case ParsedAttr::AT_BTFDeclTag:
     handleBTFDeclTagAttr(S, D, AL);
     break;
diff --git a/hicketts_test/hicketts_optional.h 
b/hicketts_test/hicketts_optional.h
index b915b01f70290..43ec98831a74b 100644
--- a/hicketts_test/hicketts_optional.h
+++ b/hicketts_test/hicketts_optional.h
@@ -26,11 +26,10 @@ class [[clang::analyse_as_class("std::optional")]] 
HickettsOptional {
   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_); }
+  [[clang::analyse_as_method("std::optional::value")]] const T &unwrap() const 
& { return *storage_; }
+  [[clang::analyse_as_method("std::optional::value")]] T &unwrap() & { return 
*storage_; }
+  [[clang::analyse_as_method("std::optional::value")]] const T &&unwrap() 
const && { return static_cast<const T &&>(*storage_); }
+  [[clang::analyse_as_method("std::optional::value")]] T &&unwrap() && { 
return static_cast<T &&>(*storage_); }
 
   const T &value() const & { return *storage_; }
   T &value() & { return *storage_; }
@@ -38,8 +37,8 @@ class [[clang::analyse_as_class("std::optional")]] 
HickettsOptional {
   T &&value() && { return static_cast<T &&>(*storage_); }
 
   // Equivalent to std::optional::operator*()
-  const T &deref() const & { return *storage_; }
-  T &deref() & { return *storage_; }
+  [[clang::analyse_as_method("std::optional::operator*")]] const T &deref() 
const & { return *storage_; }
+  [[clang::analyse_as_method("std::optional::operator*")]] T &deref() & { 
return *storage_; }
 
   // Equivalent to std::optional::operator->()
   const T* operator ->() const { return storage_; }
@@ -47,10 +46,10 @@ class [[clang::analyse_as_class("std::optional")]] 
HickettsOptional {
   const T *arrow() const { return storage_; }
   T *arrow() { return storage_; }
 
-  // Equivalent to std::optional::operator bool / has_value()
+  // Equivalent to std::optional::operator bool / hasValue()
   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; }
+  [[clang::analyse_as_method("std::optional::hasValue")]] constexpr bool 
isPresent() const noexcept { return storage_ != nullptr; }
   constexpr bool isEmpty() const noexcept { return storage_ == nullptr; }
 
   // Equivalent to std::optional::value_or()

>From 9d6520a6cac0f322cd67ac5a1619a6b8fd0128cd Mon Sep 17 00:00:00 2001
From: khickett <[email protected]>
Date: Tue, 28 Apr 2026 17:07:21 +0100
Subject: [PATCH 04/11] extend method matching

.
---
 .../Models/UncheckedOptionalAccessModel.cpp       | 11 ++++++-----
 hicketts_test/hicketts_optional.h                 | 15 +++++++--------
 hicketts_test/test_hicketts_optional.cpp          | 10 +++++-----
 3 files changed, 18 insertions(+), 18 deletions(-)

diff --git 
a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp 
b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index f2c9aa756943e..137b44560c529 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -358,7 +358,7 @@ auto isValueOrStringEmptyCall() {
       callee(cxxMethodDecl(hasName("empty"))),
       onImplicitObjectArgument(ignoringImplicit(
           cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),
-                            callee(cxxMethodDecl(hasName("value_or"),
+                            callee(cxxMethodDecl(anyOf(hasName("value_or"), 
hasAnalyseAsMethodName("value_or")),
                                                  ofClass(optionalClass()))),
                             hasArgument(0, stringLiteral(hasSize(0))))
               .bind(ValueOrCallID))));
@@ -1071,7 +1071,8 @@ auto buildTransferMatchSwitch() {
       // will also pass for other types
       .CaseOfCFGStmt<CXXMemberCallExpr>(
           isOptionalMemberCallWithNameMatcher(
-              hasAnyName("has_value", "hasValue")),
+              anyOf(hasAnyName("has_value", "hasValue"),
+              hasAnalyseAsMethodName("has_value"))),
           transferOptionalHasValueCall)
 
       // optional::operator bool
@@ -1101,7 +1102,7 @@ auto buildTransferMatchSwitch() {
 
       // optional::emplace
       .CaseOfCFGStmt<CXXMemberCallExpr>(
-          isOptionalMemberCallWithNameMatcher(hasName("emplace")),
+          isOptionalMemberCallWithNameMatcher(anyOf(hasName("emplace"), 
hasAnalyseAsMethodName("emplace"))),
           [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
              LatticeTransferState &State) {
             if (RecordStorageLocation *Loc =
@@ -1112,7 +1113,7 @@ auto buildTransferMatchSwitch() {
 
       // optional::reset
       .CaseOfCFGStmt<CXXMemberCallExpr>(
-          isOptionalMemberCallWithNameMatcher(hasName("reset")),
+          isOptionalMemberCallWithNameMatcher(anyOf(hasName("reset"), 
hasAnalyseAsMethodName("reset"))),
           [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
              LatticeTransferState &State) {
             if (RecordStorageLocation *Loc =
@@ -1124,7 +1125,7 @@ auto buildTransferMatchSwitch() {
 
       // optional::swap
       .CaseOfCFGStmt<CXXMemberCallExpr>(
-          isOptionalMemberCallWithNameMatcher(hasName("swap")),
+          isOptionalMemberCallWithNameMatcher(anyOf(hasName("swap"), 
hasAnalyseAsMethodName("swap"))),
           transferSwapCall)
 
       // std::swap
diff --git a/hicketts_test/hicketts_optional.h 
b/hicketts_test/hicketts_optional.h
index 43ec98831a74b..2c6978a7e2339 100644
--- a/hicketts_test/hicketts_optional.h
+++ b/hicketts_test/hicketts_optional.h
@@ -37,8 +37,8 @@ class [[clang::analyse_as_class("std::optional")]] 
HickettsOptional {
   T &&value() && { return static_cast<T &&>(*storage_); }
 
   // Equivalent to std::optional::operator*()
-  [[clang::analyse_as_method("std::optional::operator*")]] const T &deref() 
const & { return *storage_; }
-  [[clang::analyse_as_method("std::optional::operator*")]] T &deref() & { 
return *storage_; }
+  [[clang::analyse_as_method("std::optional::value")]] const T &deref() const 
& { return *storage_; }
+  [[clang::analyse_as_method("std::optional::value")]] T &deref() & { return 
*storage_; }
 
   // Equivalent to std::optional::operator->()
   const T* operator ->() const { return storage_; }
@@ -47,10 +47,9 @@ class [[clang::analyse_as_class("std::optional")]] 
HickettsOptional {
   T *arrow() { return storage_; }
 
   // Equivalent to std::optional::operator bool / hasValue()
-  constexpr bool hasValue() const noexcept { return storage_ != nullptr; }
+  constexpr bool has_value() const noexcept { return storage_ != nullptr; }
   constexpr explicit operator bool() const noexcept { return storage_ != 
nullptr; }
-  [[clang::analyse_as_method("std::optional::hasValue")]] constexpr bool 
isPresent() const noexcept { return storage_ != nullptr; }
-  constexpr bool isEmpty() const noexcept { return storage_ == nullptr; }
+  [[clang::analyse_as_method("std::optional::has_value")]] constexpr bool 
isPresent() const noexcept { return storage_ != nullptr; }
 
   // Equivalent to std::optional::value_or()
   template <typename U>
@@ -60,13 +59,13 @@ class [[clang::analyse_as_class("std::optional")]] 
HickettsOptional {
 
   // Equivalent to std::optional::emplace()
   template <typename... Args>
-  T &construct(Args &&...args) { return *storage_; }
+  [[clang::analyse_as_method("std::optional::emplace")]] T &construct(Args 
&&...args) { return *storage_; }
 
   // Equivalent to std::optional::reset()
-  void clear() noexcept { storage_ = nullptr; }
+  [[clang::analyse_as_method("std::optional::reset")]] void clear() noexcept { 
storage_ = nullptr; }
 
   // Equivalent to std::optional::swap()
-  void exchange(HickettsOptional &other) noexcept {
+  [[clang::analyse_as_method("std::optional::swap")]] void 
exchange(HickettsOptional &other) noexcept {
     T *tmp = storage_;
     storage_ = other.storage_;
     other.storage_ = tmp;
diff --git a/hicketts_test/test_hicketts_optional.cpp 
b/hicketts_test/test_hicketts_optional.cpp
index fa0153a79f341..1ff16d13fe174 100644
--- a/hicketts_test/test_hicketts_optional.cpp
+++ b/hicketts_test/test_hicketts_optional.cpp
@@ -30,7 +30,7 @@ static void checkedWithBool(mylib::HickettsOptional<int> 
&Val) {
 }
 
 static void checkedValueWithBool(mylib::HickettsOptional<int> &Val) {
-  if (Val.hasValue()) {
+  if (Val.has_value()) {
     Val.value(); // safe — checked via operator bool
   }
 }
@@ -41,11 +41,11 @@ static void 
checkedWithIsPresent(mylib::HickettsOptional<int> &Val) {
   }
 }
 
-static void checkedWithIsEmpty(mylib::HickettsOptional<int> &Val) {
+/* static void checkedWithIsEmpty(mylib::HickettsOptional<int> &Val) {
   if (!Val.isEmpty()) {
     Val.unwrap(); // safe — checked via !isEmpty()
   }
-}
+} NYI */
 
 // --- State changes ---
 
@@ -70,12 +70,12 @@ static void 
unsafeAfterExchange(mylib::HickettsOptional<int> &A,
 
 // --- Guarded paths ---
 
-static void constructCoversEmptyBranch(mylib::HickettsOptional<int> &Val) {
+/*static void constructCoversEmptyBranch(mylib::HickettsOptional<int> &Val) {
   if (Val.isEmpty()) {
     Val.construct(99);
   }
   Val.unwrap(); // safe — either was present, or construct filled it
-}
+}*/
 
 static void unwrapOrIsAlwaysSafe(mylib::HickettsOptional<int> &Val) {
   int X = Val.unwrapOr(0); // safe — fallback provided

>From 34ce7b2b289e5e5a4cb0d708d34f4675d7bdc426 Mon Sep 17 00:00:00 2001
From: khickett <[email protected]>
Date: Thu, 30 Apr 2026 12:48:57 +0100
Subject: [PATCH 05/11] missed a bit

---
 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 97536ac7a1966..adc127df5756e 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -921,6 +921,22 @@ def AlignValue : Attr {
   let Documentation = [AlignValueDocs];
 }
 
+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 AlignMac68k : InheritableAttr {
   // This attribute has no spellings as it is only ever created implicitly.
   let Spellings = [];

>From 3532f8f3ae825e185f7f5c3506e3b96fbd16dda3 Mon Sep 17 00:00:00 2001
From: khickett <[email protected]>
Date: Fri, 1 May 2026 15:40:38 +0100
Subject: [PATCH 06/11] add unit tests

---
 .../UncheckedOptionalAccessModelTest.cpp      | 50 +++++++++++++++++--
 1 file changed, 46 insertions(+), 4 deletions(-)

diff --git 
a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp 
b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
index 074dc95e2c388..72cf12bcd0817 100644
--- 
a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
+++ 
b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
@@ -946,7 +946,7 @@ TEST_P(UncheckedOptionalAccessTest, 
OptionalReturnedFromFuntionCall) {
   ExpectDiagnosticsFor(
       R"(
     #include "unchecked_optional_access_test.h"
-    
+
     struct S {
       $ns::$optional<float> x;
     } s;
@@ -2860,14 +2860,14 @@ TEST_P(
       struct B {
         const A& getA() const { return a; }
 
-        void callWithoutChanges() const { 
-          // no-op 
+        void callWithoutChanges() const {
+          // no-op
         }
 
         A a;
       };
 
-      void target(B& b) {  
+      void target(B& b) {
         if (b.getA().get().has_value()) {
           b.callWithoutChanges(); // calling const method which cannot change A
           b.getA().get().value();
@@ -2995,6 +2995,48 @@ TEST_P(UncheckedOptionalAccessTest, 
AssertFalseGtestMacroWithNullableValue) {
   )cc");
 }
 
+TEST_P(UncheckedOptionalAccessTest, TestCustomAttributeBalik) {
+  ExpectDiagnosticsFor(R"cc(
+    #include "unchecked_optional_access_test.h"
+
+    template <typename T>
+    class __attribute__((analyse_as_class("std::optional"))) MyOptional {
+    public:
+      bool has_value() const;
+      T& value();
+      const T& value() const;
+    };
+
+    void target(MyOptional<int> opt) {
+      opt.value(); // [[unsafe]]
+      if (opt.has_value()) {
+        opt.value();
+      }
+    }
+  )cc");
+}
+
+TEST_P(UncheckedOptionalAccessTest, TestCustomAttributeHicketts) {
+  ExpectDiagnosticsFor(R"cc(
+    #include "unchecked_optional_access_test.h"
+
+    template <typename T>
+    class __attribute__((analyse_as_class("std::optional"))) MyOptional {
+    public:
+      __attribute__((analyse_as_method("std::optional::has_value"))) bool 
isNotNull() const;
+      __attribute__((analyse_as_method("std::optional::value"))) T& unwrap();
+      __attribute__((analyse_as_method("std::optional::value"))) const T& 
unwrap() const;
+    };
+
+    void target(MyOptional<int> opt) {
+      opt.unwrap(); // [[unsafe]]
+      if (opt.isNotNull()) {
+        opt.unwrap();
+      }
+    }
+  )cc");
+}
+
 // FIXME: Add support for:
 // - constructors (copy, move)
 // - assignment operators (default, copy, move)

>From 7320c8c063be143928434c9046eb85cf7e22ed78 Mon Sep 17 00:00:00 2001
From: khickett <[email protected]>
Date: Fri, 1 May 2026 16:01:22 +0100
Subject: [PATCH 07/11] .

---
 .../bugprone/unchecked-optional-access.cpp    | 69 +++++++++++++++++++
 1 file changed, 69 insertions(+)

diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
index 337474bdf7535..cdd8fdb82174b 100644
--- 
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
@@ -384,3 +384,72 @@ void foo() {
   if (!vec.empty())
     vec[0].x = 0;
 }
+
+// Custom optional-like type using analyse_as_class / analyse_as_method 
attributes.
+template <typename T>
+class [[clang::analyse_as_class("std::optional")]] CustomOptional {
+public:
+  [[clang::analyse_as_method("std::optional::has_value")]] bool isEngaged() 
const;
+  [[clang::analyse_as_method("std::optional::value")]] T &retrieve();
+  [[clang::analyse_as_method("std::optional::value")]] const T &retrieve() 
const;
+  [[clang::analyse_as_method("std::optional::emplace")]] T &fill(T val);
+  [[clang::analyse_as_method("std::optional::reset")]] void clear();
+};
+
+void custom_unchecked_access(CustomOptional<int> opt) {
+  opt.retrieve();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional 
value [bugprone-unchecked-optional-access]
+}
+
+void custom_checked_access(CustomOptional<int> opt) {
+  if (opt.isEngaged()) {
+    opt.retrieve();
+  }
+}
+
+void custom_emplace_then_access(CustomOptional<int> opt) {
+  opt.fill(42);
+  opt.retrieve();
+}
+
+void custom_clear_then_access(CustomOptional<int> opt) {
+  opt.fill(42);
+  opt.clear();
+  opt.retrieve();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional 
value [bugprone-unchecked-optional-access]
+}
+
+// Custom optional-like type using standard method names — recognised via
+// analyse_as_class alone, without analyse_as_method attributes.
+template <typename T>
+class [[clang::analyse_as_class("std::optional")]] StdNamedOptional {
+public:
+  bool has_value() const;
+  T &value();
+  const T &value() const;
+  T &emplace(T val);
+  void reset();
+};
+
+void std_named_unchecked_access(StdNamedOptional<int> opt) {
+  opt.value();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional 
value [bugprone-unchecked-optional-access]
+}
+
+void std_named_checked_access(StdNamedOptional<int> opt) {
+  if (opt.has_value()) {
+    opt.value();
+  }
+}
+
+void std_named_emplace_then_access(StdNamedOptional<int> opt) {
+  opt.emplace(42);
+  opt.value();
+}
+
+void std_named_reset_then_access(StdNamedOptional<int> opt) {
+  opt.emplace(42);
+  opt.reset();
+  opt.value();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional 
value [bugprone-unchecked-optional-access]
+}

>From dc6922262ffdaba0b0202b93e82f320286249bca Mon Sep 17 00:00:00 2001
From: khickett <[email protected]>
Date: Fri, 1 May 2026 16:16:29 +0100
Subject: [PATCH 08/11] .

---
 hicketts_test/hicketts_optional.h        | 81 -----------------------
 hicketts_test/test_hicketts_optional.cpp | 83 ------------------------
 2 files changed, 164 deletions(-)
 delete mode 100644 hicketts_test/hicketts_optional.h
 delete mode 100644 hicketts_test/test_hicketts_optional.cpp

diff --git a/hicketts_test/hicketts_optional.h 
b/hicketts_test/hicketts_optional.h
deleted file mode 100644
index 2c6978a7e2339..0000000000000
--- a/hicketts_test/hicketts_optional.h
+++ /dev/null
@@ -1,81 +0,0 @@
-#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_; }
-  [[clang::analyse_as_method("std::optional::value")]] T &unwrap() & { return 
*storage_; }
-  [[clang::analyse_as_method("std::optional::value")]] const T &&unwrap() 
const && { return static_cast<const T &&>(*storage_); }
-  [[clang::analyse_as_method("std::optional::value")]] 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*()
-  [[clang::analyse_as_method("std::optional::value")]] const T &deref() const 
& { return *storage_; }
-  [[clang::analyse_as_method("std::optional::value")]] 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 / hasValue()
-  constexpr bool has_value() const noexcept { return storage_ != nullptr; }
-  constexpr explicit operator bool() const noexcept { return storage_ != 
nullptr; }
-  [[clang::analyse_as_method("std::optional::has_value")]] constexpr bool 
isPresent() 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>
-  [[clang::analyse_as_method("std::optional::emplace")]] T &construct(Args 
&&...args) { return *storage_; }
-
-  // Equivalent to std::optional::reset()
-  [[clang::analyse_as_method("std::optional::reset")]] void clear() noexcept { 
storage_ = nullptr; }
-
-  // Equivalent to std::optional::swap()
-  [[clang::analyse_as_method("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/test_hicketts_optional.cpp 
b/hicketts_test/test_hicketts_optional.cpp
deleted file mode 100644
index 1ff16d13fe174..0000000000000
--- a/hicketts_test/test_hicketts_optional.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-// 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) ---
-
-static void uncheckedUnwrap(mylib::HickettsOptional<int> &Val) {
-  Val.unwrap(); // 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) ---
-
-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.has_value()) {
-    Val.value(); // safe — checked via operator bool
-  }
-}
-
-static void checkedWithIsPresent(mylib::HickettsOptional<int> &Val) {
-  if (Val.isPresent()) {
-    Val.unwrap(); // safe — checked via isPresent()
-  }
-}
-
-/* static void checkedWithIsEmpty(mylib::HickettsOptional<int> &Val) {
-  if (!Val.isEmpty()) {
-    Val.unwrap(); // safe — checked via !isEmpty()
-  }
-} NYI */
-
-// --- State changes ---
-
-static void safeAfterConstruct(mylib::HickettsOptional<int> &Val) {
-  Val.construct(42);
-  Val.unwrap(); // safe — just constructed a value
-}
-
-static void unsafeAfterClear(mylib::HickettsOptional<int> &Val) {
-  Val.construct(42);
-  Val.clear();
-  Val.unwrap(); // unsafe — value was cleared
-}
-
-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 ---
-
-/*static void constructCoversEmptyBranch(mylib::HickettsOptional<int> &Val) {
-  if (Val.isEmpty()) {
-    Val.construct(99);
-  }
-  Val.unwrap(); // safe — either was present, or construct filled it
-}*/
-
-static void unwrapOrIsAlwaysSafe(mylib::HickettsOptional<int> &Val) {
-  int X = Val.unwrapOr(0); // safe — fallback provided
-  (void)X;
-}

>From e50be59fa64622856902e3b0a80d1e8c1699d9ec Mon Sep 17 00:00:00 2001
From: khickett <[email protected]>
Date: Fri, 1 May 2026 17:28:47 +0100
Subject: [PATCH 09/11] comment possible cody tidy up locations

---
 .../Models/UncheckedOptionalAccessModel.cpp          | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git 
a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp 
b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index 137b44560c529..1fee746ff7a97 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -71,6 +71,8 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) {
     return false;
   }
 
+  // this code could be removed if base::Optional and folly::Optional used
+  // [[clang::analyse_as_class("std::optional")]]
   if (RD.getName() == "Optional") {
     // Check whether namespace is "::base" or "::folly".
     const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());
@@ -78,12 +80,16 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) {
                             isFullyQualifiedNamespaceEqualTo(*N, "folly"));
   }
 
+  // this code could be removed if Optional_Base used
+  // [[clang::analyse_as_class("std::optional")]]
   if (RD.getName() == "Optional_Base") {
     const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());
     return N != nullptr &&
            isFullyQualifiedNamespaceEqualTo(*N, "bslstl", "BloombergLP");
   }
 
+  // this code could be removed if NullableValue used
+  // [[clang::analyse_as_class("std::optional")]]
   if (RD.getName() == "NullableValue") {
     const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());
     return N != nullptr &&
@@ -1069,6 +1075,8 @@ auto buildTransferMatchSwitch() {
       // optional::has_value, optional::hasValue
       // Of the supported optionals only folly::Optional uses hasValue, but 
this
       // will also pass for other types
+      // "hasValue" could be removed if folly::Optional used
+      // [[clang::analyse_as_method("std::optional::has_value")]] on hasValue()
       .CaseOfCFGStmt<CXXMemberCallExpr>(
           isOptionalMemberCallWithNameMatcher(
               anyOf(hasAnyName("has_value", "hasValue"),
@@ -1080,12 +1088,16 @@ auto buildTransferMatchSwitch() {
           isOptionalMemberCallWithNameMatcher(hasName("operator bool")),
           transferOptionalHasValueCall)
 
+      // this code could be removed if NullableValue used
+      // [[clang::analyse_as_inverse_method("std::optional::has_value")]] on 
isNull() *NYI
       // NullableValue::isNull
       // Only NullableValue has isNull
       .CaseOfCFGStmt<CXXMemberCallExpr>(
           isOptionalMemberCallWithNameMatcher(hasName("isNull")),
           transferOptionalIsNullCall)
 
+      // this code could be removed if NullableValue used
+      // [[clang::analyse_as_method("std::optional::emplace")]] on makeValue() 
and makeValueInplace()
       // NullableValue::makeValue, NullableValue::makeValueInplace
       // Only NullableValue has these methods, but this
       // will also pass for other types

>From a0d0dd9c93a309410c15030774368af98c425e1e Mon Sep 17 00:00:00 2001
From: khickett <[email protected]>
Date: Fri, 1 May 2026 17:51:13 +0100
Subject: [PATCH 10/11] remove fully qualified names for method attributes

---
 .../checkers/bugprone/unchecked-optional-access.cpp    | 10 +++++-----
 .../Models/UncheckedOptionalAccessModel.cpp            | 10 +++-------
 .../FlowSensitive/UncheckedOptionalAccessModelTest.cpp |  6 +++---
 3 files changed, 11 insertions(+), 15 deletions(-)

diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
index cdd8fdb82174b..bea941ff2104d 100644
--- 
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
@@ -389,11 +389,11 @@ void foo() {
 template <typename T>
 class [[clang::analyse_as_class("std::optional")]] CustomOptional {
 public:
-  [[clang::analyse_as_method("std::optional::has_value")]] bool isEngaged() 
const;
-  [[clang::analyse_as_method("std::optional::value")]] T &retrieve();
-  [[clang::analyse_as_method("std::optional::value")]] const T &retrieve() 
const;
-  [[clang::analyse_as_method("std::optional::emplace")]] T &fill(T val);
-  [[clang::analyse_as_method("std::optional::reset")]] void clear();
+  [[clang::analyse_as_method("has_value")]] bool isEngaged() const;
+  [[clang::analyse_as_method("value")]] T &retrieve();
+  [[clang::analyse_as_method("value")]] const T &retrieve() const;
+  [[clang::analyse_as_method("emplace")]] T &fill(T val);
+  [[clang::analyse_as_method("reset")]] void clear();
 };
 
 void custom_unchecked_access(CustomOptional<int> opt) {
diff --git 
a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp 
b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index 1fee746ff7a97..2264c7e60f190 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -240,11 +240,7 @@ AST_MATCHER_P(NamedDecl, hasAnalyseAsMethodName, 
std::string, MethodName) {
   if (const auto *MD = dyn_cast<CXXMethodDecl>(&Node)) {
     if (const auto *Attr = MD->getAttr<AnalyseAsMethodAttr>()) {
       StringRef AttrValue = Attr->getMethodName();
-      auto Pos = AttrValue.rfind("::");
-      StringRef AttrMethodName = (Pos != StringRef::npos)
-          ? AttrValue.substr(Pos + 2)
-          : AttrValue;
-      return AttrMethodName == MethodName;
+      return AttrValue == MethodName;
     }
   }
   return false;
@@ -1076,7 +1072,7 @@ auto buildTransferMatchSwitch() {
       // Of the supported optionals only folly::Optional uses hasValue, but 
this
       // will also pass for other types
       // "hasValue" could be removed if folly::Optional used
-      // [[clang::analyse_as_method("std::optional::has_value")]] on hasValue()
+      // [[clang::analyse_as_method("has_value")]] on hasValue()
       .CaseOfCFGStmt<CXXMemberCallExpr>(
           isOptionalMemberCallWithNameMatcher(
               anyOf(hasAnyName("has_value", "hasValue"),
@@ -1097,7 +1093,7 @@ auto buildTransferMatchSwitch() {
           transferOptionalIsNullCall)
 
       // this code could be removed if NullableValue used
-      // [[clang::analyse_as_method("std::optional::emplace")]] on makeValue() 
and makeValueInplace()
+      // [[clang::analyse_as_method("emplace")]] on makeValue() and 
makeValueInplace()
       // NullableValue::makeValue, NullableValue::makeValueInplace
       // Only NullableValue has these methods, but this
       // will also pass for other types
diff --git 
a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp 
b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
index 72cf12bcd0817..ac67bda300b65 100644
--- 
a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
+++ 
b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
@@ -3023,9 +3023,9 @@ TEST_P(UncheckedOptionalAccessTest, 
TestCustomAttributeHicketts) {
     template <typename T>
     class __attribute__((analyse_as_class("std::optional"))) MyOptional {
     public:
-      __attribute__((analyse_as_method("std::optional::has_value"))) bool 
isNotNull() const;
-      __attribute__((analyse_as_method("std::optional::value"))) T& unwrap();
-      __attribute__((analyse_as_method("std::optional::value"))) const T& 
unwrap() const;
+      __attribute__((analyse_as_method("has_value"))) bool isNotNull() const;
+      __attribute__((analyse_as_method("value"))) T& unwrap();
+      __attribute__((analyse_as_method("value"))) const T& unwrap() const;
     };
 
     void target(MyOptional<int> opt) {

>From 7263f13aea755ba43cbf6efcfafeaa2326cc2d59 Mon Sep 17 00:00:00 2001
From: khickett <[email protected]>
Date: Fri, 1 May 2026 17:55:12 +0100
Subject: [PATCH 11/11] American spelling is just cheating at Scrabble...

---
 .../bugprone/unchecked-optional-access.cpp    | 18 +++++------
 clang/include/clang/Basic/Attr.td             |  8 ++---
 .../Models/UncheckedOptionalAccessModel.cpp   | 30 +++++++++----------
 clang/lib/Sema/SemaDeclAttr.cpp               | 28 ++++++++---------
 .../UncheckedOptionalAccessModelTest.cpp      | 10 +++----
 5 files changed, 47 insertions(+), 47 deletions(-)

diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
index bea941ff2104d..6d68b95697925 100644
--- 
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
@@ -385,15 +385,15 @@ void foo() {
     vec[0].x = 0;
 }
 
-// Custom optional-like type using analyse_as_class / analyse_as_method 
attributes.
+// Custom optional-like type using analyze_as_class / analyze_as_method 
attributes.
 template <typename T>
-class [[clang::analyse_as_class("std::optional")]] CustomOptional {
+class [[clang::analyze_as_class("std::optional")]] CustomOptional {
 public:
-  [[clang::analyse_as_method("has_value")]] bool isEngaged() const;
-  [[clang::analyse_as_method("value")]] T &retrieve();
-  [[clang::analyse_as_method("value")]] const T &retrieve() const;
-  [[clang::analyse_as_method("emplace")]] T &fill(T val);
-  [[clang::analyse_as_method("reset")]] void clear();
+  [[clang::analyze_as_method("has_value")]] bool isEngaged() const;
+  [[clang::analyze_as_method("value")]] T &retrieve();
+  [[clang::analyze_as_method("value")]] const T &retrieve() const;
+  [[clang::analyze_as_method("emplace")]] T &fill(T val);
+  [[clang::analyze_as_method("reset")]] void clear();
 };
 
 void custom_unchecked_access(CustomOptional<int> opt) {
@@ -420,9 +420,9 @@ void custom_clear_then_access(CustomOptional<int> opt) {
 }
 
 // Custom optional-like type using standard method names — recognised via
-// analyse_as_class alone, without analyse_as_method attributes.
+// analyze_as_class alone, without analyze_as_method attributes.
 template <typename T>
-class [[clang::analyse_as_class("std::optional")]] StdNamedOptional {
+class [[clang::analyze_as_class("std::optional")]] StdNamedOptional {
 public:
   bool has_value() const;
   T &value();
diff --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index adc127df5756e..7db521598bff7 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -921,16 +921,16 @@ def AlignValue : Attr {
   let Documentation = [AlignValueDocs];
 }
 
-def AnalyseAsClass : InheritableAttr {
-  let Spellings = [Clang<"analyse_as_class">];
+def AnalyzeAsClass : InheritableAttr {
+  let Spellings = [Clang<"analyze_as_class">];
   let Args = [StringArgument<"ClassName">];
   let Subjects = SubjectList<[Record],
                              ErrorDiag>;
   let Documentation = [Undocumented];
 }
 
-def AnalyseAsMethod : InheritableAttr {
-  let Spellings = [Clang<"analyse_as_method">];
+def AnalyzeAsMethod : InheritableAttr {
+  let Spellings = [Clang<"analyze_as_method">];
   let Args = [StringArgument<"MethodName">];
   let Subjects = SubjectList<[CXXMethod],
                              ErrorDiag>;
diff --git 
a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp 
b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index 2264c7e60f190..49743b94eebeb 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -72,7 +72,7 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) {
   }
 
   // this code could be removed if base::Optional and folly::Optional used
-  // [[clang::analyse_as_class("std::optional")]]
+  // [[clang::analyze_as_class("std::optional")]]
   if (RD.getName() == "Optional") {
     // Check whether namespace is "::base" or "::folly".
     const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());
@@ -81,7 +81,7 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) {
   }
 
   // this code could be removed if Optional_Base used
-  // [[clang::analyse_as_class("std::optional")]]
+  // [[clang::analyze_as_class("std::optional")]]
   if (RD.getName() == "Optional_Base") {
     const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());
     return N != nullptr &&
@@ -89,14 +89,14 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) {
   }
 
   // this code could be removed if NullableValue used
-  // [[clang::analyse_as_class("std::optional")]]
+  // [[clang::analyze_as_class("std::optional")]]
   if (RD.getName() == "NullableValue") {
     const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());
     return N != nullptr &&
            isFullyQualifiedNamespaceEqualTo(*N, "bdlb", "BloombergLP");
   }
 
-  if (RD.hasAttr<AnalyseAsClassAttr>())
+  if (RD.hasAttr<AnalyzeAsClassAttr>())
     return true;
 
   return false;
@@ -236,9 +236,9 @@ AST_MATCHER(CXXOperatorCallExpr, 
hasOptionalOperatorObjectType) {
   return hasReceiverTypeDesugaringToOptional(Node.getArg(0));
 }
 
-AST_MATCHER_P(NamedDecl, hasAnalyseAsMethodName, std::string, MethodName) {
+AST_MATCHER_P(NamedDecl, hasAnalyzeAsMethodName, std::string, MethodName) {
   if (const auto *MD = dyn_cast<CXXMethodDecl>(&Node)) {
-    if (const auto *Attr = MD->getAttr<AnalyseAsMethodAttr>()) {
+    if (const auto *Attr = MD->getAttr<AnalyzeAsMethodAttr>()) {
       StringRef AttrValue = Attr->getMethodName();
       return AttrValue == MethodName;
     }
@@ -360,7 +360,7 @@ auto isValueOrStringEmptyCall() {
       callee(cxxMethodDecl(hasName("empty"))),
       onImplicitObjectArgument(ignoringImplicit(
           cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),
-                            callee(cxxMethodDecl(anyOf(hasName("value_or"), 
hasAnalyseAsMethodName("value_or")),
+                            callee(cxxMethodDecl(anyOf(hasName("value_or"), 
hasAnalyzeAsMethodName("value_or")),
                                                  ofClass(optionalClass()))),
                             hasArgument(0, stringLiteral(hasSize(0))))
               .bind(ValueOrCallID))));
@@ -999,7 +999,7 @@ ignorableOptional(const UncheckedOptionalAccessModelOptions 
&Options) {
 StatementMatcher
 valueCall(const std::optional<StatementMatcher> &IgnorableOptional) {
   return isOptionalMemberCallWithNameMatcher(
-      anyOf(hasName("value"), hasAnalyseAsMethodName("value")),
+      anyOf(hasName("value"), hasAnalyzeAsMethodName("value")),
       IgnorableOptional);
 }
 
@@ -1072,11 +1072,11 @@ auto buildTransferMatchSwitch() {
       // Of the supported optionals only folly::Optional uses hasValue, but 
this
       // will also pass for other types
       // "hasValue" could be removed if folly::Optional used
-      // [[clang::analyse_as_method("has_value")]] on hasValue()
+      // [[clang::analyze_as_method("has_value")]] on hasValue()
       .CaseOfCFGStmt<CXXMemberCallExpr>(
           isOptionalMemberCallWithNameMatcher(
               anyOf(hasAnyName("has_value", "hasValue"),
-              hasAnalyseAsMethodName("has_value"))),
+              hasAnalyzeAsMethodName("has_value"))),
           transferOptionalHasValueCall)
 
       // optional::operator bool
@@ -1085,7 +1085,7 @@ auto buildTransferMatchSwitch() {
           transferOptionalHasValueCall)
 
       // this code could be removed if NullableValue used
-      // [[clang::analyse_as_inverse_method("std::optional::has_value")]] on 
isNull() *NYI
+      // [[clang::analyze_as_inverse_method("std::optional::has_value")]] on 
isNull() *NYI
       // NullableValue::isNull
       // Only NullableValue has isNull
       .CaseOfCFGStmt<CXXMemberCallExpr>(
@@ -1093,7 +1093,7 @@ auto buildTransferMatchSwitch() {
           transferOptionalIsNullCall)
 
       // this code could be removed if NullableValue used
-      // [[clang::analyse_as_method("emplace")]] on makeValue() and 
makeValueInplace()
+      // [[clang::analyze_as_method("emplace")]] on makeValue() and 
makeValueInplace()
       // NullableValue::makeValue, NullableValue::makeValueInplace
       // Only NullableValue has these methods, but this
       // will also pass for other types
@@ -1110,7 +1110,7 @@ auto buildTransferMatchSwitch() {
 
       // optional::emplace
       .CaseOfCFGStmt<CXXMemberCallExpr>(
-          isOptionalMemberCallWithNameMatcher(anyOf(hasName("emplace"), 
hasAnalyseAsMethodName("emplace"))),
+          isOptionalMemberCallWithNameMatcher(anyOf(hasName("emplace"), 
hasAnalyzeAsMethodName("emplace"))),
           [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
              LatticeTransferState &State) {
             if (RecordStorageLocation *Loc =
@@ -1121,7 +1121,7 @@ auto buildTransferMatchSwitch() {
 
       // optional::reset
       .CaseOfCFGStmt<CXXMemberCallExpr>(
-          isOptionalMemberCallWithNameMatcher(anyOf(hasName("reset"), 
hasAnalyseAsMethodName("reset"))),
+          isOptionalMemberCallWithNameMatcher(anyOf(hasName("reset"), 
hasAnalyzeAsMethodName("reset"))),
           [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
              LatticeTransferState &State) {
             if (RecordStorageLocation *Loc =
@@ -1133,7 +1133,7 @@ auto buildTransferMatchSwitch() {
 
       // optional::swap
       .CaseOfCFGStmt<CXXMemberCallExpr>(
-          isOptionalMemberCallWithNameMatcher(anyOf(hasName("swap"), 
hasAnalyseAsMethodName("swap"))),
+          isOptionalMemberCallWithNameMatcher(anyOf(hasName("swap"), 
hasAnalyzeAsMethodName("swap"))),
           transferSwapCall)
 
       // std::swap
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index f999c307b7111..0aba360f7b7c2 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6463,50 +6463,50 @@ static void handleAbiTagAttr(Sema &S, Decl *D, const 
ParsedAttr &AL) {
 }
 
 // for now this only handles std::optional (POC)
-static bool isValidAnalyseAsClassAttr(Decl *D, StringRef Tag) {
+static bool isValidAnalyzeAsClassAttr(Decl *D, StringRef Tag) {
   if (Tag == "std::optional")
     return true;
   return false;
 }
 
-static void handleAnalyseAsClass(Sema &S, Decl *D, const ParsedAttr &AL) {
+static void handleAnalyzeAsClass(Sema &S, Decl *D, const ParsedAttr &AL) {
   StringRef Str;
   if (!S.checkStringLiteralArgumentAttr(AL, 0, Str))
     return;
-  if (D->hasAttr<AnalyseAsClassAttr>()) {
+  if (D->hasAttr<AnalyzeAsClassAttr>()) {
     S.Diag(AL.getLoc(), diag::err_duplicate_attribute) << AL;
     return;
   }
-  if (!isValidAnalyseAsClassAttr(D, Str)) {
+  if (!isValidAnalyzeAsClassAttr(D, Str)) {
     S.Diag(AL.getLoc(), diag::warn_attribute_type_not_supported) << AL;
     return;
   }
 
-  D->addAttr(::new (S.Context) AnalyseAsClassAttr(S.Context, AL, Str));
+  D->addAttr(::new (S.Context) AnalyzeAsClassAttr(S.Context, AL, Str));
 }
 
 // for now this only handles std::optional (POC)
-static bool isValidAnalyseAsMethodAttr(Decl *D, StringRef Tag) {
+static bool isValidAnalyzeAsMethodAttr(Decl *D, StringRef Tag) {
   // no validation is done currently.  if someone writes something with a 
nonsense name,
   // it simply won't be validated but also no warning will be emitted
   // would be nice to do something smarter in the real implementation
   return true;
 }
 
-static void handleAnalyseAsMethod(Sema &S, Decl *D, const ParsedAttr &AL) {
+static void handleAnalyzeAsMethod(Sema &S, Decl *D, const ParsedAttr &AL) {
   StringRef Str;
   if (!S.checkStringLiteralArgumentAttr(AL, 0, Str))
     return;
-  if (D->hasAttr<AnalyseAsMethodAttr>()) {
+  if (D->hasAttr<AnalyzeAsMethodAttr>()) {
     S.Diag(AL.getLoc(), diag::err_duplicate_attribute) << AL;
     return;
   }
-  if (!isValidAnalyseAsMethodAttr(D, Str)) {
+  if (!isValidAnalyzeAsMethodAttr(D, Str)) {
     S.Diag(AL.getLoc(), diag::warn_attribute_type_not_supported) << AL;
     return;
   }
 
-  D->addAttr(::new (S.Context) AnalyseAsMethodAttr(S.Context, AL, Str));
+  D->addAttr(::new (S.Context) AnalyzeAsMethodAttr(S.Context, AL, Str));
 }
 
 static bool hasBTFDeclTagAttr(Decl *D, StringRef Tag) {
@@ -7598,11 +7598,11 @@ 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);
+  case ParsedAttr::AT_AnalyzeAsClass:
+    handleAnalyzeAsClass(S, D, AL);
     break;
-  case ParsedAttr::AT_AnalyseAsMethod:
-    handleAnalyseAsMethod(S, D, AL);
+  case ParsedAttr::AT_AnalyzeAsMethod:
+    handleAnalyzeAsMethod(S, D, AL);
     break;
   case ParsedAttr::AT_BTFDeclTag:
     handleBTFDeclTagAttr(S, D, AL);
diff --git 
a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp 
b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
index ac67bda300b65..5d224ceb7cf3f 100644
--- 
a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
+++ 
b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
@@ -3000,7 +3000,7 @@ TEST_P(UncheckedOptionalAccessTest, 
TestCustomAttributeBalik) {
     #include "unchecked_optional_access_test.h"
 
     template <typename T>
-    class __attribute__((analyse_as_class("std::optional"))) MyOptional {
+    class __attribute__((analyze_as_class("std::optional"))) MyOptional {
     public:
       bool has_value() const;
       T& value();
@@ -3021,11 +3021,11 @@ TEST_P(UncheckedOptionalAccessTest, 
TestCustomAttributeHicketts) {
     #include "unchecked_optional_access_test.h"
 
     template <typename T>
-    class __attribute__((analyse_as_class("std::optional"))) MyOptional {
+    class __attribute__((analyze_as_class("std::optional"))) MyOptional {
     public:
-      __attribute__((analyse_as_method("has_value"))) bool isNotNull() const;
-      __attribute__((analyse_as_method("value"))) T& unwrap();
-      __attribute__((analyse_as_method("value"))) const T& unwrap() const;
+      __attribute__((analyze_as_method("has_value"))) bool isNotNull() const;
+      __attribute__((analyze_as_method("value"))) T& unwrap();
+      __attribute__((analyze_as_method("value"))) const T& unwrap() const;
     };
 
     void target(MyOptional<int> opt) {

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to