[llvm] add some test files for playing with (PR #192683)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Apr 17 08:16:41 PDT 2026
https://github.com/KHicketts created https://github.com/llvm/llvm-project/pull/192683
None
>From 3781e6fa8e233cf80e23eaeb6969462a860157cf Mon Sep 17 00:00:00 2001
From: Kay Hicketts <khicketts at bloomberg.net>
Date: Fri, 17 Apr 2026 14:30:14 +0000
Subject: [PATCH] add some test files for playing with
---
hicketts_test/clang-tidy-overview.md | 58 ++++++++++++++++++
hicketts_test/hicketts_optional.h | 75 ++++++++++++++++++++++++
hicketts_test/run_instructions.md | 19 ++++++
hicketts_test/test_hicketts_optional.cpp | 73 +++++++++++++++++++++++
hicketts_test/test_makevalue_fix.cpp | 68 +++++++++++++++++++++
5 files changed, 293 insertions(+)
create mode 100644 hicketts_test/clang-tidy-overview.md
create mode 100644 hicketts_test/hicketts_optional.h
create mode 100644 hicketts_test/run_instructions.md
create mode 100644 hicketts_test/test_hicketts_optional.cpp
create mode 100644 hicketts_test/test_makevalue_fix.cpp
diff --git a/hicketts_test/clang-tidy-overview.md b/hicketts_test/clang-tidy-overview.md
new file mode 100644
index 0000000000000..8bb42792f16e2
--- /dev/null
+++ b/hicketts_test/clang-tidy-overview.md
@@ -0,0 +1,58 @@
+# How clang-tidy Works
+
+## Entry Point
+
+`clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp` — parses CLI args (`--checks`, `--fix`, etc.), builds an options provider, and calls `runClangTidy()`.
+
+## Module & Check Registration
+
+Uses LLVM's static registry pattern:
+
+1. **Modules** (e.g. `BugproneModule`, `ModernizeModule`) subclass `ClangTidyModule` and register themselves statically via `ClangTidyModuleRegistry::Add<T>`.
+2. Each module's `addCheckFactories()` registers individual checks by name:
+ ```cpp
+ Factories.registerCheck<UseNullptrCheck>("modernize-use-nullptr");
+ ```
+3. `ClangTidyForceLinker.h` has linker anchors ensuring all modules get linked in.
+
+## Per-TU Processing
+
+For each translation unit, `ClangTidyASTConsumerFactory::createASTConsumer()`:
+
+1. Loads merged options for the file (CLI + `.clang-tidy` hierarchy)
+2. Instantiates only the **enabled** checks
+3. Each check calls `registerMatchers(MatchFinder*)` to register AST matchers
+4. Optionally registers preprocessor callbacks (for `#include`/`#define` analysis)
+5. `MatchFinder` traverses the AST, calling `check(MatchResult&)` on matches
+
+## Check Lifecycle
+
+Every check extends `ClangTidyCheck` (`ClangTidyCheck.h`):
+
+- **`registerMatchers()`** — declare what AST patterns to match
+- **`check()`** — called on each match; emit diagnostics via `diag()` with optional `FixItHint`s
+- **`storeOptions()`** — serialize check-specific options (read via `Options.get()`)
+- **`isLanguageVersionSupported()`** — skip checks for wrong language modes
+
+## Configuration
+
+`ClangTidyOptions.h/cpp` handles `.clang-tidy` YAML files. Options merge with priority: defaults < parent directory configs < local config < CLI flags. Each option carries a `Priority` value to resolve conflicts.
+
+## Diagnostic Flow
+
+```
+check->diag(loc, "message")
+ -> ClangTidyContext::diag()
+ -> DiagnosticsEngine
+ -> ClangTidyDiagnosticConsumer
+ -> Creates ClangTidyError (SourceManager-independent)
+ -> Converts FixItHints to tooling::Replacement
+ -> Filters by header regex, NOLINT comments
+ -> Applies fixes if --fix was passed
+```
+
+## Key Design
+
+- **Pluggable**: new checks just subclass `ClangTidyCheck` and register in a module — no core changes needed
+- **AST Matcher-based**: most checks are declarative pattern matches over the Clang AST
+- **Hierarchical config**: `.clang-tidy` files cascade up the directory tree, merged by priority
diff --git a/hicketts_test/hicketts_optional.h b/hicketts_test/hicketts_optional.h
new file mode 100644
index 0000000000000..050fd2467a0d3
--- /dev/null
+++ b/hicketts_test/hicketts_optional.h
@@ -0,0 +1,75 @@
+#ifndef HICKETTS_OPTIONAL_H_
+#define HICKETTS_OPTIONAL_H_
+
+/// A custom optional-like type with differently named functions.
+/// Mirrors std::optional semantics but uses its own vocabulary
+/// In order to test implementation of attributes for clang-tidy
+namespace mylib {
+
+struct nothing_t {
+ constexpr explicit nothing_t() {}
+};
+
+constexpr nothing_t nothing;
+
+template <typename T>
+class [[clang::analyse_as_class("std::optional")]] HickettsOptional {
+ T *storage_ = nullptr;
+
+public:
+ constexpr HickettsOptional() noexcept {}
+
+ constexpr HickettsOptional(nothing_t) noexcept {}
+
+ HickettsOptional(const HickettsOptional &) = default;
+
+ HickettsOptional(HickettsOptional &&) = default;
+
+ // Equivalent to std::optional::value()
+ [[clang_analyse_as_method(std::optional::value)]]
+ const T &unwrap() const & { return *storage_; }
+ T &unwrap() & { return *storage_; }
+ const T &&unwrap() const && { return static_cast<const T &&>(*storage_); }
+ T &&unwrap() && { return static_cast<T &&>(*storage_); }
+
+ // Equivalent to std::optional::operator*()
+ const T &deref() const & { return *storage_; }
+ T &deref() & { return *storage_; }
+
+ // Equivalent to std::optional::operator->()
+ const T *arrow() const { return storage_; }
+ T *arrow() { return storage_; }
+
+ // Equivalent to std::optional::operator bool / has_value()
+ constexpr explicit operator bool() const noexcept { return storage_ != nullptr; }
+ constexpr bool isPresent() const noexcept { return storage_ != nullptr; }
+ constexpr bool isEmpty() const noexcept { return storage_ == nullptr; }
+
+ // Equivalent to std::optional::value_or()
+ template <typename U>
+ constexpr T unwrapOr(U &&fallback) const & {
+ return storage_ ? *storage_ : static_cast<T>(fallback);
+ }
+
+ // Equivalent to std::optional::emplace()
+ template <typename... Args>
+ T &construct(Args &&...args) { return *storage_; }
+
+ // Equivalent to std::optional::reset()
+ void clear() noexcept { storage_ = nullptr; }
+
+ // Equivalent to std::optional::swap()
+ void exchange(HickettsOptional &other) noexcept {
+ T *tmp = storage_;
+ storage_ = other.storage_;
+ other.storage_ = tmp;
+ }
+
+ // Assignment
+ template <typename U>
+ HickettsOptional &operator=(const U &u) { return *this; }
+};
+
+} // namespace mylib
+
+#endif // HICKETTS_OPTIONAL_H_
diff --git a/hicketts_test/run_instructions.md b/hicketts_test/run_instructions.md
new file mode 100644
index 0000000000000..8eea4dddd823a
--- /dev/null
+++ b/hicketts_test/run_instructions.md
@@ -0,0 +1,19 @@
+# Run Instructions
+
+## Test the PR #144313 fix (makeValue/makeValueInplace)
+```bash
+clang-tidy -checks='bugprone-unchecked-optional-access' \
+ test_makevalue_fix.cpp -- \
+ -I ../clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access \
+ -Wno-undefined-inline
+```
+
+## Test HickettsOptional (custom optional-like type)
+```bash
+clang-tidy -checks='bugprone-unchecked-optional-access' \
+ test_hicketts_optional.cpp -- \
+ -I . \
+ -Wno-undefined-inline
+```
+
+All commands assume you are running from the `hicketts_test/` directory.
diff --git a/hicketts_test/test_hicketts_optional.cpp b/hicketts_test/test_hicketts_optional.cpp
new file mode 100644
index 0000000000000..9cf229440ddcf
--- /dev/null
+++ b/hicketts_test/test_hicketts_optional.cpp
@@ -0,0 +1,73 @@
+// Test cases for mylib::HickettsOptional — a custom optional-like type
+// with differently named functions.
+//
+// Run from hicketts_test/ with:
+// clang-tidy -checks='bugprone-unchecked-optional-access' \
+// test_hicketts_optional.cpp -- -I . -Wno-undefined-inline
+
+#include "hicketts_optional.h"
+
+// --- Unchecked access (should warn if the checker recognises HickettsOptional) ---
+
+void unchecked_unwrap(mylib::HickettsOptional<int> &val) {
+ val.unwrap(); // unchecked access — may be empty
+}
+
+void unchecked_deref(mylib::HickettsOptional<int> &val) {
+ val.deref(); // unchecked access — may be empty
+}
+
+// --- Checked access (should NOT warn) ---
+
+void checked_with_bool(mylib::HickettsOptional<int> &val) {
+ if (val) {
+ val.unwrap(); // safe — checked via operator bool
+ }
+}
+
+void checked_with_isPresent(mylib::HickettsOptional<int> &val) {
+ if (val.isPresent()) {
+ val.unwrap(); // safe — checked via isPresent()
+ }
+}
+
+void checked_with_isEmpty(mylib::HickettsOptional<int> &val) {
+ if (!val.isEmpty()) {
+ val.unwrap(); // safe — checked via !isEmpty()
+ }
+}
+
+// --- State changes ---
+
+void safe_after_construct(mylib::HickettsOptional<int> &val) {
+ val.construct(42);
+ val.unwrap(); // safe — just constructed a value
+}
+
+void unsafe_after_clear(mylib::HickettsOptional<int> &val) {
+ val.construct(42);
+ val.clear();
+ val.unwrap(); // unsafe — value was cleared
+}
+
+void unsafe_after_exchange(mylib::HickettsOptional<int> &a,
+ mylib::HickettsOptional<int> &b) {
+ if (a) {
+ a.exchange(b);
+ a.unwrap(); // unsafe — a's state is now unknown
+ }
+}
+
+// --- Guarded paths ---
+
+void construct_covers_empty_branch(mylib::HickettsOptional<int> &val) {
+ if (val.isEmpty()) {
+ val.construct(99);
+ }
+ val.unwrap(); // safe — either was present, or construct filled it
+}
+
+void unwrapOr_is_always_safe(mylib::HickettsOptional<int> &val) {
+ int x = val.unwrapOr(0); // safe — fallback provided
+ (void)x;
+}
diff --git a/hicketts_test/test_makevalue_fix.cpp b/hicketts_test/test_makevalue_fix.cpp
new file mode 100644
index 0000000000000..d12129f52baf6
--- /dev/null
+++ b/hicketts_test/test_makevalue_fix.cpp
@@ -0,0 +1,68 @@
+// Test cases for the fix in llvm/llvm-project#144313
+//
+// This exercises the bugprone-unchecked-optional-access check's handling
+// of BloombergLP::bdlb::NullableValue::makeValue and makeValueInplace.
+//
+// Before the fix, cases marked "OK" below would produce false-positive
+// warnings because the checker didn't know makeValue/makeValueInplace
+// establish a valid value.
+//
+// Run with:
+// clang-tidy -checks='bugprone-unchecked-optional-access' \
+// test_makevalue_fix.cpp -- \
+// -I <llvm-project>/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access
+
+#include "bde/types/bdlb_nullablevalue.h"
+
+// -- SHOULD NOT WARN (the fix) --
+
+// makeValue(val) on null branch guarantees value is present on all paths.
+void makeValue_covers_null_branch(BloombergLP::bdlb::NullableValue<int> &opt) {
+ if (opt.isNull()) {
+ opt.makeValue(42);
+ }
+ opt.value(); // OK — either was non-null already, or makeValue filled it
+}
+
+// makeValueInplace does the same thing via in-place construction.
+void makeValueInplace_covers_null_branch(BloombergLP::bdlb::NullableValue<int> &opt) {
+ if (opt.isNull()) {
+ opt.makeValueInplace(42);
+ }
+ opt.value(); // OK
+}
+
+// Unconditional makeValue — always safe to access afterwards.
+void unconditional_makeValue(BloombergLP::bdlb::NullableValue<int> &opt) {
+ opt.makeValue(100);
+ opt.value(); // OK
+}
+
+// Zero-arg makeValue — default-constructs the value.
+void makeValue_no_args(BloombergLP::bdlb::NullableValue<int> &opt) {
+ opt.makeValue();
+ opt.value(); // OK
+}
+
+// -- SHOULD WARN (not fixed by makeValue) --
+
+// Accessing without any check or makeValue is still unsafe.
+void no_check_no_makeValue(BloombergLP::bdlb::NullableValue<int> &opt) {
+ opt.value(); // WARNING: unchecked access to optional value
+}
+
+// reset() after makeValue invalidates the value.
+void makeValue_then_reset(BloombergLP::bdlb::NullableValue<int> &opt) {
+ opt.makeValue(42);
+ opt.reset();
+ opt.value(); // WARNING: value was reset
+}
+
+// makeValue on a *different* object doesn't help.
+void makeValue_wrong_object(BloombergLP::bdlb::NullableValue<int> &a,
+ BloombergLP::bdlb::NullableValue<int> &b) {
+ if (a.isNull()) {
+ b.makeValue(42);
+ }
+ a.value(); // WARNING: a may still be null
+}
More information about the llvm-commits
mailing list