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

>From 611591f1dbda2e1a60894d86bac6b3167b6496bd Mon Sep 17 00:00:00 2001
From: khickett <[email protected]>
Date: Mon, 5 Oct 2026 13:23:20 +0100
Subject: [PATCH] analyze_as_class / analyze_as_method with typestate

---
 .../bugprone/unchecked-optional-access.cpp    |  67 +++++++
 clang/include/clang/Basic/Attr.td             |  16 ++
 .../Models/UncheckedOptionalAccessModel.cpp   | 188 +++++++++++++++---
 clang/lib/Sema/SemaDeclAttr.cpp               |  53 +++++
 ...a-attribute-supported-attributes-list.test |   2 +
 .../UncheckedOptionalAccessModelTest.cpp      |  50 ++++-
 6 files changed, 349 insertions(+), 27 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 337474bdf7535..5d90f0f6a8978 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,70 @@ void foo() {
   if (!vec.empty())
     vec[0].x = 0;
 }
+
+// Custom optional-like type using analyze_as_class / analyze_as_method 
attributes.
+template <typename T>
+class [[clang::analyze_as_class("std::optional")]] MyCustomOptional {
+public:
+  bool has_value() const;
+
+  [[clang::analyze_as_method("has_value")]] bool HasValue() const;
+
+  const T& value();
+
+  [[clang::analyze_as_method("value")]] const T &GetValue();
+
+  void reset();
+
+  [[clang::analyze_as_method("reset")]] void RemoveValue();
+
+  T &emplace(T val);
+
+  [[clang::analyze_as_method("emplace")]] T &Fill(T val);
+};
+
+void custom_unchecked_access(MyCustomOptional<int> opt) {
+  opt.GetValue();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional 
value [bugprone-unchecked-optional-access]
+}
+
+void std_named_unchecked_access(MyCustomOptional<int> opt) {
+  opt.value();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional 
value [bugprone-unchecked-optional-access]
+}
+
+void custom_checked_access(MyCustomOptional<int> opt) {
+  if (opt.HasValue()) {
+    opt.GetValue();
+  }
+}
+
+void std_named_checked_access(MyCustomOptional<int> opt) {
+  if (opt.has_value()) {
+    opt.value();
+  }
+}
+
+void custom_emplace_then_access(MyCustomOptional<int> opt) {
+  opt.Fill(42);
+  opt.GetValue();
+}
+
+void std_named_emplace_then_access(MyCustomOptional<int> opt) {
+  opt.emplace(42);
+  opt.value();
+}
+
+void custom_clear_then_access(MyCustomOptional<int> opt) {
+  opt.Fill(42);
+  opt.RemoveValue();
+  opt.GetValue();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional 
value [bugprone-unchecked-optional-access]
+}
+
+void std_named_reset_then_access(MyCustomOptional<int> opt) {
+  opt.emplace(42);
+  opt.reset();
+  opt.value();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional 
value [bugprone-unchecked-optional-access]
+}
diff --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index 4ef0bd2b5ca7c..b453447a12275 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -928,6 +928,22 @@ def AlignValue : Attr {
   let Documentation = [AlignValueDocs];
 }
 
+def AnalyzeAsClass : InheritableAttr {
+  let Spellings = [Clang<"analyze_as_class">];
+  let Args = [StringArgument<"ClassName">];
+  let Subjects = SubjectList<[Record],
+                             ErrorDiag>;
+  let Documentation = [Undocumented];
+}
+
+def AnalyzeAsMethod : InheritableAttr {
+  let Spellings = [Clang<"analyze_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 = [];
diff --git 
a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp 
b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index 568564fb361f4..375e15371e36a 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -13,6 +13,7 @@
 
 #include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.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"
@@ -30,6 +31,7 @@
 #include "clang/Analysis/FlowSensitive/Value.h"
 #include "clang/Basic/OperatorKinds.h"
 #include "clang/Basic/SourceLocation.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/ErrorHandling.h"
 #include <cassert>
@@ -70,6 +72,8 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) {
     return false;
   }
 
+  // this code could be removed if base::Optional and folly::Optional used
+  // [[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());
@@ -77,18 +81,25 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) {
                             isFullyQualifiedNamespaceEqualTo(*N, "folly"));
   }
 
+  // this code could be removed if Optional_Base used
+  // [[clang::analyze_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::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<AnalyzeAsClassAttr>())
+    return true;
+
   return false;
 }
 
@@ -226,6 +237,55 @@ AST_MATCHER(CXXOperatorCallExpr, 
hasOptionalOperatorObjectType) {
   return hasReceiverTypeDesugaringToOptional(Node.getArg(0));
 }
 
+AST_MATCHER_P(NamedDecl, hasAnalyzeAsMethodName, std::string, MethodName) {
+  if (const auto *MD = dyn_cast<CXXMethodDecl>(&Node)) {
+    if (const auto *Attr = MD->getAttr<AnalyzeAsMethodAttr>()) {
+      StringRef AttrValue = Attr->getMethodName();
+      return AttrValue == MethodName;
+    }
+  }
+  return false;
+}
+
+AST_MATCHER_P(NamedDecl, hasSetTypestateAttr, SetTypestateAttr::ConsumedState,
+              State) {
+  if (const auto *MD = dyn_cast<CXXMethodDecl>(&Node)) {
+    if (const auto *Attr = MD->getAttr<SetTypestateAttr>()) {
+      return Attr->getNewState() == State;
+    }
+  }
+  return false;
+}
+
+AST_MATCHER_P(NamedDecl, hasTestTypestateAttr, 
TestTypestateAttr::ConsumedState,
+              State) {
+  if (const auto *MD = dyn_cast<CXXMethodDecl>(&Node)) {
+    if (const auto *Attr = MD->getAttr<TestTypestateAttr>()) {
+      return Attr->getTestState() == State;
+    }
+  }
+  return false;
+}
+
+AST_MATCHER_P(NamedDecl, hasReturnTypestateAttr,
+              ReturnTypestateAttr::ConsumedState, State) {
+  if (const auto *MD = dyn_cast<CXXMethodDecl>(&Node)) {
+    if (const auto *Attr = MD->getAttr<ReturnTypestateAttr>()) {
+      return Attr->getState() == State;
+    }
+  }
+  return false;
+}
+
+AST_MATCHER(NamedDecl, hasCallableWhenAttr) {
+  if (const auto *MD = dyn_cast<CXXMethodDecl>(&Node)) {
+    if (MD->getAttr<CallableWhenAttr>()) {
+      return true;
+    }
+  }
+  return false;
+}
+
 auto isOptionalMemberCallWithNameMatcher(
     ast_matchers::internal::Matcher<NamedDecl> matcher,
     const std::optional<StatementMatcher> &Ignorable = std::nullopt) {
@@ -266,14 +326,18 @@ auto inPlaceClass() {
 
 auto isOptionalNulloptConstructor() {
   return cxxConstructExpr(
-      hasDeclaration(cxxConstructorDecl(parameterCountIs(1),
-                                        hasParameter(0, hasNulloptType()))),
+      hasDeclaration(cxxConstructorDecl(
+          anyOf(allOf(parameterCountIs(1), hasParameter(0, hasNulloptType())),
+                hasReturnTypestateAttr(ReturnTypestateAttr::Consumed)))),
       hasOptionalOrDerivedType());
 }
 
 auto isOptionalInPlaceConstructor() {
-  return cxxConstructExpr(hasArgument(0, hasType(inPlaceClass())),
-                          hasOptionalOrDerivedType());
+  return cxxConstructExpr(
+      anyOf(hasArgument(0, hasType(inPlaceClass())),
+            hasDeclaration(cxxConstructorDecl(
+                hasReturnTypestateAttr(ReturnTypestateAttr::Unconsumed)))),
+      hasOptionalOrDerivedType());
 }
 
 auto isOptionalValueOrConversionConstructor() {
@@ -289,7 +353,8 @@ auto isOptionalValueOrConversionAssignment() {
       hasOverloadedOperatorName("="),
       callee(cxxMethodDecl(ofClass(optionalOrDerivedClass()))),
       unless(hasDeclaration(cxxMethodDecl(
-          anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator())))),
+          anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator(),
+                hasSetTypestateAttr(SetTypestateAttr::Consumed))))),
       argumentCountIs(2), hasArgument(1, unless(hasNulloptType())));
 }
 
@@ -297,7 +362,10 @@ auto isOptionalNulloptAssignment() {
   return cxxOperatorCallExpr(
       hasOverloadedOperatorName("="),
       callee(cxxMethodDecl(ofClass(optionalOrDerivedClass()))),
-      argumentCountIs(2), hasArgument(1, hasNulloptType()));
+      argumentCountIs(2),
+      anyOf(hasArgument(1, hasNulloptType()),
+            callee(cxxMethodDecl(
+                hasSetTypestateAttr(SetTypestateAttr::Consumed)))));
 }
 
 auto isStdSwapCall() {
@@ -339,10 +407,12 @@ auto isValueOrStringEmptyCall() {
   return cxxMemberCallExpr(
       callee(cxxMethodDecl(hasName("empty"))),
       onImplicitObjectArgument(ignoringImplicit(
-          cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),
-                            callee(cxxMethodDecl(hasName("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))));
 }
 
@@ -978,8 +1048,9 @@ ignorableOptional(const 
UncheckedOptionalAccessModelOptions &Options) {
 
 StatementMatcher
 valueCall(const std::optional<StatementMatcher> &IgnorableOptional) {
-  return isOptionalMemberCallWithNameMatcher(hasName("value"),
-                                             IgnorableOptional);
+  return isOptionalMemberCallWithNameMatcher(
+      anyOf(hasName("value"), hasAnalyzeAsMethodName("value")),
+      IgnorableOptional);
 }
 
 StatementMatcher
@@ -1016,7 +1087,8 @@ auto buildTransferMatchSwitch() {
       
.CaseOfCFGStmt<CXXConstructExpr>(isOptionalValueOrConversionConstructor(),
                                        transferValueOrConversionConstructor)
 
-      // optional::operator=
+      // optional::operator= // for e.g. opt<T> = other_opt<T> - need to check
+      // engagement
       .CaseOfCFGStmt<CXXOperatorCallExpr>(
           isOptionalValueOrConversionAssignment(),
           transferValueOrConversionAssignment)
@@ -1047,12 +1119,48 @@ auto buildTransferMatchSwitch() {
                                  transferArrowOpCall(E, E->getArg(0), State);
                                })
 
+      .CaseOfCFGStmt<CXXMemberCallExpr>(
+          isOptionalMemberCallWithNameMatcher(
+              hasSetTypestateAttr(SetTypestateAttr::Unconsumed)),
+          [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
+             LatticeTransferState &State) {
+            if (RecordStorageLocation *Loc =
+                    getImplicitObjectLocation(*E, State.Env)) {
+              setHasValue(*Loc, State.Env.getBoolLiteralValue(true), 
State.Env);
+            }
+          })
+
+      .CaseOfCFGStmt<CXXMemberCallExpr>(
+          isOptionalMemberCallWithNameMatcher(
+              hasSetTypestateAttr(SetTypestateAttr::Consumed)),
+          [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
+             LatticeTransferState &State) {
+            if (RecordStorageLocation *Loc =
+                    getImplicitObjectLocation(*E, State.Env)) {
+              setHasValue(*Loc, State.Env.getBoolLiteralValue(false),
+                          State.Env);
+            }
+          })
+
+      .CaseOfCFGStmt<CXXMemberCallExpr>(
+          isOptionalMemberCallWithNameMatcher(
+              hasTestTypestateAttr(TestTypestateAttr::Unconsumed)),
+          transferOptionalHasValueCall)
+
+      .CaseOfCFGStmt<CXXMemberCallExpr>(
+          isOptionalMemberCallWithNameMatcher(
+              hasTestTypestateAttr(TestTypestateAttr::Consumed)),
+          transferOptionalIsNullCall)
+
       // 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::analyze_as_method("has_value")]] on hasValue()
       .CaseOfCFGStmt<CXXMemberCallExpr>(
           isOptionalMemberCallWithNameMatcher(
-              hasAnyName("has_value", "hasValue")),
+              anyOf(hasAnyName("has_value", "hasValue"),
+                    hasAnalyzeAsMethodName("has_value"))),
           transferOptionalHasValueCall)
 
       // optional::operator bool
@@ -1060,15 +1168,18 @@ auto buildTransferMatchSwitch() {
           isOptionalMemberCallWithNameMatcher(hasName("operator bool")),
           transferOptionalHasValueCall)
 
-      // NullableValue::isNull
-      // Only NullableValue has isNull
+      // 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
       .CaseOfCFGStmt<CXXMemberCallExpr>(
           isOptionalMemberCallWithNameMatcher(hasName("isNull")),
           transferOptionalIsNullCall)
 
-      // NullableValue::makeValue, NullableValue::makeValueInplace
-      // Only NullableValue has these methods, but this
-      // will also pass for other types
+      // 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
       .CaseOfCFGStmt<CXXMemberCallExpr>(
           isOptionalMemberCallWithNameMatcher(
               hasAnyName("makeValue", "makeValueInplace")),
@@ -1082,7 +1193,8 @@ auto buildTransferMatchSwitch() {
 
       // optional::emplace
       .CaseOfCFGStmt<CXXMemberCallExpr>(
-          isOptionalMemberCallWithNameMatcher(hasName("emplace")),
+          isOptionalMemberCallWithNameMatcher(
+              anyOf(hasName("emplace"), hasAnalyzeAsMethodName("emplace"))),
           [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
              LatticeTransferState &State) {
             if (RecordStorageLocation *Loc =
@@ -1093,7 +1205,8 @@ auto buildTransferMatchSwitch() {
 
       // optional::reset
       .CaseOfCFGStmt<CXXMemberCallExpr>(
-          isOptionalMemberCallWithNameMatcher(hasName("reset")),
+          isOptionalMemberCallWithNameMatcher(
+              anyOf(hasName("reset"), hasAnalyzeAsMethodName("reset"))),
           [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
              LatticeTransferState &State) {
             if (RecordStorageLocation *Loc =
@@ -1105,7 +1218,8 @@ auto buildTransferMatchSwitch() {
 
       // optional::swap
       .CaseOfCFGStmt<CXXMemberCallExpr>(
-          isOptionalMemberCallWithNameMatcher(hasName("swap")),
+          isOptionalMemberCallWithNameMatcher(
+              anyOf(hasName("swap"), hasAnalyzeAsMethodName("swap"))),
           transferSwapCall)
 
       // std::swap
@@ -1224,6 +1338,28 @@ auto buildTransferMatchSwitch() {
       .Build();
 }
 
+llvm::SmallVector<UncheckedOptionalAccessDiagnostic>
+isCallableInState(const CXXMemberCallExpr *E, const Environment &Env) {
+  if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(
+          getLocBehindPossiblePointer(*E->getImplicitObjectArgument(), Env))) {
+    auto *Prop = Env.getValue(locForHasValue(*OptionalLoc));
+    if (auto *HasValueVal = cast_or_null<BoolValue>(Prop)) {
+      const auto &f = HasValueVal->formula();
+      if (Env.proves(f)) // engaged
+        return {};
+      const auto &Range = CharSourceRange::getTokenRange(E->getSourceRange());
+      if (Env.proves(Env.arena().makeNot(f))) // empty
+      {
+        return {UncheckedOptionalAccessDiagnostic{Range}};
+      }
+      // unknown
+      return {UncheckedOptionalAccessDiagnostic{Range}};
+    }
+    return {};
+  }
+  return {};
+}
+
 llvm::SmallVector<UncheckedOptionalAccessDiagnostic>
 diagnoseUnwrapCall(const Expr *ObjectExpr, const Environment &Env) {
   if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(
@@ -1258,7 +1394,13 @@ auto buildDiagnoseMatchSwitch(
               [](const CallExpr *E, const MatchFinder::MatchResult &,
                  const Environment &Env) {
                 return diagnoseUnwrapCall(E->getArg(0), Env);
-              });
+              })
+          //
+          .CaseOfCFGStmt<CXXMemberCallExpr>(
+              isOptionalMemberCallWithNameMatcher(hasCallableWhenAttr(),
+                                                  IgnorableOptional),
+              [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
+                 const Environment &Env) { return isCallableInState(E, Env); 
});
 
   auto Builder = Options.IgnoreValueCalls
                      ? std::move(DiagBuilder)
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index eb4a8c2ab9ae0..7db92ab64ee75 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6647,6 +6647,53 @@ 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 isValidAnalyzeAsClassAttr(Decl *D, StringRef Tag) {
+  if (Tag == "std::optional")
+    return true;
+  return false;
+}
+
+static void handleAnalyzeAsClass(Sema &S, Decl *D, const ParsedAttr &AL) {
+  StringRef Str;
+  if (!S.checkStringLiteralArgumentAttr(AL, 0, Str))
+    return;
+  if (D->hasAttr<AnalyzeAsClassAttr>()) {
+    S.Diag(AL.getLoc(), diag::err_duplicate_attribute) << AL;
+    return;
+  }
+  if (!isValidAnalyzeAsClassAttr(D, Str)) {
+    S.Diag(AL.getLoc(), diag::warn_attribute_type_not_supported) << AL;
+    return;
+  }
+
+  D->addAttr(::new (S.Context) AnalyzeAsClassAttr(S.Context, AL, Str));
+}
+
+// for now this only handles std::optional (POC)
+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 handleAnalyzeAsMethod(Sema &S, Decl *D, const ParsedAttr &AL) {
+  StringRef Str;
+  if (!S.checkStringLiteralArgumentAttr(AL, 0, Str))
+    return;
+  if (D->hasAttr<AnalyzeAsMethodAttr>()) {
+    S.Diag(AL.getLoc(), diag::err_duplicate_attribute) << AL;
+    return;
+  }
+  if (!isValidAnalyzeAsMethodAttr(D, Str)) {
+    S.Diag(AL.getLoc(), diag::warn_attribute_type_not_supported) << AL;
+    return;
+  }
+
+  D->addAttr(::new (S.Context) AnalyzeAsMethodAttr(S.Context, AL, Str));
+}
+
 static bool hasBTFDeclTagAttr(Decl *D, StringRef Tag) {
   for (const auto *I : D->specific_attrs<BTFDeclTagAttr>()) {
     if (I->getBTFDeclTag() == Tag)
@@ -7738,6 +7785,12 @@ ProcessDeclAttribute(Sema &S, Decl *D, const ParsedAttr 
&AL,
   case ParsedAttr::AT_BPFPreserveStaticOffset:
     handleSimpleAttribute<BPFPreserveStaticOffsetAttr>(S, D, AL);
     break;
+  case ParsedAttr::AT_AnalyzeAsClass:
+    handleAnalyzeAsClass(S, D, AL);
+    break;
+  case ParsedAttr::AT_AnalyzeAsMethod:
+    handleAnalyzeAsMethod(S, D, AL);
+    break;
   case ParsedAttr::AT_BTFDeclTag:
     handleBTFDeclTagAttr(S, D, AL);
     break;
diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test 
b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index 8f8c1aa3d3f9c..6116679f4ea0a 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -15,6 +15,8 @@
 // CHECK-NEXT: AlignValue (SubjectMatchRule_variable, 
SubjectMatchRule_type_alias)
 // CHECK-NEXT: AlwaysDestroy (SubjectMatchRule_variable)
 // CHECK-NEXT: AlwaysInline (SubjectMatchRule_function)
+// CHECK-NEXT: AnalyzeAsClass (SubjectMatchRule_record)
+// CHECK-NEXT: AnalyzeAsMethod (SubjectMatchRule_function_is_member)
 // CHECK-NEXT: Annotate ()
 // CHECK-NEXT: AnyX86NoCfCheck (SubjectMatchRule_hasType_functionType)
 // CHECK-NEXT: ArcWeakrefUnavailable (SubjectMatchRule_objc_interface)
diff --git 
a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp 
b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
index f34568a072c32..75c12c6883f02 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();
@@ -3016,6 +3016,48 @@ TEST_P(UncheckedOptionalAccessTest, 
AssertFalseGtestMacroWithNullableValue) {
   )cc");
 }
 
+TEST_P(UncheckedOptionalAccessTest, TestCustomAttributeBalik) {
+  ExpectDiagnosticsFor(R"cc(
+    #include "unchecked_optional_access_test.h"
+
+    template <typename T>
+    class __attribute__((analyze_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__((analyze_as_class("std::optional"))) MyOptional {
+    public:
+      __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) {
+      opt.unwrap(); // [[unsafe]]
+      if (opt.isNotNull()) {
+        opt.unwrap();
+      }
+    }
+  )cc");
+}
+
 // FIXME: Add support for:
 // - constructors (copy, move)
 // - assignment operators (default, copy, move)

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

Reply via email to