Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -2509,6 +2509,7 @@ def generate_pretty_printer_configs(self):
lldbinit_content += f"# \n"
lldbinit_content += f"command script import {os.path.join(self.options.llvm_src_dir, 'llvm', 'utils', 'lldbDataFormatters.py').replace(os.sep, '/')}\n"
lldbinit_content += f"command script import {os.path.join(self.options.mrdocs_src_dir, 'share', 'lldb', 'mrdocs_formatters.py').replace(os.sep, '/')}\n"
lldbinit_content += f"command script import {os.path.join(self.options.mrdocs_src_dir, 'share', 'lldb', 'clang_ast_formatters.py').replace(os.sep, '/')}\n"
with open(lldbinit_path, "w") as f:
f.write(lldbinit_content)
print(f"Generated LLDB pretty printer configuration at '{lldbinit_path}'")
Expand Down
34 changes: 23 additions & 11 deletions include/mrdocs/Metadata/ExtractionMode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,33 @@ namespace clang::mrdocs {
*/
enum class ExtractionMode
{
/// We're extracting the current symbol even though it
/// doesn't pass all filters because it's a direct dependency
/// of a symbol that does pass all filters and needs
/// information about it (e.g.: base classes outside the filters)
Dependency,

/// We're extracting the current symbol because it passes
/// all filters
/// all filters.
Regular,

/// We're extracting the current symbol because it passes
/// all filters, but we should also tag it as see-below
/// because it passes one of the see-below filters
/// because it passes one of the see-below filters.
/// This symbol has its own page but it has no details
/// and no child members.
SeeBelow,

/// We're extracting the current symbol because it passes
/// all filters, but we should also tag it as
/// implementation-defined because one of its parents
/// matches the implementation-defined filter
/// matches the implementation-defined filter.
/// This symbol has no page and other symbols that
/// depend on it will just render /*implementation-defined*/.
ImplementationDefined,

/// We're extracting the current symbol even though it
/// doesn't pass all filters because it's a direct dependency
/// of a symbol that does pass all filters and needs
/// information about it (e.g.: base classes outside the filters).
/// This symbol has no page and it might even deleted from
/// the corpus if no other symbol depends on it after we extracted
/// the information we wanted from it in post-processing steps.
Dependency,
};

/** Return the name of the InfoKind as a string.
Expand All @@ -55,14 +62,14 @@ toString(ExtractionMode kind) noexcept
{
switch(kind)
{
case ExtractionMode::Dependency:
return "dependency";
case ExtractionMode::Regular:
return "regular";
case ExtractionMode::SeeBelow:
return "see-below";
case ExtractionMode::ImplementationDefined:
return "implementation-defined";
case ExtractionMode::Dependency:
return "dependency";
}
MRDOCS_UNREACHABLE();
}
Expand All @@ -83,6 +90,11 @@ tag_invoke(

This function returns the least specific of the two
ExtractionModes in terms of number of filters passed.

If the symbol passes the filter that categorizes it
as `a` then it also passes the filter that categorizes
it as `b` (or vice-versa), then this function will return the
final category for the symbol.
*/
constexpr
ExtractionMode
Expand Down
150 changes: 150 additions & 0 deletions share/lldb/clang_ast_formatters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#
# Copyright (c) 2025 alandefreitas ([email protected])
#
# Distributed under the Boost Software License, Version 1.0.
# https://www.boost.org/LICENSE_1_0.txt
#

import lldb
import re


# ---------- tiny helpers ----------
def _ctx(v):
try:
return v.Dereference() if v.TypeIsPointerType() else v
except Exception:
return v

def _eval_str(scope, code):
opts = lldb.SBExpressionOptions()
opts.SetLanguage(lldb.eLanguageTypeC_plus_plus)
opts.SetCoerceResultToId(False)
r = scope.EvaluateExpression(code, opts)
if r and r.GetError() and r.GetError().Success():
s = r.GetSummary() or r.GetValue()
return s.strip('"') if s else None
return None

def _strip_ptr_ref(t): # SBType -> SBType (no pointers/refs)
try:
# Drop references if possible
dt = t.GetDereferencedType()
if dt and dt.IsValid(): # only valid for refs
t = dt
# Drop pointers
while t.IsPointerType():
t = t.GetPointeeType()
except Exception:
pass
return t

def _kind_from_type(valobj):
t = _strip_ptr_ref(valobj.GetType())
name = t.GetName() or ""
m = re.search(r'clang::([A-Za-z_0-9]+)Decl', name)
return m.group(1) if m else "Decl"

def _humanize_kind(kind: str) -> str:
# Insert spaces at transitions: a|A, A|A a, digit|A; then lowercase.
if not kind:
return "decl"
s = re.sub(r'(?<=[a-z0-9])(?=[A-Z])', ' ', kind)
s = re.sub(r'(?<=[A-Z])(?=[A-Z][a-z])', ' ', s)
return s.lower()

def _is_derived_from(sbtype: lldb.SBType, qualified_basename: str) -> bool:
"""Walk base classes to see if sbtype derives from qualified_basename."""
try:
t = _strip_ptr_ref(sbtype)
seen = set()
q = [t]
steps = 0
while q and steps < 128:
steps += 1
cur = q.pop()
n = cur.GetName() or ""
if n == qualified_basename or n.endswith("::" + qualified_basename.split("::")[-1]):
# Accept exact or namespaced matches
if n.endswith(qualified_basename):
return True
num = cur.GetNumberOfDirectBaseClasses()
for i in range(num):
m = cur.GetDirectBaseClassAtIndex(i) # SBTypeMember
bt = m.GetType()
if bt and bt.IsValid():
bn = bt.GetName() or ""
if bn not in seen:
seen.add(bn)
q.append(bt)
except Exception:
pass
return False


# ---------- recognizers ----------
def is_clang_nameddecl(sbtype, _dict):
t = _strip_ptr_ref(sbtype)
n = t.GetName() or ""
if not n.startswith("clang::"):
return False
return _is_derived_from(t, "clang::NamedDecl")

def is_clang_decl_not_named(sbtype, _dict):
t = _strip_ptr_ref(sbtype)
n = t.GetName() or ""
if not n.startswith("clang::"):
return False
# Must be a Decl, but NOT a NamedDecl
return _is_derived_from(t, "clang::Decl") and not _is_derived_from(t, "clang::NamedDecl")


# ---------- summaries ----------
# NamedDecl (qualified name + humanized kind)
def NamedDecl_summary(valobj, _dict):
o = _ctx(valobj)

# Qualified name first
q = _eval_str(o, "static_cast<const ::clang::NamedDecl*>(this)->getQualifiedNameAsString()")
if not q:
q = _eval_str(o, "static_cast<const ::clang::NamedDecl*>(this)->getNameAsString()")

# Anonymous namespace friendly label (for the rare case q is still empty)
if not q:
anon = _eval_str(
o,
"(static_cast<const ::clang::NamespaceDecl*>(this)->isAnonymousNamespace() "
"? std::string(\"(anonymous namespace)\") : std::string())"
)
if anon:
q = anon

# Decl kind → humanized lowercase
kind = _eval_str(o, "std::string(getDeclKindName())") or _kind_from_type(valobj)
hkind = _humanize_kind(kind)

return f"{q} ({hkind})" if q else f"<unnamed> ({hkind})"


# Non-NamedDecl (unnamed) → "<unnamed> (humanized kind)"
def UnnamedDecl_summary(valobj, _dict):
o = _ctx(valobj)
kind = _eval_str(o, "std::string(getDeclKindName())") or _kind_from_type(valobj)
hkind = _humanize_kind(kind)
return f"<unnamed> ({hkind})"


def __lldb_init_module(debugger, _dict):
# One rule for ALL NamedDecl-derived types
debugger.HandleCommand(
'type summary add '
'--python-function clang_ast_formatters.NamedDecl_summary '
'--recognizer-function clang_ast_formatters.is_clang_nameddecl'
)
# And a second rule for other Decl types (non-NamedDecl)
debugger.HandleCommand(
'type summary add '
'--python-function clang_ast_formatters.UnnamedDecl_summary '
'--recognizer-function clang_ast_formatters.is_clang_decl_not_named'
)
print("[clang-ast] NamedDecl and Unnamed Decl summaries registered via recognizers.")
4 changes: 2 additions & 2 deletions share/lldb/mrdocs_formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
# ---------- SIMPLE, WORKING REGEXES ----------
# Tip: register BOTH const and non-const; avoid fancy groups.
_INFO_REGEXES = [
r"^clang::mrdocs::.*Info$",
r"^const clang::mrdocs::.*Info$",
r"^clang::mrdocs::[^:]*Info$",
r"^const clang::mrdocs::[^:]*Info$",
]


Expand Down
9 changes: 9 additions & 0 deletions src/lib/AST/ClangHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ getParent(Decl const* D)
case Decl::CXXRecord:
// we treat anonymous unions as "transparent"
if (auto const* RD = cast<CXXRecordDecl>(D);
RD &&
RD->isAnonymousStructOrUnion())
{
break;
Expand All @@ -235,6 +236,14 @@ getParent(Decl const* D)
case Decl::Enum:
case Decl::ClassTemplateSpecialization:
case Decl::ClassTemplatePartialSpecialization:
// we treat anonymous namespaces as "transparent"
if (auto const* ND = dyn_cast<NamespaceDecl>(D);
ND &&
(ND->isInlineNamespace() ||
ND->isAnonymousNamespace()))
{
break;
}
return D;
// we consider all other DeclContexts to be "transparent"
default:
Expand Down
9 changes: 6 additions & 3 deletions src/lib/Metadata/Finalizers/OverloadsFinalizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ OverloadsFinalizer::
operator()(NamespaceInfo& I)
{
MRDOCS_CHECK_OR(!finalized_.contains(I.id));
finalized_.emplace(I.id);

foldOverloads(I.id, I.Members.Functions, true);
for (RecordInfo& RI: toDerivedView<RecordInfo>(I.Members.Records, corpus_))
{
Expand All @@ -276,14 +278,15 @@ operator()(NamespaceInfo& I)
{
operator()(UI);
}
finalized_.emplace(I.id);
}

void
OverloadsFinalizer::
operator()(RecordInfo& I)
{
MRDOCS_CHECK_OR(!finalized_.contains(I.id));
finalized_.emplace(I.id);

for (auto& b: I.Bases)
{
auto& BT = b.Type;
Expand Down Expand Up @@ -314,14 +317,15 @@ operator()(RecordInfo& I)
for (RecordInfo& RI: toDerivedView<RecordInfo>(I.Interface.Private.Records, corpus_)) {
operator()(RI);
}
finalized_.emplace(I.id);
}

void
OverloadsFinalizer::
operator()(UsingInfo& I)
{
MRDOCS_CHECK_OR(!finalized_.contains(I.id));
finalized_.emplace(I.id);

auto shadowFunctions = toDerivedView<FunctionInfo>(I.ShadowDeclarations, corpus_);
for (FunctionInfo& FI: shadowFunctions)
{
Expand All @@ -338,7 +342,6 @@ operator()(UsingInfo& I)
break;
}
foldOverloads(I.id, I.ShadowDeclarations, true);
finalized_.emplace(I.id);
}

} // clang::mrdocs
12 changes: 12 additions & 0 deletions src/lib/Support/Handlebars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4654,6 +4654,10 @@ registerStringHelpers(Handlebars& hbs)
if (isBlock)
{
str = static_cast<std::string>(fn());
if (!firstArg.isString())
{
return false;
}
prefix = firstArg.getString();
end = static_cast<std::int64_t>(str.size());
if (n > 2)
Expand All @@ -4667,7 +4671,15 @@ registerStringHelpers(Handlebars& hbs)
}
else
{
if (!firstArg.isString())
{
return false;
}
str = firstArg.getString();
if (!secondArg.isString())
{
return false;
}
prefix = secondArg.getString();
end = static_cast<std::int64_t>(str.size());
if (n > 3)
Expand Down
Loading
Loading