Skip to content
Open
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 include/mrdocs/Corpus.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ class MRDOCS_VISIBLE
lookupImpl(
SymbolID const& contextId,
ParsedRefComponent const& component,
bool qualified,
ParsedRef const& ref,
bool checkParameters) const;

Expand Down
21 changes: 21 additions & 0 deletions src/mrdocs/AST/ASTVisitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,25 @@ underlyingConversion(clang::NamedDecl const* D)
return dyn_cast<clang::CXXConversionDecl>(target);
}

// Name an inheriting constructor after the class it inherits from.
//
// `using Base::Base;` declares the constructors of `Base`, but Clang names
// the declaration as a constructor of the derived class, and, where the
// base depends on a template parameter, as a constructor of a type with no
// class behind it. The prefix built from the qualifier spells the class as
// written: a class, a class template specialization, an alias or a
// template parameter.
void
nameInheritingConstructor(UsingSymbol& I, clang::DeclarationName const& Name)
{
if (Name.getNameKind() == clang::DeclarationName::CXXConstructorName &&
I.IntroducedName->Prefix)
{
I.Name = (*I.IntroducedName->Prefix)->Identifier;
I.IntroducedName->Identifier = I.Name;
}
}

} // unnamed namespace

void
Expand Down Expand Up @@ -1414,6 +1433,7 @@ populate(
{
I.IntroducedName->Identifier = conversionNameAsWritten(CD);
}
nameInheritingConstructor(I, Name);
for (clang::UsingShadowDecl const* UDS: D->shadows())
{
ScopeExitRestore s(mode_, Dependency);
Expand Down Expand Up @@ -1442,6 +1462,7 @@ populateDependentUsing(
auto INI = toName(Name, {}, NNS);
MRDOCS_CHECK_OR(INI);
I.IntroducedName = *INI;
nameInheritingConstructor(I, Name);
}

void
Expand Down
44 changes: 42 additions & 2 deletions src/mrdocs/Corpus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,37 @@ isTransparent(Symbol const& info)
});
}

// Whether `U` inherits constructors: within a class, `using Base::Base;`
// names the constructors of `Base`, because the name repeats the last
// component of the qualifier.
bool
isInheritingConstructor(UsingSymbol const& U)
{
Name const& introduced = *U.IntroducedName;
return introduced.Prefix &&
(*introduced.Prefix)->Identifier == introduced.Identifier;
}

// Whether `M`, a member of `context`, is a constructor: an ordinary one,
// a set of overloaded ones, or inherited ones (`using Base::Base;`).
template <class MInfoTy>
bool
isConstructor(MInfoTy const& M, Symbol const& context)
{
bool result = false;
if constexpr (
std::same_as<MInfoTy, FunctionSymbol> ||
std::same_as<MInfoTy, OverloadsSymbol>)
{
result = M.FuncClass == FunctionClass::Constructor;
}
else if constexpr (std::same_as<MInfoTy, UsingSymbol>)
{
result = context.isRecord() && isInheritingConstructor(M);
}
return result;
}

SymbolID
findFirstParentInfo(
detail::SymbolSet const& info,
Expand Down Expand Up @@ -874,7 +905,7 @@ lookupImpl(
{
ParsedRefComponent const& component = ref.Components[i];
bool const isLast = i == ref.Components.size() - 1;
curContext = self.lookupImpl(curContext->id, component, ref, isLast);
curContext = self.lookupImpl(curContext->id, component, i != 0, ref, isLast);
if (!curContext)
{
break;
Expand All @@ -898,6 +929,7 @@ Corpus::
lookupImpl(
SymbolID const& contextId,
ParsedRefComponent const& component,
bool const qualified,
ParsedRef const& ref,
bool const checkParameters) const
{
Expand Down Expand Up @@ -981,6 +1013,14 @@ lookupImpl(
{
auto matchRes = MatchLevel::None;

// A constructor has no name of its own: within `Widget`,
// `Widget` names the class, and only `Widget::Widget` names
// the constructor.
MRDOCS_CHECK_OR(
!isConstructor(M, context) ||
(qualified && member.Name == context.Name),
matchRes);

// Name match
if constexpr (
std::same_as<MInfoTy, FunctionSymbol> ||
Expand Down Expand Up @@ -1111,7 +1151,7 @@ lookupImpl(
MRDOCS_CHECK_OR_CONTINUE(memberPtr);
Symbol const& member = *memberPtr;
MRDOCS_CHECK_OR_CONTINUE(isTransparent(member));
if (Symbol const* r = lookupImpl(member.id, component, ref, checkParameters))
if (Symbol const* r = lookupImpl(member.id, component, qualified, ref, checkParameters))
{
return r;
}
Expand Down
69 changes: 48 additions & 21 deletions src/mrdocs/Metadata/Finalizers/AnchorFinalizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com)
// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com)
// Copyright (c) 2026 Alan de Freitas (alandefreitas@gmail.com)
// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com)
//
// Official repository: https://github.com/cppalliance/mrdocs
//
Expand All @@ -27,23 +28,23 @@
namespace mrdocs {
namespace {

// All valid C++ identifiers begin with an underscore or
// alphabetic character, so a numeric prefix ensures no
// conflicts.
constexpr
std::string_view
func_reserved[] = {
"2function",
"2constructor",
"2conversion",
"2destructor"
};

std::string
getUnnamedInfoName(Symbol const& I)
{
// All valid c++ identifiers begin with
// an underscore or alphabetic character,
// so a numeric prefix ensures no conflicts
if (I.isFunction() || I.isOverloads())
{
static
constexpr
std::string_view
func_reserved[] = {
"2function",
"2constructor",
"2conversion",
"2destructor"
};
std::size_t func_idx = 0;
if (auto const* FI = I.asFunctionPtr())
{
Expand Down Expand Up @@ -82,6 +83,39 @@ getUnnamedInfoName(Symbol const& I)
return res;
}

// A name without the double quotes of a literal operator, `operator""_x`,
// which are not URL-safe.
std::string
withoutDoubleQuotes(std::string_view name)
{
std::string result(name);
std::erase(result, '"');
return result;
}

// The anchor of a using-declaration with no target to take one from: the
// safe name of an operator, the name that every conversion shares, or the
// name itself.
std::string
usingAnchorName(std::string_view name)
{
std::string result;
if (OperatorKind const kind = getOperatorKind(name);
kind != OperatorKind::None)
{
result = getSafeOperatorName(kind, true);
}
else if (name.starts_with("operator "))
{
result = func_reserved[to_underlying(FunctionClass::Conversion)];
}
else
{
result = withoutDoubleQuotes(name);
}
return result;
}

// Computes each symbol's unqualified, URL-safe legible name, unique among
// its siblings (siblings that would collide are disambiguated with a
// suffix taken from the SymbolID). This is a build-time helper for the
Expand Down Expand Up @@ -233,22 +267,15 @@ class LegibleNameTable
{
return getUnnamedInfoName(t);
}
// User-defined literal operators (operator""_x) carry a
// double quote in their name, which is not URL-safe; strip
// it so the anchor stays a plain identifier (operator_x).
if (t.Name.find('"') != std::string::npos)
{
std::string safe(t.Name);
std::erase(safe, '"');
return safe;
}
return withoutDoubleQuotes(t.Name);
}
else if constexpr(T::isUsing())
{
if (t.Class == UsingClass::Normal && !t.ShadowDeclarations.empty())
{
return getRawUnqualified(t.ShadowDeclarations.front());
}
return usingAnchorName(t.Name);
}
return t.Name;
});
Expand Down
Loading