diff --git a/include/mrdocs/ADT/Optional.hpp b/include/mrdocs/ADT/Optional.hpp index 2fca8b842e..ddbc80602f 100644 --- a/include/mrdocs/ADT/Optional.hpp +++ b/include/mrdocs/ADT/Optional.hpp @@ -64,35 +64,34 @@ class Optional constexpr Optional() = default; constexpr Optional(Optional const& other) = default; - constexpr Optional& operator=(Optional const& other) = default; - constexpr Optional& operator=(Optional&& other) = default; - constexpr Optional& operator=(T const& t) + template + requires std::is_constructible_v + constexpr explicit + Optional(U&& u) + : t_(std::forward(u)) { - t_ = t; - return *this; } - constexpr Optional& operator=(T&& t) + constexpr Optional& operator=(Optional const& other) = default; + constexpr Optional& operator=(Optional&& other) = default; + + template + requires std::is_constructible_v + constexpr + Optional& operator=(U&& u) { - t_ = std::move(t); + t_ = std::forward(u); return *this; } - constexpr Optional& operator=(std::nullptr_t) + constexpr + Optional& operator=(std::nullptr_t) { t_ = T(); MRDOCS_ASSERT(!this->operator bool()); return *this; } - template - requires std::is_constructible_v - constexpr explicit - Optional(U&& u) - : t_(std::forward(u)) - { - } - constexpr void reset() { *this = Optional(); diff --git a/include/mrdocs/ADT/Polymorphic.hpp b/include/mrdocs/ADT/Polymorphic.hpp index b73ab0db03..be2e7d53d1 100644 --- a/include/mrdocs/ADT/Polymorphic.hpp +++ b/include/mrdocs/ADT/Polymorphic.hpp @@ -599,6 +599,7 @@ class Polymorphic @pre bool(*this) is true. */ + constexpr Base const& operator*() const noexcept(noexcept(*std::declval())) { @@ -612,6 +613,7 @@ class Polymorphic @pre bool(*this) is true. */ + constexpr Base& operator*() noexcept(noexcept(*std::declval())) { @@ -625,6 +627,7 @@ class Polymorphic @pre bool(*this) is true. */ + constexpr Base const* operator->() const noexcept { @@ -637,6 +640,7 @@ class Polymorphic @pre bool(*this) is true. */ + constexpr Base* operator->() noexcept { @@ -647,6 +651,7 @@ class Polymorphic @return true if the Polymorphic owns an object, otherwise false. */ + constexpr explicit operator bool() const noexcept diff --git a/include/mrdocs/Metadata/Info/Function.hpp b/include/mrdocs/Metadata/Info/Function.hpp index 1f252947ff..a4c0f4f867 100644 --- a/include/mrdocs/Metadata/Info/Function.hpp +++ b/include/mrdocs/Metadata/Info/Function.hpp @@ -98,19 +98,19 @@ tag_invoke( // KRYSTIAN TODO: attributes (nodiscard, deprecated, and carries_dependency) // KRYSTIAN TODO: flag to indicate whether this is a function parameter pack /** Represents a single function parameter */ -struct Param +struct Param final { - /** The type of this parameter */ + /** The type of this parameter + */ Polymorphic Type; /** The parameter name. + */ + Optional Name; - Unnamed parameters are represented by empty strings. - */ - std::string Name; - - /** The default argument for this parameter, if any */ - std::string Default; + /** The default argument for this parameter, if any + */ + Optional Default; Param() = default; @@ -123,7 +123,8 @@ struct Param , Default(std::move(def_arg)) {} - auto operator<=>(Param const&) const = default; + auto + operator<=>(Param const&) const = default; }; /** Return the Param as a @ref dom::Value object. diff --git a/include/mrdocs/Metadata/Type.hpp b/include/mrdocs/Metadata/Type.hpp index ecd1d92ac1..4570140727 100644 --- a/include/mrdocs/Metadata/Type.hpp +++ b/include/mrdocs/Metadata/Type.hpp @@ -111,6 +111,14 @@ struct TypeInfo */ bool IsPackExpansion = false; + /** The const qualifier + */ + bool IsConst = false; + + /** The volatile qualifier + */ + bool IsVolatile = false; + /** The constraints associated with the type This represents the constraints associated with the type, @@ -135,25 +143,6 @@ struct TypeInfo constexpr bool isArray() const noexcept { return Kind == TypeKind::Array; } constexpr bool isFunction() const noexcept { return Kind == TypeKind::Function; } - /** Return the inner type. - - The inner type is the type which is modified - by a specifier (e.g. "int" in "pointer to int". - */ - virtual - TypeInfo* - innerType() noexcept - { - return nullptr; - } - - virtual - TypeInfo const* - cInnerType() const noexcept - { - return const_cast(this)->innerType(); - } - /** Return the symbol named by this type. */ SymbolID @@ -222,7 +211,6 @@ struct TypeInfoCommonBase : TypeInfo struct NamedTypeInfo final : TypeInfoCommonBase { - QualifierKind CVQualifiers = QualifierKind::None; Polymorphic Name; std::strong_ordering @@ -232,7 +220,6 @@ struct NamedTypeInfo final struct DecltypeTypeInfo final : TypeInfoCommonBase { - QualifierKind CVQualifiers = QualifierKind::None; ExprInfo Operand; auto operator<=>(DecltypeTypeInfo const&) const = default; @@ -241,7 +228,6 @@ struct DecltypeTypeInfo final struct AutoTypeInfo final : TypeInfoCommonBase { - QualifierKind CVQualifiers = QualifierKind::None; AutoKind Keyword = AutoKind::Auto; Polymorphic Constraint; @@ -254,12 +240,6 @@ struct LValueReferenceTypeInfo final { Polymorphic PointeeType; - TypeInfo* - innerType() noexcept override - { - return PointeeType.operator->(); - } - std::strong_ordering operator<=>(LValueReferenceTypeInfo const&) const; }; @@ -269,12 +249,6 @@ struct RValueReferenceTypeInfo final { Polymorphic PointeeType; - TypeInfo* - innerType() noexcept override - { - return PointeeType.operator->(); - } - std::strong_ordering operator<=>(RValueReferenceTypeInfo const&) const; }; @@ -282,15 +256,8 @@ struct RValueReferenceTypeInfo final struct PointerTypeInfo final : TypeInfoCommonBase { - QualifierKind CVQualifiers = QualifierKind::None; Polymorphic PointeeType; - TypeInfo* - innerType() noexcept override - { - return PointeeType.operator->(); - } - std::strong_ordering operator<=>(PointerTypeInfo const&) const; }; @@ -298,17 +265,9 @@ struct PointerTypeInfo final struct MemberPointerTypeInfo final : TypeInfoCommonBase { - QualifierKind CVQualifiers = QualifierKind::None; Polymorphic ParentType; Polymorphic PointeeType; - TypeInfo* - innerType() noexcept override - { - return PointeeType.operator->(); - } - - std::strong_ordering operator<=>(MemberPointerTypeInfo const&) const; }; @@ -319,12 +278,6 @@ struct ArrayTypeInfo final Polymorphic ElementType; ConstantExprInfo Bounds; - TypeInfo* - innerType() noexcept override - { - return ElementType.operator->(); - } - std::strong_ordering operator<=>(ArrayTypeInfo const&) const; }; @@ -334,17 +287,10 @@ struct FunctionTypeInfo final { Polymorphic ReturnType; std::vector> ParamTypes; - QualifierKind CVQualifiers = QualifierKind::None; ReferenceKind RefQualifier = ReferenceKind::None; NoexceptInfo ExceptionSpec; bool IsVariadic = false; - TypeInfo* - innerType() noexcept override - { - return ReturnType.operator->(); - } - std::strong_ordering operator<=>(FunctionTypeInfo const&) const; }; @@ -413,6 +359,44 @@ operator==(Polymorphic const& lhs, Polymorphic const& rhs) { return lhs <=> rhs == std::strong_ordering::equal; } +/** Return the inner type. + + The inner type is the type which is modified + by a specifier (e.g. "int" in "pointer to int". +*/ +MRDOCS_DECL +std::optional const>> +innerType(TypeInfo const& TI) noexcept; + +/// @copydoc innerType(TypeInfo const&) +MRDOCS_DECL +std::optional>> +innerType(TypeInfo& TI) noexcept; + +/// @copydoc innerType(TypeInfo const&) +MRDOCS_DECL +TypeInfo const* +innerTypePtr(TypeInfo const& TI) noexcept; + +/// @copydoc innerTypePtr(TypeInfo const&) +MRDOCS_DECL +TypeInfo* +innerTypePtr(TypeInfo& TI) noexcept; + +/** Return the innermost type. + + The innermost type is the type which is not + modified by any specifiers (e.g. "int" in + "pointer to const int"). +*/ +MRDOCS_DECL +std::optional const>> +innermostType(TypeInfo const& TI) noexcept; + +/// @copydoc innermostType(TypeInfo const&) +MRDOCS_DECL +std::optional>> +innermostType(TypeInfo& TI) noexcept; // VFALCO maybe we should rename this to `renderType` or something? MRDOCS_DECL diff --git a/include/mrdocs/Support/Algorithm.hpp b/include/mrdocs/Support/Algorithm.hpp new file mode 100644 index 0000000000..7b82f71277 --- /dev/null +++ b/include/mrdocs/Support/Algorithm.hpp @@ -0,0 +1,95 @@ +// +// Licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Copyright (c) 2025 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#ifndef MRDOCS_API_SUPPORT_ALGORITHM_HPP +#define MRDOCS_API_SUPPORT_ALGORITHM_HPP + +#include +#include + +namespace clang::mrdocs { + +/** Determine if a range contains a specific element. + @param range The range to search. + @param el The element to search for. + @return True if the element is found, false otherwise. + */ +template +requires std::equality_comparable_with> +bool +contains(Range const& range, El const& el) +{ + return std::find(range.begin(), range.end(), el) != range.end(); +} + +// A second overload where the range is an initializer list + +/** Determine if a range contains a specific element. + @param range The range to search. + @param el The element to search for. + @return True if the element is found, false otherwise. + */ +template +requires std::equality_comparable_with +bool +contains(std::initializer_list const& range, U const& el) +{ + return std::find(range.begin(), range.end(), el) != range.end(); +} + +/** Determine if a range contains any of the specified elements. + @param range The range to search. + @param els The elements to search for. + @return True if any of the elements are found, false otherwise. + */ +template +requires std::equality_comparable_with, std::ranges::range_value_t> +bool +contains_any(Range const& range, Els const& els) +{ + return std::ranges::find_first_of(range, els) != range.end(); +} + +template +requires std::equality_comparable_with> +bool +contains_any(Range const& range, std::initializer_list const& els) +{ + return std::ranges::find_first_of(range, els) != range.end(); +} + +/** Determine if a range contains at least N instances of the specified element. + @param range The range to search. + @param el The element to search for. + @param n The number of instances to search for. + @return True if the element is found, false otherwise. + */ +template +requires std::equality_comparable_with> +bool +contains_n(Range const& range, El const& el, std::size_t n) +{ + for (auto const& item : range) + { + if (item == el) + { + --n; + if (n == 0) + { + return true; + } + } + } + return false; +} + +} // clang::mrdocs + +#endif \ No newline at end of file diff --git a/share/mrdocs/addons/generator/common/partials/type/declarator-prefix.hbs b/share/mrdocs/addons/generator/common/partials/type/declarator-prefix.hbs index 46a61a4e55..b49fa81112 100644 --- a/share/mrdocs/addons/generator/common/partials/type/declarator-prefix.hbs +++ b/share/mrdocs/addons/generator/common/partials/type/declarator-prefix.hbs @@ -40,9 +40,13 @@ {{>symbol/name-info constraint nolink=nolink }} {{/if~}} {{keyword~}} {{~/if~}} -{{#if cv-qualifiers~}} +{{#if is-const~}} {{! cv-qualifiers as literal strings ~}} - {{ str ' ' }}{{cv-qualifiers}} + {{ str ' ' }}const +{{~/if~}} +{{#if is-volatile~}} + {{! cv-qualifiers as literal strings ~}} + {{ str ' ' }}volatile {{~/if~}} {{#if (eq kind "lvalue-reference")~}} {{! Refqualifiers as "&" or "&&" ~}} diff --git a/src/lib/AST/ASTVisitor.cpp b/src/lib/AST/ASTVisitor.cpp index 106e35bd08..068704feea 100644 --- a/src/lib/AST/ASTVisitor.cpp +++ b/src/lib/AST/ASTVisitor.cpp @@ -792,7 +792,7 @@ populate( MRDOCS_SYMBOL_TRACE(P, context_); Param& param = I.Params[i]; - if (param.Name.empty()) + if (!param.Name) { param.Name = P->getName(); } @@ -804,15 +804,15 @@ populate( Expr const* default_arg = P->hasUninstantiatedDefaultArg() ? P->getUninstantiatedDefaultArg() : P->getInit(); - if (param.Default.empty() && + if (!param.Default && default_arg) { param.Default = getSourceCode(default_arg->getSourceRange()); - param.Default = trim(param.Default); - if (param.Default.starts_with("= ")) + param.Default = trim(*param.Default); + if (param.Default->starts_with("= ")) { - param.Default.erase(0, 2); - param.Default = ltrim(param.Default); + param.Default->erase(0, 2); + param.Default = ltrim(*param.Default); } } } diff --git a/src/lib/AST/ParseRef.cpp b/src/lib/AST/ParseRef.cpp index 8c8d76a50f..6fb78eba9b 100644 --- a/src/lib/AST/ParseRef.cpp +++ b/src/lib/AST/ParseRef.cpp @@ -9,7 +9,10 @@ // #include "lib/AST/ParseRef.hpp" -#include "mrdocs/Metadata/Info/Function.hpp" +#include +#include +#include +#include namespace clang::mrdocs { @@ -25,16 +28,35 @@ constexpr bool isIdentifierStart(char const c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '~'; + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; } constexpr bool isIdentifierContinuation(char const c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || isDigit(c); + return isIdentifierStart(c) || isDigit(c); } +struct ParsedFunctionSuffix +{ + llvm::SmallVector, 8> Params; + bool HasVoid{false}; + bool IsVariadic{false}; + NoexceptInfo ExceptionSpec; + + virtual ~ParsedFunctionSuffix() = default; +}; + +struct ParsedMemberFunctionSuffix + : ParsedFunctionSuffix +{ + bool IsConst{false}; + bool IsVolatile{false}; + ReferenceKind Kind{ReferenceKind::None}; + bool IsExplicitObjectMemberFunction{false}; +}; + class RefParser { char const* first_; @@ -65,9 +87,17 @@ class RefParser skipWhitespace(); if (peek('(')) { - MRDOCS_CHECK_OR(parseFunctionParameters(), false); - parseMemberFunctionQualifiers(); + ParsedMemberFunctionSuffix functionParameters; + MRDOCS_CHECK_OR(parseFunctionSuffix(functionParameters), false); + result_.FunctionParameters = std::move(functionParameters.Params); + result_.IsVariadic = functionParameters.IsVariadic; + result_.IsConst = functionParameters.IsConst; + result_.IsVolatile = functionParameters.IsVolatile; + result_.Kind = functionParameters.Kind; + result_.IsExplicitObjectMemberFunction = functionParameters.IsExplicitObjectMemberFunction; } + error_.clear(); + error_pos_ = nullptr; return true; } @@ -85,17 +115,23 @@ class RefParser private: void - setError(std::string_view str) + setError(char const* pos, std::string_view str) { // Only set the error if it's not already set - // with a more specified error message - if (error_.empty()) + // with a more specific error message + if (!error_pos_ || error_.empty()) { error_ = std::string(str); - error_pos_ = ptr_; + error_pos_ = pos; } } + void + setError(std::string_view str) + { + setError(ptr_, str); + } + // Check if [ptr_, last_) starts with the literal `lit` bool parseLiteral(std::string_view lit) @@ -127,6 +163,48 @@ class RefParser return false; } + bool + parseAnyLiteral(std::initializer_list lits) + { + for (std::string_view const lit : lits) + { + if (parseLiteral(lit)) + { + return true; + } + } + return false; + } + + bool + parseKeyword(std::string_view lit) + { + char const* start = ptr_; + if (!parseLiteral(lit)) + { + return false; + } + if (peek(isIdentifierContinuation)) + { + ptr_ = start; + return false; + } + return true; + } + + bool + parseAnyKeyword(std::initializer_list lits) + { + for (std::string_view const lit : lits) + { + if (parseKeyword(lit)) + { + return true; + } + } + return false; + } + bool peek(char c) { @@ -142,18 +220,102 @@ class RefParser return std::equal(str.begin(), str.end(), ptr_); } + template F> + bool + peek(F f) + { + return ptr_ != last_ && f(*ptr_); + } + + bool + peek(char c, char skip) + { + char const* first = ptr_; + while (first != last_ && *first == skip) + { + ++first; + } + return first != last_ && *first == c; + } + + bool + peekBack(char c, char skip) + { + if (ptr_ == first_) + { + return false; + } + char const* last = ptr_; + last--; + while (last != first_ && *last == skip) + { + --last; + } + return last != first_ && *last == c; + } + + bool + peekAny(std::initializer_list chars) + { + return ptr_ != last_ && contains(chars, *ptr_); + } + + bool + peekAny(std::initializer_list chars, char skip) + { + char const* first = ptr_; + while (first != last_ && *first == skip) + { + ++first; + } + return first != last_ && contains(chars, *first); + } + + bool + rewindUntil(char c) + { + while (ptr_ != first_ && *ptr_ != c) + { + --ptr_; + } + return *ptr_ == c; + } + bool parseComponents() { - while (parseComponent()) + char const* start = ptr_; + while (true) { + char const* compStart = ptr_; + if (!parseComponent()) + { + return false; + } skipWhitespace(); if (!parseLiteral("::")) { - break; + return !result_.Components.empty(); + } + // If we have a "::" separator, so this is not + // the last component. Check the rules for + // nested-name-specifier + ParsedRefComponent const& comp = result_.Components.back(); + if (comp.isOperator()) + { + ptr_ = compStart; + setError("operator '::' is not allowed in nested-name-specifier"); + ptr_ = start; + return false; + } + if (comp.isConversion()) + { + ptr_ = compStart; + setError("conversion operator is not allowed in nested-name-specifier"); + ptr_ = start; + return false; } } - return !result_.Components.empty(); } bool @@ -161,16 +323,13 @@ class RefParser { if (!hasMore()) { + setError("expected component name"); return false; } char const *start = ptr_; - if (skipWhitespace()) - { - setError("unexpected whitespace"); - return false; - } + skipWhitespace(); result_.Components.emplace_back(); - if (!parseComponentName()) + if (!parseUnqualifiedIdentifierExpression()) { setError("expected component name"); ptr_ = start; @@ -190,8 +349,18 @@ class RefParser } bool - parseComponentName() + parseUnqualifiedIdentifierExpression() { + // https://en.cppreference.com/w/cpp/language/identifiers#Unqualified_identifiers + // Besides suitably declared identifiers, the following unqualified identifier + // expressions can be used in expressions in the same role: + // - an overloaded operator name in function notation, such as operator+ or operator new; + // - a user-defined conversion function name, such as operator bool; + // - a user-defined literal operator name, such as operator "" _km; + // - a template name followed by its argument list, such as MyTemplate; + // - the character ~ followed by a class name, such as ~MyClass; + // - the character ~ followed by a decltype specifier, such as ~decltype(str). + // - the character ~ followed by a pack indexing specifier, such as ~pack...[0]. char const* start = ptr_; if (!hasMore()) @@ -206,8 +375,14 @@ class RefParser return true; } + // Parse conversion operator + if (parseConversionOperator()) + { + return true; + } + // Parse as a regular identifier - if (!parseIdentifier()) + if (!parseDestructorOrIdentifier()) { setError("expected component name"); ptr_ = start; @@ -219,17 +394,73 @@ class RefParser } bool - parseIdentifier() + parseConversionOperator() + { + char const* start = ptr_; + if (!parseKeyword("operator")) + { + return false; + } + skipWhitespace(); + Polymorphic conversionType; + if (!parseDeclarationSpecifier(conversionType) || + !conversionType) + { + ptr_ = start; + return false; + } + currentComponent().ConversionType = std::move(conversionType); + return true; + } + + bool + parseDestructorOrIdentifier() + { + // A regular identifier or a function name + char const* start = ptr_; + skipWhitespace(); + if (parseLiteral("~")) + { + skipWhitespace(); + } + if (parseKeyword("operator")) + { + setError("'operator' is an invalid identifier"); + ptr_ = start; + return false; + } + if (!parseIdentifier(true)) + { + ptr_ = start; + return false; + } + return true; + } + + bool + parseIdentifier(bool const allowTemplateDisambiguation) { + // https://en.cppreference.com/w/cpp/language/identifiers char const* start = ptr_; skipWhitespace(); if (!hasMore()) { - setError("expected identifier"); + setError("end of string: expected identifier"); ptr_ = start; return false; } - if (!isIdentifierStart(*ptr_)) + if (allowTemplateDisambiguation) + { + if (parseAnyKeyword({"template", "typedef"})) + { + skipWhitespace(); + } + } + if (isIdentifierStart(*ptr_)) + { + ++ptr_; + } + else { setError("invalid identifier start character"); ptr_ = start; @@ -278,7 +509,7 @@ class RefParser char const* op_start = ptr_; while (hasMore()) { - if (*ptr_ == '<' || *ptr_ == '(' || *ptr_ == '.' || *ptr_ == ':') + if (*ptr_ == '<' || *ptr_ == '(' || *ptr_ == '.' || *ptr_ == ':' || *ptr_ == ' ') { break; } @@ -305,6 +536,7 @@ class RefParser bool parseTemplateArguments() { + // https://en.cppreference.com/w/cpp/language/template_parameters char const* start = ptr_; if (!parseLiteral('<')) { @@ -336,6 +568,12 @@ class RefParser bool parseTemplateArgument() + { + return parseTemplateArgument(currentComponent().TemplateArguments.emplace_back()); + } + + bool + parseTemplateArgument(Polymorphic& dest) { if (!hasMore()) { @@ -343,7 +581,6 @@ class RefParser } skipWhitespace(); char const* start = ptr_; - currentComponent().TemplateArguments.emplace_back(); if (!parseTemplateArgumentName()) { ptr_ = start; @@ -354,14 +591,20 @@ class RefParser return true; } - std::string_view& + Polymorphic& currentTemplateArgument() { return currentComponent().TemplateArguments.back(); } bool - parseTemplateArgumentName() + parseTemplateArgumentName() { + auto& dest = currentTemplateArgument(); + return parseTemplateArgumentName(dest); + } + + bool + parseTemplateArgumentName(Polymorphic& dest) { char const* start = ptr_; @@ -372,23 +615,36 @@ class RefParser } // Parse as a regular identifier - if (!parseIdentifier()) + if (!parseIdentifier(true)) { setError("expected identifier name"); ptr_ = start; return false; } - currentTemplateArgument() = std::string_view(start, ptr_ - start); + auto const nameStr = std::string_view(start, ptr_ - start); + TypeTArg arg; + NamedTypeInfo type; + NameInfo nameInfo; + nameInfo.Name = std::string(nameStr); + type.Name = nameInfo; + arg.Type = type; + dest = arg; return true; } bool - parseFunctionParameters() + parseFunctionSuffix(ParsedFunctionSuffix& dest) { - // https://en.cppreference.com/w/cpp/language/function // parameter-list: - // possibly empty, comma-separated list of the function parameters - // (see below for details) + // https://en.cppreference.com/w/cpp/language/function#Parameter_list + // possibly empty, comma-separated list of the function parameters, + // where a function parameter is: + // "void", or + // attr? this? decl-specifier-seq [declarator|abstract-declarator] [= initializer]? + // + // So, for purposes of a documentation ref, we only need: + // "void" + // this? decl-specifier-seq char const* start = ptr_; if (!parseLiteral('(')) @@ -397,8 +653,14 @@ class RefParser return false; } skipWhitespace(); - while (parseFunctionParameter()) + while (hasMore() && !peek(')')) { + if (!parseFunctionParameter(dest)) + { + setError("expected function parameter"); + ptr_ = start; + return false; + } skipWhitespace(); if (parseLiteral(',')) { @@ -416,177 +678,1395 @@ class RefParser ptr_ = start; return false; } - return true; - } - bool - parseFunctionParameter() - { - if (!hasMore()) - { - return false; - } - skipWhitespace(); - char const* start = ptr_; - result_.FunctionParameters.emplace_back(); - if (!parseFunctionParameterName()) + if (!parseFunctionQualifiers(dest)) { + setError("invalid function qualifiers"); ptr_ = start; - setError("expected function parameter"); return false; } - skipWhitespace(); - return true; - } - std::string_view& - currentFunctionParameter() - { - return result_.FunctionParameters.back(); + return true; } bool - parseFunctionParameterName() + parseFunctionParameter(ParsedFunctionSuffix& dest) { - char const* start = ptr_; if (!hasMore()) { - setError("expected parameter name"); return false; } - skipWhitespace(); + char const* start = ptr_; - // Empty parameter (MrDocs will fall back to a function with the same - // number of parameters) - if (parseLiteral(",")) + // Void parameter: accepted, but doesn't need to be stored + skipWhitespace(); + char const* voidStart = ptr_; + if (parseKeyword("void")) { - currentFunctionParameter() = {}; - return true; + skipWhitespace(); + if (peekAny({',', ')'})) + { + if (dest.Params.size() != 0) + { + ptr_ = voidStart; + setError("expected 'void' to be the only parameter"); + ptr_ = start; + return false; + } + if (dest.HasVoid) + { + ptr_ = voidStart; + setError("multiple 'void' parameters"); + ptr_ = start; + return false; + } + dest.HasVoid = true; + skipWhitespace(); + return true; + } + ptr_ = start; + skipWhitespace(); } - // Variadic parameter + // Variadic parameter: accepted, but doesn't need to be stored + // in the parameter list. if (parseLiteral("...")) { - currentFunctionParameter() = trim(std::string_view(start, ptr_ - start)); + skipWhitespace(); + dest.IsVariadic = true; return true; } - // Parse leading qualifiers - while (parseLiteral("const") || parseLiteral("volatile")) + // Empty parameter + if (peekAny({',', ')'})) + { + setError("expected parameter type"); + ptr_ = start; + return false; + } + + // Parse as a regular parameter + // https://en.cppreference.com/w/cpp/language/function#Parameter_list + // this? decl-specifier-seq [declarator/abstract-declarator]? + + // "this" parameter: accepted, but doesn't need to be stored + // in the parameter list + if (auto* destMF = dynamic_cast(&dest); + destMF && + parseKeyword("this")) { + if (!dest.Params.empty()) + { + setError("expected 'this' to be the first parameter"); + ptr_ = start; + return false; + } + destMF->IsExplicitObjectMemberFunction = true; skipWhitespace(); } - // Parse as a type, which is a list of regular identifiers - if (!parseTypeName()) + // https://en.cppreference.com/w/cpp/language/function#Parameter_list + // decl-specifier-seq + dest.Params.emplace_back(); + auto& curParam = dest.Params.back(); + if (!parseDeclarationSpecifiers(curParam)) { - setError("expected type name"); ptr_ = start; + setError("expected parameter qualified type"); return false; } - currentFunctionParameter() = std::string_view(start, ptr_ - start); - return true; - } - - bool - parseTypeName() - { - // A list of identifiers separated by "::" - char const* start = ptr_; - if (!(parseModifiedFundamentalType() || parseIdentifier())) + // If a parameter is not used in the function body, it does + // not need to be named (it's sufficient to use an + // abstract declarator). + // MrDocs refs only use abstract declarators. Any parameter + // name is ignored. + if (!parseAbstractDeclarator(curParam)) { + setError("expected abstract declarator"); + ptr_ = start; return false; } - while (parseLiteral("::")) + + // 2. After determining the type of each parameter, any parameter + // of type “array of T” or of function type T is adjusted to be + // “pointer to T”. + // https://en.cppreference.com/w/cpp/language/function#Function_type + if (curParam->isArray()) { - if (!parseIdentifier()) - { - ptr_ = start; - return false; - } + auto& ATI = dynamic_cast(*curParam); + PointerTypeInfo PTI; + dynamic_cast(PTI) = dynamic_cast(ATI); + PTI.PointeeType = std::move(ATI.ElementType); + curParam = std::move(PTI); } + + // 3. After producing the list of parameter types, any top-level + // cv-qualifiers modifying a parameter type are deleted when + // forming the function type. + // https://en.cppreference.com/w/cpp/language/function#Function_type + curParam->IsConst = false; + curParam->IsVolatile = false; + + skipWhitespace(); return true; } bool - parseModifiedFundamentalType() + parseDeclarationSpecifiers(Polymorphic& dest) { + // https://en.cppreference.com/w/cpp/language/declarations#Specifiers + // decl-specifier-seq is a sequence whitespace-separated decl-specifier's char const* start = ptr_; - bool hasSignModifier = false; - bool hasSizeModifier = false; - bool hasFundamentalType = false; + llvm::SmallVector specifiers; while (true) { skipWhitespace(); - if (!hasSignModifier) + char const* specStart = ptr_; + if (peekAny({',', ')', '&'})) { - if (parseLiteral("signed") || parseLiteral("unsigned")) - { - hasSignModifier = true; - continue; - } + break; } - if (parseLiteral("short") || parseLiteral("long")) + if (!parseDeclarationSpecifier(dest)) { - hasSizeModifier = true; - continue; + // This could be the end of the specifiers, followed + // by declarators, or an error. We need to check if + // dest was properly set. + // If dest was not set, we need to return an error. + // If we have one of the "long", "short", "signed", "unsigned" + // specifiers, then we don't have an error because + // we can later infer the type from these. + if (!dest && + !contains_any(specifiers, {"long", "short", "signed", "unsigned"})) + { + setError(specStart, "expected declaration specifier"); + ptr_ = start; + return false; + } + error_.clear(); + error_pos_ = nullptr; + return true; } - if (!hasFundamentalType) + auto const specifierStr = + trim(std::string_view(specStart, ptr_ - specStart)); + if (!specifiers.empty()) { - if (parseLiteral("int") || parseLiteral("char") || parseLiteral("float") || parseLiteral("double")) + // If we already have a declaration specifier, we need to + // check if we have a valid sequence of specifiers: + // - In general, only one type specifier is allowed + // - "const" and "volatile" can be combined with any type specifier + // except itself + if (specifierStr == "const" && + contains(specifiers, "const")) { - hasFundamentalType = true; - continue; + setError(specStart, "multiple 'const' specifiers"); + ptr_ = start; + return false; } - } - break; - } - if (!hasFundamentalType && !hasSizeModifier && !hasSignModifier) - { - ptr_ = start; - return false; - } - // Any of the other combinations are valid - return true; - } - - bool - parseMemberFunctionQualifiers() - { - // https://en.cppreference.com/w/cpp/language/function - - // Parse cv: - // const/volatile qualification, only allowed in non-static member - // function declarations - // Parse ref: - // ref-qualification, only allowed in non-static member function - // declarations + if (specifierStr == "volatile" && + contains(specifiers, "volatile")) + { + setError(specStart, "multiple 'volatile' specifiers"); + ptr_ = start; + return false; + } - // Parse except: - // dynamic exception specification, dynamic exception specification - // or noexcept specification, noexcept specification + // - "signed" or "unsigned" can be combined with "char", "long", "short", or "int". + if (contains({"signed", "unsigned"}, specifierStr) && + contains_any(specifiers, {"signed", "unsigned"})) + { + setError(specStart, "multiple 'signed' or 'unsigned' specifiers"); + ptr_ = start; + return false; + } + // - "short" or "long" can be combined with int. + // - "long" can be combined with "double" and "long" + // "short" is allowed only once + // "long" is allowed twice + if (specifierStr == "short") + { + if (contains(specifiers, "short")) + { + setError(specStart, "multiple 'short' specifiers"); + ptr_ = start; + return false; + } + if (contains(specifiers, "long")) + { + setError(specStart, "cannot combine 'short' with 'long'"); + ptr_ = start; + return false; + } + } - // Parse potential qualifiers at the end of a function - // to identify the qualifiers to set - // ReferenceKind Kind = ReferenceKind::None; and - // bool IsConst = false; - skipWhitespace(); - if (parseLiteral("const")) - { - result_.IsConst = true; + if (specifierStr == "long") + { + if (contains_n(specifiers, "long", 2)) + { + setError(specStart, "too many 'long' specifiers"); + ptr_ = start; + return false; + } + if (contains(specifiers, "short")) + { + setError(specStart, "cannot combine 'long' with 'short'"); + ptr_ = start; + return false; + } + } + } + specifiers.push_back(specifierStr); + if (!skipWhitespace()) + { + break; + } } - skipWhitespace(); - if (parseLiteral("&&")) + if (specifiers.empty()) { - result_.Kind = ReferenceKind::RValue; + // We need at least one declarator specifier + ptr_ = start; + return false; } - else if (parseLiteral("&")) + + // "signed" or "unsigned" can be combined with "char", "long", "short", or "int". + if (contains_any(specifiers, {"signed", "unsigned"})) { - result_.Kind = ReferenceKind::LValue; + bool explicitlySigned = contains(specifiers, "signed"); + if (!dest) + { + NamedTypeInfo NTI; + NameInfo NI; + NI.Name = "int"; + NTI.Name = NI; + dest = NTI; + } + else + { + if (!dest->isNamed()) + { + if (explicitlySigned) + { + setError("expected type for 'signed' specifier"); + } + else + { + setError("expected type for 'unsigned' specifier"); + } + ptr_ = start; + return false; + } + if (auto& namedParam = dynamic_cast(*dest); + namedParam.Name->Name != "int" && + namedParam.Name->Name != "char") + { + if (explicitlySigned) + { + setError(start, "expected 'int' or 'char' for 'signed' specifier"); + } + else + { + setError(start, "expected 'int' or 'char' for 'unsigned' specifier"); + } + ptr_ = start; + return false; + } + } + } + + // - "short" can be combined with int. + if (contains(specifiers, "short")) + { + if (!dest) + { + NamedTypeInfo NTI; + NameInfo NI; + NI.Name = "int"; + NTI.Name = NI; + dest = NTI; + } + else + { + if (!dest->isNamed()) + { + setError(start, "expected type for 'short' specifier"); + ptr_ = start; + return false; + } + if (auto& namedParam = dynamic_cast(*dest); + namedParam.Name->Name != "int") + { + setError(start, "expected 'int' for 'short' specifier"); + ptr_ = start; + return false; + } + } + } + + // - "long" can be combined with "int", "double" and "long" + if (contains(specifiers, "long")) + { + if (!dest) + { + NamedTypeInfo NTI; + NameInfo NI; + NI.Name = "int"; + NTI.Name = NI; + dest = NTI; + } + else + { + if (!dest->isNamed()) + { + setError(start, "expected type for 'long' specifier"); + ptr_ = start; + return false; + } + if (auto& namedParam = dynamic_cast(*dest); + namedParam.Name->Name != "int" && + namedParam.Name->Name != "double" && + namedParam.Name->Name != "long") + { + setError(start, "expected 'int', 'double' or 'long' for 'long' specifier"); + ptr_ = start; + return false; + } + } + } + + if (!dest) + { + ptr_ = start; + setError("expected parameter type"); + return false; + } + + dest->IsConst = contains(specifiers, "const"); + dest->IsVolatile = contains(specifiers, "volatile"); + + return true; + } + + bool + parseDeclarationSpecifier(Polymorphic& dest) + { + char const* start = ptr_; + + // Some rules are only valid if dest was initially empty + auto checkDestWasEmpty = [destWasEmpty=!dest, start, this]() { + if (!destWasEmpty) + { + setError(start, "multiple type declaration specifiers"); + ptr_ = start; + return false; + } + return true; + }; + + // https://en.cppreference.com/w/cpp/language/declarations#Specifiers + // decl-specifier is one of the following specifiers: + // - typedef specifier (The typedef specifier may not appear in the declaration of a function parameter) + // - "inline", "virtual", "explicit" (only allowed in function declarations) + // - "inline" specifier (also allowed in variable declarations) + // - "friend" specifier (allowed in class and function declarations) + // - "constexpr" specifier (allowed in variable and function declarations) + // - "consteval" specifier (allowed in function declarations) + // - "constinit" specifier, (allowed in variable declarations) + // - "register", "static", "extern", "mutable", "thread_local" (storage-class specifiers) + // - Type specifiers (type-specifier-seq) (a sequence of specifiers that names a type): + // - cv qualifier + if (parseAnyKeyword({"const", "volatile"})) + { + return true; + } + + // - simple type specifier: "char", "char8_t", "char16_t", "char32_t", + // "wchar_t", "bool", "short", "int", "long", "signed", "unsigned", + // "float", "double", "void" + if (parseAnyKeyword({"signed", "unsigned", "short", "long"})) + { + // These specifiers are handled in parseDeclarationSpecifiers + // because they can represent or modify the type. + return true; + } + + if (parseAnyKeyword( + { "char", + "char8_t", + "char16_t", + "char32_t", + "wchar_t", + "bool", + "int", + "float", + "double", + "void" })) + { + MRDOCS_CHECK_OR(checkDestWasEmpty(), false); + NamedTypeInfo NTI; + NameInfo NI; + NI.Name = std::string_view(start, ptr_ - start); + NTI.Name = NI; + dest = NTI; + return true; + } + + // - "auto" + if (parseKeyword("auto")) + { + MRDOCS_CHECK_OR(checkDestWasEmpty(), false); + dest = AutoTypeInfo(); + return true; + } + + if (parseKeyword("decltype")) + { + skipWhitespace(); + // - "decltype(auto)" + if (parseLiteral("(")) + { + skipWhitespace(); + if (parseKeyword("auto")) + { + skipWhitespace(); + if (parseLiteral(")")) + { + MRDOCS_CHECK_OR(checkDestWasEmpty(), false); + AutoTypeInfo res; + res.Keyword = AutoKind::DecltypeAuto; + dest = std::move(res); + return true; + } + } + // - "decltype(expression)" + if (!hasMore()) + { + setError("expected expression in decltype"); + ptr_ = start; + return false; + } + // rewind ptr_ to '(' + --ptr_; + while (*ptr_ != '(') + { + ptr_--; + } + char const* exprStart = ptr_; + if (parseBalanced("(", ")")) + { + std::string_view expr(exprStart + 1, ptr_ - exprStart - 2); + expr = trim(expr); + if (expr.empty()) + { + setError("expected expression in decltype"); + ptr_ = start; + return false; + } + MRDOCS_CHECK_OR(checkDestWasEmpty(), false); + DecltypeTypeInfo DTI; + DTI.Operand.Written = expr; + dest = DTI; + return true; + } + setError("expected expression in decltype"); + ptr_ = start; + return false; + } + ptr_ = start; + } + + // - pack indexing specifier (C++26) + // auto f(Ts...[0] arg, type_seq) + // (unsupported) + + // - "class" specifier + // - elaborated type specifier + // - "class", "struct" or "union" followed by the identifier + // optionally qualified + // - "class", "struct" or "union" followed by the template + // name with template arguments optionally qualified + // - typename specifier + if (parseAnyKeyword({"class", "struct", "union", "typename"})) + { + skipWhitespace(); + if (parseQualifiedIdentifier(dest, true, true)) + { + return checkDestWasEmpty(); + } + ptr_ = start; + } + + // - "enum" specifier + // - "enum" followed by the identifier optionally qualified + if (parseKeyword("enum")) + { + skipWhitespace(); + if (parseQualifiedIdentifier(dest, true, false)) + { + return checkDestWasEmpty(); + } + ptr_ = start; + } + + // - previously declared class/enum/typedef name + // - template name with template arguments optionally qualified + // - template name without template arguments optionally qualified + if (parseQualifiedIdentifier(dest, true, true)) + { + return checkDestWasEmpty(); + } + + ptr_ = start; + return false; + } + + bool + parseBalanced( + std::string_view const openTag, + std::string_view const closeTag) + { + char const* start = ptr_; + std::size_t depth = 0; + while (hasMore()) + { + if (parseLiteral(openTag)) + { + ++depth; + } + else if (parseLiteral(closeTag)) + { + --depth; + if (depth == 0) + { + return true; + } + } + else + { + ++ptr_; + } + } + ptr_ = start; + return false; + } + + bool + parseQualifiedIdentifier( + Polymorphic& dest, + bool const allowTemplateDisambiguation, + bool const allowTemplateArguments) + { + if (dest) + { + setError("type specifier is already set"); + return false; + } + // Identifiers separated by "::" + char const* start = ptr_; + parseLiteral("::"); + skipWhitespace(); + while (true) + { + char const* idStart = ptr_; + if (!parseIdentifier(allowTemplateDisambiguation)) + { + break; + } + + // Populate dest + auto const idStr = std::string_view(idStart, ptr_ - idStart); + if (!dest) + { + NamedTypeInfo NTI; + NameInfo NI; + NI.Name = idStr; + NTI.Name = NI; + dest = NTI; + } + else + { + MRDOCS_CHECK_OR(dest->isNamed(), false); + NamedTypeInfo NTI; + NameInfo NI; + NI.Name = idStr; + auto& parent = dynamic_cast(*dest); + NI.Prefix = std::move(parent.Name); + NTI.Name = NI; + dest = NTI; + } + + // Look for the next "::" + char const* compStart = ptr_; + skipWhitespace(); + if (!parseLiteral("::")) + { + ptr_ = compStart; + break; + } + skipWhitespace(); + } + if (!dest) + { + ptr_ = start; + return false; + } + if (allowTemplateArguments) + { + char const* templateStart = ptr_; + skipWhitespace(); + if (peek('<')) + { + if (!parseTemplateArguments()) + { + ptr_ = start; + return false; + } + } + else + { + ptr_ = templateStart; + } + } + return true; + } + + enum declarator_property : int { + // abstract-declarator - it does not need to be named + abstract = 1, + // An internal declarator is any declarator other + // than a reference declarator (there are no pointers + // or references to references). + internal_declarator = 2, + // noptr-declarator - any valid declarator, but if it begins with *, + // &, or &&, it has to be surrounded by parentheses. + no_ptr_declarator = 4, + }; + + bool + parseAbstractDeclarator(Polymorphic& dest) + { + return parseDeclarator(dest); + } + + template + bool + parseDeclarator(Polymorphic& dest) + { + char const *start = ptr_; + if (!parseDeclaratorOrNoPtrDeclarator(dest)) + { + // Maybe a valid declarator is parenthesized + if (peek('(', ' ')) + { + skipWhitespace(); + MRDOCS_ASSERT(parseLiteral('(')); + if (!parseDeclarator(dest)) + { + ptr_ = start; + return false; + } + skipWhitespace(); + if (!parseLiteral(')')) + { + setError("expected ')'"); + ptr_ = start; + return false; + } + return true; + } + // We expected a valid declarator either as the + // complete declarator or as the noptr-declarator + // for an array or function + setError("expected declarator"); + ptr_ = start; + return false; + } + if (!dest) + { + setError("no type defined by specifiers and declarator"); + ptr_ = start; + return false; + } + std::size_t suffixLevel = 0; + auto isNoPtrDeclarator = [&dest, &suffixLevel, this]() { + if (suffixLevel == 0) + { + if (dest->isLValueReference() || dest->isRValueReference() || dest->isPointer()) + { + return peekBack(')', ' '); + } + // Other types don't need to be surrounded by parentheses + return true; + } + // At other levels, we don't need to check for parentheses + return true; + }; + while ( + peekAny({'[', '('}, ' ') && + isNoPtrDeclarator()) + { + // The function return type is the type from the specifiers + // For instance, in "int (*)", we have a pointer to int. + // But in "int (*)()", where "int (*)" is the noptr-declarator, + // the pointer wraps the function type: a pointer to function + // and not a function to pointer. + // So parsing "int (*)" gives us a pointer to int (the content + // of dest), but parsing the function should invert this logic, + // the pointer points to the function and the function returns int. + // The same logic other elements that have inner types (pointers, + // arrays, and references). + // The current inner type of dest is the function return type. + auto inner = innerType(*dest); + // The more suffixes we have, the more levels of inner types + // the suffix affects. + // For instance, in "int (*)[3][6]", we have a pointer to an + // array of 3 arrays of 6 ints. + std::size_t curSuffixLevel = suffixLevel; + while (curSuffixLevel > 0 && inner && inner->get()) + { + auto& ref = inner->get(); + inner = innerType(*ref); + --curSuffixLevel; + } + char const* parenStart = ptr_; + if (!parseArrayOrFunctionDeclaratorSuffix( + inner ? inner->get() : dest)) + { + setError(parenStart, "expected declarator"); + ptr_ = start; + return false; + } + ++suffixLevel; + } + return true; + } + + template + bool + parseDeclaratorOrNoPtrDeclarator(Polymorphic& dest) + { + static constexpr bool isAbstractDeclarator = static_cast(declarator_type & abstract); + static constexpr bool isInternalDeclarator = static_cast(declarator_type & internal_declarator); + + // https://en.cppreference.com/w/cpp/language/declarations#Declarators + char const* start = ptr_; + + if (!dest) + { + setError("expected parameter type for '...'"); + ptr_ = start; + return false; + } + + // The declarator cannot be another specifier keyword + // that could also be a declarator + if (peek(isIdentifierContinuation) + && parseAnyKeyword( + { "char", + "char8_t", + "char16_t", + "char32_t", + "wchar_t", + "bool", + "int", + "float", + "double", + "void", + "auto", + "decltype" })) + { + setError("expected declarator, not another specifier"); + ptr_ = start; + return false; + } + + // Declarators might be surrounded by an arbitrary + // number of parentheses. We need to keep track + // of them. + skipWhitespace(); + std::size_t parenDepth = 0; + while (parseLiteral("(")) + { + ++parenDepth; + skipWhitespace(); + } + + // At the end, we need to consume the closing parentheses + // in reverse order. + auto const parseClosingParens = [&]() { + while (parenDepth > 0) + { + skipWhitespace(); + if (!parseLiteral(")")) + { + setError("expected ')'"); + ptr_ = start; + return false; + } + --parenDepth; + } + return true; + }; + + // https://en.cppreference.com/w/cpp/language/declarations#Declarators + // declarator - one of the following: + // (1) The name that is declared: + // unqualified-id attr (optional) + // https://en.cppreference.com/w/cpp/language/identifiers#Names + char const* idStart = ptr_; + if (parseIdentifier(false)) + { + if (parenDepth != 0 && + peek(',', ' ')) + { + // This is a function parameter declaration + // and this identifier is actually the type + // of the first parameter + ptr_ = idStart; + MRDOCS_ASSERT(rewindUntil('(')); + parenDepth--; + if (parseFunctionDeclaratorSuffix(dest)) + { + return parseClosingParens(); + } + } + else if (!peek(':', ' ')) + { + // We ignore the name and just return true + // The current parameter type does not change + return parseClosingParens(); + } + // id is qualified-id, so fall through to the next cases + ptr_ = idStart; + } + + // (2) A declarator that uses a qualified identifier (qualified-id) + // defines or redeclares a previously declared namespace member + // or class member. + // qualified-id attr (optional) + // We do not implement this case for function parameters. + + // (3) Parameter pack, only appears in parameter declarations. + // ... identifier attr (optional) + // https://en.cppreference.com/w/cpp/language/pack + if (parseLiteral("...")) + { + skipWhitespace(); + parseIdentifier(false); + dest->IsPackExpansion = true; + return parseClosingParens(); + } + + // (4) Pointer declarator: the declaration `S * D`; declares declarator + // `D` as a pointer to the type determined by decl-specifier-seq `S`. + // * attr (optional) cv (optional) declarator + // https://en.cppreference.com/w/cpp/language/pointer + if (parseLiteral("*")) + { + if (dest->isLValueReference() || + dest->isRValueReference()) + { + setError("pointer to reference not allowed"); + ptr_ = start; + return false; + } + + // Change current type to pointer type + PointerTypeInfo PTI; + PTI.PointeeType = std::move(dest); + dest = PTI; + + skipWhitespace(); + // cv is a sequence of const and volatile qualifiers, + // where either qualifier may appear at most once + // in the sequence. + parseCV(dest->IsConst, dest->IsVolatile); + // Parse the next declarator, potentially wrapping + // the destination type in another type + // If this declarator is abstract, the pointer + // declarator is also abstract. + if (constexpr int nextDeclaratorType = (declarator_type & abstract) + | internal_declarator; + !parseDeclarator(dest)) + { + setError("expected declarator after pointer"); + ptr_ = start; + return false; + } + return parseClosingParens(); + } + + // (5) Pointer to member declaration: the declaration `S C::* D`; + // declares `D` as a pointer to member of `C` of type determined + // by decl-specifier-seq `S`. nested-name-specifier is a + // sequence of names and scope resolution operators :: + // nested-name-specifier * attr (optional) cv (optional) declarator + // https://en.cppreference.com/w/cpp/language/pointer + if (parseNestedNameSpecifier()) + { + char const* NNSEnd = ptr_; + skipWhitespace(); + if (!parseLiteral("*")) + { + ptr_ = start; + return false; + } + + if constexpr (isInternalDeclarator) + { + setError("pointer to member declarator not allowed in this context"); + ptr_ = start; + return false; + } + + // Assemble the parent type for the NNS + NamedTypeInfo ParentType; + auto NNSString = std::string_view(start, NNSEnd - start); + NameInfo NNS; + auto const NNSRange = llvm::split(NNSString, "::"); + MRDOCS_ASSERT(!NNSRange.empty()); + auto NNSIt = NNSRange.begin(); + std::string_view unqualID = *NNSIt; + NNS.Name = std::string(unqualID); + ++NNSIt; + while (NNSIt != NNSRange.end()) + { + unqualID = *NNSIt; + if (unqualID.empty()) + { + break; + } + NameInfo NewNNS; + NewNNS.Name = std::string(unqualID); + NewNNS.Prefix = std::move(NNS); + NNS = NewNNS; + ++NNSIt; + } + ParentType.Name = std::move(NNS); + + // Change current type to member pointer type + MemberPointerTypeInfo MPTI; + MPTI.PointeeType = std::move(dest); + MPTI.ParentType = std::move(ParentType); + dest = MPTI; + + skipWhitespace(); + // cv is a sequence of const and volatile qualifiers, + // where either qualifier may appear at most once + // in the sequence. + parseCV(dest->IsConst, dest->IsVolatile); + parseIdentifier(false); + // We ignore the name and just return true + return parseClosingParens(); + } + + // (6) Lvalue reference declarator: the declaration `S & D`; declares + // `D` as an lvalue reference to the type determined by + // decl-specifier-seq `S`. + // & attr (optional) declarator + // https://en.cppreference.com/w/cpp/language/reference + if (parseLiteral("&")) + { + if constexpr (isInternalDeclarator) + { + setError("lvalue reference to pointer not allowed"); + ptr_ = start; + return false; + } + + skipWhitespace(); + + // (7) Rvalue reference declarator: the declaration `S && D`; + // declares D as an rvalue reference to the type determined + // by decl-specifier-seq `S`. + // && attr (optional) declarator + + // Change current type to reference type + if (bool const isRValue = parseLiteral("&"); + !isRValue) + { + LValueReferenceTypeInfo RTI; + RTI.PointeeType = std::move(dest); + dest = RTI; + } + else + { + RValueReferenceTypeInfo RTI; + RTI.PointeeType = std::move(dest); + dest = RTI; + } + + skipWhitespace(); + + // Parse the next declarator, potentially wrapping + // the destination type in another type + static constexpr int nextDeclaratorType + = (declarator_type & abstract) | internal_declarator; + if (!parseDeclarator(dest)) + { + setError("expected declarator after reference"); + ptr_ = start; + return false; + } + + return parseClosingParens(); + } + + // (8-9) Array and function declarators are handled in a separate + // function + parenDepth = 0; + ptr_ = start; + if (parseArrayOrFunctionDeclaratorSuffix(dest)) + { + return true; + } + + // (10) An abstract declarator can also be an empty string, which + // is equivalent to unnamed (1) unqualified-id. + if constexpr (isAbstractDeclarator) + { + return parseClosingParens(); + } + else + { + setError("expected a concrete declarator"); + ptr_ = start; + return false; + } + } + + // This function assumes the noptr-declarator prefix + // already parsed. Otherwise, we assume the noptr-declarator + // is empty. + template + bool + parseArrayOrFunctionDeclaratorSuffix(Polymorphic& dest) + { + char const* start = ptr_; + + // (8) Array declarator. noptr-declarator any valid declarator, but + // if it begins with *, &, or &&, it has to be surrounded by + // parentheses. + // noptr-declarator [expr (optional)] attr (optional) + // https://en.cppreference.com/w/cpp/language/array + if (parseArrayDeclaratorSuffix(dest)) + { + return true; + } + ptr_ = start; + + // (9) Function declarator. noptr-declarator any valid declarator, + // but if it begins with *, &, or &&, it has to be surrounded by + // parentheses. It may end with the optional trailing return type. + // noptr-declarator ( parameter-list ) cv (optional) ref (optional) except (optional) attr (optional) + // https://en.cppreference.com/w/cpp/language/function + // https://en.cppreference.com/w/cpp/language/function#Function_type + if (parseFunctionDeclaratorSuffix(dest)) + { + return true; + } + + return false; + } + + template + bool + parseArrayDeclaratorSuffix(Polymorphic& dest) + { + char const* start = ptr_; + + // (8) Array declarator. noptr-declarator any valid declarator, but + // if it begins with *, &, or &&, it has to be surrounded by + // parentheses. + // noptr-declarator [expr (optional)] attr (optional) + // https://en.cppreference.com/w/cpp/language/array + if (parseLiteral("[")) + { + if constexpr (declarator_type & internal_declarator) + { + setError("pointer to array declarator requires noptr-declarator"); + ptr_ = start; + return false; + } + + // Change current type to array type + ArrayTypeInfo ATI; + ATI.ElementType = std::move(dest); + + // expr (optional) + char const* exprStart = ptr_; + skipWhitespace(); + + if (!parseLiteral("]")) + { + // Parse the array size + // Bounds.Value is an optional integer with the value + // Bounds.Written is the original string representation + // of the bounds + std::optional boundsValue = 0; + if (ConstantExprInfo Bounds; + parseInteger(boundsValue) && + peek(']', ' ')) + { + Bounds.Value = boundsValue; + Bounds.Written = std::string_view(exprStart, ptr_ - exprStart); + ATI.Bounds = Bounds; + if (!parseLiteral("]")) + { + ptr_ = start; + return false; + } + } + else + { + ptr_ = start; + // Parse everything up to the next + // closing bracket + if (!parseBalanced("[", "]")) + { + setError("expected ']' in array declarator"); + ptr_ = start; + return false; + } + auto expr = std::string_view(exprStart, ptr_ - exprStart - 1); + Bounds.Written = trim(expr); + ATI.Bounds = Bounds; + } + } + dest = std::move(ATI); + skipWhitespace(); + + // We ignore the name and just return true + return true; } + return false; + } + + template + bool + parseFunctionDeclaratorSuffix(Polymorphic& dest) + { + char const* start = ptr_; + + // (9) Function declarator. noptr-declarator any valid declarator, + // but if it begins with *, &, or &&, it has to be surrounded by + // parentheses. It may end with the optional trailing return type. + // noptr-declarator ( parameter-list ) cv (optional) ref (optional) except (optional) attr (optional) + // https://en.cppreference.com/w/cpp/language/function + // https://en.cppreference.com/w/cpp/language/function#Function_type + if (peek('(', ' ')) + { + if constexpr (declarator_type & internal_declarator) + { + setError("pointer to function declarator requires noptr-declarator"); + ptr_ = start; + return false; + } + + // Change current type to function type + // The function type as a parameter has the following members: + // FTI.ReturnType is the return type of the function + // FTI.ParamTypes is a list of parameter types + // FTI.RefQualifier is the reference qualifier + // FTI.ExceptionSpec is the exception specification + // FTI.IsVariadic is true if the function is variadic + // Parse the function parameters + ParsedFunctionSuffix function; + if (!parseFunctionSuffix(function)) + { + ptr_ = start; + return false; + } + FunctionTypeInfo FTI; + FTI.ReturnType = std::move(dest); + FTI.ParamTypes.insert( + FTI.ParamTypes.end(), + std::make_move_iterator(function.Params.begin()), + std::make_move_iterator(function.Params.end())); + FTI.ExceptionSpec = std::move(function.ExceptionSpec); + FTI.IsVariadic = function.IsVariadic; + dest = FTI; + return true; + } + + return false; + } + + bool + parseInteger(std::optional& dest) + { + if (!hasMore()) + { + return false; + } + if (!isDigit(*ptr_)) + { + return false; + } + std::uint64_t value = 0; + while (isDigit(*ptr_)) + { + value = value * 10 + (*ptr_ - '0'); + ++ptr_; + } + dest = value; + return true; + } + + bool + parseCV(bool& isConst, bool& isVolatile) { + char const* start = ptr_; + while (true) + { + skipWhitespace(); + bool matchedAny = false; + if (parseKeyword("const")) + { + if (isConst) + { + setError("multiple 'const' qualifiers"); + ptr_ = start; + return false; + } + isConst = true; + matchedAny = true; + } + if (parseKeyword("volatile")) + { + if (isVolatile) + { + setError("multiple 'volatile' qualifiers"); + ptr_ = start; + return false; + } + isVolatile = true; + matchedAny = true; + } + if (!matchedAny) + { + break; + } + } + return true; + } + + bool + parseNestedNameSpecifier() + { + // nested-name-specifier is a sequence of names and + // scope resolution operators :: + char const* start = ptr_; + parseLiteral("::"); + bool hasAnyIdentifier = false; + while (true) + { + if (parseIdentifier(false)) + { + hasAnyIdentifier = true; + } + else + { + if (hasAnyIdentifier) + { + return true; + } + ptr_ = start; + return false; + } + skipWhitespace(); + if (!parseLiteral("::")) + { + setError("expected '::' in nested name specifier"); + ptr_ = start; + return false; + } + skipWhitespace(); + } + } + + bool + parseFunctionQualifiers(ParsedFunctionSuffix& dest) + { + // https://en.cppreference.com/w/cpp/language/function + char const* start = ptr_; + + if (auto* destMF = dynamic_cast(&dest)) + { + // Parse cv: + // const/volatile qualification, only allowed in non-static member + // function declarations + if (!parseCV(destMF->IsConst, destMF->IsVolatile)) + { + setError("expected cv qualifiers"); + ptr_ = start; + return false; + } + } + + // Parse ref: + // ref-qualification, only allowed in non-static member function + // declarations + if (auto* destMF = dynamic_cast(&dest)) + { + skipWhitespace(); + if (parseLiteral("&")) + { + destMF->Kind = ReferenceKind::LValue; + skipWhitespace(); + if (parseLiteral("&")) + { + destMF->Kind = ReferenceKind::RValue; + skipWhitespace(); + } + } + } + + // Parse except: + // dynamic exception specification, dynamic exception specification + // or noexcept specification, noexcept specification + // https://en.cppreference.com/w/cpp/language/noexcept_spec + if (parseKeyword("noexcept")) + { + dest.ExceptionSpec.Implicit = false; + skipWhitespace(); + char const *noexceptStart = ptr_; + if (parseBalanced("(", ")")) + { + char const* noexceptEnd = ptr_; + std::string_view const expression( + noexceptStart + 1, + noexceptEnd - noexceptStart - 2); + dest.ExceptionSpec.Operand = trim(expression); + dest.ExceptionSpec.Kind = + dest.ExceptionSpec.Operand == "true" ? + NoexceptKind::True : + dest.ExceptionSpec.Operand == "false" ? + NoexceptKind::False : + NoexceptKind::Dependent; + } + } + else if (parseKeyword("throw")) + { + skipWhitespace(); + if (!parseLiteral("(")) + { + setError("expected '(' in 'throw' exception specification"); + ptr_ = start; + return false; + } + skipWhitespace(); + if (!parseLiteral(")")) + { + setError("expected ')' for empty 'throw' exception specification"); + ptr_ = start; + return false; + } + dest.ExceptionSpec.Implicit = false; + dest.ExceptionSpec.Operand = "true"; + dest.ExceptionSpec.Kind = NoexceptKind::True; + } + return true; } diff --git a/src/lib/AST/ParseRef.hpp b/src/lib/AST/ParseRef.hpp index c82e9806fe..153c9e6fbc 100644 --- a/src/lib/AST/ParseRef.hpp +++ b/src/lib/AST/ParseRef.hpp @@ -8,21 +8,31 @@ // Official repository: https://github.com/cppalliance/mrdocs // -#ifndef MRDOCS_LIB_PARSELOOKUPNAME_HPP -#define MRDOCS_LIB_PARSELOOKUPNAME_HPP +#ifndef MRDOCS_LIB_PARSEREF_HPP +#define MRDOCS_LIB_PARSEREF_HPP -#include -#include #include +#include +#include #include #include namespace clang::mrdocs { struct ParsedRefComponent { + // Component name std::string_view Name; + + // If not empty, this is a specialization + llvm::SmallVector, 8> TemplateArguments; + + // If not None, this is an operator + // Only the last component can be an operator OperatorKind Operator; - llvm::SmallVector TemplateArguments; + + // If not empty, this is a conversion operator + // Only the last component can be a conversion operator + Polymorphic ConversionType; constexpr bool @@ -31,6 +41,13 @@ struct ParsedRefComponent { return Operator != OperatorKind::None; } + constexpr + bool + isConversion() const + { + return static_cast(ConversionType); + } + bool isSpecialization() const { @@ -40,10 +57,15 @@ struct ParsedRefComponent { struct ParsedRef { bool IsFullyQualified = false; - llvm::SmallVector Components; - llvm::SmallVector FunctionParameters; + llvm::SmallVector Components; + + // The following are populated when the last element is a function + llvm::SmallVector, 8> FunctionParameters; + bool IsVariadic = false; + bool IsExplicitObjectMemberFunction = false; ReferenceKind Kind = ReferenceKind::None; bool IsConst = false; + bool IsVolatile = false; }; Expected diff --git a/src/lib/AST/TypeInfoBuilder.cpp b/src/lib/AST/TypeInfoBuilder.cpp index 5070ed1175..8f8bb71a7a 100644 --- a/src/lib/AST/TypeInfoBuilder.cpp +++ b/src/lib/AST/TypeInfoBuilder.cpp @@ -20,7 +20,8 @@ TypeInfoBuilder:: buildPointer(PointerType const*, unsigned quals) { PointerTypeInfo I; - I.CVQualifiers = toQualifierKind(quals); + I.IsConst = quals & Qualifiers::Const; + I.IsVolatile = quals & Qualifiers::Volatile; *Inner = std::move(I); Inner = &get(*Inner).PointeeType; } @@ -50,7 +51,8 @@ buildMemberPointer( unsigned const quals) { MemberPointerTypeInfo I; - I.CVQualifiers = toQualifierKind(quals); + I.IsConst = quals & Qualifiers::Const; + I.IsVolatile = quals & Qualifiers::Volatile; // do not set NNS because the parent type is *not* // a nested-name-specifier which qualifies the pointee type I.ParentType = getASTVisitor().toTypeInfo( @@ -88,8 +90,9 @@ populate(FunctionType const* T) } I.RefQualifier = toReferenceKind( FPT->getRefQualifier()); - I.CVQualifiers = toQualifierKind( - FPT->getMethodQuals().getFastQualifiers()); + unsigned const quals = FPT->getMethodQuals().getFastQualifiers(); + I.IsConst = quals & Qualifiers::Const; + I.IsVolatile = quals & Qualifiers::Volatile; I.IsVariadic = FPT->isVariadic(); getASTVisitor().populate(I.ExceptionSpec, FPT); *Inner = std::move(I); @@ -106,7 +109,8 @@ buildDecltype( DecltypeTypeInfo I;; getASTVisitor().populate( I.Operand, T->getUnderlyingExpr()); - I.CVQualifiers = toQualifierKind(quals); + I.IsConst = quals & Qualifiers::Const; + I.IsVolatile = quals & Qualifiers::Volatile; I.Constraints = this->Constraints; *Inner = std::move(I); Result->IsPackExpansion = pack; @@ -120,7 +124,8 @@ buildAuto( bool const pack) { AutoTypeInfo I; - I.CVQualifiers = toQualifierKind(quals); + I.IsConst = quals & Qualifiers::Const; + I.IsVolatile = quals & Qualifiers::Volatile; I.Keyword = convertToAutoKind(T->getKeyword()); if(T->isConstrained()) { @@ -149,7 +154,8 @@ buildTerminal( bool pack) { NamedTypeInfo TI; - TI.CVQualifiers = toQualifierKind(quals); + TI.IsConst = quals & Qualifiers::Const; + TI.IsVolatile = quals & Qualifiers::Volatile; TI.Name = MakePolymorphic(); TI.Name->Name = getASTVisitor().toString(T); TI.Name->Prefix = getASTVisitor().toNameInfo(NNS); @@ -169,7 +175,8 @@ buildTerminal( bool pack) { NamedTypeInfo I; - I.CVQualifiers = toQualifierKind(quals); + I.IsConst = quals & Qualifiers::Const; + I.IsVolatile = quals & Qualifiers::Volatile; if (TArgs) { @@ -217,7 +224,8 @@ buildTerminal( MRDOCS_SYMBOL_TRACE(ID, getASTVisitor().context_); NamedTypeInfo TI; - TI.CVQualifiers = toQualifierKind(quals); + TI.IsConst = quals & Qualifiers::Const; + TI.IsVolatile = quals & Qualifiers::Volatile; auto populateNameInfo = [&](NameInfo& Name, NamedDecl* D) { diff --git a/src/lib/Gen/xml/CXXTags.hpp b/src/lib/Gen/xml/CXXTags.hpp index cd6e75c69a..ac20601141 100644 --- a/src/lib/Gen/xml/CXXTags.hpp +++ b/src/lib/Gen/xml/CXXTags.hpp @@ -154,10 +154,22 @@ writeType( } } - if constexpr(requires { t.CVQualifiers; }) + std::string cvQualifiers; + if (t.IsConst) { - if(t.CVQualifiers != QualifierKind::None) - attrs.push({"cv-qualifiers", toString(t.CVQualifiers)}); + cvQualifiers += "const"; + } + if (t.IsVolatile) + { + if (!cvQualifiers.empty()) + { + cvQualifiers += ' '; + } + cvQualifiers += "volatile"; + } + if (!cvQualifiers.empty()) + { + attrs.push({"cv-qualifiers", cvQualifiers}); } if constexpr(T::isArray()) @@ -240,8 +252,10 @@ writeType( inline void writeReturnType(TypeInfo const& I, XMLTags& tags) { // KRYSTIAN NOTE: we don't *have* to do this... - if(toString(I) == "void") + if (toString(I) == "void") + { return; + } tags.open(returnTagName); writeType(I, tags); tags.close(returnTagName); @@ -250,8 +264,8 @@ inline void writeReturnType(TypeInfo const& I, XMLTags& tags) inline void writeParam(Param const& P, XMLTags& tags) { tags.open(paramTagName, { - { "name", P.Name, ! P.Name.empty() }, - { "default", P.Default, ! P.Default.empty() }, + { "name", *P.Name, P.Name.has_value() }, + { "default", *P.Default, P.Default.has_value() }, }); writeType(*P.Type, tags); tags.close(paramTagName); diff --git a/src/lib/Lib/TagfileWriter.cpp b/src/lib/Lib/TagfileWriter.cpp index 7ca098e3ea..ac398bd67f 100644 --- a/src/lib/Lib/TagfileWriter.cpp +++ b/src/lib/Lib/TagfileWriter.cpp @@ -212,7 +212,7 @@ writeFunctionMember(FunctionInfo const& I) { arglist += toString(*J.Type); arglist += " "; - arglist += J.Name; + arglist += *J.Name; arglist += ", "; } if (arglist.size() > 2) { diff --git a/src/lib/Metadata/Finalizers/JavadocFinalizer.cpp b/src/lib/Metadata/Finalizers/JavadocFinalizer.cpp index 9dc1fcde68..82ec5ec3ef 100644 --- a/src/lib/Metadata/Finalizers/JavadocFinalizer.cpp +++ b/src/lib/Metadata/Finalizers/JavadocFinalizer.cpp @@ -378,7 +378,7 @@ copyBriefAndDetails(Javadoc& javadoc) if (std::ranges::find_if(FI.Params, [&srcParam](Param const& destParam) { - return srcParam.name == destParam.Name; + return srcParam.name == *destParam.Name; }) == FI.Params.end()) { // param does not exist in the destination function @@ -604,7 +604,7 @@ void JavadocFinalizer:: finalize(TypeInfo& type) { - finalize(type.innerType()); + finalize(innerTypePtr(type)); visit(type, [this](Ty& T) { @@ -1234,7 +1234,8 @@ warnParamErrors(FunctionInfo const& I) // Check for documented parameters that don't exist in the function auto paramNames = std::views::transform(I.Params, &Param::Name) | - std::views::filter([](std::string_view const& name) { return !name.empty(); }); + std::views::filter([](Optional const& name) { return static_cast(name); }) | + std::views::transform([](Optional const& name) -> std::string_view { return *name; }); for (std::string_view javadocParamName: javadocParamNames) { if (std::ranges::find(paramNames, javadocParamName) == paramNames.end()) @@ -1272,6 +1273,7 @@ warnNoParamDocs(FunctionInfo const& I) auto javadocParamNames = getJavadocParamNames(*I.javadoc); auto paramNames = std::views::transform(I.Params, &Param::Name) | + std::views::transform([](Optional const& name) -> std::string_view { return *name; }) | std::views::filter([](std::string_view const& name) { return !name.empty(); }); for (auto const& paramName: paramNames) { @@ -1361,7 +1363,7 @@ warnUnnamedParams(FunctionInfo const& I) for (std::size_t i = 0; i < I.Params.size(); ++i) { - if (I.Params[i].Name.empty()) + if (!I.Params[i].Name) { this->warn( *getPrimaryLocation(I), diff --git a/src/lib/Metadata/Info/Function.cpp b/src/lib/Metadata/Info/Function.cpp index d54f96d667..561d942101 100644 --- a/src/lib/Metadata/Info/Function.cpp +++ b/src/lib/Metadata/Info/Function.cpp @@ -195,9 +195,9 @@ tag_invoke( Param const& p, DomCorpus const*) { - io.map("name", dom::stringOrNull(p.Name)); + io.map("name", dom::stringOrNull(*p.Name)); io.map("type", p.Type); - io.map("default", dom::stringOrNull(p.Default)); + io.map("default", dom::stringOrNull(*p.Default)); } void diff --git a/src/lib/Metadata/Type.cpp b/src/lib/Metadata/Type.cpp index 24b83315e1..2397071b7e 100644 --- a/src/lib/Metadata/Type.cpp +++ b/src/lib/Metadata/Type.cpp @@ -147,7 +147,7 @@ operator()( auto& write, std::bool_constant) const { - if (TypeInfo const* inner = t.cInnerType()) + if (TypeInfo const* inner = innerTypePtr(t)) { visit(*inner, *this, write, std::bool_constant(NamedTypeInfo const& other) const { return br; } - if (auto const br = CVQualifiers <=> other.CVQualifiers; + if (auto const br = IsConst <=> other.IsConst; + !std::is_eq(br)) + { + return br; + } + if (auto const br = IsVolatile <=> other.IsVolatile; !std::is_eq(br)) { return br; @@ -364,8 +388,8 @@ operator<=>(FunctionTypeInfo const& other) const { return r; } } - return std::tie(CVQualifiers, RefQualifier, ExceptionSpec, IsVariadic) <=> - std::tie(other.CVQualifiers, other.RefQualifier, other.ExceptionSpec, other.IsVariadic); + return std::tie(IsConst, IsVolatile, RefQualifier, ExceptionSpec, IsVariadic) <=> + std::tie(other.IsConst, IsVolatile, other.RefQualifier, other.ExceptionSpec, other.IsVariadic); } @@ -418,10 +442,8 @@ tag_invoke( io.map("constraint", t.Constraint); } } - if constexpr(requires { t.CVQualifiers; }) - { - io.map("cv-qualifiers", t.CVQualifiers); - } + io.map("is-const", t.IsConst); + io.map("is-volatile", t.IsVolatile); if constexpr(requires { t.ParentType; }) { io.map("parent-type", t.ParentType); @@ -475,5 +497,118 @@ operator<=>(Polymorphic const& lhs, Polymorphic const& rhs) : std::strong_ordering::greater; } +namespace { +template < + class TypeInfoTy, + bool isMutable = !std::is_const_v>, + class Ptr = std::conditional_t*, Polymorphic const*>, + class Ref = std::conditional_t>, std::reference_wrapper const>>> +requires std::same_as, TypeInfo> +std::optional +innerTypeImpl(TypeInfoTy&& TI) noexcept +{ + // Get a pointer to the inner type + Ptr innerPtr = visit(TI, [](T& t) -> Ptr + { + if constexpr(requires { t.PointeeType; }) + { + return &t.PointeeType; + } + if constexpr(requires { t.ElementType; }) + { + return &t.ElementType; + } + if constexpr(requires { t.ReturnType; }) + { + return &t.ReturnType; + } + return nullptr; + }); + // Convert pointer to reference wrapper if possible + if (innerPtr) + { + if constexpr (isMutable) + { + return std::ref(*innerPtr); + } + else + { + return std::cref(*innerPtr); + } + } + return std::nullopt; +} + +template +auto +innerTypePtrImpl(TypeInfoTy&& TI) noexcept +{ + auto res = innerTypeImpl(TI); + if (res) + { + auto& ref = res->get(); + return &*ref; + } + return decltype(&*res->get())(nullptr); +} + +template +auto +innermostTypeImpl(TypeInfoTy&& TI) noexcept +{ + auto inner = innerTypeImpl(TI); + while (inner) + { + auto& ref = inner->get(); + if (!ref) + { + return inner; + } + if (ref->isNamed()) + { + return inner; + } + inner = innerTypeImpl(*ref); + } + return inner; +} + +} + +std::optional const>> +innerType(TypeInfo const& TI) noexcept +{ + return innerTypeImpl(TI); +} + +std::optional>> +innerType(TypeInfo& TI) noexcept +{ + return innerTypeImpl(TI); +} + +TypeInfo const* +innerTypePtr(TypeInfo const& TI) noexcept +{ + return innerTypePtrImpl(TI); +} + +TypeInfo* +innerTypePtr(TypeInfo& TI) noexcept +{ + return innerTypePtrImpl(TI); +} + +std::optional const>> +innermostType(TypeInfo const& TI) noexcept +{ + return innermostTypeImpl(TI); +} + +std::optional>> +innermostType(TypeInfo& TI) noexcept +{ + return innermostTypeImpl(TI); +} } // clang::mrdocs diff --git a/src/test/lib/AST/ParseRef.cpp b/src/test/lib/AST/ParseRef.cpp new file mode 100644 index 0000000000..ff39208943 --- /dev/null +++ b/src/test/lib/AST/ParseRef.cpp @@ -0,0 +1,313 @@ +// +// Copyright (c) 2023 alandefreitas (alandefreitas@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// + +#include +#include +#include +#include "lib/AST/ParseRef.hpp" + +namespace clang::mrdocs { + +struct ParseRef_test +{ + +#define ok(str) BOOST_TEST(parseRef(str).has_value()) +#define fail(str) BOOST_TEST(!parseRef(str).has_value()) + +void +testComponents() +{ + fail(""); + ok("a"); + ok(" a"); + ok(" a "); + ok("::a"); + ok("a::b"); + ok("a::b::c"); + ok("a::~b"); + ok("a:: ~ b"); + ok("a::operator+"); + ok("a::operator()"); + ok("a:: operator () "); + fail("a:: operator ( ) "); + ok("a::operator bool"); + fail("a::operator bool::c"); + fail("a::operator+::c"); +} + +void +testFunctionParameters() +{ + ok("f()"); + ok("f ( ) "); + ok("f(void)"); + fail("f(void, void)"); + fail("f(int, void)"); + ok("f(...)"); + ok("f(int)"); + ok("f(this T)"); + fail("f(int, this T)"); + ok("f(int, int)"); + fail("f(,)"); +} + +void +testParameterDeclarationSpecifiers() +{ + // cv specifiers + { + ok("f(const int)"); + fail("f(const const int)"); + ok("f(volatile int)"); + fail("f(volatile volatile int)"); + ok("f(const volatile int)"); + } + + // signed/unsigned specifiers + { + ok("f(signed int)"); + ok("f(signed char)"); + fail("f(signed signed int)"); + ok("f(unsigned int)"); + fail("f(unsigned unsigned int)"); + fail("f(signed unsigned int)"); + ok("f(signed)"); + ok("f(unsigned)"); + fail("f(signed A)"); + fail("f(unsigned A)"); + fail("f(signed double)"); + fail("f(unsigned double)"); + fail("f(signed auto)"); + fail("f(unsigned auto)"); + } + + // short/long specifiers + { + ok("f(short int)"); + fail("f(short short int)"); + ok("f(long int)"); + ok("f(long long int)"); + fail("f(long long long int)"); + fail("f(long short int)"); + ok("f(short)"); + ok("f(long)"); + fail("f(short A)"); + fail("f(long A)"); + fail("f(short double)"); + ok("f(long double)"); + fail("f(short auto)"); + fail("f(long auto)"); + } + + // auto + { + ok("f(auto)"); + ok("f(const auto)"); + ok("f(volatile auto)"); + ok("f(const volatile auto)"); + ok("f(auto const)"); + fail("f(auto int)"); + fail("f(auto auto)"); + fail("f(auto decltype(auto))"); + } + + // decltype(auto) + { + ok("f(decltype(auto))"); + fail("f(decltype(auto) int)"); + fail("f(decltype(auto) auto)"); + } + + // decltype(expression) + { + ok("f(decltype(1))"); + ok("f(decltype(1 + 1))"); + ok("f(decltype((1) + 2 * (3)))"); + fail("f(decltype(1 + 1) int)"); + fail("f(decltype(1 + 1) auto)"); + } + + // elaborated type specifier + // typename specifier + { + ok("f(class A)"); + ok("f(class A::B)"); + ok("f(struct A)"); + ok("f(union A)"); + ok("f(typename A)"); + ok("f(enum A)"); + ok("f(enum A::B)"); + fail("f(class A::B int)"); + fail("f(class A::B auto)"); + } +} + +void +testParameterDeclarators() +{ + // unqualified-id + { + ok("f(int x)"); + ok("f(A x)"); + ok("f(A (x))"); + ok("f(A ((x)))"); + ok("f(A ( ((x ) ) ) )"); + } + + // ... identifier + { + ok("f(auto...)"); + ok("f(Args... args)"); + } + + // * attr (optional) cv (optional) declarator + { + ok("f(A* ptr)"); + ok("f(A *ptr)"); + ok("f(A * ptr)"); + ok("f(A* const ptr)"); + ok("f(A* volatile ptr)"); + ok("f(A* const volatile ptr)"); + ok("f(A* const volatile *ptr)"); + ok("f(A* const volatile * ptr)"); + ok("f(A* const volatile * const ptr)"); + ok("f(A* const volatile * const *ptr)"); + ok("f(A* const volatile * const * ptr)"); + // internal declarators + ok("f(A*ptr)"); + ok("f(A**ptr)"); + ok("f(Args*...ptr)"); + fail("f(A*&ptr)"); + fail("f(A*&&ptr)"); + fail("f(A* C::* ptr)"); + fail("f(A*[] ptr)"); + fail("f(A*() ptr)"); + } + + // nested-name-specifier * attr (optional) cv (optional) declarator + { + ok("f(S C::* D)"); + ok("f(S C::D::* E)"); + // invalid internal declarators + fail("f(S C::** D)"); + fail("f(S C::*& D)"); + fail("f(S C::*&& D)"); + } + + // & attr (optional) declarator + { + ok("f(A& x)"); + ok("f(const A& x)"); + ok("f(A const& x)"); + ok("f(A const&... x)"); + // invalid internal declarators + fail("f(A&* x)"); + fail("f(A&&& x)"); + fail("f(A&[] x)"); + fail("f(A&() x)"); + } + + // && attr (optional) declarator + { + ok("f(A&& x)"); + ok("f(const A&& x)"); + ok("f(A const&& x)"); + ok("f(A const&&... x)"); + // invalid internal declarators + fail("f(A&&* x)"); + fail("f(A&&&& x)"); + fail("f(A&&[] x)"); + fail("f(A&&() x)"); + } + + // noptr-declarator [ expr (optional) ] attr (optional) + { + ok("f(A[])"); + ok("f(A x[])"); + ok("f(A x[][])"); + ok("f(A [64])"); + ok("f(A x[64])"); + ok("f(A x[64][64])"); + ok("f(A x[1+2])"); + ok("f(A x[b[2]+c[4]])"); + ok("f(int (*p)[3])"); + ok("f(int (&a)[3])"); + ok("f(int (&a)[3][6])"); + ok("f(int (&&x)[3][6])"); + } + + // noptr-declarator ( parameter-list ) cv (optional) ref (optional) except (optional) attr (optional) + { + ok("f(A())"); + ok("f(A (A))"); // -> identifier + ok("f(A (int, A))"); + ok("f(A (int, A)) noexcept"); + ok("f(A ((int, A))) noexcept"); + fail("f(A fn((int, A))) noexcept"); + ok("f(A (fn(int, A))) noexcept"); + ok("f(A (fn(int, A))) noexcept(true)"); + ok("f(A (fn(int, A))) noexcept(2+2)"); + ok("f(A (fn(int, A))) noexcept((2+5)+(3+2))"); + ok("f(A (fn(int, A))) throw()"); + // noptr-declarator is pointer + ok("f(A (*fn)(int, A))"); + ok("f(A (*)(int, A))"); + ok("f(A (&)(int, A))"); + ok("f(A (&&)(int, A))"); + } +} + +void +testMainFunctionQualifiers() +{ + ok("f(int) const"); + ok("f(int) volatile"); + ok("f(int) &"); + ok("f(int) &&"); + ok("f(int) noexcept"); + ok("f(int) noexcept(true)"); + ok("f(int) noexcept(2+2)"); + ok("f(int) noexcept((2+5)+(3+2))"); + + fail("f(int) const const"); + ok("f(int) volatile const"); + ok("f(int) const &"); + ok("f(int) const &&"); + ok("f(int) const noexcept"); + + ok("f(int) const volatile"); + fail("f(int) volatile volatile"); + ok("f(int) volatile &"); + ok("f(int) volatile &&"); + ok("f(int) volatile noexcept"); + + ok("f(int) const &"); + ok("f(int) volatile &"); + ok("f(int) & noexcept"); + + ok("f(int) const &&"); + ok("f(int) volatile &&"); + ok("f(int) && noexcept"); +} + +void +run() +{ + testComponents(); + testFunctionParameters(); + testParameterDeclarationSpecifiers(); + testParameterDeclarators(); + testMainFunctionQualifiers(); +} + +}; + +TEST_SUITE( + ParseRef_test, + "clang.mrdocs.ParseRef"); + +} // clang::mrdocs