[clang] [clang-tools-extra] [llvm] Attributes kay (PR #196272)

2026-07-10 Thread via cfe-commits

github-actions[bot] wrote:


# :penguin: Linux x64 Test Results

The build failed before running any tests. Click on a failure below to see the 
details.


tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/SemaDeclAttr.cpp.o

```
FAILED: tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/SemaDeclAttr.cpp.o
sccache /opt/llvm/bin/clang++ -DCLANG_EXPORTS -D_DEBUG -D_GLIBCXX_ASSERTIONS 
-D_GLIBCXX_USE_CXX11_ABI=1 -D_GNU_SOURCE 
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE 
-D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS 
-I/home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/lib/Sema
 -I/home/gha/actions-runner/_work/llvm-project/llvm-project/clang/lib/Sema 
-I/home/gha/actions-runner/_work/llvm-project/llvm-project/clang/include 
-I/home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/include
 -I/home/gha/actions-runner/_work/llvm-project/llvm-project/build/include 
-I/home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/include -gmlt 
-fPIC -fno-semantic-interposition -fvisibility-inlines-hidden -Werror 
-Werror=date-time -Werror=unguarded-availability-new -Wall -Wextra 
-Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers 
-pedantic -Wno-long-long -Wc++98-compat-extra-semi -Wimplicit-fallthrough 
-Wcovered-switch-default -Wno-noexcept-type -Wnon-virtual-dtor 
-Wdelete-non-virtual-dtor -Wsuggest-override -Wstring-conversion 
-Wno-pass-failed -Wmisleading-indentation -Wctad-maybe-unsupported 
-fdiagnostics-color -ffunction-sections -fdata-sections -fno-common 
-Woverloaded-virtual -Wno-nested-anon-types -O3 -DNDEBUG -std=c++17 -UNDEBUG 
-fno-exceptions -funwind-tables -fno-rtti -MD -MT 
tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/SemaDeclAttr.cpp.o -MF 
tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/SemaDeclAttr.cpp.o.d -o 
tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/SemaDeclAttr.cpp.o -c 
/home/gha/actions-runner/_work/llvm-project/llvm-project/clang/lib/Sema/SemaDeclAttr.cpp
/home/gha/actions-runner/_work/llvm-project/llvm-project/clang/lib/Sema/SemaDeclAttr.cpp:6694:10:
 error: unused variable 'SegStart' [-Werror,-Wunused-variable]
6694 |   size_t SegStart = 0;
|  ^~~~
1 error generated.
```


If these failures are unrelated to your changes (for example tests are broken 
or flaky at HEAD), please open an issue at 
https://github.com/llvm/llvm-project/issues and add the `infrastructure` label.

https://github.com/llvm/llvm-project/pull/196272
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-tools-extra] [llvm] Attributes kay (PR #196272)

2026-07-10 Thread via cfe-commits

github-actions[bot] wrote:


# :window: Windows x64 Test Results

* 55741 tests passed
* 1379 tests skipped
* 2 tests failed

## Failed Tests
(click on a test name to see its output)

### Clang

Clang.AST/undocumented-attrs.cpp

```
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
c:\_work\llvm-project\llvm-project\build\bin\clang-tblgen.exe 
-gen-clang-attr-undocumented-list 
-IC:\_work\llvm-project\llvm-project\clang\test\AST/../../include/ 
C:\_work\llvm-project\llvm-project\clang\test\AST/../../include/clang/Basic/Attr.td
 -o - | c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe 
C:\_work\llvm-project\llvm-project\clang\test\AST\undocumented-attrs.cpp
# executed command: 
'c:\_work\llvm-project\llvm-project\build\bin\clang-tblgen.exe' 
-gen-clang-attr-undocumented-list 
'-IC:\_work\llvm-project\llvm-project\clang\test\AST/../../include/' 
'C:\_work\llvm-project\llvm-project\clang\test\AST/../../include/clang/Basic/Attr.td'
 -o -
# note: command had no output on stdout or stderr
# executed command: 
'c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe' 
'C:\_work\llvm-project\llvm-project\clang\test\AST\undocumented-attrs.cpp'
# .---command stderr
# | 
C:\_work\llvm-project\llvm-project\clang\test\AST\undocumented-attrs.cpp:13:13: 
error: CHECK-NEXT: is not on the line after the previous match
# | CHECK-NEXT: AnalyzerNoReturn
# | ^
# | :8:1: note: 'next' match was here
# | AnalyzerNoReturn
# | ^
# | :5:8: note: previous match ended here
# | Aligned
# |^
# | :6:1: note: non-matching line after previous match is here
# | AnalyzeAsClass
# | ^
# | 
# | Input file: 
# | Check file: 
C:\_work\llvm-project\llvm-project\clang\test\AST\undocumented-attrs.cpp
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<
# |1: Undocumented attributes: 
# |2: AcquiredAfter 
# |3: AcquiredBefore 
# |4: Alias 
# |5: Aligned 
# | next:13'0   {   search range start (exclusive)
# |6: AnalyzeAsClass 
# |7: AnalyzeAsMethod 
# |8: AnalyzerNoReturn 
# | next:13'1 !~~~   error: match on wrong line
# |9: ArcWeakrefUnavailable 
# |   10: AvailableOnlyInDefaultEvalMethod 
# |   11: Blocks 
# |   12: CDecl 
# |   13: CFAuditedTransfer 
# |.
# |.
# |.
# |   82: VectorSize 
# |   83: Visibility 
# |   84: WeakImport 
# |   85: WeakRef 
# |   86: WorkGroupSizeHint 
# |   87: Total: 85 
# | next:13'2   } search range end (exclusive)
# | >>
# `-
# error: command failed with exit status: 1

--

```


Clang.Misc/pragma-attribute-supported-attributes-list.test

```
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
c:\_work\llvm-project\llvm-project\build\bin\clang-tblgen.exe 
-gen-clang-test-pragma-attribute-supported-attributes 
-IC:\_work\llvm-project\llvm-project\clang/include 
C:\_work\llvm-project\llvm-project\clang/include/clang/Basic/Attr.td -o - | 
c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe 
C:\_work\llvm-project\llvm-project\clang\test\Misc\pragma-attribute-supported-attributes-list.test
# executed command: 
'c:\_work\llvm-project\llvm-project\build\bin\clang-tblgen.exe' 
-gen-clang-test-pragma-attribute-supported-attributes 
'-IC:\_work\llvm-project\llvm-project\clang/include' 
'C:\_work\llvm-project\llvm-project\clang/include/clang/Basic/Attr.td' -o -
# note: command had no output on stdout or stderr
# executed command: 
'c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe' 
'C:\_work\llvm-project\llvm-project\clang\test\Misc\pragma-attribute-supported-attributes-list.test'
# .---command stderr
# | 
C:\_work\llvm-project\llvm-project\clang\test\Misc\pragma-attribute-supported-attributes-list.test:18:16:
 error: CHECK-NEXT: is not on the line after the previous match
# | // CHECK-NEXT: Annotate ()
# |^
# | :16:1: note: 'next' match was here
# | Annotate ()
# | ^
# | :13:41: note: previous match ended here
# | AlwaysInline (SubjectMatchRule_function)
# | ^
# | :14:1: note: non-matching line after previous match is here
# | AnalyzeAsClass (SubjectMatchRule_record)
# | ^
# | 
# | Input file: 
# | Check file: 
C:\_work\llvm-project\llvm-project\clang\test\Misc\pragma-attribute-supported-attributes-list.test
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<
# |.
# |.
# |.
# |8: AbiTag (SubjectMatchRule_record_not_is_union, 
SubjectMatchRule_variable, SubjectMatchRule_function, 
SubjectMatchRule_namespace) 
# |9: AcquireHandle (SubjectMatchRule_function, 
SubjectMatchRule_type_alias, SubjectMatchRule_variable_is_parameter) 
# |   10: Alias (SubjectMatch

[clang] [clang-tools-extra] [llvm] Attributes kay (PR #196272)

2026-07-10 Thread via cfe-commits

github-actions[bot] wrote:




:warning: C/C++ code formatter, clang-format found issues in your code. 
:warning:



You can test this locally with the following command:


``bash
git-clang-format --diff origin/main HEAD --extensions cpp,h -- 
hicketts/hicketts_optional.h hicketts/isValidAnalyzeAsMethodAttr.cpp 
hicketts/test_hicketts_optional.cpp 
clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
 clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp 
clang/lib/Sema/SemaDeclAttr.cpp 
clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp 
--diff_from_common_commit
``

:warning:
The reproduction instructions above might return results for more than one PR
in a stack if you are using a stacked PR workflow. You can limit the results by
changing `origin/main` to the base branch/commit you want to compare against.
:warning:





View the diff from clang-format here.


``diff
diff --git 
a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp 
b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index 0f448a60d..021b5697c 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -12,8 +12,8 @@
 
//===--===//
 
 #include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h"
-#include "clang/AST/Attr.h"
 #include "clang/AST/ASTContext.h"
+#include "clang/AST/Attr.h"
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
@@ -362,10 +362,12 @@ auto isValueOrStringEmptyCall() {
   return cxxMemberCallExpr(
   callee(cxxMethodDecl(hasName("empty"))),
   onImplicitObjectArgument(ignoringImplicit(
-  cxxMemberCallExpr(on(expr(unless(cxxThisExpr(,
-callee(cxxMethodDecl(anyOf(hasName("value_or"), 
hasAnalyzeAsMethodName("value_or")),
- ofClass(optionalClass(,
-hasArgument(0, stringLiteral(hasSize(0
+  cxxMemberCallExpr(
+  on(expr(unless(cxxThisExpr(,
+  callee(cxxMethodDecl(anyOf(hasName("value_or"),
+ hasAnalyzeAsMethodName("value_or")),
+   ofClass(optionalClass(,
+  hasArgument(0, stringLiteral(hasSize(0
   .bind(ValueOrCallID;
 }
 
@@ -1079,7 +1081,7 @@ auto buildTransferMatchSwitch() {
   .CaseOfCFGStmt(
   isOptionalMemberCallWithNameMatcher(
   anyOf(hasAnyName("has_value", "hasValue"),
-  hasAnalyzeAsMethodName("has_value"))),
+hasAnalyzeAsMethodName("has_value"))),
   transferOptionalHasValueCall)
 
   // optional::operator bool
@@ -1088,18 +1090,17 @@ auto buildTransferMatchSwitch() {
   transferOptionalHasValueCall)
 
   // this code could be removed if NullableValue used
-  // [[clang::analyze_as_inverse_method("std::optional::has_value")]] on 
isNull() *NYI
-  // NullableValue::isNull
-  // Only NullableValue has isNull
+  // [[clang::analyze_as_inverse_method("std::optional::has_value")]] on
+  // isNull() *NYI NullableValue::isNull Only NullableValue has isNull
   .CaseOfCFGStmt(
   isOptionalMemberCallWithNameMatcher(hasName("isNull")),
   transferOptionalIsNullCall)
 
   // this code could be removed if NullableValue used
-  // [[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
+  // [[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
   .CaseOfCFGStmt(
   isOptionalMemberCallWithNameMatcher(
   hasAnyName("makeValue", "makeValueInplace")),
@@ -1113,7 +1114,8 @@ auto buildTransferMatchSwitch() {
 
   // optional::emplace
   .CaseOfCFGStmt(
-  isOptionalMemberCallWithNameMatcher(anyOf(hasName("emplace"), 
hasAnalyzeAsMethodName("emplace"))),
+  isOptionalMemberCallWithNameMatcher(
+  anyOf(hasName("emplace"), hasAnalyzeAsMethodName("emplace"))),
   [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
  LatticeTransferState &State) {
 if (RecordStorageLocation *Loc =
@@ -1124,7 +1126,8 @@ auto buildTransferMatchSwitch() {
 
   // optional::reset
   .CaseOfCFGStmt(
-  isOptionalMemberCallWithNameMatcher(anyOf(hasName("reset"), 
hasAnalyzeAsMethodNa

[clang] [clang-tools-extra] [llvm] Attributes kay (PR #196272)

2026-07-10 Thread Kay Hicketts via cfe-commits

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

>From 52d11c1082709188e7fa4d8b6995febf1bcf19fa Mon Sep 17 00:00:00 2001
From: khickett 
Date: Tue, 28 Apr 2026 14:37:52 +0100
Subject: [PATCH 01/12] 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()) {
+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()) {
 if (I->getBTFDeclTag() == Tag)
@@ -7551,6 +7574,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, 
const ParsedAttr &AL,
   case ParsedAttr::AT_BPFPreserveStaticOffset:
 handleSimpleAttribute(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 
Date: Tue, 28 Apr 2026 15:43:52 +0100
Subject: [PATCH 02/12] 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())
+return true;
+
   return false;
 }
 
diff --git a/hicketts_test/hicketts_optional.h 
b/hicketts_test/hicketts_optional.h
new file mode 100644
index 0..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 
+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(*storage_); }
+  T &&unwrap() && { return static_cast(*storage_); }
+
+  const T &value() const & { return *storage_; }
+  T &value() & { return *storage_; }
+  const T &&value() const && { return static_cast(*storage_); }
+  T &&value() && { return static_cast(*storage_); }
+
+  // Equivalent to std::optional::operator*()
+  const T &deref() const & { return *storage_; }
+  T &deref() & { return *storage_; }
+
+  // Equivalent to std::optional::operator->()
+  const T* operator ->() cons

[clang] [clang-tools-extra] [llvm] Attributes kay (PR #196272)

2026-05-07 Thread Kay Hicketts via cfe-commits

https://github.com/KHicketts edited 
https://github.com/llvm/llvm-project/pull/196272
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-tools-extra] [llvm] Attributes kay (PR #196272)

2026-05-07 Thread Kay Hicketts via cfe-commits

https://github.com/KHicketts edited 
https://github.com/llvm/llvm-project/pull/196272
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-tools-extra] [llvm] Attributes kay (PR #196272)

2026-05-07 Thread via cfe-commits

github-actions[bot] wrote:



Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this 
page.

If this is not working for you, it is probably because you do not have write 
permissions for the repository. In which case you can instead tag reviewers by 
name in a comment by using `@` followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a 
review by "ping"ing the PR by adding a comment “Ping”. The common courtesy 
"ping" rate is once a week. Please remember that you are asking for valuable 
time from other developers.

If you have further questions, they may be answered by the [LLVM GitHub User 
Guide](https://llvm.org/docs/GitHub.html).

You can also ask questions in a comment on this PR, on the [LLVM 
Discord](https://discord.com/invite/xS7Z362) or on the 
[forums](https://discourse.llvm.org/).

https://github.com/llvm/llvm-project/pull/196272
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-tools-extra] [llvm] Attributes kay (PR #196272)

2026-05-07 Thread Kay Hicketts via cfe-commits

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 
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()) {
+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()) {
 if (I->getBTFDeclTag() == Tag)
@@ -7551,6 +7574,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, 
const ParsedAttr &AL,
   case ParsedAttr::AT_BPFPreserveStaticOffset:
 handleSimpleAttribute(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 
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())
+return true;
+
   return false;
 }
 
diff --git a/hicketts_test/hicketts_optional.h 
b/hicketts_test/hicketts_optional.h
new file mode 100644
index 0..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 
+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(*storage_); }
+  T &&unwrap() && { return static_cast(*storage_); }
+
+  const T &value() const & { return *storage_; }
+  T &value() & { return *storage_; }
+  const T &&value() const && { return static_cast(*storage_); }
+  T &&value() && { return static_cast(*storage_); }
+
+  // Equivalent to std::optional::operator*()
+  const T &deref() const & { return *storage_; }
+  T &deref() & { return *storage_; }
+
+  // Equivalent to std::optional::operator->()
+  const T* operator ->(