diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5869eb727c..f4b22f7789 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -536,38 +536,59 @@ jobs: trace-commands: true - name: Set up llvm-symbolizer - if: ${{ runner.os == 'macOS' }} + if: ${{ runner.os != 'Windows' }} run: | set -x - - # Step 1: Check if llvm-symbolizer is installed - if ! command -v llvm-symbolizer &> /dev/null; then - echo "llvm-symbolizer is not installed. Installing via Homebrew..." - # Step 2: Install llvm if not installed - if command -v brew &> /dev/null; then - brew install llvm - else - echo "Homebrew is not installed. Please install Homebrew first: https://brew.sh/" - exit 1 - fi - fi - - # Step 3: Ensure llvm-symbolizer is in your PATH - llvm_bin_path=$(brew --prefix)/opt/llvm/bin - PATH="$PATH:$llvm_bin_path" - LLVM_SYMBOLIZER_PATH=$(which llvm-symbolizer) - if [ -z "$LLVM_SYMBOLIZER_PATH" ]; then - echo "llvm-symbolizer installation failed or it's not in the PATH." - exit 1 + + if [[ $RUNNER_OS == 'macOS' ]]; then + # Step 1: Check if llvm-symbolizer is installed + if ! command -v llvm-symbolizer &> /dev/null; then + echo "llvm-symbolizer is not installed. Installing via Homebrew..." + # Step 2: Install llvm if not installed + if command -v brew &> /dev/null; then + brew install llvm + else + echo "Homebrew is not installed. Please install Homebrew first: https://brew.sh/" + exit 1 + fi + fi + + # Step 3: Ensure llvm-symbolizer is in your PATH + llvm_bin_path=$(brew --prefix)/opt/llvm/bin + PATH="$PATH:$llvm_bin_path" + LLVM_SYMBOLIZER_PATH=$(which llvm-symbolizer) + if [ -z "$LLVM_SYMBOLIZER_PATH" ]; then + echo "llvm-symbolizer installation failed or it's not in the PATH." + exit 1 + else + echo "llvm-symbolizer found at: $LLVM_SYMBOLIZER_PATH" + fi + elif [[ $RUNNER_OS == 'Linux' ]]; then + # Step 1: Check if llvm-symbolizer is installed + if ! command -v llvm-symbolizer &> /dev/null; then + echo "llvm-symbolizer is not installed. Installing via apt-get..." + apt-get update + apt-get install -y llvm + fi + + # Step 2: Ensure llvm-symbolizer is in your PATH + LLVM_SYMBOLIZER_PATH=$(which llvm-symbolizer) + if [ -z "$LLVM_SYMBOLIZER_PATH" ]; then + echo "llvm-symbolizer installation failed or it's not in the PATH." + exit 1 + else + echo "llvm-symbolizer found at: $LLVM_SYMBOLIZER_PATH" + fi else - echo "llvm-symbolizer found at: $LLVM_SYMBOLIZER_PATH" + echo "Unsupported OS: $RUNNER_OS" + exit 1 fi - + # Step 4: Export LLVM_SYMBOLIZER_PATH environment variable export LLVM_SYMBOLIZER_PATH="$LLVM_SYMBOLIZER_PATH" echo -e "LLVM_SYMBOLIZER_PATH=$LLVM_SYMBOLIZER_PATH" >> $GITHUB_ENV echo "Environment variable LLVM_SYMBOLIZER_PATH set to: $LLVM_SYMBOLIZER_PATH" - + - name: Generate Landing Page working-directory: docs/website run: | diff --git a/docs/modules/ROOT/pages/design-notes.adoc b/docs/modules/ROOT/pages/design-notes.adoc index b12c481e77..b26eb78eed 100644 --- a/docs/modules/ROOT/pages/design-notes.adoc +++ b/docs/modules/ROOT/pages/design-notes.adoc @@ -78,7 +78,8 @@ Here are a few constructs MrDocs attempts to understand and document automatical * Private APIs * Unspecified Return Types * Concepts -* Typedef / Aliases +* Typedefs +* Namespace Aliases * Constants * Automatic Related Types * Macros diff --git a/docs/modules/ROOT/pages/generators.adoc b/docs/modules/ROOT/pages/generators.adoc index 1198edc9f2..52cf001933 100644 --- a/docs/modules/ROOT/pages/generators.adoc +++ b/docs/modules/ROOT/pages/generators.adoc @@ -499,7 +499,7 @@ When the symbol kind is `friend`, the symbol object has the following additional | The friend type. |=== -When the symbol kind is `alias`, the symbol object has the following additional properties: +When the symbol kind is `namespace-alias`, the symbol object has the following additional properties: |=== | Property | Type | Description @@ -527,14 +527,14 @@ When the symbol kind is `using`, the symbol object has the following additional | The qualifier of the using declaration. |=== -When the symbol kind is `enumerator`, the symbol object has the following additional properties: +When the symbol kind is `enum-constant`, the symbol object has the following additional properties: |=== | Property | Type | Description | `initializer` | `string` -| The initializer of the enumerator. +| The initializer of the enum-constant. |=== When the symbol kind is `guide`, the symbol object has the following additional properties: diff --git a/docs/modules/ROOT/partials/InfoNodes.inc b/docs/modules/ROOT/partials/InfoNodes.inc index cf13513878..857f35ce3c 100644 --- a/docs/modules/ROOT/partials/InfoNodes.inc +++ b/docs/modules/ROOT/partials/InfoNodes.inc @@ -73,7 +73,7 @@ INFO(Variable, Variables, VARIABLE, variable, variables, INFO(Field, Fields, FIELD, field, fields, The symbol is a field) INFO(Specialization, Specializations, SPECIALIZATION, specialization, specializations, The symbol is a template specialization) INFO(Friend, Friends, FRIEND, friend, friends, The symbol is a friend declaration) -INFO(Enumerator, Enumerators, ENUMERATOR, enumerator, enumerators, The symbol is an enumerator) +INFO(EnumConstant, EnumConstants, ENUM_CONSTANT, enumerator, enumerators, The symbol is an enumerator) INFO(Guide, Guides, GUIDE, guide, guides, The symbol is a deduction guide) INFO(Alias, Aliases, ALIAS, alias, aliases, The symbol is a namespace alias) INFO(Using, Usings, USING, using, usings, The symbol is a using declaration) diff --git a/docs/mrdocs.schema.json b/docs/mrdocs.schema.json index e18489464d..5a3645549b 100644 --- a/docs/mrdocs.schema.json +++ b/docs/mrdocs.schema.json @@ -111,7 +111,7 @@ }, "implementation-defined": { "default": [], - "description": "Namespaces for symbols rendered as \"implementation-defined\". Symbols in these namespaces are not extracted and are rendered as \"implementation-defined\" in the documentation. This option is used to exclude symbols from the documentation that are considered part of the private API of the project.", + "description": "Namespaces for symbols rendered as \"implementation-defined\". Symbols in these are rendered as \"implementation-defined\" in the documentation. This option is used to exclude symbols from the documentation that are considered part of the private API of the project. An \"implementation-defined\" symbol has no documentation page in the output. If any other symbol refers to it, the reference is rendered as \"implementation-defined\".", "items": { "type": "string" }, @@ -130,7 +130,7 @@ }, "inaccessible-members": { "default": "always", - "description": "Determine whether inaccessible members should be extracted. When set to `always`, inaccessible members are always extracted. When set to `dependency`, inaccessible members are extracted only if they are referenced by the source code. When set to `never`, inaccessible members are never extracted.", + "description": "Determine whether inaccessible members, such as private members of records, should be extracted. When set to `always`, inaccessible members are always extracted. When set to `dependency`, inaccessible members are extracted only if they are referenced by the source code. When set to `never`, inaccessible members are never extracted.", "enum": [ "always", "dependency", @@ -193,7 +193,7 @@ }, "referenced-declarations": { "default": "dependency", - "description": "Determine whether external declarations should be extracted when they are referenced in the source code. When set to `always`, external declarations are always extracted. When set to `dependency`, external declarations are extracted only if they are referenced by the source code. When set to `never`, external declarations are never extracted.", + "description": "Determine whether external declarations should be extracted when they are referenced in the source code. If this option is not `never`, a second pass happens in the extraction process to extract dependencies in the Corpus. When set to `always`, external declarations are always extracted. When set to `dependency`, external declarations are extracted only if they are referenced by the source code. When set to `never`, external declarations are never extracted.", "enum": [ "always", "dependency", @@ -211,7 +211,7 @@ }, "see-below": { "default": [], - "description": "Namespaces for symbols rendered as \"see-below\". Symbols in these namespaces are not extracted and are rendered as \"see-below\" in the documentation. This option is used to exclude symbols from the documentation that are considered part of the private API of the project.", + "description": "Namespaces for symbols rendered as \"see-below\". Symbols in these namespaces are rendered as \"see-below\" in the documentation. This option is used to exclude details about symbols from the documentation that are considered part of the private API of the project. In the documentation page for this symbol, the synopsis of the implementation of a \"see-below\" symbol is rendered as \"see-below\". When the symbol is a scope (such as a namespace or record), its members are not listed. The rest of the documentation is rendered as usual.", "items": { "type": "string" }, diff --git a/docs/website/render.js b/docs/website/render.js index 3fb5b8aa3b..31952f65f2 100644 --- a/docs/website/render.js +++ b/docs/website/render.js @@ -46,6 +46,8 @@ if (!fs.existsSync(mrdocsExecutable)) { // Read panel snippet files and create documentation const absSnippetsDir = path.join(__dirname, 'snippets') for (let panel of data.panels) { + console.log(`Generating documentation for panel ${panel.source}`) + // Find source file const sourcePath = path.join(absSnippetsDir, panel.source) assert(sourcePath.endsWith('.cpp')) @@ -102,6 +104,9 @@ target_compile_features(${sourceBasename} PRIVATE cxx_std_23) // Delete these temporary files fs.rmSync(mrdocsOutput, {recursive: true}); fs.unlinkSync(cmakeListsPath); + + console.log(`Documentation generated successfully for panel ${panel.source}`) + console.log(`====================================`) } // Render the template with the data containing the snippet data diff --git a/docs/website/snippets/mrdocs.yml b/docs/website/snippets/mrdocs.yml index 0b1f4ad881..e7067b0574 100644 --- a/docs/website/snippets/mrdocs.yml +++ b/docs/website/snippets/mrdocs.yml @@ -1 +1,4 @@ source-root: . +input: + include: + - . diff --git a/include/mrdocs/Metadata.hpp b/include/mrdocs/Metadata.hpp index 69590866b3..55e8757f66 100644 --- a/include/mrdocs/Metadata.hpp +++ b/include/mrdocs/Metadata.hpp @@ -17,10 +17,9 @@ // All headers related to // metadata extracted from AST -#include #include #include -#include +#include #include #include #include @@ -31,10 +30,11 @@ #include #include #include +#include #include #include -#include #include +#include #include #include #include diff --git a/include/mrdocs/Metadata/Concept.hpp b/include/mrdocs/Metadata/Concept.hpp index b97dc171ce..abf7778e7c 100644 --- a/include/mrdocs/Metadata/Concept.hpp +++ b/include/mrdocs/Metadata/Concept.hpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace clang { namespace mrdocs { diff --git a/include/mrdocs/Metadata/Enumerator.hpp b/include/mrdocs/Metadata/EnumConstant.hpp similarity index 77% rename from include/mrdocs/Metadata/Enumerator.hpp rename to include/mrdocs/Metadata/EnumConstant.hpp index 46b253b54b..85eba8cf64 100644 --- a/include/mrdocs/Metadata/Enumerator.hpp +++ b/include/mrdocs/Metadata/EnumConstant.hpp @@ -8,8 +8,8 @@ // Official repository: https://github.com/cppalliance/mrdocs // -#ifndef MRDOCS_API_METADATA_ENUMERATOR_HPP -#define MRDOCS_API_METADATA_ENUMERATOR_HPP +#ifndef MRDOCS_API_METADATA_ENUMCONSTANT_HPP +#define MRDOCS_API_METADATA_ENUMCONSTANT_HPP #include #include @@ -21,10 +21,10 @@ namespace clang { namespace mrdocs { -/** Info for enumerators. +/** Info for enum constants. */ -struct EnumeratorInfo - : InfoCommonBase +struct EnumConstantInfo + : InfoCommonBase , SourceInfo { /** The initializer expression, if any @@ -33,7 +33,7 @@ struct EnumeratorInfo //-------------------------------------------- - explicit EnumeratorInfo(SymbolID ID) noexcept + explicit EnumConstantInfo(SymbolID ID) noexcept : InfoCommonBase(ID) { } diff --git a/include/mrdocs/Metadata/Alias.hpp b/include/mrdocs/Metadata/NamespaceAlias.hpp similarity index 78% rename from include/mrdocs/Metadata/Alias.hpp rename to include/mrdocs/Metadata/NamespaceAlias.hpp index 21af00e961..e11ebd6910 100644 --- a/include/mrdocs/Metadata/Alias.hpp +++ b/include/mrdocs/Metadata/NamespaceAlias.hpp @@ -8,8 +8,8 @@ // Official repository: https://github.com/cppalliance/mrdocs // -#ifndef MRDOCS_API_METADATA_ALIAS_HPP -#define MRDOCS_API_METADATA_ALIAS_HPP +#ifndef MRDOCS_API_METADATA_NAMESPACEALIAS_HPP +#define MRDOCS_API_METADATA_NAMESPACEALIAS_HPP #include #include @@ -22,8 +22,8 @@ namespace mrdocs { /** Info for namespace aliases. */ -struct AliasInfo - : InfoCommonBase +struct NamespaceAliasInfo + : InfoCommonBase , SourceInfo { /** The aliased symbol. */ @@ -31,7 +31,7 @@ struct AliasInfo //-------------------------------------------- - explicit AliasInfo(SymbolID ID) noexcept + explicit NamespaceAliasInfo(SymbolID ID) noexcept : InfoCommonBase(ID) { } diff --git a/include/mrdocs/Metadata/Source.hpp b/include/mrdocs/Metadata/Source.hpp index bfe79106be..44748f4a7d 100644 --- a/include/mrdocs/Metadata/Source.hpp +++ b/include/mrdocs/Metadata/Source.hpp @@ -72,11 +72,11 @@ struct MRDOCS_DECL //-------------------------------------------- Location( - std::string_view filepath = "", - std::string_view filename = "", - unsigned line = 0, - FileKind kind = FileKind::Source, - bool documented = false) + std::string_view const filepath = {}, + std::string_view const filename = {}, + unsigned const line = 0, + FileKind const kind = FileKind::Source, + bool const documented = false) : Path(filepath) , Filename(filename) , LineNumber(line) diff --git a/include/mrdocs/Support/Error.hpp b/include/mrdocs/Support/Error.hpp index bdf638a235..3b99ba7dee 100644 --- a/include/mrdocs/Support/Error.hpp +++ b/include/mrdocs/Support/Error.hpp @@ -572,7 +572,7 @@ namespace detail /// Check existing expected-like type # define MRDOCS_CHECK_VOID(var) \ - if (detail::failed(var)) { \ + if (detail::failed(var)) { \ return Unexpected(detail::error(var)); \ } \ void(0) @@ -585,13 +585,20 @@ namespace detail # define MRDOCS_CHECK(...) \ MRDOCS_CHECK_GET_MACRO(__VA_ARGS__, MRDOCS_CHECK_MSG, MRDOCS_CHECK_VOID)(__VA_ARGS__) -# define MRDOCS_CHECK_OR(var, expr) \ - if (detail::failed(var)) { \ - return expr; \ +/// Check existing expected-like type and return custom value otherwise +# define MRDOCS_CHECK_OR_VOID(var) \ + if (detail::failed(var)) { \ + return; \ } \ void(0) - - +# define MRDOCS_CHECK_OR_VALUE(var, value) \ + if (detail::failed(var)) { \ + return value; \ + } \ + void(0) +# define MRDOCS_CHECK_GET_OR_MACRO(_1, _2, NAME, ...) NAME +# define MRDOCS_CHECK_OR(...) \ + MRDOCS_CHECK_GET_OR_MACRO(__VA_ARGS__, MRDOCS_CHECK_OR_VALUE, MRDOCS_CHECK_OR_VOID)(__VA_ARGS__) #endif diff --git a/include/mrdocs/mrdocs.natvis b/include/mrdocs/mrdocs.natvis index 6b347961a7..516bd791c9 100644 --- a/include/mrdocs/mrdocs.natvis +++ b/include/mrdocs/mrdocs.natvis @@ -31,9 +31,9 @@ - + - + {Kind,en} {Name} (ID = {id}) @@ -49,9 +49,9 @@ (FieldInfo*)this,view(noinherit) (SpecializationInfo*)this,view(noinherit) (FriendInfo*)this,view(noinherit) - (EnumeratorInfo*)this,view(noinherit) + (EnumConstantInfo*)this,view(noinherit) (GuideInfo*)this,view(noinherit) - (AliasInfo*)this,view(noinherit) + (NamespaceAliasInfo*)this,view(noinherit) (UsingInfo*)this,view(noinherit) diff --git a/mrdocs.rnc b/mrdocs.rnc index ff39bcc5d9..4ebb5468c6 100644 --- a/mrdocs.rnc +++ b/mrdocs.rnc @@ -6,13 +6,14 @@ # Copyright (c) 2023 Klemens Morgenstern (klemens.morgenstern@gmx.net) # Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) # Copyright (c) 2024 Fernando Pelliccioni (fpelliccioni@gmail.com) +# Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) # # Official repository: https://github.com/cppalliance/mrdocs # # https://relaxng.org/compact-tutorial-20030326.html # # convert to mrdocs.rng with tools/trang.jar (https://relaxng.org/jclark/trang.html) -# when commiting beacuse xmllint doesn't understand the compact format +# when commiting because xmllint doesn't understand the compact format # namespace xsi= "http://www.w3.org/2001/XMLSchema-instance" @@ -134,8 +135,8 @@ grammar #--------------------------------------------- - Enumerator = - element enumerator + EnumConstant = + element enum-constant { Name, ID, @@ -147,7 +148,7 @@ grammar #--------------------------------------------- Typedef = - element alias + element namespace-alias { Name, ID, @@ -216,8 +217,8 @@ grammar #--------------------------------------------- - Alias = - element alias + NamespaceAlias = + element namespace-alias { Name, Access ?, @@ -351,7 +352,7 @@ grammar Variable | Guide | Template | - Alias | + NamespaceAlias | Using | Concept | Specialization @@ -368,12 +369,12 @@ grammar Guide | Template | Friend | - Alias | + NamespaceAlias | Using | Specialization )* - EnumScope = Enumerator * + EnumScope = EnumConstant * #--------------------------------------------- # diff --git a/share/mrdocs/addons/generator/adoc/partials/symbol.adoc.hbs b/share/mrdocs/addons/generator/adoc/partials/symbol.adoc.hbs index 666e0cf519..051251da81 100644 --- a/share/mrdocs/addons/generator/adoc/partials/symbol.adoc.hbs +++ b/share/mrdocs/addons/generator/adoc/partials/symbol.adoc.hbs @@ -53,7 +53,7 @@ |=== |Name |Description {{#each symbol.members}} -{{#if (ne kind "enumerator")}} +{{#if (ne kind "enum-constant")}} |xref:{{{anchor}}}[`{{>symbol/name . nolink=true}}`] {{else}} |`{{>symbol/name . nolink=true}}` diff --git a/share/mrdocs/addons/generator/common/partials/symbol/name-info.hbs b/share/mrdocs/addons/generator/common/partials/symbol/name-info.hbs index 05bfc57e9f..ff216262e4 100644 --- a/share/mrdocs/addons/generator/common/partials/symbol/name-info.hbs +++ b/share/mrdocs/addons/generator/common/partials/symbol/name-info.hbs @@ -7,7 +7,7 @@ nolink: If true, the name will not be linked. Example: - {{> name-info symbol }} + {{> name-info name }} See: https://mrdocs.com/docs/mrdocs/develop/generators.html#dom_reference --}} diff --git a/share/mrdocs/addons/generator/common/partials/symbol/signature/enumerator.hbs b/share/mrdocs/addons/generator/common/partials/symbol/signature/enum-constant.hbs similarity index 100% rename from share/mrdocs/addons/generator/common/partials/symbol/signature/enumerator.hbs rename to share/mrdocs/addons/generator/common/partials/symbol/signature/enum-constant.hbs diff --git a/share/mrdocs/addons/generator/common/partials/symbol/signature/alias.hbs b/share/mrdocs/addons/generator/common/partials/symbol/signature/namespace-alias.hbs similarity index 100% rename from share/mrdocs/addons/generator/common/partials/symbol/signature/alias.hbs rename to share/mrdocs/addons/generator/common/partials/symbol/signature/namespace-alias.hbs diff --git a/share/mrdocs/addons/generator/html/partials/symbol.html.hbs b/share/mrdocs/addons/generator/html/partials/symbol.html.hbs index d9629742aa..93e66ba10c 100644 --- a/share/mrdocs/addons/generator/html/partials/symbol.html.hbs +++ b/share/mrdocs/addons/generator/html/partials/symbol.html.hbs @@ -67,7 +67,7 @@ {{#each symbol.members}} -{{#if (ne kind "enumerator")}} +{{#if (ne kind "enum-constant")}} {{>symbol/name . nolink=true}} {{else}} {{>symbol/name . nolink=true}} diff --git a/src/lib/AST/ASTAction.cpp b/src/lib/AST/ASTAction.cpp new file mode 100644 index 0000000000..485251f669 --- /dev/null +++ b/src/lib/AST/ASTAction.cpp @@ -0,0 +1,59 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#include "lib/AST/ASTAction.hpp" +#include "lib/AST/ASTVisitorConsumer.hpp" +#include +#include + +namespace clang { +namespace mrdocs { + +void +ASTAction:: +ExecuteAction() +{ + CompilerInstance& CI = getCompilerInstance(); + if (!CI.hasPreprocessor()) + { + return; + } + + // Ensure comments in system headers are retained. + // We may want them if, e.g., a declaration was extracted + // as a dependency + CI.getLangOpts().RetainCommentsFromSystemHeaders = true; + + if (!CI.hasSema()) + { + CI.createSema(getTranslationUnitKind(), nullptr); + } + + ParseAST( + CI.getSema(), + false, // ShowStats + true); // SkipFunctionBodies +} + +std::unique_ptr +ASTAction:: +CreateASTConsumer( + clang::CompilerInstance& Compiler, + llvm::StringRef InFile) +{ + return std::make_unique( + config_, ex_, Compiler); +} + +} // mrdocs +} // clang diff --git a/src/lib/AST/ASTAction.hpp b/src/lib/AST/ASTAction.hpp new file mode 100644 index 0000000000..cb79bdcfea --- /dev/null +++ b/src/lib/AST/ASTAction.hpp @@ -0,0 +1,92 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#ifndef MRDOCS_LIB_AST_ASTACTION_HPP +#define MRDOCS_LIB_AST_ASTACTION_HPP + +#include "lib/Lib/ConfigImpl.hpp" +#include "lib/Lib/ExecutionContext.hpp" +#include +#include + +namespace clang { +namespace mrdocs { + +/** The clang frontend action for visiting the AST + + This is the MrDocs Clang Frontend Action used by the Clang tooling + to extract information from the AST. + + This is an AST consumer-based frontend action + (`clang::ASTFrontendAction`) that 1) can create a + `clang::ASTConsumer` that uses an `ASTVisitor` to traverse + the AST and extract information and 2) parses the AST + with this consumer. + + By overriding these methods, Clang will invoke the + @ref ASTVisitor for each translation unit. +*/ +class ASTAction + : public clang::ASTFrontendAction +{ + ExecutionContext& ex_; + ConfigImpl const& config_; + +public: + ASTAction( + ExecutionContext& ex, + ConfigImpl const& config) noexcept + : ex_(ex) + , config_(config) + { + } + + /** Execute the action + + This is called by the tooling infrastructure to execute + the action for each translation unit. + + The function will set options on the `CompilerInstance` + and parse the AST with the consumer that will have have + been previously created with @ref CreateASTConsumer. + + This `clang::ASTConsumer` then creates an + @ref ASTVisitor that will convert the AST into a set + of MrDocs Info objects. + */ + void + ExecuteAction() override; + + /** Create the object that will traverse the AST + + This is called by the tooling infrastructure to create + a `clang::ASTConsumer` for each translation unit. + + This consumer takes the TU and creates an + @ref ASTVisitor that will convert the AST into a + set of MrDocs Info types. + + The main function of the ASTVisitorConsumer is + the `HandleTranslationUnit` function, which is called + to traverse the AST with the @ref ASTVisitor. + */ + std::unique_ptr + CreateASTConsumer( + clang::CompilerInstance& Compiler, + llvm::StringRef InFile) override; +}; + +} // mrdocs +} // clang + +#endif diff --git a/src/lib/AST/ASTVisitor.cpp b/src/lib/AST/ASTVisitor.cpp index 15240ff069..00d2e14edb 100644 --- a/src/lib/AST/ASTVisitor.cpp +++ b/src/lib/AST/ASTVisitor.cpp @@ -10,4864 +10,2952 @@ // Official repository: https://github.com/cppalliance/mrdocs // -#include "ASTVisitor.hpp" -#include "ASTVisitorHelpers.hpp" -#include "ParseJavadoc.hpp" +#include "lib/AST/ASTVisitor.hpp" +#include "lib/AST/NameInfoBuilder.hpp" +#include "lib/AST/ClangHelpers.hpp" +#include "lib/AST/ParseJavadoc.hpp" +#include "lib/AST/TypeInfoBuilder.hpp" #include "lib/Support/Path.hpp" #include "lib/Support/Debug.hpp" #include "lib/Support/Glob.hpp" #include "lib/Lib/Diagnostics.hpp" #include "lib/Lib/Filters.hpp" -#include "lib/Lib/Info.hpp" +#include "lib/AST/SymbolFilterScope.hpp" #include #include #include -#include #include #include #include #include #include -#include #include #include #include -#include -#include -#include #include -#include #include #include #include #include #include #include -#include #include -namespace clang { -namespace mrdocs { +namespace clang::mrdocs { -namespace { - -struct SymbolFilter +ASTVisitor::FileInfo +ASTVisitor::FileInfo::build( + std::span> search_dirs, + std::string_view const file_path, + std::string_view const sourceRoot) { - const FilterNode& root; - const FilterNode* current = nullptr; - const FilterNode* last_explicit = nullptr; - bool detached = false; + FileInfo file_info; + file_info.full_path = std::string(file_path); + file_info.short_path = file_info.full_path; - SymbolFilter(const FilterNode& root_node) - : root(root_node) + std::ptrdiff_t best_length = 0; + auto check_dir = [&]( + std::string_view const dir_path, + FileKind const kind) { - setCurrent(&root, false); - } + auto NI = llvm::sys::path::begin(file_path); + auto const NE = llvm::sys::path::end(file_path); + auto DI = llvm::sys::path::begin(dir_path); + auto const DE = llvm::sys::path::end(dir_path); + + for(; NI != NE; ++NI, ++DI) + { + // reached the end of the directory path + if(DI == DE) + { + // update the best prefix length + if(std::ptrdiff_t length = + NI - llvm::sys::path::begin(file_path); + length > best_length) + { + best_length = length; + file_info.kind = kind; + return true; + } + break; + } + // separators always match + if(NI->size() == 1 && DI->size() == 1 && + llvm::sys::path::is_separator(NI->front()) && + llvm::sys::path::is_separator(DI->front())) + continue; + // components don't match + if(*NI != *DI) + break; + } + return false; + }; + + bool const in_sourceRoot = check_dir( + sourceRoot, FileKind::Source); - SymbolFilter(const SymbolFilter&) = delete; - SymbolFilter(SymbolFilter&&) = delete; + // only use a sourceRoot relative path if we + // don't find anything in the include search directories + // bool any_match = false; + // for (auto const& [dir_path, kind]: search_dirs) + // { + // any_match |= check_dir(dir_path, kind); + // } - void - setCurrent( - const FilterNode* node, - bool node_detached) + // KRYSTIAN TODO: if we don't find any matches, + // make the path relative to sourceRoot and return it + + // override the file kind if + // the file was found in sourceRoot + if (in_sourceRoot) { - current = node; - detached = node_detached; - if(node && node->Explicit) - last_explicit = node; + file_info.kind = FileKind::Source; } - class FilterScope - { - SymbolFilter& filter_; - const FilterNode* current_prev_; - const FilterNode* last_explicit_prev_; - bool detached_prev_; + file_info.short_path.erase(0, best_length); + return file_info; +} - public: - FilterScope(SymbolFilter& filter) - : filter_(filter) - , current_prev_(filter.current) - , last_explicit_prev_(filter.last_explicit) - , detached_prev_(filter.detached) - { - } +ASTVisitor:: +ASTVisitor( + const ConfigImpl& config, + Diagnostics& diags, + CompilerInstance& compiler, + ASTContext& context, + Sema& sema) noexcept + : config_(config) + , diags_(diags) + , compiler_(compiler) + , context_(context) + , source_(context.getSourceManager()) + , sema_(sema) + , symbolFilter_(config->symbolFilter) +{ + // Install handlers for our custom commands + initCustomCommentCommands(context_); + + // The traversal scope should *only* consist of the + // top-level TranslationUnitDecl. + // If this `assert` fires, then it means + // ASTContext::setTraversalScope is being (erroneously) + // used somewhere + MRDOCS_ASSERT(context_.getTraversalScope() == + std::vector{context_.getTranslationUnitDecl()}); - ~FilterScope() + auto make_posix_absolute = [&](std::string_view old_path) + { + llvm::SmallString<128> new_path(old_path); + if(! llvm::sys::path::is_absolute(new_path)) { - filter_.current = current_prev_; - filter_.last_explicit = last_explicit_prev_; - filter_.detached = detached_prev_; - } + // KRYSTIAN FIXME: use FileManager::makeAbsolutePath? + auto& cwd = source_.getFileManager(). + getFileSystemOpts().WorkingDir; + // we can't normalize a relative path + // without a base directory + // MRDOCS_ASSERT(! cwd.empty()); + llvm::sys::fs::make_absolute(cwd, new_path); + } + // remove ./ and ../ + llvm::sys::path::remove_dots(new_path, true, llvm::sys::path::Style::posix); + // convert to posix style + llvm::sys::path::native(new_path, llvm::sys::path::Style::posix); + return std::string(new_path); }; -}; - -static const Expr *SubstituteConstraintExpressionWithoutSatisfaction( - Sema &S, const Sema::TemplateCompareNewDeclInfo &DeclInfo, - const Expr *ConstrExpr) { - MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs( - DeclInfo.getDecl(), DeclInfo.getLexicalDeclContext(), /*Final=*/false, - /*Innermost=*/std::nullopt, - /*RelativeToPrimary=*/true, - /*Pattern=*/nullptr, /*ForConstraintInstantiation=*/true, - /*SkipForSpecialization*/ false); - - if (MLTAL.getNumSubstitutedLevels() == 0) - return ConstrExpr; - - Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/false); - - Sema::InstantiatingTemplate Inst( - S, DeclInfo.getLocation(), - Sema::InstantiatingTemplate::ConstraintNormalization{}, - const_cast(DeclInfo.getDecl()), SourceRange{}); - if (Inst.isInvalid()) - return nullptr; - // Set up a dummy 'instantiation' scope in the case of reference to function - // parameters that the surrounding function hasn't been instantiated yet. Note - // this may happen while we're comparing two templates' constraint - // equivalence. - LocalInstantiationScope ScopeForParameters(S); - if (auto *FD = DeclInfo.getDecl()->getAsFunction()) - for (auto *PVD : FD->parameters()) - ScopeForParameters.InstantiatedLocal(PVD, PVD); - - std::optional ThisScope; - - // See TreeTransform::RebuildTemplateSpecializationType. A context scope is - // essential for having an injected class as the canonical type for a template - // specialization type at the rebuilding stage. This guarantees that, for - // out-of-line definitions, injected class name types and their equivalent - // template specializations can be profiled to the same value, which makes it - // possible that e.g. constraints involving C> and C are - // perceived identical. - std::optional ContextScope; - if (auto *RD = dyn_cast(DeclInfo.getDeclContext())) { - ThisScope.emplace(S, const_cast(RD), Qualifiers()); - ContextScope.emplace(S, const_cast(cast(RD)), - /*NewThisContext=*/false); - } - ExprResult SubstConstr = S.SubstConstraintExprWithoutSatisfaction( - const_cast(ConstrExpr), MLTAL); - if (SFINAE.hasErrorOccurred() || !SubstConstr.isUsable()) - return nullptr; - return SubstConstr.get(); -} + std::string sourceRoot = make_posix_absolute(config_->sourceRoot); + std::vector> search_dirs; + + Preprocessor& PP = sema_.getPreprocessor(); + HeaderSearch& HS = PP.getHeaderSearchInfo(); + search_dirs.reserve(HS.search_dir_size()); + // first, convert all the include search directories into POSIX style + for(const DirectoryLookup& DL : HS.search_dir_range()) + { + OptionalDirectoryEntryRef DR = DL.getDirRef(); + // only consider normal directories + if(! DL.isNormalDir() || ! DR) + continue; + // store the normalized path + search_dirs.emplace_back( + make_posix_absolute(DR->getName()), + DL.isSystemHeaderDirectory() ? + FileKind::System : FileKind::Other); + } + + auto cacheFileInfo = [&](const FileEntry* entry) + { + // "try" implies this may fail, so fallback to getName + // if an empty string is returned + std::string_view file_path = + entry->tryGetRealPathName(); + files_.emplace( + entry, + FileInfo::build( + search_dirs, + make_posix_absolute(file_path), + sourceRoot)); + }; -//------------------------------------------------ -// -// ASTVisitor -// -//------------------------------------------------ + // build the file info for the main file + cacheFileInfo(source_.getFileEntryForID(source_.getMainFileID())); -/** Convert AST to metadata representation. + // build the file info for all included files + for(const FileEntry* file : PP.getIncludedFiles()) + { + cacheFileInfo(file); + } +} - An instance of this object traverses the AST - for a translation unit and translate AST nodes - into our metadata. -*/ -class ASTVisitor +void +ASTVisitor:: +build() { -public: - const ConfigImpl& config_; - Diagnostics diags_; + // traverse the translation unit, only extracting + // declarations which satisfy all filter conditions. + // dependencies will be tracked, but not extracted + traverseAny(context_.getTranslationUnitDecl()); - CompilerInstance& compiler_; - ASTContext& context_; - SourceManager& source_; - Sema& sema_; + // This is to ensure that the global namespace is always present + upsert(SymbolID::global); - InfoSet info_; - std::unordered_set dependencies_; + // if dependency extraction is disabled, we are done + if(config_->referencedDeclarations == + ConfigImpl::SettingsImpl::ExtractPolicy::Never) { + return; + } + buildDependencies(); +} - struct FileInfo +void +ASTVisitor:: +buildDependencies() +{ + auto scope = enterMode(ExtractMode::DirectDependency); + std::unordered_set currentPass(std::move(dependencies_)); + while (!currentPass.empty()) { - FileInfo(std::string_view path) - : full_path(path) - , short_path(full_path) + for (Decl* D : currentPass) { + SymbolID id = generateID(D); + bool const invalidId = !id; + bool const alreadyExtracted = info_.contains(id); + if (invalidId || alreadyExtracted) + { + continue; + } + traverseAny(D); } + currentPass = std::move(dependencies_); + } +} - std::string full_path; - std::string_view short_path; - FileKind kind; - }; - - std::unordered_map< - const FileEntry*, - FileInfo> files_; - - llvm::SmallString<128> usr_; - ODRHash odr_hash_; - - SymbolFilter symbolFilter_; - - enum class ExtractMode - { - // extraction of declarations which pass all filters - Normal, - // extraction of declarations as direct dependencies - DirectDependency, - // extraction of declarations as indirect dependencies - IndirectDependency, - }; +template +void +ASTVisitor:: +traverseAny( + Decl* D, + Args&&... args) +{ + MRDOCS_ASSERT(D); + MRDOCS_CHECK_OR(!D->isInvalidDecl()); - ExtractMode mode = ExtractMode::Normal; + // Restore the symbol filter state when leaving the scope + SymbolFilterScope scope(symbolFilter_); - struct [[nodiscard]] ExtractionScope + // Convert to the most derived type of the Decl + // and call the appropriate traverse function + visit(D, [&](DeclTy* DD) { - ASTVisitor& visitor_; - ExtractMode previous_; - - public: - ExtractionScope( - ASTVisitor& visitor, - ExtractMode mode) noexcept - : visitor_(visitor) - , previous_(visitor.mode) + if constexpr( + std::derived_from) { - visitor_.mode = mode; + // Only ClassTemplateDecl, FunctionTemplateDecl, + // VarTemplateDecl, and TypeAliasDecl are derived + // from RedeclarableTemplateDecl. + // This doesn't include ConceptDecl. + // Recursively call traverseAny so traverse is called with + // a pointer to the most derived type of the templated Decl + traverseAny(DD->getTemplatedDecl(), DD); } - - ~ExtractionScope() + else if constexpr( + std::derived_from || + std::derived_from) { - visitor_.mode = previous_; + // A class template specialization + traverse(DD, DD->getSpecializedTemplate()); } - }; + else + { + // Call the appropriate traverse function + // for the most derived type of the Decl + traverse(DD, std::forward(args)...); + } + }); +} - ExtractionScope - enterMode( - ExtractMode new_mode) noexcept +template +void +ASTVisitor:: +traverse(Decl* D, Args&&...) +{ + if (auto* DC = dyn_cast(D)) { - return ExtractionScope(*this, new_mode); + traverse(DC); } +} - ExtractMode - currentMode() const noexcept +void +ASTVisitor:: +traverse(DeclContext* DC) +{ + for(auto* D : DC->decls()) { - return mode; + traverseAny(D); } +} - ASTVisitor( - const ConfigImpl& config, - Diagnostics& diags, - CompilerInstance& compiler, - ASTContext& context, - Sema& sema) noexcept - : config_(config) - , diags_(diags) - , compiler_(compiler) - , context_(context) - , source_(context.getSourceManager()) - , sema_(sema) - , symbolFilter_(config->symbolFilter) +template +requires + std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as +void +ASTVisitor:: +traverse(DeclTy* D) +{ + if (auto exp = upsert(D)) { - // install handlers for our custom commands - initCustomCommentCommands(context_); - - // the traversal scope should *only* consist of the - // top-level TranslationUnitDecl. if this assert fires, - // then it means ASTContext::setTraversalScope is being - // (erroneously) used somewhere - MRDOCS_ASSERT(context_.getTraversalScope() == - std::vector{context_.getTranslationUnitDecl()}); + populate(exp->I, exp->created, D); + if constexpr (std::derived_from) + { + traverse(dyn_cast(D)); + } + } +} - using namespace llvm::sys; - Preprocessor& PP = sema_.getPreprocessor(); - HeaderSearch& HS = PP.getHeaderSearchInfo(); +template CXXRecordDeclTy> +void +ASTVisitor:: +traverse( + CXXRecordDeclTy* D, + ClassTemplateDecl* CTD) +{ + // Upsert the corresponding Info object + auto exp = upsert(D); + if (!exp) + { + return; + } + auto [I, created] = *exp; - auto normalize_path = [&](std::string_view old_path) - { - llvm::SmallString<128> new_path(old_path); - // KRYSTIAN FIXME: use FileManager::makeAbsolutePath? - if(! path::is_absolute(new_path)) - { - auto& cwd = source_.getFileManager(). - getFileSystemOpts().WorkingDir; - // we can't normalize a relative path - // without a base directory - // MRDOCS_ASSERT(! cwd.empty()); - fs::make_absolute(cwd, new_path); - } - // remove ./ and ../ - path::remove_dots(new_path, true, path::Style::posix); - // convert to posix style - path::native(new_path, path::Style::posix); - return std::string(new_path); - }; - - std::string sourceRoot = normalize_path(config_->sourceRoot); - std::vector> search_dirs; - - search_dirs.reserve(HS.search_dir_size()); - // first, convert all the include search directories into POSIX style - for(const DirectoryLookup& DL : HS.search_dir_range()) + // CTD is the specialized template if D is a partial or + // explicit specialization, and the described template otherwise + if (CTD) + { + if (!I.Template) { - OptionalDirectoryEntryRef DR = DL.getDirRef(); - // only consider normal directories - if(! DL.isNormalDir() || ! DR) - continue; - // store the normalized path - search_dirs.emplace_back( - normalize_path(DR->getName()), - DL.isSystemHeaderDirectory() ? - FileKind::System : FileKind::Other); + I.Template = std::make_unique(); } - auto build_file_info = [&](const FileEntry* file) - { - // "try" implies this may fail, so fallback to getName - // if an empty string is returned - std::string_view file_path = - file->tryGetRealPathName(); - files_.emplace(file, - getFileInfo(search_dirs, - normalize_path(file_path), - sourceRoot)); - }; - - // build the file info for the main file - build_file_info( - source_.getFileEntryForID( - source_.getMainFileID())); - - // build the file info for all included files - for(const FileEntry* file : PP.getIncludedFiles()) - build_file_info(file); - } - - FileInfo - getFileInfo( - std::span> search_dirs, - std::string_view file_path, - std::string_view sourceRoot) - { - using namespace llvm::sys; - FileInfo file_info(file_path); - - std::ptrdiff_t best_length = 0; - auto check_dir = [&]( - std::string_view dir_path, - FileKind kind) + // If D is a partial/explicit specialization, extract the template arguments + if (auto* CTSD = dyn_cast(D)) { - auto NI = path::begin(file_path), NE = path::end(file_path); - auto DI = path::begin(dir_path), DE = path::end(dir_path); + generateID(getInstantiatedFrom(CTD), I.Template->Primary); - for(; NI != NE; ++NI, ++DI) + // Extract the template arguments of the specialization + populate(I.Template->Args, CTSD->getTemplateArgsAsWritten()); + + // Extract the template parameters if this is a partial specialization + if(auto* CTPSD = dyn_cast(D)) { - // reached the end of the directory path - if(DI == DE) - { - // update the best prefix length - if(std::ptrdiff_t length = - NI - path::begin(file_path); - length > best_length) - { - best_length = length; - file_info.kind = kind; - return true; - } - break; - } - // separators always match - if(NI->size() == 1 && DI->size() == 1 && - path::is_separator(NI->front()) && - path::is_separator(DI->front())) - continue; - // components don't match - if(*NI != *DI) - break; + populate(*I.Template, CTPSD->getTemplateParameters()); } - return false; - }; - - bool in_sourceRoot = check_dir( - sourceRoot, FileKind::Source); - - // only use a sourceRoot relative path if we - // don't find anything in the include search directories - bool any_match = false; - for(const auto& [dir_path, kind] : search_dirs) - any_match |= check_dir(dir_path, kind); - - // KRYSTIAN TODO: if we don't find any matches, - // make the path relative to sourceRoot and return it - static_cast(any_match); - - // override the file kind if - // the file was found in sourceRoot - if(in_sourceRoot) - file_info.kind = FileKind::Source; - - file_info.short_path.remove_prefix(best_length); - return file_info; + } + else + { + // Otherwise, extract the template parameter list from CTD + populate(*I.Template, CTD->getTemplateParameters()); + } } - InfoSet& results() - { - return info_; - } + populate(I, created, D); + traverse(static_cast(D)); +} - void build() +template FunctionDeclTy> +void +ASTVisitor:: +traverse( + FunctionDeclTy* D, + FunctionTemplateDecl* FTD) +{ + auto exp = upsert(D); + if (!exp) { - // traverse the translation unit, only extracting - // declarations which satisfy all filter conditions. - // dependencies will be tracked, but not extracted - traverseDecl(context_.getTranslationUnitDecl()); - - // This is to ensure that the global namespace is always present - getOrCreateInfo(SymbolID::global); - - // if dependency extraction is disabled, we are done - if(config_->referencedDeclarations == - ConfigImpl::SettingsImpl::ExtractPolicy::Never) - return; - - // traverse the current set of dependencies, - // and generate a new set based on the results. - // if the new set is non-empty, perform another pass. - // do this until no new dependencies are generated - std::unordered_set previous; - buildDependencies(previous); + return; } + auto [I, created] = *exp; - void buildDependencies( - std::unordered_set& previous) + // D is the templated declaration if FTD is non-null + if (FTD || D->isFunctionTemplateSpecialization()) { - auto scope = enterMode( - ExtractMode::DirectDependency); + if (!I.Template) + { + I.Template = std::make_unique(); + } - previous.clear(); - dependencies_.swap(previous); + if(auto* FTSI = D->getTemplateSpecializationInfo()) + { + generateID(getInstantiatedFrom( + FTSI->getTemplate()), I.Template->Primary); - for(Decl* D : previous) + // TemplateArguments is used instead of TemplateArgumentsAsWritten + // because explicit specializations of function templates may have + // template arguments deduced from their return type and parameters + if(auto* Args = FTSI->TemplateArguments) + { + populate(I.Template->Args, Args->asArray()); + } + } + else if(auto* DFTSI = D->getDependentSpecializationInfo()) { - // skip declarations which generate invalid symbol IDs, - // or which already have been extract - if(SymbolID id = extractSymbolID(D); - ! id || info_.contains(id)) - continue; - traverseDecl(D); + // Only extract the ID of the primary template if there is + // a single candidate primary template. + if (auto Candidates = DFTSI->getCandidates(); Candidates.size() == 1) + { + generateID(getInstantiatedFrom( + Candidates.front()), I.Template->Primary); + } + if(auto* Args = DFTSI->TemplateArgumentsAsWritten) + { + populate(I.Template->Args, Args); + } + } + else + { + populate(*I.Template, FTD->getTemplateParameters()); } - - // perform another pass if there are new dependencies - if(! dependencies_.empty()) - buildDependencies(previous); } - //------------------------------------------------ + populate(I, created, D); +} - /** Get Info from ASTVisitor InfoSet - */ - Info* - getInfo(SymbolID const& id) +template TypedefNameDeclTy> +void +ASTVisitor:: +traverse( + TypedefNameDeclTy* D, + TypeAliasTemplateDecl* ATD) +{ + auto exp = upsert(D); + if (!exp) { - if(auto it = info_.find(id); it != info_.end()) - return it->get(); - return nullptr; + return; } + auto [I, created] = *exp; - /** Get or create the Info for a declaration. - - This function will return the Info for a declaration - if it has already been extracted, or create a new - Info for the declaration if it has not been extracted. + if (isa(D)) + { + I.IsUsing = true; + } - @return a pair containing a reference to the Info - and a boolean indicating whether the Info was created. - */ - template - std::pair - getOrCreateInfo(const SymbolID& id) + if (ATD) { - assert(id != SymbolID::invalid && - "creating symbol with invalid SymbolID?"); - Info* info = getInfo(id); - bool created = ! info; - if(! info) + if (!I.Template) { - info = info_.emplace(std::make_unique< - InfoTy>(id)).first->get(); - if(currentMode() != ExtractMode::Normal) - info->Implicit = true; + I.Template = std::make_unique(); } - MRDOCS_ASSERT(info->Kind == InfoTy::kind_id); - return {static_cast(*info), created}; + populate(*I.Template, ATD->getTemplateParameters()); } - void - getDependencyID( - const Decl* D, - SymbolID& id) + populate(I, created, D); +} + +template VarDeclTy> +void +ASTVisitor:: +traverse( + VarDeclTy* D, + VarTemplateDecl* VTD) +{ + auto exp = upsert(D); + if (!exp) { - return getDependencyID(const_cast(D), id); + return; } + auto [I, created] = *exp; - void - getDependencyID( - Decl* D, - SymbolID& id) + // VTD is the specialized template if D is a partial or + // explicit specialization, and the described template otherwise + if (VTD) { - if(TemplateDecl* TD = D->getDescribedTemplate()) - D = TD; - - if(D->isImplicit() || - isa(D) || - isa(D)) - return; - - id = extractSymbolID(D); - - // don't register a dependency if we never extract them - if(config_->referencedDeclarations == - ConfigImpl::SettingsImpl::ExtractPolicy::Never) - return; - - if(config_->referencedDeclarations == - ConfigImpl::SettingsImpl::ExtractPolicy::Dependency) + if (!I.Template) { - if(currentMode() != ExtractMode::DirectDependency) - return; + I.Template = std::make_unique(); } - // if it was already extracted, we are done - if(getInfo(id)) - return; - - // FIXME: we need to handle member specializations correctly. - // we do not want to extract all members of the enclosing - // implicit instantiation - Decl* Outer = D; - DeclContext* DC = D->getDeclContext(); - do + // If D is a partial/explicit specialization, extract the template arguments + if(auto* VTSD = dyn_cast(D)) { - if(DC->isFileContext() || - DC->isFunctionOrMethod()) - break; - - if(auto* RD = dyn_cast(DC); - RD && RD->isAnonymousStructOrUnion()) - continue; - - // when extracting dependencies, we want to extract - // all members of the containing class, not just this one - if(auto* TD = dyn_cast(DC)) - Outer = TD; + generateID(getInstantiatedFrom(VTD), I.Template->Primary); + // extract the template arguments of the specialization + populate(I.Template->Args, VTSD->getTemplateArgsAsWritten()); + // extract the template parameters if this is a partial specialization + if(auto* VTPSD = dyn_cast(D)) + populate(*I.Template, VTPSD->getTemplateParameters()); + } + else + { + // otherwise, extract the template parameter list from VTD + populate(*I.Template, VTD->getTemplateParameters()); } - while((DC = DC->getParent())); - - if(TemplateDecl* TD = Outer->getDescribedTemplate()) - Outer = TD; - - // add the adjusted declaration to the set of dependencies - if(! isa(Outer)) - dependencies_.insert(Outer); } - //------------------------------------------------ - - /** Generates a USR for a declaration. + populate(I, created, D); +} - Returns true if USR generation failed, - and false otherwise. - */ - bool - generateUSR(const Decl* D) +void +ASTVisitor:: +traverse( + CXXDeductionGuideDecl* D, + FunctionTemplateDecl const* FTD) +{ + auto exp = upsert(D); + if (!exp) { - MRDOCS_ASSERT(D); - MRDOCS_ASSERT(usr_.empty()); + return; + } + auto [I, created] = *exp; - if (const auto* NAD = dyn_cast(D)) + // D is the templated declaration if FTD is non-null + if(FTD) + { + if(! I.Template) { - if (index::generateUSRForDecl(cast(NAD->getNamespace()), usr_)) - return true; - usr_.append("@NA"); - usr_.append(NAD->getNameAsString()); - return false; + I.Template = std::make_unique(); } + populate(*I.Template, FTD->getTemplateParameters()); + } - // Handling UsingDecl - if (const auto* UD = dyn_cast(D)) - { - for (const auto* shadow : UD->shadows()) - { - if (index::generateUSRForDecl(shadow->getTargetDecl(), usr_)) - return true; - } - usr_.append("@UDec"); - usr_.append(UD->getQualifiedNameAsString()); - return false; - } + populate(I, created, D); +} - // Handling UnresolvedUsingTypenameDecl - if (const auto* UD = dyn_cast(D)) - { - if (index::generateUSRForDecl(UD, usr_)) - return true; - usr_.append("@UUTDec"); - usr_.append(UD->getQualifiedNameAsString()); - return false; - } - - // Handling UnresolvedUsingValueDecl - if (const auto* UD = dyn_cast(D)) - { - if (index::generateUSRForDecl(UD, usr_)) - return true; - usr_.append("@UUV"); - usr_.append(UD->getQualifiedNameAsString()); - return false; - } - - // Handling UsingPackDecl - if (const auto* UD = dyn_cast(D)) - { - if (index::generateUSRForDecl(UD, usr_)) - return true; - usr_.append("@UPD"); - usr_.append(UD->getQualifiedNameAsString()); - return false; - } - - // Handling UsingEnumDecl - if (const auto* UD = dyn_cast(D)) - { - if (index::generateUSRForDecl(UD, usr_)) - return true; - usr_.append("@UED"); - EnumDecl const* ED = UD->getEnumDecl(); - usr_.append(ED->getQualifiedNameAsString()); - return false; - } - - // KRYSTIAN NOTE: clang doesn't currently support - // generating USRs for friend declarations, so we - // will improvise until I can merge a patch which - // adds support for them - if(const auto* FD = dyn_cast(D)) - { - // first, generate the USR for the containing class - if(index::generateUSRForDecl( - cast(FD->getDeclContext()), usr_)) - return true; - // add a seperator for uniqueness - usr_.append("@FD"); - // if the friend declaration names a type, - // use the USR generator for types - if(TypeSourceInfo* TSI = FD->getFriendType()) - return index::generateUSRForType( - TSI->getType(), context_, usr_); - // otherwise, fallthrough and append the - // USR of the nominated declaration - if(! (D = FD->getFriendDecl())) - return true; - } - - if (index::generateUSRForDecl(D, usr_)) - return true; - - const auto* Described = dyn_cast_if_present(D); - const auto* Templated = D; - if(auto* DT = D->getDescribedTemplate()) - { - Described = DT; - if (auto* TD = DT->getTemplatedDecl()) - Templated = TD; - } - - if(Described) - { - const TemplateParameterList* TPL = Described->getTemplateParameters(); - if(const auto* RC = TPL->getRequiresClause()) - { - RC = SubstituteConstraintExpressionWithoutSatisfaction( - sema_, cast(isa(Described) ? Described : Templated), RC); - if(! RC) - return true; - odr_hash_.AddStmt(RC); - usr_.append("@TPL#"); - usr_.append(llvm::itostr(odr_hash_.CalculateHash())); - odr_hash_.clear(); - } - } - - if(auto* FD = dyn_cast(Templated); - FD && FD->getTrailingRequiresClause()) - { - const Expr* RC = FD->getTrailingRequiresClause(); - RC = SubstituteConstraintExpressionWithoutSatisfaction( - sema_, cast(Described ? Described : Templated), RC); - if(! RC) - return true; - odr_hash_.AddStmt(RC); - usr_.append("@TRC#"); - usr_.append(llvm::itostr(odr_hash_.CalculateHash())); - odr_hash_.clear(); - } - - return false; - } - - /** Extracts the symbol ID for a declaration. - - This function will extract the symbol ID for a - declaration, and store it in the `id` input - parameter. - - As USRs (Unified Symbol Resolution) could be - large, especially for functions with long type - arguments, we use 160-bits SHA1(USR) values. - - To guarantee the uniqueness of symbols while using - a relatively small amount of memory (vs storing - USRs directly), this function hashes the Decl - USR value with SHA1. - - @return true if the symbol ID could not be - extracted, and false otherwise. - - */ - bool - extractSymbolID( - const Decl* D, - SymbolID& id) - { - if(! D) - return false; - if(isa(D)) - { - id = SymbolID::global; - return true; - } - usr_.clear(); - if(generateUSR(D)) - return false; - auto h = llvm::SHA1::hash(arrayRefFromStringRef(usr_)); - id = SymbolID(h.data()); - return true; - } - - /** Extracts the symbol ID for a declaration. - - This function will extract the symbol ID for a - declaration, and return it. - - As USRs (Unified Symbol Resolution) could be - large, especially for functions with long type - arguments, we use 160-bits SHA1(USR) values. - - To guarantee the uniqueness of symbols while using - a relatively small amount of memory (vs storing - USRs directly), this function hashes the Decl - USR value with SHA1. - - @return the symbol ID for the declaration. - */ - SymbolID - extractSymbolID( - const Decl* D) - { - SymbolID id = SymbolID::invalid; - extractSymbolID(D, id); - return id; - } - - //------------------------------------------------ - - AccessSpecifier - getAccess(const Decl* D) - { - // First, get the declaration this was instantiated from - D = getInstantiatedFrom(D); - - // If this is the template declaration of a template, - // use the access of the template - if(TemplateDecl const* TD = D->getDescribedTemplate()) - return TD->getAccessUnsafe(); - - // For class/variable template partial/explicit specializations, - // we want to use the access of the primary template - if(auto const* CTSD = dyn_cast(D)) - return CTSD->getSpecializedTemplate()->getAccessUnsafe(); - - if(auto const* VTSD = dyn_cast(D)) - return VTSD->getSpecializedTemplate()->getAccessUnsafe(); - - // For function template specializations, use the access of the - // primary template if it has been resolved - if(auto const* FD = dyn_cast(D)) - { - if(auto const* FTD = FD->getPrimaryTemplate()) - return FTD->getAccessUnsafe(); - } - - // Since friend declarations are not members, this hack computes - // their access based on the default access for the tag they - // appear in, and any AccessSpecDecls which appears lexically - // before them - if(auto const* FD = dyn_cast(D)) - { - auto const* RD = dyn_cast( - FD->getLexicalDeclContext()); - // RD should never be null in well-formed code, - // but clang error recovery may build an AST - // where the assumption will not hold - if(! RD) - return AccessSpecifier::AS_public; - auto access = RD->isClass() ? - AccessSpecifier::AS_private : - AccessSpecifier::AS_public; - for(auto* M : RD->decls()) - { - if(auto* AD = dyn_cast(M)) - access = AD->getAccessUnsafe(); - else if(M == FD) - return access; - } - // KRYSTIAN FIXME: will this ever be hit? - // it would require a friend declaration that is - // not in the lexical traversal of its lexical context - MRDOCS_UNREACHABLE(); - } - - // In all other cases, use the access of this declaration - return D->getAccessUnsafe(); - } - - //------------------------------------------------ - - // KRYSTIAN NOTE: we *really* should not have a - // type named "SourceLocation"... - FileInfo* getFileInfo(clang::SourceLocation loc) - { - // KRYSTIAN FIXME: we really should not be - // calling getPresumedLoc this often, - // it's quite expensive - auto presumed = source_.getPresumedLoc(loc, false); - if(presumed.isInvalid()) - return nullptr; - const FileEntry* file = - source_.getFileEntryForID( - presumed.getFileID()); - // KRYSTIAN NOTE: i have no idea under what - // circumstances the file entry would be null - if(! file) - return nullptr; - // KRYSTIAN NOTE: i have no idea under what - // circumstances the file would not be in either - // the main file, or an included file - auto it = files_.find(file); - if(it == files_.end()) - return nullptr; - return &it->second; - } - - /** Add a source location to an Info object. - - This function will add a source location to an Info, - if it is not already present. - - @param I the Info to add the source location to - @param loc the source location to add - @param definition true if the source location is a definition - @param documented true if the source location is documented - */ - void - addSourceLocation( - SourceInfo& I, - clang::SourceLocation loc, - bool definition, - bool documented) - { - unsigned line = source_.getPresumedLoc( - loc, false).getLine(); - FileInfo* file = getFileInfo(loc); - MRDOCS_ASSERT(file); - if(definition) - { - if(I.DefLoc) - return; - I.DefLoc.emplace(file->full_path, - file->short_path, line, file->kind, - documented); - } - else - { - auto existing = std::find_if(I.Loc.begin(), I.Loc.end(), - [line, file](const Location& l) - { - return l.LineNumber == line && - l.Path == file->full_path; - }); - if(existing != I.Loc.end()) - return; - I.Loc.emplace_back(file->full_path, - file->short_path, line, file->kind, - documented); - } - } - - std::string - getSourceCode( - SourceRange const& R) - { - return Lexer::getSourceText( - CharSourceRange::getTokenRange(R), - source_, - context_.getLangOpts()).str(); - } - - //------------------------------------------------ - - std::string getExprAsString(const Expr* E) - { - std::string result; - llvm::raw_string_ostream stream(result); - E->printPretty(stream, nullptr, context_.getPrintingPolicy()); - return result; - } - - std::string getTypeAsString(const Type* T) - { - if(auto* AT = dyn_cast_if_present(T)) - { - switch(AT->getKeyword()) - { - case AutoTypeKeyword::Auto: - case AutoTypeKeyword::GNUAutoType: - return "auto"; - case AutoTypeKeyword::DecltypeAuto: - return "decltype(auto)"; - default: - MRDOCS_UNREACHABLE(); - } - } - if(auto* TTPT = dyn_cast_if_present(T)) - { - if(TemplateTypeParmDecl* TTPD = TTPT->getDecl(); - TTPD && TTPD->isImplicit()) - return "auto"; - } - return QualType(T, 0).getAsString( - context_.getPrintingPolicy()); - } - - /** Get the user-written `Decl` for a `Decl` - - Given a `Decl` `D`, `getInstantiatedFrom` will return the - user-written `Decl` corresponding to `D`. For specializations - which were implicitly instantiated, this will be whichever `Decl` - was used as the pattern for instantiation. - */ - template - DeclTy* - getInstantiatedFrom(DeclTy* D); - - template - requires std::derived_from || - std::same_as> - FunctionDecl* - getInstantiatedFrom(DeclTy* D) - { - return dyn_cast_if_present( - getInstantiatedFrom(D)); - } - - template - requires std::derived_from || - std::same_as> - CXXRecordDecl* - getInstantiatedFrom(DeclTy* D) - { - return dyn_cast_if_present( - getInstantiatedFrom(D)); - } - - template - requires std::derived_from || - std::same_as> - VarDecl* - getInstantiatedFrom(DeclTy* D) - { - return dyn_cast_if_present( - getInstantiatedFrom(D)); - } - - template - requires std::derived_from || - std::same_as> - TypedefNameDecl* - getInstantiatedFrom(DeclTy* D) - { - return dyn_cast_if_present( - getInstantiatedFrom(D)); - } - - std::unique_ptr - buildTypeInfo( - QualType qt, - ExtractMode extract_mode = ExtractMode::IndirectDependency); - - std::unique_ptr - buildNameInfo( - const NestedNameSpecifier* NNS, - ExtractMode extract_mode = ExtractMode::IndirectDependency); - - #if 0 - std::unique_ptr - buildNameInfo( - const Decl* D, - ExtractMode extract_mode = ExtractMode::IndirectDependency); - #endif - - template> - std::unique_ptr - buildNameInfo( - DeclarationName Name, - std::optional TArgs = std::nullopt, - const NestedNameSpecifier* NNS = nullptr, - ExtractMode extract_mode = ExtractMode::IndirectDependency); - - template> - std::unique_ptr - buildNameInfo( - const Decl* D, - std::optional TArgs = std::nullopt, - const NestedNameSpecifier* NNS = nullptr, - ExtractMode extract_mode = ExtractMode::IndirectDependency); - - - template - Integer - getValue(const llvm::APInt& V) - { - if constexpr(std::is_signed_v) - return static_cast( - V.getSExtValue()); - else - return static_cast( - V.getZExtValue()); - } - - void - buildNoexceptInfo( - NoexceptInfo& I, - const FunctionProtoType* FPT) - { - MRDOCS_ASSERT(FPT); - I.Implicit = ! FPT->hasNoexceptExceptionSpec(); - #if 0 - // if the exception specification is unevaluated, - // we just consider it to be dependent - if(FPT->getExceptionSpecType() == - ExceptionSpecificationType::EST_Unevaluated) - I.Kind = NoexceptKind::Dependent; - else - I.Kind = convertToNoexceptKind(FPT->canThrow()); - #else - I.Kind = convertToNoexceptKind( - FPT->getExceptionSpecType()); - #endif - - // store the operand, if any - if(Expr* NoexceptExpr = FPT->getNoexceptExpr()) - I.Operand = getExprAsString(NoexceptExpr); - } - - void - buildExplicitInfo( - ExplicitInfo& I, - const ExplicitSpecifier& ES) - { - I.Implicit = ! ES.isSpecified(); - I.Kind = convertToExplicitKind(ES); - - // store the operand, if any - if(const Expr* ExplicitExpr = ES.getExpr()) - I.Operand = getExprAsString(ExplicitExpr); - } - - void - buildExprInfo( - ExprInfo& I, - const Expr* E) - { - if(! E) - return; - I.Written = getSourceCode( - E->getSourceRange()); - } - - template - void - buildExprInfo( - ConstantExprInfo& I, - const Expr* E) - { - buildExprInfo( - static_cast(I), E); - // if the expression is dependent, - // we cannot get its value - if(! E || E->isValueDependent()) - return; - I.Value.emplace(getValue( - E->EvaluateKnownConstInt(context_))); - } - - template - void - buildExprInfo( - ConstantExprInfo& I, - const Expr* E, - const llvm::APInt& V) - { - buildExprInfo(I, E); - I.Value.emplace(getValue(V)); - } - - QualType - getDeclaratorType( - const DeclaratorDecl* DD) +void +ASTVisitor:: +traverse(UsingDirectiveDecl* D) +{ + // A using directive such as `using namespace std;` + if (!shouldExtract(D, getAccess(D))) { - if(auto* TSI = DD->getTypeSourceInfo(); - TSI && ! TSI->getType().isNull()) - return TSI->getType(); - return DD->getType(); + return; } - void - buildTemplateParam( - std::unique_ptr& I, - const NamedDecl* N) - { - visit(N, [&](const DeclTy* P) - { - constexpr Decl::Kind kind = - DeclToKind(); - - if constexpr(kind == Decl::TemplateTypeParm) - { - if(! I) - I = std::make_unique(); - auto* R = static_cast(I.get()); - if(P->wasDeclaredWithTypename()) - R->KeyKind = TParamKeyKind::Typename; - if(P->hasDefaultArgument() && !R->Default) - R->Default = buildTemplateArg( - P->getDefaultArgument().getArgument()); - if(const TypeConstraint* TC = P->getTypeConstraint()) - { - const NestedNameSpecifier* NNS = - TC->getNestedNameSpecifierLoc().getNestedNameSpecifier(); - std::optional TArgs; - if(TC->hasExplicitTemplateArgs()) - TArgs.emplace(TC->getTemplateArgsAsWritten()); - R->Constraint = buildNameInfo(TC->getNamedConcept(), TArgs, NNS); - } - return; - } - else if constexpr(kind == Decl::NonTypeTemplateParm) - { - if(! I) - I = std::make_unique(); - auto* R = static_cast(I.get()); - R->Type = buildTypeInfo(P->getType()); - if(P->hasDefaultArgument() && !R->Default) - R->Default = buildTemplateArg( - P->getDefaultArgument().getArgument()); - return; - } - else if constexpr(kind == Decl::TemplateTemplateParm) - { - if(! I) - I = std::make_unique(); - auto* R = static_cast(I.get()); - if(R->Params.empty()) - { - for(const NamedDecl* NP : *P->getTemplateParameters()) - buildTemplateParam(R->Params.emplace_back(), NP); - } - if(P->hasDefaultArgument() && !R->Default) - R->Default = buildTemplateArg( - P->getDefaultArgument().getArgument()); - return; - } - MRDOCS_UNREACHABLE(); - }); - - if(I->Name.empty()) - I->Name = extractName(N); - // KRYSTIAN NOTE: Decl::isParameterPack - // returns true for function parameter packs - I->IsParameterPack = - N->isTemplateParameterPack(); - } + Decl* PD = getParentDecl(D); - void - buildTemplateParams( - TemplateInfo& TI, - const TemplateParameterList* TPL) + bool const isNamespaceScope = cast(PD)->isFileContext(); + if (!isNamespaceScope) { - for(std::size_t I = 0; I < TPL->size(); ++I) - { - auto& PI = I < TI.Params.size() ? - TI.Params[I] : TI.Params.emplace_back(); - buildTemplateParam(PI, TPL->getParam(I)); - } - if(auto* RC = TPL->getRequiresClause()) - buildExprInfo(TI.Requires, RC); + return; } - std::unique_ptr - buildTemplateArg( - const TemplateArgument& A) + if (Info* PI = find(generateID(PD))) { - // TypePrinter generates an internal placeholder name (e.g. type-parameter-0-0) - // for template type parameters used as arguments. it also cannonicalizes - // types, which we do not want (although, PrintingPolicy has an option to change this). - // thus, we use the template arguments as written. - - // KRYSTIAN NOTE: this can probably be changed to select - // the argument as written when it is not dependent and is a type. - // FIXME: constant folding behavior should be consistent with that of other - // constructs, e.g. noexcept specifiers & explicit specifiers - switch(A.getKind()) - { - // empty template argument (e.g. not yet deduced) - case TemplateArgument::Null: - break; - - // a template argument pack (any kind) - case TemplateArgument::Pack: - { - // we should never a TemplateArgument::Pack here - MRDOCS_UNREACHABLE(); - break; - } - // type - case TemplateArgument::Type: - { - auto R = std::make_unique(); - QualType QT = A.getAsType(); - MRDOCS_ASSERT(! QT.isNull()); - // if the template argument is a pack expansion, - // use the expansion pattern as the type & mark - // the template argument as a pack expansion - if(const Type* T = QT.getTypePtr(); - auto* PT = dyn_cast(T)) - { - R->IsPackExpansion = true; - QT = PT->getPattern(); - } - R->Type = buildTypeInfo(QT); - return R; - } - // pack expansion of a template name - case TemplateArgument::TemplateExpansion: - // template name - case TemplateArgument::Template: - { - auto R = std::make_unique(); - R->IsPackExpansion = A.isPackExpansion(); - - // KRYSTIAN FIXME: template template arguments are - // id-expression, so we don't properly support them yet. - // for the time being, we will use the name & SymbolID of - // the referenced declaration (if it isn't dependent), - // and fallback to printing the template name otherwise - TemplateName TN = A.getAsTemplateOrTemplatePattern(); - if(auto* TD = TN.getAsTemplateDecl()) - { - if(auto* II = TD->getIdentifier()) - R->Name = II->getName(); - // do not extract a SymbolID or build Info if - // the template template parameter names a - // template template parameter or builtin template - if(! isa(TD) && - ! isa(TD)) - { - getDependencyID(getInstantiatedFrom< - NamedDecl>(TD), R->Template); - } - } - else - { - llvm::raw_string_ostream stream(R->Name); - TN.print(stream, context_.getPrintingPolicy(), - TemplateName::Qualified::AsWritten); - } - return R; - } - // nullptr value - case TemplateArgument::NullPtr: - // expression referencing a declaration - case TemplateArgument::Declaration: - // integral expression - case TemplateArgument::Integral: - // expression - case TemplateArgument::Expression: - { - auto R = std::make_unique(); - R->IsPackExpansion = A.isPackExpansion(); - // if this is a pack expansion, use the template argument - // expansion pattern in place of the template argument pack - const TemplateArgument& adjusted = - R->IsPackExpansion ? - A.getPackExpansionPattern() : A; - - llvm::raw_string_ostream stream(R->Value.Written); - adjusted.print(context_.getPrintingPolicy(), stream, false); - - return R; - } - default: - MRDOCS_UNREACHABLE(); - } - return nullptr; + MRDOCS_ASSERT(PI->isNamespace()); + auto* NI = dynamic_cast(PI); + upsertDependency( + D->getNominatedNamespaceAsWritten(), + NI->UsingDirectives.emplace_back()); } +} - template - void - buildTemplateArgs( - std::vector>& result, - Range&& args) +void +ASTVisitor:: +traverse(ConceptDecl* D) +{ + auto exp = upsert(D); + if (!exp) { - for(const TemplateArgument& arg : args) - { - // KRYSTIAN NOTE: is this correct? should we have a - // separate TArgKind for packs instead of "unlaminating" - // them as we are doing here? - if(arg.getKind() == TemplateArgument::Pack) - buildTemplateArgs(result, arg.pack_elements()); - else - result.emplace_back(buildTemplateArg(arg)); - } + return; } - - void - buildTemplateArgs( - std::vector>& result, - const ASTTemplateArgumentListInfo* args) + auto [I, created] = *exp; + if (!I.Template) { - return buildTemplateArgs(result, - std::views::transform(args->arguments(), - [](auto& x) -> auto& - { - return x.getArgument(); - })); - } - - /** Parse the comments above a declaration as Javadoc - - This function will parse the comments above a declaration - as Javadoc, and store the results in the `javadoc` input - parameter. - - @return true if the comments were successfully parsed as - Javadoc, and false otherwise. - */ - bool - parseRawComment( - std::unique_ptr& javadoc, - Decl const* D) - { - // VFALCO investigate whether we can use - // ASTContext::getCommentForDecl instead - #if 1 - RawComment* RC = - D->getASTContext().getRawCommentForDeclNoCache(D); - if(! RC) - return false; - comments::FullComment* FC = - RC->parse(D->getASTContext(), &sema_.getPreprocessor(), D); - #else - comments::FullComment* FC = - D->getASTContext().getCommentForDecl( - D, &sema_.getPreprocessor()); - #endif - if(! FC) - return false; - // KRYSTIAN FIXME: clang ignores documentation comments - // when there is a preprocessor directive between the end - // of the comment and the declaration location. there are two - // ways to fix this: either set the declaration begin location - // to be before and preprocessor directives, or submit a patch - // which disables this behavior (it's not entirely clear why - // this check occurs anyways, so some investigation is needed) - parseJavadoc(javadoc, FC, D, config_, diags_); - return true; + I.Template = std::make_unique(); } + populate(*I.Template, D->getTemplateParameters()); + populate(I, created, D); +} - //------------------------------------------------ - - bool - checkSymbolFilter(const NamedDecl* ND) - { - if(currentMode() != ExtractMode::Normal || - symbolFilter_.detached) - return true; - - std::string name = extractName(ND); - const FilterNode* parent = symbolFilter_.current; - if(const FilterNode* child = parent->findChild(name)) - { - // if there is a matching node, skip extraction if it's - // explicitly excluded AND has no children. the presence - // of child nodes indicates that some child exists that - // is explicitly whitelisted - if(child->Explicit && child->Excluded && - child->isTerminal()) - return false; - symbolFilter_.setCurrent(child, false); - } - else - { - // if there was no matching node, check the most - // recently entered explicitly specified parent node. - // if it's blacklisted, then the "filtering default" - // is to exclude symbols unless a child is explicitly - // whitelisted - if(symbolFilter_.last_explicit && - symbolFilter_.last_explicit->Excluded) - return false; +void +ASTVisitor:: +traverse(IndirectFieldDecl* D) +{ + traverse(D->getAnonField()); +} - if(const auto* DC = dyn_cast(ND); - ! DC || ! DC->isInlineNamespace()) - { - // if this namespace does not match a child - // of the current filter node, set the detached flag - // so we don't update the namespace filter state - // while traversing the children of this namespace - symbolFilter_.detached = true; - } - } - return true; - } +Expected> +ASTVisitor:: +generateUSR(const Decl* D) const +{ + MRDOCS_ASSERT(D); + llvm::SmallString<128> res; - // This also sets IsFileInRootDir - bool - inExtractedFile( - const Decl* D) + if (auto const* NAD = dyn_cast(D)) { - namespace path = llvm::sys::path; - - if(const auto* ND = dyn_cast(D)) + if (index::generateUSRForDecl(cast(NAD->getNamespace()), res)) { - // out-of-line declarations require us to rebuild - // the symbol filtering state - if(ND->isOutOfLine()) - { - symbolFilter_.setCurrent( - &symbolFilter_.root, false); - - // collect all parent classes/enums/namespaces - llvm::SmallVector parents; - const Decl* P = ND; - while((P = getParentDecl(P))) - { - if(isa(P)) - break; - parents.push_back(cast(P)); - } - - // check whether each parent passes the symbol filters - // as-if the declaration was inline - for(const auto* PND : std::views::reverse(parents)) - { - if(! checkSymbolFilter(PND)) - return false; - } - } - - if(! checkSymbolFilter(ND)) - return false; + return Unexpected(Error("Failed to generate USR")); } - - FileInfo* file = getFileInfo(D->getBeginLoc()); - // KRYSTIAN NOTE: i'm unsure under what conditions - // this assert would fire. - MRDOCS_ASSERT(file); - // only extract from files in source root - return file->kind == FileKind::Source; + res.append("@NA"); + res.append(NAD->getNameAsString()); + return res; } - /** Determine if a declaration should be extracted - - This function will determine whether a declaration - should be extracted based on the current extraction - mode, and the current symbol filter state. - - The function filters private symbols, symbols outside - the input files, and symbols in files that do not match - the input file patterns. - - @param D the declaration to check - @param access the access specifier of the declaration - @return true if the declaration should be extracted, - and false otherwise. - */ - bool - shouldExtract( - const Decl* D, - AccessSpecifier access) + // Handling UsingDecl + if (auto const* UD = dyn_cast(D)) { - if (config_->inaccessibleMembers != - ConfigImpl::SettingsImpl::ExtractPolicy::Always) - { - // KRYSTIAN FIXME: this doesn't handle direct - // dependencies on inaccessible declarations - if(access == AccessSpecifier::AS_private || - access == AccessSpecifier::AS_protected) - return false; - } - - // don't extract anonymous unions - if(const auto* RD = dyn_cast(D); - RD && RD->isAnonymousStructOrUnion()) - return false; - - // don't extract implicitly generated declarations - // (except for IndirectFieldDecls) - if(D->isImplicit() && !isa(D)) - return false; - - if (!config_->input.include.empty()) - { - // Get filename - FileInfo* file = getFileInfo(D->getBeginLoc()); - if (!file) - return false; - std::string filename = file->full_path; - bool matchPrefix = std::ranges::any_of( - config_->input.include, - [&filename](const std::string& prefix) - { - return files::startsWith(filename, prefix); - }); - if (!matchPrefix) - { - return false; - } - } - - if (!config_->input.filePatterns.empty()) + for (const auto* shadow : UD->shadows()) { - // Get filename - FileInfo* file = getFileInfo(D->getBeginLoc()); - if (!file) - return false; - std::string filename = file->full_path; - bool matchPattern = std::ranges::any_of( - config_->input.filePatterns, - [&filename](const std::string& pattern) - { - return globMatch(pattern, filename); - }); - if (!matchPattern) + if (index::generateUSRForDecl(shadow->getTargetDecl(), res)) { - return false; + return Unexpected(Error("Failed to generate USR")); } } + res.append("@UDec"); + res.append(UD->getQualifiedNameAsString()); + return res; + } - #if 0 - bool extract = inExtractedFile(D); - // if we're extracting a declaration as a dependency, - // override the current extraction mode if - // it would be extracted anyway - if(extract) - mode = ExtractMode::Normal; - - return extract || currentMode() != ExtractMode::Normal; - #else - return inExtractedFile(D) || - currentMode() != ExtractMode::Normal; - #endif - } - - const NonTypeTemplateParmDecl* - getNTTPFromExpr( - const Expr* E, - unsigned Depth) - { - while(true) - { - if(const auto* ICE = dyn_cast(E)) - { - E = ICE->getSubExpr(); - continue; - } - if(const auto* CE = dyn_cast(E)) - { - E = CE->getSubExpr(); - continue; - } - if(const auto* SNTTPE = dyn_cast(E)) - { - E = SNTTPE->getReplacement(); - continue; - } - if(const auto* CCE = dyn_cast(E); - CCE && CCE->getParenOrBraceRange().isInvalid()) - { - // look through implicit copy construction from an lvalue of the same type - E = CCE->getArg(0); - continue; - } - break; - } - - const auto* DRE = dyn_cast(E); - if(! DRE) - return nullptr; - - const auto* NTTPD = dyn_cast(DRE->getDecl()); - if(! NTTPD || NTTPD->getDepth() != Depth) - return nullptr; - - return NTTPD; - } - - std::optional - tryGetTemplateArgument( - TemplateParameterList* Parameters, - ArrayRef Arguments, - unsigned Index) - { - if(Index == static_cast(-1)) - return std::nullopt; - if(Index < Arguments.size()) - return Arguments[Index]; - if(Parameters && Index < Parameters->size()) - { - NamedDecl* ND = Parameters->getParam(Index); - if(auto* TTPD = dyn_cast(ND); - TTPD && TTPD->hasDefaultArgument()) - { - return TTPD->getDefaultArgument().getArgument(); - } - if(auto* NTTPD = dyn_cast(ND); - NTTPD && NTTPD->hasDefaultArgument()) - { - return NTTPD->getDefaultArgument().getArgument(); - } - } - return std::nullopt; - } - - std::optional> - isSFINAETemplate( - TemplateDecl* TD, - const IdentifierInfo* Member) - { - if(! TD) - return std::nullopt; - - auto FindParam = [this]( - ArrayRef Arguments, - const TemplateArgument& Arg) -> unsigned - { - if(Arg.getKind() != TemplateArgument::Type) - return -1; - auto Found = std::ranges::find_if(Arguments, [&](const TemplateArgument& Other) - { - if(Other.getKind() != TemplateArgument::Type) - return false; - return context_.hasSameType(Other.getAsType(), Arg.getAsType()); - }); - return Found != Arguments.end() ? Found - Arguments.data() : -1; - }; - - if(auto* ATD = dyn_cast(TD)) - { - auto Underlying = ATD->getTemplatedDecl()->getUnderlyingType(); - auto sfinae_info = getSFINAETemplate(Underlying, !Member); - if(! sfinae_info) - return std::nullopt; - if(Member) - sfinae_info->Member = Member; - auto sfinae_result = isSFINAETemplate( - sfinae_info->Template, sfinae_info->Member); - if(! sfinae_result) - return std::nullopt; - auto [template_params, controlling_params, param_idx] = *sfinae_result; - auto param_arg = tryGetTemplateArgument( - template_params, sfinae_info->Arguments, param_idx); - if(! param_arg) - return std::nullopt; - unsigned ParamIdx = FindParam(ATD->getInjectedTemplateArgs(context_), *param_arg); - return std::make_tuple(ATD->getTemplateParameters(), std::move(controlling_params), ParamIdx); - } - - auto* CTD = dyn_cast(TD); - if(! CTD) - return std::nullopt; - - auto PrimaryArgs = CTD->getInjectedTemplateArgs(context_); - llvm::SmallBitVector ControllingParams(PrimaryArgs.size()); - - QualType MemberType; - unsigned ParamIdx = -1; - auto IsMismatch = [&](CXXRecordDecl* RD, ArrayRef Args) - { - if(! RD->hasDefinition()) - return false; - auto MemberLookup = RD->lookup(Member); - QualType CurrentType; - if(MemberLookup.empty()) - { - if(! RD->getNumBases()) - return false; - for(auto& Base : RD->bases()) - { - auto sfinae_info = getSFINAETemplate(Base.getType(), false); - if(! sfinae_info) - { - // if the base is an opaque dependent type, we can't determine - // whether it's a SFINAE type - if(Base.getType()->isDependentType()) - return true; - continue; - } - // if the class inherits from itself, we can't determine whether - // it's a SFINAE type - if(declaresSameEntity(TD, sfinae_info->Template)) - return true; - - auto sfinae_result = isSFINAETemplate( - sfinae_info->Template, Member); - if(! sfinae_result) - return true; - - auto [template_params, controlling_params, param_idx] = *sfinae_result; - auto param_arg = tryGetTemplateArgument( - template_params, sfinae_info->Arguments, param_idx); - if(! param_arg) - return true; - auto CurrentTypeFromBase = param_arg->getAsType(); - if(CurrentType.isNull()) - CurrentType = CurrentTypeFromBase; - else if(! context_.hasSameType(CurrentType, CurrentTypeFromBase)) - return true; - } - // didn't find a base that defines the specified member - if(CurrentType.isNull()) - return false; - } - else - { - // ambiguous lookup - if(! MemberLookup.isSingleResult()) - return true; - if(auto* TND = dyn_cast(MemberLookup.front())) - CurrentType = TND->getUnderlyingType(); - else - // the specialization has a member with the right name, - // but it isn't an alias declaration/typedef declaration... - return true; - } - - #if 0 - if(! CurrentType->isDependentType()) - return ! context_.hasSameType(MemberType, CurrentType); - - auto FoundIdx = FindParam(Args, TemplateArgument(CurrentType)); - if(ParamIdx != -1 && FoundIdx != ParamIdx) - return true; - - ParamIdx = FoundIdx; - #endif - - if(CurrentType->isDependentType()) - { - auto FoundIdx = FindParam(Args, TemplateArgument(CurrentType)); - if(FoundIdx == static_cast(-1) || FoundIdx >= PrimaryArgs.size()) - return true; - ParamIdx = FoundIdx; - TemplateArgument MappedPrimary = PrimaryArgs[FoundIdx]; - assert(MappedPrimary.getKind() == TemplateArgument::Type); - CurrentType = MappedPrimary.getAsType(); - } - - if(MemberType.isNull()) - MemberType = CurrentType; - - return ! context_.hasSameType(MemberType, CurrentType); - }; - - if (IsMismatch(CTD->getTemplatedDecl(), PrimaryArgs)) - return std::nullopt; - - for(auto* CTSD : CTD->specializations()) - { - if(CTSD->isExplicitSpecialization() && - IsMismatch(CTSD, CTSD->getTemplateArgs().asArray())) - return std::nullopt; - } - - SmallVector PartialSpecs; - CTD->getPartialSpecializations(PartialSpecs); - - for(auto* CTPSD : PartialSpecs) - { - auto PartialArgs = CTPSD->getTemplateArgs().asArray(); - if(IsMismatch(CTPSD, PartialArgs)) - return std::nullopt; - for(std::size_t I = 0; I < PartialArgs.size(); ++I) - { - TemplateArgument Arg = PartialArgs[I]; - switch(Arg.getKind()) - { - case TemplateArgument::Integral: - case TemplateArgument::Declaration: - case TemplateArgument::StructuralValue: - case TemplateArgument::NullPtr: - break; - case TemplateArgument::Expression: - if(getNTTPFromExpr( - Arg.getAsExpr(), - CTPSD->getTemplateDepth() - 1)) - continue; - break; - default: - continue; - } - ControllingParams.set(I); - //.getAsExpr() - } - } - - return std::make_tuple(CTD->getTemplateParameters(), std::move(ControllingParams), ParamIdx); - } - - struct SFINAEInfo - { - TemplateDecl* Template = nullptr; - const IdentifierInfo* Member = nullptr; - ArrayRef Arguments; - }; - - std::optional - getSFINAETemplate(QualType T, bool AllowDependentNames) - { - assert(!T.isNull()); - SFINAEInfo SFINAE; - if(auto* ET = T->getAs()) - T = ET->getNamedType(); - - if(auto* DNT = T->getAsAdjusted(); - DNT && AllowDependentNames) - { - SFINAE.Member = DNT->getIdentifier(); - T = QualType(DNT->getQualifier()->getAsType(), 0); - } - - if(auto* TST = T->getAsAdjusted()) - { - SFINAE.Template = TST->getTemplateName().getAsTemplateDecl(); - SFINAE.Arguments = TST->template_arguments(); - return SFINAE; - } - return std::nullopt; - } - - std::optional>> - isSFINAEType(QualType T) - { - if(! config_->detectSfinae) - return std::nullopt; - - auto sfinae_info = getSFINAETemplate(T, true); - if(! sfinae_info) - return std::nullopt; - - auto sfinae_result = isSFINAETemplate( - sfinae_info->Template, sfinae_info->Member); - - if(! sfinae_result) - return std::nullopt; - - auto [template_params, controlling_params, param_idx] = *sfinae_result; - - auto Args = sfinae_info->Arguments; - auto param_arg = tryGetTemplateArgument( - template_params, Args, param_idx); - if(! param_arg) - return std::nullopt; - - std::vector ControllingArgs; - for(std::size_t I = 0; I < Args.size(); ++I) - { - if(controlling_params[I]) - ControllingArgs.emplace_back(Args[I]); - } - - return std::make_pair(param_arg->getAsType(), std::move(ControllingArgs)); - } - - std::optional>> - isSFINAEType(const Type* T) - { - return isSFINAEType(QualType(T, 0)); - } - - std::string extractName(DeclarationName N) - { - std::string result; - if(N.isEmpty()) - return result; - switch(N.getNameKind()) - { - case DeclarationName::Identifier: - if(const auto* I = N.getAsIdentifierInfo()) - result.append(I->getName()); - break; - case DeclarationName::CXXDestructorName: - result.push_back('~'); - [[fallthrough]]; - case DeclarationName::CXXConstructorName: - if(const auto* R = N.getCXXNameType()->getAsCXXRecordDecl()) - result.append(R->getIdentifier()->getName()); - break; - case DeclarationName::CXXDeductionGuideName: - if(const auto* T = N.getCXXDeductionGuideTemplate()) - result.append(T->getIdentifier()->getName()); - break; - case DeclarationName::CXXConversionFunctionName: - { - result.append("operator "); - // KRYSTIAN FIXME: we *really* should not be - // converting types to strings like this - result.append(toString( - *buildTypeInfo(N.getCXXNameType()))); - break; - } - case DeclarationName::CXXOperatorName: - { - OperatorKind K = convertToOperatorKind( - N.getCXXOverloadedOperator()); - result.append("operator"); - std::string_view name = getOperatorName(K); - if(std::isalpha(name.front())) - result.push_back(' '); - result.append(name); - break; - } - case DeclarationName::CXXLiteralOperatorName: - case DeclarationName::CXXUsingDirective: - break; - default: - MRDOCS_UNREACHABLE(); - } - return result; - } - - std::string extractName(const NamedDecl* D) - { - return extractName(D->getDeclName()); - } - - //------------------------------------------------ - - const Decl* getParentDecl(const Decl* D) - { - return getParentDecl(const_cast(D)); - } - - Decl* getParentDecl(Decl* D) - { - while((D = cast_if_present< - Decl>(D->getDeclContext()))) - { - switch(D->getKind()) - { - case Decl::CXXRecord: - // we treat anonymous unions as "transparent" - if(auto* RD = cast(D); - RD->isAnonymousStructOrUnion()) - break; - [[fallthrough]]; - case Decl::TranslationUnit: - case Decl::Namespace: - case Decl::Enum: - case Decl::ClassTemplateSpecialization: - case Decl::ClassTemplatePartialSpecialization: - return D; - // we consider all other DeclContexts to be "transparent" - default: - break; - } - } - return nullptr; - } - - /** Populate the Info with its parent namespaces - - Given a Decl `D`, `getParentNamespaces` will populate - the `Info` `I` with the SymbolID of each parent namespace - of `D`. The SymbolID of the global namespace is always - included as the first element of `I.Namespace`. - - @param I The mrdocs Info to populate - @param D The Decl to extract - */ - void - getParentNamespaces( - Info& I, - Decl* D) - { - Decl* PD = getParentDecl(D); - SymbolID ParentID = extractSymbolID(PD); - switch(PD->getKind()) - { - // The TranslationUnit DeclContext is the global namespace; - // it uses SymbolID::global and should *always* exist - case Decl::TranslationUnit: - { - MRDOCS_ASSERT(ParentID == SymbolID::global); - auto [P, created] = getOrCreateInfo< - NamespaceInfo>(ParentID); - emplaceChild(P, I); - break; - } - case Decl::Namespace: - { - auto [P, created] = getOrCreateInfo< - NamespaceInfo>(ParentID); - buildNamespace(P, created, cast(PD)); - emplaceChild(P, I); - break; - } - // special case for an explicit specializations of - // a member of an implicit instantiation. - case Decl::ClassTemplateSpecialization: - case Decl::ClassTemplatePartialSpecialization: - if(auto* S = dyn_cast(PD); - S && S->getSpecializationKind() == TSK_ImplicitInstantiation) - { - // KRYSTIAN FIXME: i'm pretty sure DeclContext::getDeclKind() - // will never be Decl::ClassTemplatePartialSpecialization for - // implicit instantiations; instead, the ClassTemplatePartialSpecializationDecl - // is accessible through S->getSpecializedTemplateOrPartial - // if the implicit instantiation used a partially specialized template, - MRDOCS_ASSERT(PD->getKind() != - Decl::ClassTemplatePartialSpecialization); - - auto [P, created] = getOrCreateInfo< - SpecializationInfo>(ParentID); - buildSpecialization(P, created, S); - emplaceChild(P, I); - break; - } - // non-implicit instantiations should be - // treated like normal CXXRecordDecls - [[fallthrough]]; - // we should never encounter a Record - // that is not a CXXRecord - case Decl::CXXRecord: - { - auto [P, created] = getOrCreateInfo< - RecordInfo>(ParentID); - buildRecord(P, created, cast(PD)); - emplaceChild(P, I); - break; - } - case Decl::Enum: - { - auto [P, created] = getOrCreateInfo< - EnumInfo>(ParentID); - buildEnum(P, created, cast(PD)); - emplaceChild(P, I); - break; - } - default: - MRDOCS_UNREACHABLE(); - } - - Info* P = getInfo(ParentID); - MRDOCS_ASSERT(P); - - I.Namespace.emplace_back(ParentID); - I.Namespace.insert(I.Namespace.end(), - P->Namespace.begin(), P->Namespace.end()); - } - - /** Emplace a child Info into a ScopeInfo - - Given a ScopeInfo `P` and an Info `C`, `emplaceChild` will - add the SymbolID of `C` to `P.Members` and `P.Lookups[C.Name]`. - - @param P The parent ScopeInfo - @param C The child Info - */ - void - emplaceChild( - ScopeInfo& P, - Info& C) - { - // Include C.id in P.Members if it's not already there - if(std::ranges::find(P.Members, C.id) == P.Members.end()) - P.Members.emplace_back(C.id); - - // Include C.id in P.Lookups[C.Name] if it's not already there - auto& lookups = P.Lookups.try_emplace(C.Name).first->second; - if(std::ranges::find(lookups, C.id) == lookups.end()) - lookups.emplace_back(C.id); - } - - //------------------------------------------------ - - void - buildSpecialization( - SpecializationInfo& I, - bool created, - ClassTemplateSpecializationDecl* D) - { - if(! created) - return; - - CXXRecordDecl* PD = getInstantiatedFrom(D); - - buildTemplateArgs(I.Args, - D->getTemplateArgs().asArray()); - - extractSymbolID(PD, I.Primary); - I.Name = extractName(PD); - - getParentNamespaces(I, D); - } - - //------------------------------------------------ - // Decl types which have isThisDeclarationADefinition: - // - // VarTemplateDecl - // FunctionTemplateDecl - // FunctionDecl - // TagDecl - // ClassTemplateDecl - // CXXDeductionGuideDecl - - /** Populate the NamespaceInfo with the content of a NamespaceDecl - - @param I The mrdocs NamespaceInfo to populate - @param created Whether the NamespaceInfo was just created - @param D The NamespaceDecl to extract - */ - void - buildNamespace( - NamespaceInfo& I, - bool created, - NamespaceDecl* D) - { - if(! created) - // Namespace already extracted: - // nothing to populate - return; - - // KRYSTIAN NOTE: we do not extract - // javadocs for namespaces - if(D->isAnonymousNamespace()) - I.IsAnonymous = true; - else - I.Name = extractName(D); - I.IsInline = D->isInline(); - - getParentNamespaces(I, D); - } - - //------------------------------------------------ - - void - buildRecord( - RecordInfo& I, - bool created, - CXXRecordDecl* D) - { - bool documented = parseRawComment(I.javadoc, D); - addSourceLocation(I, D->getBeginLoc(), - D->isThisDeclarationADefinition(), documented); - - if(! created) - return; - - NamedDecl* ND = D; - if(TypedefNameDecl* TD = - D->getTypedefNameForAnonDecl()) - { - I.IsTypeDef = true; - ND = TD; - } - I.Name = extractName(ND); - - I.KeyKind = convertToRecordKeyKind(D->getTagKind()); - - // These are from CXXRecordDecl::isEffectivelyFinal() - I.IsFinal = D->template hasAttr(); - if(const auto* DT = D->getDestructor()) - I.IsFinalDestructor = DT->template hasAttr(); - - // extract direct bases. D->bases() will get the bases - // from whichever declaration is the definition (if any) - if(D->hasDefinition()) - { - for(const CXXBaseSpecifier& B : D->bases()) - { - AccessSpecifier access = B.getAccessSpecifier(); - // KRYSTIAN FIXME: we need finer-grained control - // for protected bases, since an inheriting class - // will have access to the bases public members... - if(config_->inaccessibleBases != - ConfigImpl::SettingsImpl::ExtractPolicy::Always) - { - if(access == AccessSpecifier::AS_private || - access == AccessSpecifier::AS_protected) - continue; - } - // the extraction of the base type is - // performed in direct dependency mode - auto BaseType = buildTypeInfo( - B.getType(), - ExtractMode::DirectDependency); - // CXXBaseSpecifier::getEllipsisLoc indicates whether the - // base was a pack expansion; a PackExpansionType is not built - // for base-specifiers - if(BaseType && B.getEllipsisLoc().isValid()) - BaseType->IsPackExpansion = true; - I.Bases.emplace_back( - std::move(BaseType), - convertToAccessKind(access), - B.isVirtual()); - } - } - - getParentNamespaces(I, D); - } - - //------------------------------------------------ - - void - buildEnum( - EnumInfo& I, - bool created, - EnumDecl* D) - { - bool documented = parseRawComment(I.javadoc, D); - addSourceLocation(I, D->getBeginLoc(), - D->isThisDeclarationADefinition(), documented); - - if(! created) - return; - - I.Name = extractName(D); - - I.Scoped = D->isScoped(); - - if(D->isFixed()) - I.UnderlyingType = buildTypeInfo( - D->getIntegerType()); - - getParentNamespaces(I, D); - } - - //------------------------------------------------ - - void - buildEnumerator( - EnumeratorInfo& I, - bool created, - EnumConstantDecl* D) - { - bool documented = parseRawComment(I.javadoc, D); - addSourceLocation(I, D->getBeginLoc(), true, documented); - - if(! created) - return; - - I.Name = extractName(D); - - buildExprInfo( - I.Initializer, - D->getInitExpr(), - D->getInitVal()); - - getParentNamespaces(I, D); - } - - //------------------------------------------------ - - void - buildTypedef( - TypedefInfo& I, - bool created, - TypedefNameDecl* D) - { - bool documented = parseRawComment(I.javadoc, D); - // KRYSTIAN FIXME: we currently treat typedef/alias - // declarations as having a single definition; however, - // such declarations are never definitions and can - // be redeclared multiple times (even in the same scope) - addSourceLocation(I, D->getBeginLoc(), true, documented); - - if(! created) - return; - - I.Name = extractName(D); - - // When a symbol has a dependency on a typedef, we also - // consider the symbol to have a dependency on the aliased - // type. Therefore, we propagate the current dependency mode - // when building the TypeInfo for the aliased type - I.Type = buildTypeInfo( - D->getUnderlyingType(), - currentMode()); - - #if 0 - if(I.Type.Name.empty()) - { - // Typedef for an unnamed type. This is like - // "typedef struct { } Foo;". The record serializer - // explicitly checks for this syntax and constructs - // a record with that name, so we don't want to emit - // a duplicate here. - return; - } - #endif - - getParentNamespaces(I, D); - } - - //------------------------------------------------ - - void - buildVariable( - VariableInfo& I, - bool created, - VarDecl* D) - { - bool documented = parseRawComment(I.javadoc, D); - addSourceLocation(I, D->getBeginLoc(), - D->isThisDeclarationADefinition(), documented); - - // KRYSTIAN FIXME: we need to properly merge storage class - if(StorageClass SC = D->getStorageClass()) - I.StorageClass = convertToStorageClassKind(SC); - - // this handles thread_local, as well as the C - // __thread and __Thread_local specifiers - I.IsThreadLocal |= D->getTSCSpec() != - ThreadStorageClassSpecifier::TSCS_unspecified; - - // KRYSTIAN NOTE: VarDecl does not provide getConstexprKind, - // nor does it use getConstexprKind to store whether - // a variable is constexpr/constinit. Although - // only one is permitted in a variable declaration, - // it is possible to declare a static data member - // as both constexpr and constinit in separate declarations.. - I.IsConstinit |= D->hasAttr(); - if(D->isConstexpr()) - I.Constexpr = ConstexprKind::Constexpr; - - if(const Expr* E = D->getInit()) - buildExprInfo(I.Initializer, E); - - if(! created) - return; - - I.Name = extractName(D); - - I.Type = buildTypeInfo(D->getType()); - - getParentNamespaces(I, D); - } - - //------------------------------------------------ - - void - buildField( - FieldInfo& I, - bool created, - FieldDecl* D) - { - bool documented = parseRawComment(I.javadoc, D); - // fields (i.e. non-static data members) - // cannot have multiple declarations - addSourceLocation(I, D->getBeginLoc(), true, documented); - - if(! created) - return; - - I.Name = extractName(D); - - I.Type = buildTypeInfo(D->getType()); - - I.IsVariant = D->getParent()->isUnion(); - - I.IsMutable = D->isMutable(); - - if(const Expr* E = D->getInClassInitializer()) - buildExprInfo(I.Default, E); - - if(D->isBitField()) - { - I.IsBitfield = true; - buildExprInfo( - I.BitfieldWidth, - D->getBitWidth()); - } - - I.HasNoUniqueAddress = D->hasAttr(); - I.IsDeprecated = D->hasAttr(); - I.IsMaybeUnused = D->hasAttr(); - - getParentNamespaces(I, D); - } - - //------------------------------------------------ - - template - void - buildFunction( - FunctionInfo& I, - bool created, - DeclTy* D) - { - bool documented = parseRawComment(I.javadoc, D); - addSourceLocation(I, D->getBeginLoc(), - D->isThisDeclarationADefinition(), documented); - - // KRYSTIAN TODO: move other extraction that requires - // a valid function type here - if(auto FT = getDeclaratorType(D); ! FT.isNull()) - { - const auto* FPT = FT->template getAs(); - - buildNoexceptInfo(I.Noexcept, FPT); - - I.HasTrailingReturn |= FPT->hasTrailingReturn(); - } - - // - // FunctionDecl - // - I.OverloadedOperator = convertToOperatorKind( - D->getOverloadedOperator()); - I.IsVariadic |= D->isVariadic(); - I.IsDefaulted |= D->isDefaulted(); - I.IsExplicitlyDefaulted |= D->isExplicitlyDefaulted(); - I.IsDeleted |= D->isDeleted(); - I.IsDeletedAsWritten |= D->isDeletedAsWritten(); - I.IsNoReturn |= D->isNoReturn(); - // subsumes D->hasAttr() - // subsumes D->hasAttr() - // subsumes D->hasAttr() - // subsumes D->getType()->getAs()->getNoReturnAttr() - I.HasOverrideAttr |= D->template hasAttr(); - - if(ConstexprSpecKind CSK = D->getConstexprKind(); - CSK != ConstexprSpecKind::Unspecified) - I.Constexpr = convertToConstexprKind(CSK); - - if(StorageClass SC = D->getStorageClass()) - I.StorageClass = convertToStorageClassKind(SC); - - I.IsNodiscard |= D->template hasAttr(); - I.IsExplicitObjectMemberFunction |= D->hasCXXExplicitFunctionObjectParameter(); - // - // CXXMethodDecl - // - if constexpr(std::derived_from) - { - I.IsVirtual |= D->isVirtual(); - I.IsVirtualAsWritten |= D->isVirtualAsWritten(); - I.IsPure |= D->isPureVirtual(); - I.IsConst |= D->isConst(); - I.IsVolatile |= D->isVolatile(); - I.RefQualifier = convertToReferenceKind(D->getRefQualifier()); - I.IsFinal |= D->template hasAttr(); - //D->isCopyAssignmentOperator() - //D->isMoveAssignmentOperator() - //D->isOverloadedOperator(); - //D->isStaticOverloadedOperator(); - } - - // - // CXXDestructorDecl - // - if constexpr(std::derived_from) - { - } - - // - // CXXConstructorDecl - // - if constexpr(std::derived_from) - { - buildExplicitInfo(I.Explicit, D->getExplicitSpecifier()); - } - - // - // CXXConversionDecl - // - if constexpr(std::derived_from) - { - buildExplicitInfo(I.Explicit, D->getExplicitSpecifier()); - } - - for(const ParmVarDecl* P : D->parameters()) - { - Param& param = created ? - I.Params.emplace_back() : - I.Params[P->getFunctionScopeIndex()]; - - if(param.Name.empty()) - param.Name = P->getName(); - - if(! param.Type) - param.Type = buildTypeInfo(P->getOriginalType()); - - const Expr* default_arg = P->hasUninstantiatedDefaultArg() ? - P->getUninstantiatedDefaultArg() : P->getInit(); - if(param.Default.empty() && default_arg) - param.Default = getSourceCode( - default_arg->getSourceRange()); - } - - if(! created) - return; - - I.Name = extractName(D); - - I.Class = convertToFunctionClass( - D->getDeclKind()); - - QualType RT = D->getReturnType(); - ExtractMode next_mode = ExtractMode::IndirectDependency; - if(auto* AT = RT->getContainedAutoType(); - AT && AT->hasUnnamedOrLocalType()) - { - next_mode = ExtractMode::DirectDependency; - } - // extract the return type in direct dependency mode - // if it contains a placeholder type which is - // deduceded as a local class type - I.ReturnType = buildTypeInfo(RT, next_mode); - - if(auto* TRC = D->getTrailingRequiresClause()) - buildExprInfo(I.Requires, TRC); - - getParentNamespaces(I, D); - } - - //------------------------------------------------ - - void - buildGuide( - GuideInfo& I, - bool created, - CXXDeductionGuideDecl* D) - { - bool documented = parseRawComment(I.javadoc, D); - addSourceLocation(I, D->getBeginLoc(), true, documented); - - // deduction guides cannot be redeclared, so there is nothing to merge - if(! created) - return; - - I.Name = extractName(D->getDeducedTemplate()); - - I.Deduced = buildTypeInfo(D->getReturnType()); - - for(const ParmVarDecl* P : D->parameters()) - { - I.Params.emplace_back( - buildTypeInfo(P->getOriginalType()), - P->getNameAsString(), - // deduction guides cannot have default arguments - std::string()); - } - - buildExplicitInfo(I.Explicit, D->getExplicitSpecifier()); - - getParentNamespaces(I, D); - } - - //------------------------------------------------ - - void - buildFriend( - FriendInfo& I, - bool created, - FriendDecl* D) - { - bool documented = parseRawComment(I.javadoc, D); - addSourceLocation(I, D->getBeginLoc(), true, documented); - - if(! created) - return; - - // A NamedDecl nominated by a FriendDecl - // will be one of the following: - // - FunctionDecl - // - FunctionTemplateDecl - // - ClassTemplateDecl - if(NamedDecl* ND = D->getFriendDecl()) - { - extractSymbolID(ND, I.FriendSymbol); - // If this is a friend function declaration naming - // a previously undeclared function, traverse it. - // in addition to this, traverse the declaration if - // it's a class templates first declared as a friend - if((ND->isFunctionOrFunctionTemplate() && - ND->getFriendObjectKind() == Decl::FOK_Undeclared) || - (isa(ND) && ND->isFirstDecl())) - traverseDecl(ND); - } - // Since a friend declaration which name non-class types - // will be ignored, a type nominated by a FriendDecl can - // essentially be anything - if(TypeSourceInfo* TSI = D->getFriendType()) - I.FriendType = buildTypeInfo(TSI->getType()); - - getParentNamespaces(I, D); - } - - //------------------------------------------------ - - void - buildAlias( - AliasInfo& I, - bool created, - NamespaceAliasDecl* D) - { - bool documented = parseRawComment(I.javadoc, D); - addSourceLocation(I, D->getBeginLoc(), true, documented); - - if(! created) - return; - - I.Name = extractName(D); - auto& Underlying = I.AliasedSymbol = - std::make_unique(); - - NamedDecl* Aliased = D->getAliasedNamespace(); - Underlying->Name = Aliased->getIdentifier()->getName(); - getDependencyID(Aliased, Underlying->id); - if(NestedNameSpecifier* NNS = D->getQualifier()) - Underlying->Prefix = buildNameInfo(NNS); - - getParentNamespaces(I, D); - } - - //------------------------------------------------ - - void - buildUsing( - UsingInfo& I, - bool created, - UsingDecl* D) - { - bool documented = parseRawComment(I.javadoc, D); - addSourceLocation(I, D->getBeginLoc(), true, documented); - - if(! created) - return; - - I.Name = extractName(D); - I.Class = UsingClass::Normal; - I.Qualifier = buildNameInfo(D->getQualifier()); - - for (auto const* UDS : D->shadows()) - getDependencyID(UDS->getTargetDecl(), I.UsingSymbols.emplace_back()); - - getParentNamespaces(I, D); - } - - void - buildConcept( - ConceptInfo& I, - bool created, - ConceptDecl* D) - { - bool documented = parseRawComment(I.javadoc, D); - addSourceLocation(I, D->getBeginLoc(), true, documented); - - if(! created) - return; - - I.Name = extractName(D); - buildExprInfo(I.Constraint, D->getConstraintExpr()); - - getParentNamespaces(I, D); - } - - //------------------------------------------------ - - /** Get the DeclType as a MrDocs Info object - - The function will get or create the MrDocs Info - object for the `DeclType` and set the initial - Access specifier. - - @param D The declaration to extract - */ - template - Expected< - std::pair< - MrDocsType_t&, - bool>> - upsertMrDocsInfoFor(DeclType* D) - { - AccessSpecifier access = getAccess(D); - MRDOCS_CHECK_MSG( - shouldExtract(D, access), - "Symbol should not be extracted"); - - SymbolID id; - MRDOCS_CHECK_MSG( - extractSymbolID(D, id), - "Failed to extract symbol ID"); - - auto [I, created] = getOrCreateInfo>(id); - I.Access = convertToAccessKind(access); - return std::make_pair(std::ref(I), created); - } - - - /** Traverse a C++ namespace declaration - - This function is called by traverseDecl to traverse a - namespace declaration. - - A NamespaceDecl inherits from NamedDecl. - - */ - void - traverse(NamespaceDecl*); - - /** Traverse an enum declaration - - This function is called by traverseDecl to traverse an - enum declaration. - - An EnumDecl inherits from TagDecl. - - */ - void - traverse(EnumDecl*); - - /** Traverse an enum constant - - This function is called by traverseDecl to traverse an - enum constant. - - An EnumConstantDecl inherits from ValueDecl. - - */ - void - traverse(EnumConstantDecl*); - - /** Traverse a friend declaration - - This function is called by traverseDecl to traverse - a friend declaration. - - A FriendDecl inherits from Decl. - - */ - void - traverse(FriendDecl*); - - - /** Traverse a namespace alias declaration - - This function is called by traverseDecl to traverse - a namespace alias declaration. - - A NamespaceAliasDecl inherits from NamedDecl. - - */ - void - traverse(NamespaceAliasDecl*); - - /** Traverse a using directive - - This function is called by traverseDecl to traverse - a using directive. - - A UsingDirectiveDecl inherits from NamedDecl. - - */ - void - traverse(UsingDirectiveDecl*); - - /** Traverse a using declaration - - This function is called by traverseDecl to traverse - a using declaration. - - A UsingDecl inherits from NamedDecl. - - */ - void - traverse(UsingDecl*); - - /** Traverse a member of a struct, union, or class - - This function is called by traverseDecl to traverse - a member of a struct, union, or class. - - A FieldDecl inherits from DeclaratorDecl. - - */ - void - traverse(FieldDecl*); - - /** Traverse a member of an anonymous union. - - This function is called by traverseDecl to traverse - a member of an anonymous union. - - A IndirectFieldDecl inherits from ValueDecl. - - */ - void - traverse(IndirectFieldDecl*); - - /** Traverse a concept definition - - This function is called by traverseDecl to traverse a - C++ concept definition. - */ - void - traverse(ConceptDecl*); - - /** Traverse a deduction guide - - This function is called by traverseDecl to traverse a - C++ deduction guide. - - Deduction guides inherit from FunctionDecl. - - */ - void - traverse(CXXDeductionGuideDecl*, FunctionTemplateDecl* = nullptr); - - /** Traverse a C++ struct, union, or class - - This function is called by traverseDecl to traverse a - C++ struct, union, or class. - - */ - template CXXRecordTy> - void - traverse(CXXRecordTy*, ClassTemplateDecl* = nullptr); - - /** Traverse a variable declaration or definition - - This function is called by traverseDecl to traverse a - variable declaration or definition. - - */ - template VarTy> - void - traverse(VarTy*, VarTemplateDecl* = nullptr); - - /** Traverse a function declaration or definition - - This function is called by traverseDecl to traverse a - typedef declaration. - - */ - template FunctionTy> - void - traverse(FunctionTy*, FunctionTemplateDecl* = nullptr); - - /** Traverse a typedef declaration - - This function is called by traverseDecl to traverse a - typedef declaration. - - */ - template TypedefNameTy> - void - traverse(TypedefNameTy*, TypeAliasTemplateDecl* = nullptr); - -#if 0 - // includes both linkage-specification forms in [dcl.link]: - // extern string-literal { declaration-seq(opt) } - // extern string-literal name-declaration - void traverse(LinkageSpecDecl*); - void traverse(ExternCContextDecl*); - void traverse(ExportDecl*); -#endif - - /** Traverse any declaration - - Catch-all function so overload resolution does not - cause a hard error in the Traverse function for Decl. - - The function will attempt to convert the declaration - to a DeclContext and call traverseContext if - successful. - - */ - template - void - traverse(Decl* D, Args&&...); - - /** Traverse a declaration context - - This function is called to traverse the members of a declaration - context. - - The build() function will call this function with - traverseDecl(context_.getTranslationUnitDecl()) to initiate - the traversal of the entire AST. - - The function traverseContext(DeclContext* DC) will - also call this function to traverse each member of - the declaration context. - - */ - template - void - traverseDecl(Decl* D, Args&&... args); - - /** Traverse declaration contexts - - This function is called to traverse the members of a declaration - context. These decls are: - - * TranslationUnitDecl - * ExternCContext - * NamespaceDecl - * TagDecl - * OMPDeclareReductionDecl - * OMPDeclareMapperDecl - * FunctionDecl - * ObjCMethodDecl - * ObjCContainerDecl - * LinkageSpecDecl - * ExportDecl - * BlockDecl - * CapturedDecl - - The function will call traverseDecl for each member of the - declaration context. - - */ - void - traverseContext(DeclContext* DC); - - bool - isInSpecialNamespace( - const Decl* D, - std::span Patterns) + // Handling UnresolvedUsingTypenameDecl + if (auto const* UD = dyn_cast(D)) { - if(! D || Patterns.empty()) - return false; - const DeclContext* DC = isa(D) ? - dyn_cast(D) : D->getDeclContext(); - for(; DC; DC = DC->getParent()) + if (index::generateUSRForDecl(UD, res)) { - const NamespaceDecl* ND = dyn_cast(DC); - if(! ND) - continue; - for(const auto& Pattern : Patterns) - if(Pattern.matches(ND->getQualifiedNameAsString())) - return true; + return Unexpected(Error("Failed to generate USR")); } - return false; + res.append("@UUTDec"); + res.append(UD->getQualifiedNameAsString()); + return res; } - bool - isInSpecialNamespace( - const NestedNameSpecifier* NNS, - std::span Patterns) + // Handling UnresolvedUsingValueDecl + if (auto const* UD = dyn_cast(D)) { - const NamedDecl* ND = nullptr; - while(NNS) + if (index::generateUSRForDecl(UD, res)) { - if((ND = NNS->getAsNamespace())) - break; - if((ND = NNS->getAsNamespaceAlias())) - break; - NNS = NNS->getPrefix(); + return Unexpected(Error("Failed to generate USR")); } - return ND && isInSpecialNamespace(ND, Patterns); + res.append("@UUV"); + res.append(UD->getQualifiedNameAsString()); + return res; } - bool - checkSpecialNamespace( - std::unique_ptr& I, - const NestedNameSpecifier* NNS, - const Decl* D) + // Handling UsingPackDecl + if (auto const* UD = dyn_cast(D)) { - if(isInSpecialNamespace(NNS, config_->seeBelowFilter) || - isInSpecialNamespace(D, config_->seeBelowFilter)) + if (index::generateUSRForDecl(UD, res)) { - I = std::make_unique(); - I->Name = "see-below"; - return true; + return Unexpected(Error("Failed to generate USR")); } + res.append("@UPD"); + res.append(UD->getQualifiedNameAsString()); + return res; + } - if(isInSpecialNamespace(NNS, config_->implementationDefinedFilter) || - isInSpecialNamespace(D, config_->implementationDefinedFilter)) + // Handling UsingEnumDecl + if (auto const* UD = dyn_cast(D)) + { + if (index::generateUSRForDecl(UD, res)) { - I = std::make_unique(); - I->Name = "implementation-defined"; - return true; + return Unexpected(Error("Failed to generate USR")); } - return false; + res.append("@UED"); + EnumDecl const* ED = UD->getEnumDecl(); + res.append(ED->getQualifiedNameAsString()); + return res; } - bool - checkSpecialNamespace( - std::unique_ptr& I, - const NestedNameSpecifier* NNS, - const Decl* D) + // KRYSTIAN NOTE: clang doesn't currently support + // generating USRs for friend declarations, so we + // will improvise until I can merge a patch which + // adds support for them + if(auto const* FD = dyn_cast(D)) { - std::unique_ptr Name; - if(checkSpecialNamespace(Name, NNS, D)) + // first, generate the USR for the containing class + if (index::generateUSRForDecl(cast(FD->getDeclContext()), res)) { - auto T = std::make_unique(); - T->Name = std::move(Name); - I = std::move(T); - return true; + return Unexpected(Error("Failed to generate USR")); + } + // add a seperator for uniqueness + res.append("@FD"); + // if the friend declaration names a type, + // use the USR generator for types + if (TypeSourceInfo* TSI = FD->getFriendType()) + { + if (index::generateUSRForType(TSI->getType(), context_, res)) + { + return Unexpected(Error("Failed to generate USR")); + } + return res; + } + // otherwise, fallthrough and append the + // USR of the nominated declaration + if (!((D = FD->getFriendDecl()))) + { + return Unexpected(Error("Failed to generate USR")); } - return false; } -}; - -//------------------------------------------------ -// NamespaceDecl -void -ASTVisitor:: -traverse(NamespaceDecl* D) -{ - if(! shouldExtract(D, AccessSpecifier::AS_none)) - return; + if (index::generateUSRForDecl(D, res)) + return Unexpected(Error("Failed to generate USR")); - if(D->isAnonymousNamespace() && - config_->anonymousNamespaces != - ConfigImpl::SettingsImpl::ExtractPolicy::Always) + const auto* Described = dyn_cast_if_present(D); + const auto* Templated = D; + if (auto const* DT = D->getDescribedTemplate()) { - // Always skip anonymous namespaces if so configured - if(config_->anonymousNamespaces == - ConfigImpl::SettingsImpl::ExtractPolicy::Never) - return; - - // Otherwise, skip extraction if this isn't a dependency - // KRYSTIAN FIXME: is this correct? a namespace should not - // be extracted as a dependency (until namespace aliases and - // using directives are supported) - if(currentMode() == ExtractMode::Normal) - return; + Described = DT; + if (auto const* TD = DT->getTemplatedDecl()) + { + Templated = TD; + } } - SymbolID id; - if(! extractSymbolID(D, id)) - return; - auto [I, created] = getOrCreateInfo(id); - - buildNamespace(I, created, D); - traverseContext(D); -} - -//------------------------------------------------ -// EnumDecl - -void -ASTVisitor:: -traverse(EnumDecl* D) -{ - auto exp = upsertMrDocsInfoFor(D); - if(! exp) { return; } - auto [I, created] = *exp; - buildEnum(I, created, D); - traverseContext(D); -} - -//------------------------------------------------ -// FieldDecl + if(Described) + { + const TemplateParameterList* TPL = Described->getTemplateParameters(); + if(const auto* RC = TPL->getRequiresClause()) + { + RC = SubstituteConstraintExpressionWithoutSatisfaction( + sema_, cast(isa(Described) ? Described : Templated), RC); + if (!RC) + { + return Unexpected(Error("Failed to generate USR")); + } + ODRHash odr_hash; + odr_hash.AddStmt(RC); + res.append("@TPL#"); + res.append(llvm::itostr(odr_hash.CalculateHash())); + } + } -void -ASTVisitor:: -traverse(FieldDecl* D) -{ - auto exp = upsertMrDocsInfoFor(D); - if(! exp) { return; } - auto [I, created] = *exp; - buildField(I, created, D); -} + if(auto* FD = dyn_cast(Templated); + FD && FD->getTrailingRequiresClause()) + { + const Expr* RC = FD->getTrailingRequiresClause(); + RC = SubstituteConstraintExpressionWithoutSatisfaction( + sema_, cast(Described ? Described : Templated), RC); + if (!RC) + { + return Unexpected(Error("Failed to generate USR")); + } + ODRHash odr_hash; + odr_hash.AddStmt(RC); + res.append("@TRC#"); + res.append(llvm::itostr(odr_hash.CalculateHash())); + } -void -ASTVisitor:: -traverse(IndirectFieldDecl* D) -{ - traverse(D->getAnonField()); + return res; } -//------------------------------------------------ -// ConceptDecl - -void +bool ASTVisitor:: -traverse(ConceptDecl* D) +generateID( + const Decl* D, + SymbolID& id) const { - auto exp = upsertMrDocsInfoFor(D); - if(! exp) { return; } - auto [I, created] = *exp; + if (!D) + { + return false; + } - if(! I.Template) - I.Template = std::make_unique(); - buildTemplateParams(*I.Template, D->getTemplateParameters()); - buildConcept(I, created, D); -} + if (isa(D)) + { + id = SymbolID::global; + return true; + } -//------------------------------------------------ -// EnumConstantDecl + if (auto exp = generateUSR(D)) + { + auto h = llvm::SHA1::hash(arrayRefFromStringRef(*exp)); + id = SymbolID(h.data()); + return true; + } -void -ASTVisitor:: -traverse(EnumConstantDecl* D) -{ - auto exp = upsertMrDocsInfoFor(D); - if(! exp) { return; } - auto [I, created] = *exp; - buildEnumerator(I, created, D); + return false; } -//------------------------------------------------ -// FriendDecl - -void +SymbolID ASTVisitor:: -traverse(FriendDecl* D) +generateID(const Decl* D) const { - auto exp = upsertMrDocsInfoFor(D); - if(! exp) { return; } - auto [I, created] = *exp; - buildFriend(I, created, D); + SymbolID id = SymbolID::invalid; + generateID(D, id); + return id; } -//------------------------------------------------ -// NamespaceAliasDecl - void ASTVisitor:: -traverse(NamespaceAliasDecl* D) +populate( + NamespaceInfo& I, + bool const created, + NamespaceDecl* D) { - auto exp = upsertMrDocsInfoFor(D); - if(! exp) { return; } - auto [I, created] = *exp; - buildAlias(I, created, D); + MRDOCS_CHECK_OR(created); + I.IsAnonymous = D->isAnonymousNamespace(); + if (!I.IsAnonymous) + { + I.Name = extractName(D); + } + I.IsInline = D->isInline(); + populateNamespaces(I, D); } -//------------------------------------------------ -// UsingDirectiveDecl - void ASTVisitor:: -traverse(UsingDirectiveDecl* D) +populate( + RecordInfo& I, + bool const created, + CXXRecordDecl* D) { - if(! shouldExtract(D, getAccess(D))) - return; + bool const documented = generateJavadoc(I.javadoc, D); + populate(I, D->getBeginLoc(), + D->isThisDeclarationADefinition(), documented); - Decl* PD = getParentDecl(D); - // only extract using-directives in namespace scope - if(! cast(PD)->isFileContext()) + if (!created) + { return; + } - if(Info* PI = getInfo(extractSymbolID(PD))) + NamedDecl const* ND = D; + if (TypedefNameDecl const* TD = D->getTypedefNameForAnonDecl()) { - assert(PI->isNamespace()); - NamespaceInfo* NI = static_cast(PI); - getDependencyID( - D->getNominatedNamespaceAsWritten(), - NI->UsingDirectives.emplace_back()); + I.IsTypeDef = true; + ND = TD; } -} + I.Name = extractName(ND); -//------------------------------------------------ -// UsingDecl + I.KeyKind = toRecordKeyKind(D->getTagKind()); -void -ASTVisitor:: -traverse(UsingDecl* D) -{ - auto const exp = upsertMrDocsInfoFor(D); - if(! exp) { return; } - auto [I, created] = *exp; - buildUsing(I, created, D); -} + // These are from CXXRecordDecl::isEffectivelyFinal() + I.IsFinal = D->hasAttr(); + if (auto const* DT = D->getDestructor()) + { + I.IsFinalDestructor = DT->hasAttr(); + } -//------------------------------------------------ + // Extract direct bases. D->bases() will get the bases + // from whichever declaration is the definition (if any) + if(D->hasDefinition()) + { + for(const CXXBaseSpecifier& B : D->bases()) + { + AccessSpecifier const access = B.getAccessSpecifier(); + // KRYSTIAN FIXME: we need finer-grained control + // for protected bases, since an inheriting class + // will have access to the bases public members... + if(config_->inaccessibleBases != + ConfigImpl::SettingsImpl::ExtractPolicy::Always) + { + if(access == AccessSpecifier::AS_private || + access == AccessSpecifier::AS_protected) + continue; + } + // the extraction of the base type is + // performed in direct dependency mode + auto BaseType = toTypeInfo( + B.getType(), + ExtractMode::DirectDependency); + // CXXBaseSpecifier::getEllipsisLoc indicates whether the + // base was a pack expansion; a PackExpansionType is not built + // for base-specifiers + if(BaseType && B.getEllipsisLoc().isValid()) + BaseType->IsPackExpansion = true; + I.Bases.emplace_back( + std::move(BaseType), + convertToAccessKind(access), + B.isVirtual()); + } + } + + populateNamespaces(I, D); +} -template CXXRecordTy> +template DeclTy> void ASTVisitor:: -traverse( - CXXRecordTy* D, - ClassTemplateDecl* CTD) +populate( + FunctionInfo& I, + bool const created, + DeclTy* D) { - auto exp = upsertMrDocsInfoFor(D); - if(! exp) { return; } - auto [I, created] = *exp; + bool const documented = generateJavadoc(I.javadoc, D); + populate(I, D->getBeginLoc(), + D->isThisDeclarationADefinition(), documented); - // CTD is the specialized template if D is a partial or - // explicit specialization, and the described template otherwise - if(CTD) + // KRYSTIAN TODO: move other extraction that requires + // a valid function type here + if (auto FT = getDeclaratorType(D); ! FT.isNull()) { - if(! I.Template) - I.Template = std::make_unique(); - // if D is a partial/explicit specialization, extract the template arguments - if(auto* CTSD = dyn_cast(D)) - { - extractSymbolID(getInstantiatedFrom(CTD), I.Template->Primary); - // extract the template arguments of the specialization - buildTemplateArgs(I.Template->Args, CTSD->getTemplateArgsAsWritten()); - // extract the template parameters if this is a partial specialization - if(auto* CTPSD = dyn_cast(D)) - buildTemplateParams(*I.Template, CTPSD->getTemplateParameters()); - } - else - { - // otherwise, extract the template parameter list from CTD - buildTemplateParams(*I.Template, CTD->getTemplateParameters()); - } + const auto* FPT = FT->template getAs(); + populate(I.Noexcept, FPT); + I.HasTrailingReturn |= FPT->hasTrailingReturn(); } - buildRecord(I, created, D); - traverseContext(D); -} + // + // FunctionDecl + // + I.OverloadedOperator = convertToOperatorKind( + D->getOverloadedOperator()); + I.IsVariadic |= D->isVariadic(); + I.IsDefaulted |= D->isDefaulted(); + I.IsExplicitlyDefaulted |= D->isExplicitlyDefaulted(); + I.IsDeleted |= D->isDeleted(); + I.IsDeletedAsWritten |= D->isDeletedAsWritten(); + I.IsNoReturn |= D->isNoReturn(); + // subsumes D->hasAttr() + // subsumes D->hasAttr() + // subsumes D->hasAttr() + // subsumes D->getType()->getAs()->getNoReturnAttr() + I.HasOverrideAttr |= D->template hasAttr(); -template VarTy> -void -ASTVisitor:: -traverse( - VarTy* D, - VarTemplateDecl* VTD) -{ - auto exp = upsertMrDocsInfoFor(D); - if(! exp) { return; } - auto [I, created] = *exp; + if (ConstexprSpecKind const CSK = D->getConstexprKind(); + CSK != ConstexprSpecKind::Unspecified) + { + I.Constexpr = toConstexprKind(CSK); + } - // VTD is the specialized template if D is a partial or - // explicit specialization, and the described template otherwise - if(VTD) + if (StorageClass const SC = D->getStorageClass()) { - if(! I.Template) - I.Template = std::make_unique(); - // if D is a partial/explicit specialization, extract the template arguments - if(auto* VTSD = dyn_cast(D)) - { - extractSymbolID(getInstantiatedFrom(VTD), I.Template->Primary); - // extract the template arguments of the specialization - buildTemplateArgs(I.Template->Args, VTSD->getTemplateArgsAsWritten()); - // extract the template parameters if this is a partial specialization - if(auto* VTPSD = dyn_cast(D)) - buildTemplateParams(*I.Template, VTPSD->getTemplateParameters()); - } - else - { - // otherwise, extract the template parameter list from VTD - buildTemplateParams(*I.Template, VTD->getTemplateParameters()); - } + I.StorageClass = toStorageClassKind(SC); } - buildVariable(I, created, D); -} + I.IsNodiscard |= D->template hasAttr(); + I.IsExplicitObjectMemberFunction |= D->hasCXXExplicitFunctionObjectParameter(); + // + // CXXMethodDecl + // + if constexpr(std::derived_from) + { + I.IsVirtual |= D->isVirtual(); + I.IsVirtualAsWritten |= D->isVirtualAsWritten(); + I.IsPure |= D->isPureVirtual(); + I.IsConst |= D->isConst(); + I.IsVolatile |= D->isVolatile(); + I.RefQualifier = convertToReferenceKind(D->getRefQualifier()); + I.IsFinal |= D->template hasAttr(); + //D->isCopyAssignmentOperator() + //D->isMoveAssignmentOperator() + //D->isOverloadedOperator(); + //D->isStaticOverloadedOperator(); + } -void -ASTVisitor:: -traverse( - CXXDeductionGuideDecl* D, - FunctionTemplateDecl* FTD) -{ - auto exp = upsertMrDocsInfoFor(D); - if(! exp) { return; } - auto [I, created] = *exp; + // + // CXXDestructorDecl + // + // if constexpr(std::derived_from) + // { + // } - // D is the templated declaration if FTD is non-null - if(FTD) + // + // CXXConstructorDecl + // + if constexpr(std::derived_from) { - if(! I.Template) - I.Template = std::make_unique(); - buildTemplateParams(*I.Template, FTD->getTemplateParameters()); + populate(I.Explicit, D->getExplicitSpecifier()); } - buildGuide(I, created, D); -} - -template FunctionTy> -void -ASTVisitor:: -traverse( - FunctionTy* D, - FunctionTemplateDecl* FTD) -{ - auto exp = upsertMrDocsInfoFor(D); - if(! exp) { return; } - auto [I, created] = *exp; + // + // CXXConversionDecl + // + if constexpr(std::derived_from) + { + populate(I.Explicit, D->getExplicitSpecifier()); + } - // D is the templated declaration if FTD is non-null - if(FTD || D->isFunctionTemplateSpecialization()) + for (ParmVarDecl const* P : D->parameters()) { - if(! I.Template) - I.Template = std::make_unique(); + Param& param = created ? + I.Params.emplace_back() : + I.Params[P->getFunctionScopeIndex()]; - if(auto* FTSI = D->getTemplateSpecializationInfo()) + if (param.Name.empty()) { - extractSymbolID(getInstantiatedFrom( - FTSI->getTemplate()), I.Template->Primary); - // TemplateArguments is used instead of TemplateArgumentsAsWritten - // because explicit specializations of function templates may have - // template arguments deduced from their return type and parameters - if(auto* Args = FTSI->TemplateArguments) - buildTemplateArgs(I.Template->Args, Args->asArray()); + param.Name = P->getName(); } - else if(auto* DFTSI = D->getDependentSpecializationInfo()) + + if (!param.Type) { - // Only extract the ID of the primary template if there is - // a single candidate primary template. - if(auto Candidates = DFTSI->getCandidates(); Candidates.size() == 1) - extractSymbolID(getInstantiatedFrom( - Candidates.front()), I.Template->Primary); - if(auto* Args = DFTSI->TemplateArgumentsAsWritten) - buildTemplateArgs(I.Template->Args, Args); + param.Type = toTypeInfo(P->getOriginalType()); } - else + + const Expr* default_arg = P->hasUninstantiatedDefaultArg() ? + P->getUninstantiatedDefaultArg() : P->getInit(); + if (param.Default.empty() && + default_arg) { - buildTemplateParams(*I.Template, FTD->getTemplateParameters()); + param.Default = getSourceCode(default_arg->getSourceRange()); } } - if constexpr(std::same_as) - buildGuide(I, created, D); - else - buildFunction(I, created, D); -} + if (!created) + { + return; + } -template TypedefNameTy> -void -ASTVisitor:: -traverse( - TypedefNameTy* D, - TypeAliasTemplateDecl* ATD) -{ - auto exp = upsertMrDocsInfoFor(D); - if(! exp) { return; } - auto [I, created] = *exp; + I.Name = extractName(D); - if(isa(D)) - I.IsUsing = true; + I.Class = toFunctionClass(D->getDeclKind()); - if(ATD) + QualType const RT = D->getReturnType(); + auto next_mode = ExtractMode::IndirectDependency; + if (auto const* AT = RT->getContainedAutoType(); + AT && AT->hasUnnamedOrLocalType()) { - if(! I.Template) - I.Template = std::make_unique(); - buildTemplateParams(*I.Template, - ATD->getTemplateParameters()); + next_mode = ExtractMode::DirectDependency; } + // extract the return type in direct dependency mode + // if it contains a placeholder type which is + // deduceded as a local class type + I.ReturnType = toTypeInfo(RT, next_mode); - buildTypedef(I, created, D); -} + if(auto* TRC = D->getTrailingRequiresClause()) + populate(I.Requires, TRC); -template -void -ASTVisitor:: -traverse(Decl* D, Args&&...) -{ - // If this is a DeclContext, traverse its members - if(auto* DC = dyn_cast(D)) - traverseContext(DC); + populateNamespaces(I, D); } -//------------------------------------------------ - -template void ASTVisitor:: -traverseDecl( - Decl* D, - Args&&... args) +populate( + EnumInfo& I, + bool const created, + EnumDecl* D) { - MRDOCS_ASSERT(D); + bool const documented = generateJavadoc(I.javadoc, D); + populate(I, D->getBeginLoc(), + D->isThisDeclarationADefinition(), documented); - // Decl had a semantic error - if(D->isInvalidDecl()) + if (!created) + { return; + } - SymbolFilter::FilterScope scope(symbolFilter_); + I.Name = extractName(D); - // Convert to the most derived type of the Decl - // and call the appropriate traverse function - visit(D, [&](DeclTy* DD) + I.Scoped = D->isScoped(); + + if (D->isFixed()) { - if constexpr(std::derived_from) - { - // Only ClassTemplateDecl, FunctionTemplateDecl, - // VarTemplateDecl, and TypeAliasDecl are derived - // from RedeclarableTemplateDecl. - // This doesn't include ConceptDecl. - // Recursively call traverseDecl so traverse is called with - // a pointer to the most derived type of the templated Decl - traverseDecl(DD->getTemplatedDecl(), DD); - } - else if constexpr(std::derived_from) - { - // A class template specialization - traverse(DD, DD->getSpecializedTemplate()); - } - else if constexpr(std::derived_from) - { - // A variable template specialization - traverse(DD, DD->getSpecializedTemplate()); - } - else - { - // Call the appropriate traverse function - // for the most derived type of the Decl - traverse(DD, std::forward(args)...); - } - }); + I.UnderlyingType = toTypeInfo(D->getIntegerType()); + } + + populateNamespaces(I, D); } void ASTVisitor:: -traverseContext(DeclContext* DC) +populate( + EnumConstantInfo& I, + bool const created, + EnumConstantDecl* D) { - for(auto* D : DC->decls()) - traverseDecl(D); -} + bool const documented = generateJavadoc(I.javadoc, D); + populate(I, D->getBeginLoc(), true, documented); + + if (!created) + { + return; + } + + I.Name = extractName(D); -//------------------------------------------------ + populate( + I.Initializer, + D->getInitExpr(), + D->getInitVal()); -/** Get the user-written `Decl` for a `Decl` + populateNamespaces(I, D); +} - Given a `Decl` `D`, `InstantiatedFromVisitor` will return the - user-written `Decl` corresponding to `D`. For specializations - which were implicitly instantiated, this will be whichever `Decl` - was used as the pattern for instantiation. -*/ -class InstantiatedFromVisitor - : public DeclVisitor +void +ASTVisitor:: +populate( + TypedefInfo& I, + bool const created, + TypedefNameDecl* D) { -public: - Decl* - VisitDecl(Decl* D) + bool const documented = generateJavadoc(I.javadoc, D); + + // KRYSTIAN FIXME: we currently treat typedef/alias + // declarations as having a single definition; however, + // such declarations are never definitions and can + // be redeclared multiple times (even in the same scope) + populate(I, D->getBeginLoc(), true, documented); + + if (!created) { - return D; + return; } - FunctionDecl* - VisitFunctionTemplateDecl(FunctionTemplateDecl* D) + I.Name = extractName(D); + + // When a symbol has a dependency on a typedef, we also + // consider the symbol to have a dependency on the aliased + // type. Therefore, we propagate the current dependency mode + // when building the TypeInfo for the aliased type + I.Type = toTypeInfo( + D->getUnderlyingType(), + currentMode()); + + populateNamespaces(I, D); +} + +void +ASTVisitor:: +populate( + VariableInfo& I, + bool const created, + VarDecl* D) +{ + bool const documented = generateJavadoc(I.javadoc, D); + populate(I, D->getBeginLoc(), + D->isThisDeclarationADefinition(), documented); + + // KRYSTIAN FIXME: we need to properly merge storage class + if (StorageClass const SC = D->getStorageClass()) { - while(auto* MT = D->getInstantiatedFromMemberTemplate()) - { - if(D->isMemberSpecialization()) - break; - D = MT; - } - return D->getTemplatedDecl(); + I.StorageClass = toStorageClassKind(SC); } - CXXRecordDecl* - VisitClassTemplateDecl(ClassTemplateDecl* D) + // this handles thread_local, as well as the C + // __thread and __Thread_local specifiers + I.IsThreadLocal |= D->getTSCSpec() != + ThreadStorageClassSpecifier::TSCS_unspecified; + + // KRYSTIAN NOTE: VarDecl does not provide getConstexprKind, + // nor does it use getConstexprKind to store whether + // a variable is constexpr/constinit. Although + // only one is permitted in a variable declaration, + // it is possible to declare a static data member + // as both constexpr and constinit in separate declarations.. + I.IsConstinit |= D->hasAttr(); + if (D->isConstexpr()) { - while (auto* MT = D->getInstantiatedFromMemberTemplate()) - { - if(D->isMemberSpecialization()) - break; - D = MT; - } - return D->getTemplatedDecl(); + I.Constexpr = ConstexprKind::Constexpr; } - VarDecl* - VisitVarTemplateDecl(VarTemplateDecl* D) + if (Expr const* E = D->getInit()) { - while(auto* MT = D->getInstantiatedFromMemberTemplate()) - { - if(D->isMemberSpecialization()) - break; - D = MT; - } - return D->getTemplatedDecl(); + populate(I.Initializer, E); } - TypedefNameDecl* - VisitTypeAliasTemplateDecl(TypeAliasTemplateDecl* D) + if (!created) { - if(auto* MT = D->getInstantiatedFromMemberTemplate()) - { - // KRYSTIAN NOTE: we don't really need to check this - if(! D->isMemberSpecialization()) - D = MT; - } - return VisitTypedefNameDecl(D->getTemplatedDecl()); + return; } - FunctionDecl* - VisitFunctionDecl(FunctionDecl* D) - { - FunctionDecl const* DD = nullptr; - if(D->isDefined(DD, false)) - D = const_cast(DD); + I.Name = extractName(D); - if(MemberSpecializationInfo* MSI = D->getMemberSpecializationInfo()) - { - if(! MSI->isExplicitSpecialization()) - D = cast( - MSI->getInstantiatedFrom()); - } - else if(D->getTemplateSpecializationKind() != - TSK_ExplicitSpecialization) - { - D = D->getFirstDecl(); - if(auto* FTD = D->getPrimaryTemplate()) - D = VisitFunctionTemplateDecl(FTD); - } - return D; - } + I.Type = toTypeInfo(D->getType()); + + populateNamespaces(I, D); +} + +void +ASTVisitor:: +populate( + FieldInfo& I, + bool const created, + FieldDecl* D) +{ + bool const documented = generateJavadoc(I.javadoc, D); + // fields (i.e. non-static data members) + // cannot have multiple declarations + populate(I, D->getBeginLoc(), true, documented); - CXXRecordDecl* - VisitClassTemplatePartialSpecializationDecl(ClassTemplatePartialSpecializationDecl* D) + if (!created) { - while (auto* MT = D->getInstantiatedFromMember()) - { - if(D->isMemberSpecialization()) - break; - D = MT; - } - return VisitClassTemplateSpecializationDecl(D); + return; } - CXXRecordDecl* - VisitClassTemplateSpecializationDecl(ClassTemplateSpecializationDecl* D) + I.Name = extractName(D); + + I.Type = toTypeInfo(D->getType()); + + I.IsVariant = D->getParent()->isUnion(); + + I.IsMutable = D->isMutable(); + + if(const Expr* E = D->getInClassInitializer()) + populate(I.Default, E); + + if(D->isBitField()) { - if(! D->isExplicitSpecialization()) - { - auto inst_from = D->getSpecializedTemplateOrPartial(); - if(auto* CTPSD = inst_from.dyn_cast< - ClassTemplatePartialSpecializationDecl*>()) - { - MRDOCS_ASSERT(D != CTPSD); - return VisitClassTemplatePartialSpecializationDecl(CTPSD); - } - // Explicit instantiation declaration/definition - else if(auto* CTD = inst_from.dyn_cast< - ClassTemplateDecl*>()) - { - return VisitClassTemplateDecl(CTD); - } - } - return VisitCXXRecordDecl(D); + I.IsBitfield = true; + populate( + I.BitfieldWidth, + D->getBitWidth()); } - CXXRecordDecl* - VisitCXXRecordDecl(CXXRecordDecl* D) + I.HasNoUniqueAddress = D->hasAttr(); + I.IsDeprecated = D->hasAttr(); + I.IsMaybeUnused = D->hasAttr(); + + populateNamespaces(I, D); +} + +void +ASTVisitor:: +populate( + SpecializationInfo& I, + bool const created, + ClassTemplateSpecializationDecl* D) +{ + if (!created) { - while(MemberSpecializationInfo* MSI = - D->getMemberSpecializationInfo()) - { - // if this is a member of an explicit specialization, - // then we have the correct declaration - if(MSI->isExplicitSpecialization()) - break; - D = cast(MSI->getInstantiatedFrom()); - } - return D; + return; } - VarDecl* - VisitVarTemplatePartialSpecializationDecl(VarTemplatePartialSpecializationDecl* D) + CXXRecordDecl const* PD = getInstantiatedFrom(D); + + populate(I.Args, D->getTemplateArgs().asArray()); + + generateID(PD, I.Primary); + I.Name = extractName(PD); + + populateNamespaces(I, D); +} + +void +ASTVisitor:: +populate( + FriendInfo& I, + bool const created, + FriendDecl* D) +{ + bool const documented = generateJavadoc(I.javadoc, D); + populate(I, D->getBeginLoc(), true, documented); + + if (!created) { - while(auto* MT = D->getInstantiatedFromMember()) - { - if(D->isMemberSpecialization()) - break; - D = MT; - } - return VisitVarTemplateSpecializationDecl(D); + return; } - VarDecl* - VisitVarTemplateSpecializationDecl(VarTemplateSpecializationDecl* D) + // A NamedDecl nominated by a FriendDecl + // will be one of the following: + // - FunctionDecl + // - FunctionTemplateDecl + // - ClassTemplateDecl + if (NamedDecl* ND = D->getFriendDecl()) { - if(! D->isExplicitSpecialization()) + generateID(ND, I.FriendSymbol); + // If this is a friend function declaration naming + // a previously undeclared function, traverse it. + // in addition to this, traverse the declaration if + // it's a class templates first declared as a friend + if((ND->isFunctionOrFunctionTemplate() && + ND->getFriendObjectKind() == Decl::FOK_Undeclared) || + (isa(ND) && ND->isFirstDecl())) { - auto inst_from = D->getSpecializedTemplateOrPartial(); - if(auto* VTPSD = inst_from.dyn_cast< - VarTemplatePartialSpecializationDecl*>()) - { - MRDOCS_ASSERT(D != VTPSD); - return VisitVarTemplatePartialSpecializationDecl(VTPSD); - } - // explicit instantiation declaration/definition - else if(auto* VTD = inst_from.dyn_cast< - VarTemplateDecl*>()) - { - return VisitVarTemplateDecl(VTD); - } + traverseAny(ND); } - return VisitVarDecl(D); } - VarDecl* - VisitVarDecl(VarDecl* D) + // Since a friend declaration which name non-class types + // will be ignored, a type nominated by a FriendDecl can + // essentially be anything + if (TypeSourceInfo const* TSI = D->getFriendType()) { - while(MemberSpecializationInfo* MSI = - D->getMemberSpecializationInfo()) - { - if(MSI->isExplicitSpecialization()) - break; - D = cast(MSI->getInstantiatedFrom()); - } - return D; + I.FriendType = toTypeInfo(TSI->getType()); } - EnumDecl* - VisitEnumDecl(EnumDecl* D) + populateNamespaces(I, D); +} + +void +ASTVisitor:: +populate( + GuideInfo& I, + bool const created, + CXXDeductionGuideDecl* D) +{ + bool const documented = generateJavadoc(I.javadoc, D); + populate(I, D->getBeginLoc(), true, documented); + + // deduction guides cannot be redeclared, so there is nothing to merge + if (!created) { - while(MemberSpecializationInfo* MSI = - D->getMemberSpecializationInfo()) - { - if(MSI->isExplicitSpecialization()) - break; - D = cast(MSI->getInstantiatedFrom()); - } - return D; + return; } - TypedefNameDecl* - VisitTypedefNameDecl(TypedefNameDecl* D) + I.Name = extractName(D->getDeducedTemplate()); + + I.Deduced = toTypeInfo(D->getReturnType()); + + for (const ParmVarDecl* P : D->parameters()) { - DeclContext* Context = D->getNonTransparentDeclContext(); - if(Context->isFileContext()) - return D; - DeclContext* ContextPattern = - cast(Visit(cast(Context))); - if(Context == ContextPattern) - return D; - auto lookup = ContextPattern->lookup(D->getDeclName()); - for(NamedDecl* ND : lookup) - { - if(auto* TND = dyn_cast(ND)) - return TND; - if(auto* TATD = dyn_cast(ND)) - return TATD->getTemplatedDecl(); - } - return D; + I.Params.emplace_back( + toTypeInfo(P->getOriginalType()), + P->getNameAsString(), + // deduction guides cannot have default arguments + std::string()); } -}; -template -DeclTy* -ASTVisitor:: -getInstantiatedFrom(DeclTy* D) -{ - if(! D) - return nullptr; - Decl* decayedD = const_cast(static_cast(D)); - Decl* resultDecl = InstantiatedFromVisitor().Visit(decayedD); - return cast(resultDecl); -} + populate(I.Explicit, D->getExplicitSpecifier()); -//------------------------------------------------ + populateNamespaces(I, D); +} -template -class TerminalTypeVisitor - : public TypeVisitor, bool> +void +ASTVisitor:: +populate( + NamespaceAliasInfo& I, + bool const created, + NamespaceAliasDecl* D) { - friend class TerminalTypeVisitor::TypeVisitor; + bool const documented = generateJavadoc(I.javadoc, D); + populate(I, D->getBeginLoc(), true, documented); - ASTVisitor& Visitor_; + if (!created) + { + return; + } - unsigned Quals_ = 0; - bool IsPack_ = false; - const NestedNameSpecifier* NNS_; + I.Name = extractName(D); + auto const& Underlying = I.AliasedSymbol = + std::make_unique(); - Derived& getDerived() + NamedDecl* Aliased = D->getAliasedNamespace(); + Underlying->Name = Aliased->getIdentifier()->getName(); + upsertDependency(Aliased, Underlying->id); + if (NestedNameSpecifier const* NNS = D->getQualifier()) { - return static_cast(*this); + Underlying->Prefix = toNameInfo(NNS); } - bool - VisitParenType( - const ParenType* T) + populateNamespaces(I, D); +} + +void +ASTVisitor:: +populate( + UsingInfo& I, + bool const created, + UsingDecl* D) +{ + bool const documented = generateJavadoc(I.javadoc, D); + populate(I, D->getBeginLoc(), true, documented); + + if (!created) { - return Visit(T->getInnerType()); + return; } - bool - VisitMacroQualified( - const MacroQualifiedType* T) + I.Name = extractName(D); + I.Class = UsingClass::Normal; + I.Qualifier = toNameInfo(D->getQualifier()); + + for (auto const* UDS: D->shadows()) { - return Visit(T->getUnderlyingType()); + upsertDependency( + UDS->getTargetDecl(), + I.UsingSymbols.emplace_back()); } - bool - VisitAttributedType( - const AttributedType* T) + populateNamespaces(I, D); +} + +void +ASTVisitor:: +populate( + ConceptInfo& I, + bool const created, + ConceptDecl* D) +{ + bool const documented = generateJavadoc(I.javadoc, D); + populate(I, D->getBeginLoc(), true, documented); + if (!created) { - return Visit(T->getModifiedType()); + return; } - bool - VisitAdjustedType( - const AdjustedType* T) + I.Name = extractName(D); + populate(I.Constraint, D->getConstraintExpr()); + + populateNamespaces(I, D); +} + +void +ASTVisitor:: +populate( + SourceInfo& I, + clang::SourceLocation const loc, + bool const definition, + bool const documented) +{ + unsigned line = source_.getPresumedLoc( + loc, false).getLine(); + FileInfo* file = findFileInfo(loc); + MRDOCS_ASSERT(file); + + if (definition) { - return Visit(T->getOriginalType()); + if (I.DefLoc) + { + return; + } + I.DefLoc.emplace(file->full_path, + file->short_path, line, file->kind, + documented); } - - bool - VisitUsingType( - const UsingType* T) + else { - return Visit(T->getUnderlyingType()); + auto const existing = std::ranges:: + find_if(I.Loc, + [line, file](const Location& l) + { + return l.LineNumber == line && + l.Path == file->full_path; + }); + if (existing != I.Loc.end()) + { + return; + } + I.Loc.emplace_back(file->full_path, + file->short_path, line, file->kind, + documented); } +} - bool - VisitSubstTemplateTypeParmType( - const SubstTemplateTypeParmType* T) +void +ASTVisitor:: +populate( + NoexceptInfo& I, + const FunctionProtoType* FPT) +{ + MRDOCS_ASSERT(FPT); + I.Implicit = ! FPT->hasNoexceptExceptionSpec(); + I.Kind = convertToNoexceptKind( + FPT->getExceptionSpecType()); + // store the operand, if any + if (Expr const* NoexceptExpr = FPT->getNoexceptExpr()) { - return Visit(T->getReplacementType()); + I.Operand = toString(NoexceptExpr); } +} - // ---------------------------------------------------------------- +void +ASTVisitor:: +populate( + ExplicitInfo& I, + const ExplicitSpecifier& ES) +{ + I.Implicit = ! ES.isSpecified(); + I.Kind = convertToExplicitKind(ES); - bool - VisitElaboratedType( - const ElaboratedType* T) + // store the operand, if any + if (Expr const* ExplicitExpr = ES.getExpr()) { - NNS_ = T->getQualifier(); - return Visit(T->getNamedType()); + I.Operand = toString(ExplicitExpr); } +} - bool - VisitPackExpansionType( - const PackExpansionType* T) +void +ASTVisitor:: +populate( + ExprInfo& I, + const Expr* E) +{ + if (!E) { - IsPack_ = true; - return Visit(T->getPattern()); + return; } + I.Written = getSourceCode( + E->getSourceRange()); +} - // ---------------------------------------------------------------- - - bool - VisitPointerType( - const PointerType* T) +template +void +ASTVisitor:: +populate( + ConstantExprInfo& I, + const Expr* E) +{ + populate(static_cast(I), E); + // if the expression is dependent, + // we cannot get its value + if (!E || E->isValueDependent()) { - getDerived().buildPointer(T, std::exchange(Quals_, 0)); - return Visit(T->getPointeeType()); + return; } + I.Value.emplace(toInteger(E->EvaluateKnownConstInt(context_))); +} + +template +void +ASTVisitor:: +populate( + ConstantExprInfo& I, + const Expr* E, + const llvm::APInt& V) +{ + populate(I, E); + I.Value.emplace(toInteger(V)); +} - bool - VisitLValueReferenceType( - const LValueReferenceType* T) +template +void +ASTVisitor:: +populate( + ConstantExprInfo& I, + const Expr* E, + const llvm::APInt& V); + +void +ASTVisitor:: +populate( + std::unique_ptr& I, + const NamedDecl* N) +{ + visit(N, [&](const DeclTy* P) { - getDerived().buildLValueReference(T); - Quals_ = 0; - return Visit(T->getPointeeType()); - } + constexpr Decl::Kind kind = + DeclToKind(); + + if constexpr(kind == Decl::TemplateTypeParm) + { + if (!I) + { + I = std::make_unique(); + } + auto* R = dynamic_cast(I.get()); + if (P->wasDeclaredWithTypename()) + { + R->KeyKind = TParamKeyKind::Typename; + } + if (P->hasDefaultArgument() && !R->Default) + { + R->Default = toTArg( + P->getDefaultArgument().getArgument()); + } + if(const TypeConstraint* TC = P->getTypeConstraint()) + { + const NestedNameSpecifier* NNS = + TC->getNestedNameSpecifierLoc().getNestedNameSpecifier(); + std::optional TArgs; + if (TC->hasExplicitTemplateArgs()) + { + TArgs.emplace(TC->getTemplateArgsAsWritten()); + } + R->Constraint = toNameInfo(TC->getNamedConcept(), TArgs, NNS); + } + return; + } + else if constexpr(kind == Decl::NonTypeTemplateParm) + { + if (!I) + { + I = std::make_unique(); + } + auto* R = dynamic_cast(I.get()); + R->Type = toTypeInfo(P->getType()); + if (P->hasDefaultArgument() && !R->Default) + { + R->Default = toTArg( + P->getDefaultArgument().getArgument()); + } + return; + } + else if constexpr(kind == Decl::TemplateTemplateParm) + { + if (!I) + { + I = std::make_unique(); + } + auto* R = dynamic_cast(I.get()); + if(R->Params.empty()) + { + for (NamedDecl const* NP: *P->getTemplateParameters()) + { + populate(R->Params.emplace_back(), NP); + } + } + if (P->hasDefaultArgument() && !R->Default) + { + R->Default = toTArg( + P->getDefaultArgument().getArgument()); + } + return; + } + MRDOCS_UNREACHABLE(); + }); - bool - VisitRValueReferenceType( - const RValueReferenceType* T) + if (I->Name.empty()) { - getDerived().buildRValueReference(T); - Quals_ = 0; - return Visit(T->getPointeeType()); + I->Name = extractName(N); } + // KRYSTIAN NOTE: Decl::isParameterPack + // returns true for function parameter packs + I->IsParameterPack = + N->isTemplateParameterPack(); +} - bool - VisitMemberPointerType( - const MemberPointerType* T) +void +ASTVisitor:: +populate( + TemplateInfo& TI, + const TemplateParameterList* TPL) +{ + for(std::size_t I = 0; I < TPL->size(); ++I) { - getDerived().buildMemberPointer(T, std::exchange(Quals_, 0)); - return Visit(T->getPointeeType()); + auto& PI = I < TI.Params.size() ? + TI.Params[I] : TI.Params.emplace_back(); + populate(PI, TPL->getParam(I)); } - - bool - VisitFunctionType( - const FunctionType* T) + if (auto* RC = TPL->getRequiresClause()) { - getDerived().buildFunction(T); - return Visit(T->getReturnType()); + populate(TI.Requires, RC); } +} - bool - VisitArrayType( - const ArrayType* T) +template +void +ASTVisitor:: +populate( + std::vector>& result, + Range&& args) +{ + for(const TemplateArgument& arg : args) { - getDerived().buildArray(T); - return Visit(T->getElementType()); + // KRYSTIAN NOTE: is this correct? should we have a + // separate TArgKind for packs instead of "unlaminating" + // them as we are doing here? + if (arg.getKind() == TemplateArgument::Pack) + { + populate(result, arg.pack_elements()); + } else + { + result.emplace_back(toTArg(arg)); + } } +} - // ---------------------------------------------------------------- +template +void +ASTVisitor:: +populate const&>( + std::vector>& result, + llvm::ArrayRef const& args); - bool - VisitDecltypeType( - const DecltypeType* T) - { - getDerived().buildDecltype(T, Quals_, IsPack_); - return true; - } +template +void +ASTVisitor:: +populate&>( + std::vector>& result, + llvm::ArrayRef& args); - bool - VisitAutoType( - const AutoType* T) - { - #if 0 - // KRYSTIAN NOTE: we don't use isDeduced because it will - // return true if the type is dependent - // if the type has been deduced, use the deduced type - if(QualType DT = T->getDeducedType(); ! DT.isNull()) - return Visit(DT); - #endif - getDerived().buildAuto(T, Quals_, IsPack_); - return true; - } +void +ASTVisitor:: +populate( + std::vector>& result, + const ASTTemplateArgumentListInfo* args) +{ + return populate(result, + std::views::transform(args->arguments(), + [](auto& x) -> auto& + { + return x.getArgument(); + })); +} - bool - VisitDeducedTemplateSpecializationType( - const DeducedTemplateSpecializationType* T) - { - // KRYSTIAN TODO: we should probably add a TypeInfo - // to represent deduced types that also stores what - // it was deduced as. - if(QualType DT = T->getDeducedType(); ! DT.isNull()) - return Visit(DT); - TemplateName TN = T->getTemplateName(); - MRDOCS_ASSERT(! TN.isNull()); - NamedDecl* ND = TN.getAsTemplateDecl(); - getDerived().buildTerminal(NNS_, ND, - std::nullopt, Quals_, IsPack_); - return true; - } - bool - VisitDependentNameType( - const DependentNameType* T) - { - if(auto SFINAE = getASTVisitor().isSFINAEType(T); SFINAE.has_value()) - return getDerived().Visit(SFINAE->first); +std::string +ASTVisitor:: +extractName(NamedDecl const* D) +{ + return extractName(D->getDeclName()); +} - if(auto* NNS = T->getQualifier()) - NNS_ = NNS; - getDerived().buildTerminal(NNS_, T->getIdentifier(), - std::nullopt, Quals_, IsPack_); - return true; +std::string +ASTVisitor:: +extractName(DeclarationName const N) +{ + std::string result; + if (N.isEmpty()) + { + return result; } - - bool - VisitDependentTemplateSpecializationType( - const DependentTemplateSpecializationType* T) + switch(N.getNameKind()) + { + case DeclarationName::Identifier: + if (auto const* I = N.getAsIdentifierInfo()) + { + result.append(I->getName()); + } + break; + case DeclarationName::CXXDestructorName: + result.push_back('~'); + [[fallthrough]]; + case DeclarationName::CXXConstructorName: + if (auto const* R = N.getCXXNameType()->getAsCXXRecordDecl()) + { + result.append(R->getIdentifier()->getName()); + } + break; + case DeclarationName::CXXDeductionGuideName: + if (auto const* T = N.getCXXDeductionGuideTemplate()) + { + result.append(T->getIdentifier()->getName()); + } + break; + case DeclarationName::CXXConversionFunctionName: { - if(auto* NNS = T->getQualifier()) - NNS_ = NNS; - getDerived().buildTerminal(NNS_, T->getIdentifier(), - T->template_arguments(), Quals_, IsPack_); - return true; + result.append("operator "); + // KRYSTIAN FIXME: we *really* should not be + // converting types to strings like this + result.append(mrdocs::toString(*toTypeInfo(N.getCXXNameType()))); + break; } - - bool - VisitTemplateSpecializationType( - const TemplateSpecializationType* T) + case DeclarationName::CXXOperatorName: { - if(auto SFINAE = getASTVisitor().isSFINAEType(T); SFINAE.has_value()) - return getDerived().Visit(SFINAE->first); - - TemplateName TN = T->getTemplateName(); - MRDOCS_ASSERT(! TN.isNull()); - NamedDecl* ND = TN.getAsTemplateDecl(); - if(! T->isTypeAlias()) + OperatorKind const K = convertToOperatorKind( + N.getCXXOverloadedOperator()); + result.append("operator"); + std::string_view const name = getOperatorName(K); + if (std::isalpha(name.front())) { - auto* CT = T->getCanonicalTypeInternal().getTypePtrOrNull(); - if(auto* ICT = dyn_cast_or_null(CT)) - ND = ICT->getDecl(); - else if(auto* RT = dyn_cast_or_null(CT)) - ND = RT->getDecl(); + result.push_back(' '); } - getDerived().buildTerminal(NNS_, ND, - T->template_arguments(), Quals_, IsPack_); - return true; + result.append(name); + break; } - - bool - VisitRecordType( - const RecordType* T) - { - RecordDecl* RD = T->getDecl(); - // if this is an instantiation of a class template, - // create a SpecializationTypeInfo & extract the template arguments - std::optional> TArgs = std::nullopt; - if(auto* CTSD = dyn_cast(RD)) - TArgs = CTSD->getTemplateArgs().asArray(); - getDerived().buildTerminal(NNS_, RD, - TArgs, Quals_, IsPack_); - return true; + case DeclarationName::CXXLiteralOperatorName: + case DeclarationName::CXXUsingDirective: + break; + default: + MRDOCS_UNREACHABLE(); } + return result; +} - bool - VisitInjectedClassNameType( - const InjectedClassNameType* T) - { - getDerived().buildTerminal(NNS_, T->getDecl(), - std::nullopt, Quals_, IsPack_); - return true; - } +void +ASTVisitor:: +populateNamespaces( + Info& I, + Decl* D) +{ + Decl* PD = getParentDecl(D); + SymbolID ParentID = generateID(PD); + switch(PD->getKind()) + { + // The TranslationUnit DeclContext is the global namespace; + // it uses SymbolID::global and should *always* exist + case Decl::TranslationUnit: + { + MRDOCS_ASSERT(ParentID == SymbolID::global); + auto [P, created] = upsert< + NamespaceInfo>(ParentID); + addMember(P, I); + break; + } + case Decl::Namespace: + { + auto [P, created] = upsert< + NamespaceInfo>(ParentID); + populate(P, created, cast(PD)); + addMember(P, I); + break; + } + // special case for an explicit specializations of + // a member of an implicit instantiation. + case Decl::ClassTemplateSpecialization: + case Decl::ClassTemplatePartialSpecialization: + if(auto* S = dyn_cast(PD); + S && S->getSpecializationKind() == TSK_ImplicitInstantiation) + { + // KRYSTIAN FIXME: i'm pretty sure DeclContext::getDeclKind() + // will never be Decl::ClassTemplatePartialSpecialization for + // implicit instantiations; instead, the ClassTemplatePartialSpecializationDecl + // is accessible through S->getSpecializedTemplateOrPartial + // if the implicit instantiation used a partially specialized template, + MRDOCS_ASSERT(PD->getKind() != + Decl::ClassTemplatePartialSpecialization); + + auto [P, created] = upsert< + SpecializationInfo>(ParentID); + populate(P, created, S); + addMember(P, I); + break; + } + // non-implicit instantiations should be + // treated like normal CXXRecordDecls + [[fallthrough]]; + // we should never encounter a Record + // that is not a CXXRecord + case Decl::CXXRecord: + { + auto [P, created] = upsert< + RecordInfo>(ParentID); + populate(P, created, cast(PD)); + addMember(P, I); + break; + } + case Decl::Enum: + { + auto [P, created] = upsert< + EnumInfo>(ParentID); + populate(P, created, cast(PD)); + addMember(P, I); + break; + } + default: + MRDOCS_UNREACHABLE(); + } + + Info* P = find(ParentID); + MRDOCS_ASSERT(P); + + I.Namespace.emplace_back(ParentID); + I.Namespace.insert( + I.Namespace.end(), + P->Namespace.begin(), + P->Namespace.end()); +} - bool - VisitEnumType( - const EnumType* T) +void +ASTVisitor:: +addMember( + ScopeInfo& P, + Info& C) +{ + // Include C.id in P.Members if it's not already there + if (bool const exists = std::ranges::find(P.Members, C.id) + != P.Members.end(); + !exists) { - getDerived().buildTerminal(NNS_, T->getDecl(), - std::nullopt, Quals_, IsPack_); - return true; + P.Members.emplace_back(C.id); } - bool - VisitTypedefType( - const TypedefType* T) + // Include C.id in P.Lookups[C.Name] if it's not already there + auto& lookups = P.Lookups.try_emplace(C.Name).first->second; + if (bool const exists = std::ranges::find(lookups, C.id) != lookups.end(); + !exists) { - getDerived().buildTerminal(NNS_, T->getDecl(), - std::nullopt, Quals_, IsPack_); - return true; + lookups.emplace_back(C.id); } +} - bool - VisitTemplateTypeParmType( - const TemplateTypeParmType* T) +bool +ASTVisitor:: +generateJavadoc( + std::unique_ptr& javadoc, + Decl const* D) +{ + RawComment const* RC = + D->getASTContext().getRawCommentForDeclNoCache(D); + if (!RC) { - const IdentifierInfo* II = nullptr; - if(TemplateTypeParmDecl* D = T->getDecl()) - { - if(D->isImplicit()) - { - // special case for implicit template parameters - // resulting from abbreviated function templates - getDerived().buildTerminal( - NNS_, T, Quals_, IsPack_); - return true; - } - II = D->getIdentifier(); - } - getDerived().buildTerminal(NNS_, II, - std::nullopt, Quals_, IsPack_); - return true; + return false; } - - bool - VisitSubstTemplateTypeParmPackType( - const SubstTemplateTypeParmPackType* T) + comments::FullComment* FC = + RC->parse(D->getASTContext(), &sema_.getPreprocessor(), D); + if (!FC) { - getDerived().buildTerminal(NNS_, T->getIdentifier(), - std::nullopt, Quals_, IsPack_); - return true; + return false; } + // KRYSTIAN FIXME: clang ignores documentation comments + // when there is a preprocessor directive between the end + // of the comment and the declaration location. there are two + // ways to fix this: either set the declaration begin location + // to be before and preprocessor directives, or submit a patch + // which disables this behavior (it's not entirely clear why + // this check occurs anyways, so some investigation is needed) + parseJavadoc(javadoc, FC, D, config_, diags_); + return true; +} - bool - VisitType(const Type* T) - { - getDerived().buildTerminal( - NNS_, T, Quals_, IsPack_); - return true; - } +std::unique_ptr +ASTVisitor:: +toTypeInfo( + QualType const qt, + ExtractMode const extract_mode) +{ + // extract_mode is only used during the extraction + // the terminal type & its parents; the extraction of + // function parameters, template arguments, and the parent class + // of member pointers is done in ExtractMode::IndirectDependency + ExtractionScope scope = enterMode(extract_mode); + // build the TypeInfo representation for the type + TypeInfoBuilder Builder(*this); + Builder.Visit(qt); + return Builder.result(); +} -public: - TerminalTypeVisitor( - ASTVisitor& Visitor, - const NestedNameSpecifier* NNS = nullptr) - : Visitor_(Visitor) - , NNS_(NNS) - { - } +std::unique_ptr +ASTVisitor:: +toNameInfo( + NestedNameSpecifier const* NNS, + ExtractMode const extract_mode) +{ + ExtractionScope scope = enterMode(extract_mode); - ASTVisitor& getASTVisitor() + std::unique_ptr I = nullptr; + if (!NNS) { - return Visitor_; + return I; } - using TerminalTypeVisitor::TypeVisitor::Visit; - - bool - Visit(QualType QT) + if (checkSpecialNamespace(I, NNS, nullptr)) { - Quals_ |= QT.getLocalFastQualifiers(); - return Visit(QT.getTypePtrOrNull()); + return I; } - void - buildPointer - (const PointerType* T, - unsigned quals) + if(const Type* T = NNS->getAsType()) { + NameInfoBuilder Builder(*this, NNS->getPrefix()); + Builder.Visit(T); + I = Builder.result(); } - - void - buildLValueReference( - const LValueReferenceType* T) + else if(const IdentifierInfo* II = NNS->getAsIdentifier()) { + I = std::make_unique(); + I->Name = II->getName(); + I->Prefix = toNameInfo(NNS->getPrefix(), extract_mode); } - - void - buildRValueReference( - const RValueReferenceType* T) + else if(const NamespaceDecl* ND = NNS->getAsNamespace()) { + I = std::make_unique(); + I->Name = ND->getIdentifier()->getName(); + upsertDependency(ND, I->id); + I->Prefix = toNameInfo(NNS->getPrefix(), extract_mode); } - - void - buildMemberPointer( - const MemberPointerType* T, unsigned quals) + else if(const NamespaceAliasDecl* NAD = NNS->getAsNamespaceAlias()) { + I = std::make_unique(); + I->Name = NAD->getIdentifier()->getName(); + upsertDependency(NAD, I->id); + I->Prefix = toNameInfo(NNS->getPrefix(), extract_mode); } + return I; +} - void - buildArray( - const ArrayType* T) +template +std::unique_ptr +ASTVisitor:: +toNameInfo( + DeclarationName const Name, + std::optional TArgs, + NestedNameSpecifier const* NNS, + ExtractMode const extract_mode) +{ + if (Name.isEmpty()) { + return nullptr; } - - void - buildFunction( - const FunctionType* T) + std::unique_ptr I = nullptr; + if(TArgs) { + auto Specialization = std::make_unique(); + populate(Specialization->TemplateArgs, *TArgs); + I = std::move(Specialization); } - - void - buildDecltype( - const DecltypeType* T, - unsigned quals, - bool pack) + else { + I = std::make_unique(); } - - void - buildAuto( - const AutoType* T, - unsigned quals, - bool pack) + I->Name = extractName(Name); + if (NNS) { + I->Prefix = toNameInfo(NNS, extract_mode); } + return I; +} - void - buildTerminal( - const NestedNameSpecifier* NNS, - const Type* T, - unsigned quals, - bool pack) +template +std::unique_ptr +ASTVisitor:: +toNameInfo( + Decl const* D, + std::optional TArgs, + NestedNameSpecifier const* NNS, + ExtractMode extract_mode) +{ + const auto* ND = dyn_cast_if_present(D); + if (!ND) { + return nullptr; } - - void - buildTerminal( - const NestedNameSpecifier* NNS, - const IdentifierInfo* II, - std::optional> TArgs, - unsigned quals, - bool pack) + auto I = toNameInfo(ND->getDeclName(), + std::move(TArgs), NNS, extract_mode); + if (!I) { + return nullptr; } + upsertDependency(getInstantiatedFrom(D), I->id); + return I; +} - void - buildTerminal( - const NestedNameSpecifier* NNS, - const NamedDecl* D, - std::optional> TArgs, - unsigned quals, - bool pack) +template +std::unique_ptr +ASTVisitor:: +toNameInfo>( + Decl const* D, + std::optional> TArgs, + NestedNameSpecifier const* NNS, + ExtractMode extract_mode); + +std::unique_ptr +ASTVisitor:: +toTArg(const TemplateArgument& A) +{ + // TypePrinter generates an internal placeholder name (e.g. type-parameter-0-0) + // for template type parameters used as arguments. it also cannonicalizes + // types, which we do not want (although, PrintingPolicy has an option to change this). + // thus, we use the template arguments as written. + + // KRYSTIAN NOTE: this can probably be changed to select + // the argument as written when it is not dependent and is a type. + // FIXME: constant folding behavior should be consistent with that of other + // constructs, e.g. noexcept specifiers & explicit specifiers + switch(A.getKind()) + { + // empty template argument (e.g. not yet deduced) + case TemplateArgument::Null: + break; + + // a template argument pack (any kind) + case TemplateArgument::Pack: + { + // we should never a TemplateArgument::Pack here + MRDOCS_UNREACHABLE(); + break; + } + // type + case TemplateArgument::Type: + { + auto R = std::make_unique(); + QualType QT = A.getAsType(); + MRDOCS_ASSERT(! QT.isNull()); + // if the template argument is a pack expansion, + // use the expansion pattern as the type & mark + // the template argument as a pack expansion + if(const Type* T = QT.getTypePtr(); + auto* PT = dyn_cast(T)) + { + R->IsPackExpansion = true; + QT = PT->getPattern(); + } + R->Type = toTypeInfo(QT); + return R; + } + // pack expansion of a template name + case TemplateArgument::TemplateExpansion: + // template name + case TemplateArgument::Template: + { + auto R = std::make_unique(); + R->IsPackExpansion = A.isPackExpansion(); + + // KRYSTIAN FIXME: template template arguments are + // id-expression, so we don't properly support them yet. + // for the time being, we will use the name & SymbolID of + // the referenced declaration (if it isn't dependent), + // and fallback to printing the template name otherwise + TemplateName const TN = A.getAsTemplateOrTemplatePattern(); + if(auto* TD = TN.getAsTemplateDecl()) + { + if(auto* II = TD->getIdentifier()) + R->Name = II->getName(); + // do not extract a SymbolID or build Info if + // the template template parameter names a + // template template parameter or builtin template + if(! isa(TD) && + ! isa(TD)) + { + upsertDependency(getInstantiatedFrom< + NamedDecl>(TD), R->Template); + } + } + else + { + llvm::raw_string_ostream stream(R->Name); + TN.print(stream, context_.getPrintingPolicy(), + TemplateName::Qualified::AsWritten); + } + return R; + } + // nullptr value + case TemplateArgument::NullPtr: + // expression referencing a declaration + case TemplateArgument::Declaration: + // integral expression + case TemplateArgument::Integral: + // expression + case TemplateArgument::Expression: { + auto R = std::make_unique(); + R->IsPackExpansion = A.isPackExpansion(); + // if this is a pack expansion, use the template argument + // expansion pattern in place of the template argument pack + const TemplateArgument& adjusted = + R->IsPackExpansion ? + A.getPackExpansionPattern() : A; + + llvm::raw_string_ostream stream(R->Value.Written); + adjusted.print(context_.getPrintingPolicy(), stream, false); + + return R; } -}; + default: + MRDOCS_UNREACHABLE(); + } + return nullptr; +} -class TypeInfoBuilder - : public TerminalTypeVisitor -{ - std::unique_ptr Result; - std::unique_ptr* Inner = &Result; -public: - using TerminalTypeVisitor::TerminalTypeVisitor; +std::string +ASTVisitor:: +toString(const Expr* E) +{ + std::string result; + llvm::raw_string_ostream stream(result); + E->printPretty(stream, nullptr, context_.getPrintingPolicy()); + return result; +} - std::unique_ptr result() +std::string +ASTVisitor:: +toString(const Type* T) +{ + if(auto* AT = dyn_cast_if_present(T)) { - return std::move(Result); + switch(AT->getKeyword()) + { + case AutoTypeKeyword::Auto: + case AutoTypeKeyword::GNUAutoType: + return "auto"; + case AutoTypeKeyword::DecltypeAuto: + return "decltype(auto)"; + default: + MRDOCS_UNREACHABLE(); + } } - - void buildPointer(const PointerType* T, unsigned quals) + if(auto* TTPT = dyn_cast_if_present(T)) { - auto I = std::make_unique(); - I->CVQualifiers = convertToQualifierKind(quals); - *std::exchange(Inner, &I->PointeeType) = std::move(I); + if (TemplateTypeParmDecl* TTPD = TTPT->getDecl(); + TTPD && TTPD->isImplicit()) + { + return "auto"; + } } + return QualType(T, 0).getAsString( + context_.getPrintingPolicy()); +} - void buildLValueReference(const LValueReferenceType* T) +template +Integer +ASTVisitor:: +toInteger(const llvm::APInt& V) +{ + if constexpr (std::is_signed_v) { - auto I = std::make_unique(); - *std::exchange(Inner, &I->PointeeType) = std::move(I); + return static_cast(V.getSExtValue()); } - - void buildRValueReference(const RValueReferenceType* T) + else { - auto I = std::make_unique(); - *std::exchange(Inner, &I->PointeeType) = std::move(I); + return static_cast(V.getZExtValue()); } +} + +std::string +ASTVisitor:: +getSourceCode(SourceRange const& R) const +{ + return Lexer::getSourceText( + CharSourceRange::getTokenRange(R), + source_, + context_.getLangOpts()).str(); +} - void buildMemberPointer(const MemberPointerType* T, unsigned quals) +std::optional>> +ASTVisitor:: +isSFINAEType(QualType const T) +{ + if (!config_->detectSfinae) { - auto I = std::make_unique(); - I->CVQualifiers = convertToQualifierKind(quals); - // do not set NNS because the parent type is *not* - // a nested-name-specifier which qualifies the pointee type - I->ParentType = getASTVisitor().buildTypeInfo( - QualType(T->getClass(), 0)); - *std::exchange(Inner, &I->PointeeType) = std::move(I); + return std::nullopt; } - void buildArray(const ArrayType* T) + auto sfinae_info = getSFINAETemplate(T, true); + if (!sfinae_info) { - auto I = std::make_unique(); - if(auto* CAT = dyn_cast(T)) - getASTVisitor().buildExprInfo( - I->Bounds, CAT->getSizeExpr(), CAT->getSize()); - else if(auto* DAT = dyn_cast(T)) - getASTVisitor().buildExprInfo( - I->Bounds, DAT->getSizeExpr()); - *std::exchange(Inner, &I->ElementType) = std::move(I); + return std::nullopt; } - void buildFunction(const FunctionType* T) + auto sfinae_result = isSFINAETemplate( + sfinae_info->Template, sfinae_info->Member); + + if (!sfinae_result) { - auto* FPT = cast(T); - auto I = std::make_unique(); - for(QualType PT : FPT->getParamTypes()) - I->ParamTypes.emplace_back( - getASTVisitor().buildTypeInfo(PT)); - I->RefQualifier = convertToReferenceKind( - FPT->getRefQualifier()); - I->CVQualifiers = convertToQualifierKind( - FPT->getMethodQuals().getFastQualifiers()); - I->IsVariadic = FPT->isVariadic(); - getASTVisitor().buildNoexceptInfo(I->ExceptionSpec, FPT); - *std::exchange(Inner, &I->ReturnType) = std::move(I); + return std::nullopt; } - void - buildDecltype( - const DecltypeType* T, - unsigned quals, - bool pack) + auto [template_params, controlling_params, param_idx] = *sfinae_result; + + auto const Args = sfinae_info->Arguments; + auto const param_arg = tryGetTemplateArgument( + template_params, Args, param_idx); + if (!param_arg) { - auto I = std::make_unique(); - getASTVisitor().buildExprInfo( - I->Operand, T->getUnderlyingExpr()); - I->CVQualifiers = convertToQualifierKind(quals); - *Inner = std::move(I); - Result->IsPackExpansion = pack; + return std::nullopt; } - void - buildAuto( - const AutoType* T, - unsigned quals, - bool pack) + std::vector ControllingArgs; + for (std::size_t I = 0; I < Args.size(); ++I) { - auto I = std::make_unique(); - I->CVQualifiers = convertToQualifierKind(quals); - I->Keyword = convertToAutoKind(T->getKeyword()); - if(T->isConstrained()) + if (controlling_params[I]) { - std::optional> TArgs; - if(auto Args = T->getTypeConstraintArguments(); - ! Args.empty()) - TArgs.emplace(Args); - I->Constraint = getASTVisitor().buildNameInfo( - T->getTypeConstraintConcept(), TArgs); - // Constraint->Prefix = getASTVisitor().buildNameInfo( - // cast(CD->getDeclContext())); + ControllingArgs.emplace_back(Args[I]); } - *Inner = std::move(I); - Result->IsPackExpansion = pack; } - void - buildTerminal( - const NestedNameSpecifier* NNS, - const Type* T, - unsigned quals, - bool pack) - { - if(getASTVisitor().checkSpecialNamespace(*Inner, NNS, nullptr)) - return; - - auto I = std::make_unique(); - I->CVQualifiers = convertToQualifierKind(quals); + return std::make_pair(param_arg->getAsType(), std::move(ControllingArgs)); +} - auto Name = std::make_unique(); - Name->Name = getASTVisitor().getTypeAsString(T); - Name->Prefix = getASTVisitor().buildNameInfo(NNS); - I->Name = std::move(Name); - *Inner = std::move(I); - Result->IsPackExpansion = pack; +std::optional> +ASTVisitor:: +isSFINAETemplate( + TemplateDecl* TD, + const IdentifierInfo* Member) +{ + if (!TD) + { + return std::nullopt; } - void - buildTerminal( - const NestedNameSpecifier* NNS, - const IdentifierInfo* II, - std::optional> TArgs, - unsigned quals, - bool pack) + auto FindParam = [this]( + ArrayRef Arguments, + const TemplateArgument& Arg) -> unsigned { - ASTVisitor& V = getASTVisitor(); - if(V.checkSpecialNamespace(*Inner, NNS, nullptr)) - return; - - auto I = std::make_unique(); - I->CVQualifiers = convertToQualifierKind(quals); + if (Arg.getKind() != TemplateArgument::Type) + { + return -1; + } + auto Found = std::ranges::find_if(Arguments, [&](const TemplateArgument& Other) + { + if (Other.getKind() != TemplateArgument::Type) + { + return false; + } + return context_.hasSameType(Other.getAsType(), Arg.getAsType()); + }); + return Found != Arguments.end() ? Found - Arguments.data() : -1; + }; - if(TArgs) + if(auto* ATD = dyn_cast(TD)) + { + auto Underlying = ATD->getTemplatedDecl()->getUnderlyingType(); + auto sfinae_info = getSFINAETemplate(Underlying, !Member); + if (!sfinae_info) { - auto Name = std::make_unique(); - if(II) - Name->Name = II->getName(); - Name->Prefix = getASTVisitor().buildNameInfo(NNS); - V.buildTemplateArgs(Name->TemplateArgs, *TArgs); - I->Name = std::move(Name); + return std::nullopt; } - else + if (Member) + { + sfinae_info->Member = Member; + } + auto sfinae_result = isSFINAETemplate( + sfinae_info->Template, sfinae_info->Member); + if (!sfinae_result) + { + return std::nullopt; + } + auto [template_params, controlling_params, param_idx] = *sfinae_result; + auto param_arg = tryGetTemplateArgument( + template_params, sfinae_info->Arguments, param_idx); + if (!param_arg) { - auto Name = std::make_unique(); - if(II) - Name->Name = II->getName(); - Name->Prefix = getASTVisitor().buildNameInfo(NNS); - I->Name = std::move(Name); + return std::nullopt; } - *Inner = std::move(I); - Result->IsPackExpansion = pack; + unsigned ParamIdx = FindParam(ATD->getInjectedTemplateArgs(context_), *param_arg); + return std::make_tuple(ATD->getTemplateParameters(), std::move(controlling_params), ParamIdx); } - void - buildTerminal( - const NestedNameSpecifier* NNS, - const NamedDecl* D, - std::optional> TArgs, - unsigned quals, - bool pack) + auto* CTD = dyn_cast(TD); + if (!CTD) { - ASTVisitor& V = getASTVisitor(); - if(V.checkSpecialNamespace(*Inner, NNS, D)) - return; + return std::nullopt; + } - auto I = std::make_unique(); - I->CVQualifiers = convertToQualifierKind(quals); + auto PrimaryArgs = CTD->getInjectedTemplateArgs(context_); + llvm::SmallBitVector ControllingParams(PrimaryArgs.size()); - if(TArgs) + QualType MemberType; + unsigned ParamIdx = -1; + auto IsMismatch = [&](CXXRecordDecl* RD, ArrayRef Args) + { + if (!RD->hasDefinition()) { - auto Name = std::make_unique(); - if(const IdentifierInfo* II = D->getIdentifier()) - Name->Name = II->getName(); - V.getDependencyID(V.getInstantiatedFrom(D), Name->id); - if(NNS) - Name->Prefix = V.buildNameInfo(NNS); - - V.buildTemplateArgs(Name->TemplateArgs, *TArgs); - I->Name = std::move(Name); + return false; } - else + auto MemberLookup = RD->lookup(Member); + QualType CurrentType; + if(MemberLookup.empty()) { - auto Name = std::make_unique(); - if(const IdentifierInfo* II = D->getIdentifier()) - Name->Name = II->getName(); - V.getDependencyID(V.getInstantiatedFrom(D), Name->id); - if(NNS) - Name->Prefix = V.buildNameInfo(NNS); - I->Name = std::move(Name); - } - *Inner = std::move(I); - Result->IsPackExpansion = pack; - } -}; + if (!RD->getNumBases()) + { + return false; + } + for(auto& Base : RD->bases()) + { + auto sfinae_info = getSFINAETemplate(Base.getType(), false); + if(! sfinae_info) + { + // if the base is an opaque dependent type, we can't determine + // whether it's a SFINAE type + if (Base.getType()->isDependentType()) + { + return true; + } + continue; + } + // if the class inherits from itself, we can't determine whether + // it's a SFINAE type + if (declaresSameEntity(TD, sfinae_info->Template)) + { + return true; + } -std::unique_ptr -ASTVisitor:: -buildTypeInfo( - QualType qt, - ExtractMode extract_mode) -{ - // extract_mode is only used during the extraction - // the terminal type & its parents; the extraction of - // function parameters, template arguments, and the parent class - // of member pointers is done in ExtractMode::IndirectDependency - ExtractionScope scope = enterMode(extract_mode); - // build the TypeInfo representation for the type - TypeInfoBuilder Builder(*this); - Builder.Visit(qt); - return Builder.result(); -} + auto sfinae_result = isSFINAETemplate( + sfinae_info->Template, Member); + if (!sfinae_result) + { + return true; + } -class NameInfoBuilder - : public TerminalTypeVisitor -{ - std::unique_ptr Result; + auto [template_params, controlling_params, param_idx] = *sfinae_result; + auto param_arg = tryGetTemplateArgument( + template_params, sfinae_info->Arguments, param_idx); + if(! param_arg) + return true; + auto CurrentTypeFromBase = param_arg->getAsType(); + if (CurrentType.isNull()) + { + CurrentType = CurrentTypeFromBase; + } + else if ( + !context_.hasSameType(CurrentType, CurrentTypeFromBase)) + { + return true; + } + } + // didn't find a base that defines the specified member + if (CurrentType.isNull()) + { + return false; + } + } + else + { + // ambiguous lookup + if (!MemberLookup.isSingleResult()) + { + return true; + } + if (auto* TND = dyn_cast(MemberLookup.front())) + { + CurrentType = TND->getUnderlyingType(); + } else + { + // the specialization has a member with the right name, + // but it isn't an alias declaration/typedef declaration... + return true; + } + } -public: - using TerminalTypeVisitor::TerminalTypeVisitor; + if(CurrentType->isDependentType()) + { + auto FoundIdx = FindParam(Args, TemplateArgument(CurrentType)); + if (FoundIdx == static_cast(-1) + || FoundIdx >= PrimaryArgs.size()) + { + return true; + } + ParamIdx = FoundIdx; + TemplateArgument MappedPrimary = PrimaryArgs[FoundIdx]; + assert(MappedPrimary.getKind() == TemplateArgument::Type); + CurrentType = MappedPrimary.getAsType(); + } - std::unique_ptr result() - { - return std::move(Result); - } + if (MemberType.isNull()) + { + MemberType = CurrentType; + } - void - buildDecltype( - const DecltypeType* T, - unsigned quals, - bool pack) - { - // KRYSTIAN TODO: support decltype in names - // (e.g. within nested-name-specifiers). - } + return ! context_.hasSameType(MemberType, CurrentType); + }; - void - buildTerminal( - const NestedNameSpecifier* NNS, - const Type* T, - unsigned quals, - bool pack) + if (IsMismatch(CTD->getTemplatedDecl(), PrimaryArgs)) { - if(getASTVisitor().checkSpecialNamespace(Result, NNS, nullptr)) - return; - - auto I = std::make_unique(); - I->Name = getASTVisitor().getTypeAsString(T); - Result = std::move(I); - if(NNS) - Result->Prefix = getASTVisitor().buildNameInfo(NNS); + return std::nullopt; } - void - buildTerminal( - const NestedNameSpecifier* NNS, - const IdentifierInfo* II, - std::optional> TArgs, - unsigned quals, - bool pack) + for(auto* CTSD : CTD->specializations()) { - ASTVisitor& V = getASTVisitor(); - if(V.checkSpecialNamespace(Result, NNS, nullptr)) - return; - - if(TArgs) - { - auto I = std::make_unique(); - if(II) - I->Name = II->getName(); - V.buildTemplateArgs(I->TemplateArgs, *TArgs); - Result = std::move(I); - } - else + if (CTSD->isExplicitSpecialization() + && IsMismatch(CTSD, CTSD->getTemplateArgs().asArray())) { - auto I = std::make_unique(); - if(II) - I->Name = II->getName(); - Result = std::move(I); + return std::nullopt; } - if(NNS) - Result->Prefix = V.buildNameInfo(NNS); } - void - buildTerminal( - const NestedNameSpecifier* NNS, - const NamedDecl* D, - std::optional> TArgs, - unsigned quals, - bool pack) - { - ASTVisitor& V = getASTVisitor(); - if(V.checkSpecialNamespace(Result, NNS, D)) - return; + SmallVector PartialSpecs; + CTD->getPartialSpecializations(PartialSpecs); - const IdentifierInfo* II = D->getIdentifier(); - if(TArgs) + for(auto* CTPSD : PartialSpecs) + { + auto PartialArgs = CTPSD->getTemplateArgs().asArray(); + if (IsMismatch(CTPSD, PartialArgs)) { - auto I = std::make_unique(); - if(II) - I->Name = II->getName(); - V.getDependencyID(V.getInstantiatedFrom(D), I->id); - V.buildTemplateArgs(I->TemplateArgs, *TArgs); - Result = std::move(I); + return std::nullopt; } - else + for(std::size_t I = 0; I < PartialArgs.size(); ++I) { - auto I = std::make_unique(); - if(II) - I->Name = II->getName(); - V.getDependencyID(V.getInstantiatedFrom(D), I->id); - Result = std::move(I); + TemplateArgument Arg = PartialArgs[I]; + switch(Arg.getKind()) + { + case TemplateArgument::Integral: + case TemplateArgument::Declaration: + case TemplateArgument::StructuralValue: + case TemplateArgument::NullPtr: + break; + case TemplateArgument::Expression: + if(getNTTPFromExpr( + Arg.getAsExpr(), + CTPSD->getTemplateDepth() - 1)) + continue; + break; + default: + continue; + } + ControllingParams.set(I); + //.getAsExpr() } - if(NNS) - Result->Prefix = V.buildNameInfo(NNS); } -}; -std::unique_ptr + return std::make_tuple(CTD->getTemplateParameters(), std::move(ControllingParams), ParamIdx); +} + +std::optional ASTVisitor:: -buildNameInfo( - const NestedNameSpecifier* NNS, - ExtractMode extract_mode) +getSFINAETemplate(QualType T, bool const AllowDependentNames) { - ExtractionScope scope = enterMode(extract_mode); - - std::unique_ptr I = nullptr; - if(! NNS) - return I; + assert(!T.isNull()); + SFINAEInfo SFINAE; + if (auto* ET = T->getAs()) + { + T = ET->getNamedType(); + } - if(checkSpecialNamespace(I, NNS, nullptr)) - return I; + if(auto* DNT = T->getAsAdjusted(); + DNT && AllowDependentNames) + { + SFINAE.Member = DNT->getIdentifier(); + T = QualType(DNT->getQualifier()->getAsType(), 0); + } - if(const Type* T = NNS->getAsType()) + if(auto* TST = T->getAsAdjusted()) { - NameInfoBuilder Builder(*this, NNS->getPrefix()); - Builder.Visit(T); - I = Builder.result(); + SFINAE.Template = TST->getTemplateName().getAsTemplateDecl(); + SFINAE.Arguments = TST->template_arguments(); + return SFINAE; } - else if(const IdentifierInfo* II = NNS->getAsIdentifier()) + return std::nullopt; +} + +std::optional +ASTVisitor:: +tryGetTemplateArgument( + TemplateParameterList* Parameters, + ArrayRef Arguments, + unsigned const Index) +{ + if (Index == static_cast(-1)) { - I = std::make_unique(); - I->Name = II->getName(); - I->Prefix = buildNameInfo(NNS->getPrefix(), extract_mode); + return std::nullopt; } - else if(const NamespaceDecl* ND = NNS->getAsNamespace()) + if (Index < Arguments.size()) { - I = std::make_unique(); - I->Name = ND->getIdentifier()->getName(); - getDependencyID(ND, I->id); - I->Prefix = buildNameInfo(NNS->getPrefix(), extract_mode); + return Arguments[Index]; } - else if(const NamespaceAliasDecl* NAD = NNS->getAsNamespaceAlias()) + if(Parameters && Index < Parameters->size()) { - I = std::make_unique(); - I->Name = NAD->getIdentifier()->getName(); - getDependencyID(NAD, I->id); - I->Prefix = buildNameInfo(NNS->getPrefix(), extract_mode); + NamedDecl* ND = Parameters->getParam(Index); + if(auto* TTPD = dyn_cast(ND); + TTPD && TTPD->hasDefaultArgument()) + { + return TTPD->getDefaultArgument().getArgument(); + } + if(auto* NTTPD = dyn_cast(ND); + NTTPD && NTTPD->hasDefaultArgument()) + { + return NTTPD->getDefaultArgument().getArgument(); + } } - return I; + return std::nullopt; } -#if 0 -std::unique_ptr +bool ASTVisitor:: -buildNameInfo( +shouldExtract( const Decl* D, - ExtractMode extract_mode) + AccessSpecifier const access) { - const auto* ND = dyn_cast_if_present(D); - if(! ND || ND->getKind() == Decl::TranslationUnit) - return nullptr; - auto I = std::make_unique(); - if(checkSpecialNamespace(I, nullptr, ND)) - return I; - if(const IdentifierInfo* II = ND->getIdentifier()) - I->Name = II->getName(); - getDependencyID(getInstantiatedFrom(D), I->id); - I->Prefix = buildNameInfo(getParentDecl(D), extract_mode); - return I; -} -#endif + using enum ConfigImpl::SettingsImpl::ExtractPolicy; + if (config_->inaccessibleMembers != Always) + { + // KRYSTIAN FIXME: this doesn't handle direct + // dependencies on inaccessible declarations + MRDOCS_CHECK_OR( + access != AccessSpecifier::AS_private && + access != AccessSpecifier::AS_protected, false); + } + + // Don't extract anonymous unions + const auto* RD = dyn_cast(D); + MRDOCS_CHECK_OR(!RD || !RD->isAnonymousStructOrUnion(), false); + + // Don't extract implicitly generated declarations + // (except for IndirectFieldDecls) + MRDOCS_CHECK_OR(!D->isImplicit() || isa(D), false); + + // Don't extract declarations that fail the input filter + if (!config_->input.include.empty()) + { + // Get filename + FileInfo* file = findFileInfo(D->getBeginLoc()); + MRDOCS_CHECK_OR(file, false); + std::string filename = file->full_path; + MRDOCS_CHECK_OR(std::ranges::any_of( + config_->input.include, + [&filename](const std::string& prefix) + { + return files::startsWith(filename, prefix); + }), false); + } -template -std::unique_ptr -ASTVisitor:: -buildNameInfo( - DeclarationName Name, - std::optional TArgs, - const NestedNameSpecifier* NNS, - ExtractMode extract_mode) -{ - if(Name.isEmpty()) - return nullptr; - std::unique_ptr I = nullptr; - if(TArgs) + // Don't extract declarations that fail the file pattern filter + if (!config_->input.filePatterns.empty()) { - auto Specialization = std::make_unique(); - buildTemplateArgs(Specialization->TemplateArgs, *TArgs); - I = std::move(Specialization); + // Get filename + FileInfo* file = findFileInfo(D->getBeginLoc()); + MRDOCS_CHECK_OR(file, false); + std::string filename = file->full_path; + MRDOCS_CHECK_OR(std::ranges::any_of( + config_->input.filePatterns, + [&filename](const std::string& pattern) + { + return globMatch(pattern, filename); + }), false); } - else + + // Don't extract declarations that fail the symbol filter + MRDOCS_CHECK_OR( + currentMode() != ExtractMode::Normal || + inExtractedFile(D), false); + + // Don't extract anonymous namespaces unless configured to do so + if(const auto* ND = dyn_cast(D); + ND && + ND->isAnonymousNamespace() && + config_->anonymousNamespaces != Always) { - I = std::make_unique(); + // Always skip anonymous namespaces if so configured + MRDOCS_CHECK_OR(config_->anonymousNamespaces != Never, false); + + // Otherwise, skip extraction if this isn't a dependency + // KRYSTIAN FIXME: is this correct? a namespace should not + // be extracted as a dependency (until namespace aliases and + // using directives are supported) + MRDOCS_CHECK_OR(currentMode() != ExtractMode::Normal, false); } - I->Name = extractName(Name); - if(NNS) - I->Prefix = buildNameInfo(NNS, extract_mode); - return I; -} -template -std::unique_ptr -ASTVisitor:: -buildNameInfo( - const Decl* D, - std::optional TArgs, - const NestedNameSpecifier* NNS, - ExtractMode extract_mode) -{ - const auto* ND = dyn_cast_if_present(D); - if(! ND) - return nullptr; - auto I = buildNameInfo(ND->getDeclName(), - std::move(TArgs), NNS, extract_mode); - if(! I) - return nullptr; - getDependencyID(getInstantiatedFrom(D), I->id); - return I; + return true; } -//------------------------------------------------ - -#if 0 -class ASTInstantiationCallbacks - : public TemplateInstantiationCallback +bool +ASTVisitor:: +checkSymbolFilter(NamedDecl const* ND) { - void initialize(const Sema&) override { } - void finalize(const Sema&) override { } - - void - atTemplateBegin( - const Sema& S, - const Sema::CodeSynthesisContext& Context) override + if (currentMode() != ExtractMode::Normal || + symbolFilter_.detached) { + return true; } - void - atTemplateEnd( - const Sema& S, - const Sema::CodeSynthesisContext& Context) override + std::string const name = extractName(ND); + const FilterNode* parent = symbolFilter_.current; + if(const FilterNode* child = parent->findChild(name)) { - if(Context.Kind != Sema::CodeSynthesisContext::TemplateInstantiation) - return; + // if there is a matching node, skip extraction if it's + // explicitly excluded AND has no children. the presence + // of child nodes indicates that some child exists that + // is explicitly whitelisted + if (child->Explicit && + child->Excluded && + child->isTerminal()) + { + return false; + } + symbolFilter_.setCurrent(child, false); + } + else + { + // if there was no matching node, check the most + // recently entered explicitly specified parent node. + // if it's blacklisted, then the "filtering default" + // is to exclude symbols unless a child is explicitly + // whitelisted + if (symbolFilter_.last_explicit + && symbolFilter_.last_explicit->Excluded) + { + return false; + } - Decl* D = Context.Entity; - if(! D) - return; + if (const auto* DC = dyn_cast(ND); + !DC || + !DC->isInlineNamespace()) + { + // if this namespace does not match a child + // of the current filter node, set the detached flag + // so we don't update the namespace filter state + // while traversing the children of this namespace + symbolFilter_.detached = true; + } + } + return true; +} + +// This also sets IsFileInRootDir +bool +ASTVisitor:: +inExtractedFile( + const Decl* D) +{ + namespace path = llvm::sys::path; - bool skip = visit(D, [&](DeclTy* DT) -> bool + if(const auto* ND = dyn_cast(D)) + { + // out-of-line declarations require us to rebuild + // the symbol filtering state + if(ND->isOutOfLine()) { - constexpr Decl::Kind kind = DeclToKind(); + symbolFilter_.setCurrent( + &symbolFilter_.root, false); - if constexpr(std::derived_from) + // collect all parent classes/enums/namespaces + llvm::SmallVector parents; + const Decl* P = ND; + while((P = getParentDecl(P))) { - CXXMethodDecl* MD = DT; - return MD->isFunctionTemplateSpecialization(); + if (isa(P)) + { + break; + } + parents.push_back(cast(P)); } - if constexpr(kind == Decl::TypeAliasTemplate) + // check whether each parent passes the symbol filters + // as-if the declaration was inline + for(const auto* PND : std::views::reverse(parents)) { - TypeAliasTemplateDecl* TAD = DT; - - if(DeclContext* DC = TAD->getDeclContext()) + if (!checkSymbolFilter(PND)) { - Decl* DCD = cast(DC); - return ! DCD->isImplicit(); + return false; } } + } + if (!checkSymbolFilter(ND)) + { return false; - }); - - if(skip) - return; - - D->setImplicit(); + } } -}; -#endif + FileInfo const* file = findFileInfo(D->getBeginLoc()); + // KRYSTIAN NOTE: I'm unsure under what conditions + // this assert would fire. + MRDOCS_ASSERT(file); + // only extract from files in source root + return file->kind == FileKind::Source; +} -//------------------------------------------------ -// -// ASTVisitorConsumer -// -//------------------------------------------------ - -class ASTVisitorConsumer - : public SemaConsumer +bool +ASTVisitor:: +isInSpecialNamespace( + const Decl* D, + std::span const Patterns) { - const ConfigImpl& config_; - ExecutionContext& ex_; - CompilerInstance& compiler_; - - Sema* sema_ = nullptr; - - void - InitializeSema(Sema& S) override + if (!D || Patterns.empty()) { - // Sema should not have been initialized yet - MRDOCS_ASSERT(! sema_); - sema_ = &S; - -#if 0 - S.TemplateInstCallbacks.emplace_back( - std::make_unique()); -#endif + return false; } - - void - ForgetSema() override + const DeclContext* DC = isa(D) ? + dyn_cast(D) : D->getDeclContext(); + for(; DC; DC = DC->getParent()) { - sema_ = nullptr; + const auto* ND = dyn_cast(DC); + if (!ND) + { + continue; + } + for (auto const& Pattern: Patterns) + { + if (Pattern.matches(ND->getQualifiedNameAsString())) + { + return true; + } + } } + return false; +} - void - HandleTranslationUnit(ASTContext& Context) override +bool +ASTVisitor:: +isInSpecialNamespace( + const NestedNameSpecifier* NNS, + std::span Patterns) +{ + const NamedDecl* ND = nullptr; + while(NNS) { - // the Sema better be valid - MRDOCS_ASSERT(sema_); - - // initialize the diagnostics reporter first - // so errors prior to traversal are reported - Diagnostics diags; - - // loads and caches source files into memory - SourceManager& source = Context.getSourceManager(); - // get the name of the translation unit. - // will be std::nullopt_t if it isn't a file - std::optional> file_name = - source.getNonBuiltinFilenameForID(source.getMainFileID()); - // KRYSTIAN NOTE: should we report anything here? - if (!file_name) + if ((ND = NNS->getAsNamespace())) { - return; + break; } + if ((ND = NNS->getAsNamespaceAlias())) + { + break; + } + NNS = NNS->getPrefix(); + } + return ND && isInSpecialNamespace(ND, Patterns); +} - // skip the translation unit if configured to do so - convert_to_slash(*file_name); - - ASTVisitor visitor( - config_, - diags, - compiler_, - Context, - *sema_); - - // Traverse the translation unit - visitor.build(); - - // VFALCO If we returned from the function early - // then this line won't execute, which means we - // will miss error and warnings emitted before - // the return. - ex_.report(std::move(visitor.results()), std::move(diags)); +bool +ASTVisitor:: +checkSpecialNamespace( + std::unique_ptr& I, + const NestedNameSpecifier* NNS, + const Decl* D) const +{ + if (isInSpecialNamespace(NNS, config_->seeBelowFilter) || + isInSpecialNamespace(D, config_->seeBelowFilter)) + { + I = std::make_unique(); + I->Name = "see-below"; + return true; } - /** Skip function bodies + if(isInSpecialNamespace(NNS, config_->implementationDefinedFilter) || + isInSpecialNamespace(D, config_->implementationDefinedFilter)) + { + I = std::make_unique(); + I->Name = "implementation-defined"; + return true; + } - This is called by Sema when parsing a function that has a body and: - - is constexpr, or - - uses a placeholder for a deduced return type + return false; +} - We always return `true` because whenever this function *is* called, - it will be for a function that cannot be used in a constant expression, - nor one that introduces a new type via returning a local class. - */ - bool - shouldSkipFunctionBody(Decl*) override +bool +ASTVisitor:: +checkSpecialNamespace( + std::unique_ptr& I, + const NestedNameSpecifier* NNS, + const Decl* D) const +{ + if (std::unique_ptr Name; + checkSpecialNamespace(Name, NNS, D)) { + auto T = std::make_unique(); + T->Name = std::move(Name); + I = std::move(T); return true; } + return false; +} + - bool - HandleTopLevelDecl(DeclGroupRef) override +Info* +ASTVisitor:: +find(SymbolID const& id) +{ + if (auto const it = info_.find(id); it != info_.end()) { - return true; + return it->get(); } + return nullptr; +} - ASTMutationListener* - GetASTMutationListener() override +// KRYSTIAN NOTE: we *really* should not have a +// type named "SourceLocation"... +ASTVisitor::FileInfo* +ASTVisitor:: +findFileInfo(clang::SourceLocation const loc) +{ + // KRYSTIAN FIXME: we really should not be + // calling getPresumedLoc this often, + // it's quite expensive + auto const presumed = source_.getPresumedLoc(loc, false); + if (presumed.isInvalid()) { return nullptr; } - - void - HandleCXXStaticMemberVarInstantiation(VarDecl* D) override + const FileEntry* file = + source_.getFileEntryForID( + presumed.getFileID()); + // KRYSTIAN NOTE: i have no idea under what + // circumstances the file entry would be null + if (!file) { - // implicitly instantiated definitions of non-inline - // static data members of class templates are added to - // the end of the TU DeclContext. Decl::isImplicit returns - // false for these VarDecls, so we manually set it here. - D->setImplicit(); + return nullptr; } - - void - HandleCXXImplicitFunctionInstantiation(FunctionDecl* D) override + // KRYSTIAN NOTE: i have no idea under what + // circumstances the file would not be in either + // the main file, or an included file + auto const it = files_.find(file); + if (it == files_.end()) { - D->setImplicit(); + return nullptr; } + return &it->second; +} - void HandleInlineFunctionDefinition(FunctionDecl*) override { } - void HandleTagDeclDefinition(TagDecl*) override { } - void HandleTagDeclRequiredDefinition(const TagDecl*) override { } - void HandleInterestingDecl(DeclGroupRef) override { } - void CompleteTentativeDefinition(VarDecl*) override { } - void CompleteExternalDeclaration(DeclaratorDecl*) override { } - void AssignInheritanceModel(CXXRecordDecl*) override { } - void HandleVTable(CXXRecordDecl*) override { } - void HandleImplicitImportDecl(ImportDecl*) override { } - void HandleTopLevelDeclInObjCContainer(DeclGroupRef) override { } +template InfoTy> +ASTVisitor::upsertResult +ASTVisitor:: +upsert(SymbolID const& id) +{ + // Creating symbol with invalid SymbolID + MRDOCS_ASSERT(id != SymbolID::invalid); + Info* info = find(id); + bool const created = !info; + if (!info) + { + info = info_.emplace(std::make_unique< + InfoTy>(id)).first->get(); + info->Implicit = currentMode() != ExtractMode::Normal; + } + MRDOCS_ASSERT(info->Kind == InfoTy::kind_id); + return {static_cast(*info), created}; +} -public: - ASTVisitorConsumer( - const ConfigImpl& config, - ExecutionContext& ex, - CompilerInstance& compiler) noexcept - : config_(config) - , ex_(ex) - , compiler_(compiler) - { - } -}; +template DeclType> +Expected>> +ASTVisitor:: +upsert(DeclType* D) +{ + AccessSpecifier access = getAccess(D); + MRDOCS_CHECK_MSG( + shouldExtract(D, access), + "Symbol should not be extracted"); -//------------------------------------------------ -// -// ASTAction -// -//------------------------------------------------ + SymbolID id = generateID(D); + MRDOCS_CHECK_MSG(id, "Failed to extract symbol ID"); -/** A frontend action for visiting the AST + auto [I, created] = upsert>(id); + I.Access = convertToAccessKind(access); - This is used by the tooling infrastructure to create - an ASTAction for each translation unit. + using R = upsertResult>; + return R{std::ref(I), created}; +} - The ASTAction is responsible for creating the ASTConsumer - which will be used to traverse the AST. -*/ -struct ASTAction - : public clang::ASTFrontendAction +void +ASTVisitor:: +upsertDependency(Decl* D, SymbolID& id) { - ASTAction( - ExecutionContext& ex, - ConfigImpl const& config) noexcept - : ex_(ex) - , config_(config) + if (TemplateDecl* TD = D->getDescribedTemplate()) { + D = TD; } - /** Execute the action + if (D->isImplicit() || isa(D) + || isa(D)) + { + return; + } - This is called by the tooling infrastructure to execute - the action for each translation unit. + id = generateID(D); - The action will parse the AST with the consumer - that should have been previously created with - CreateASTConsumer. + // Don't register a dependency if we never extract them + if (config_->referencedDeclarations + == ConfigImpl::SettingsImpl::ExtractPolicy::Never) + { + return; + } - This consumer then creates a ASTVisitor that - will convert the AST into a set of MrDocs Info - objects. - */ - void - ExecuteAction() override + if(config_->referencedDeclarations == + ConfigImpl::SettingsImpl::ExtractPolicy::Dependency) { - CompilerInstance& CI = getCompilerInstance(); - if (!CI.hasPreprocessor()) + if (currentMode() != ExtractMode::DirectDependency) { return; } - - // Ensure comments in system headers are retained. - // We may want them if, e.g., a declaration was extracted - // as a dependency - CI.getLangOpts().RetainCommentsFromSystemHeaders = true; - - if (!CI.hasSema()) - { - CI.createSema(getTranslationUnitKind(), nullptr); - } - - ParseAST( - CI.getSema(), - false, // ShowStats - true); // SkipFunctionBodies } - /** Create the object that will traverse the AST - - This is called by the tooling infrastructure to create - an ASTConsumer for each translation unit. - - This consumer creates a ASTVisitor that will convert - the AST into a set of our objects. - - The main function of the ASTVisitorConsumer is - the HandleTranslationUnit function, which is called - to traverse the AST. - - */ - std::unique_ptr - CreateASTConsumer( - clang::CompilerInstance& Compiler, - llvm::StringRef InFile) override + // If it was already extracted, we are done + if (find(id)) { - return std::make_unique( - config_, ex_, Compiler); + return; } -private: - ExecutionContext& ex_; - ConfigImpl const& config_; -}; + // FIXME: we need to handle member specializations correctly. + // we do not want to extract all members of the enclosing + // implicit instantiation + Decl* Outer = D; + DeclContext* DC = D->getDeclContext(); + do + { + if (DC->isFileContext() || + DC->isFunctionOrMethod()) + { + break; + } -//------------------------------------------------ + if (auto const* RD = dyn_cast(DC); + RD && + RD->isAnonymousStructOrUnion()) + { + continue; + } -/** A frontend action factory for ASTAction + // when extracting dependencies, we want to extract + // all members of the containing class, not just this one + if (auto* TD = dyn_cast(DC)) + { + Outer = TD; + } + } + while((DC = DC->getParent())); - This is used by the tooling infrastructure to create - an ASTAction for each translation unit. -*/ -struct ASTActionFactory : - tooling::FrontendActionFactory -{ - ASTActionFactory( - ExecutionContext& ex, - ConfigImpl const& config) noexcept - : ex_(ex) - , config_(config) + if (TemplateDecl* TD = Outer->getDescribedTemplate()) { + Outer = TD; } - std::unique_ptr - create() override + // Add the adjusted declaration to the set of dependencies + if (!isa(Outer)) { - return std::make_unique(ex_, config_); + dependencies_.insert(Outer); } - -private: - ExecutionContext& ex_; - ConfigImpl const& config_; -}; - -} // (anon) - -//------------------------------------------------ - -std::unique_ptr -makeFrontendActionFactory( - ExecutionContext& ex, - ConfigImpl const& config) -{ - return std::make_unique(ex, config); } -} // mrdocs -} // clang +} // clang::mrdocs diff --git a/src/lib/AST/ASTVisitor.hpp b/src/lib/AST/ASTVisitor.hpp index 6aecc2363c..495a159e76 100644 --- a/src/lib/AST/ASTVisitor.hpp +++ b/src/lib/AST/ASTVisitor.hpp @@ -6,6 +6,7 @@ // // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) // Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // @@ -15,20 +16,865 @@ #include "lib/Lib/ConfigImpl.hpp" #include "lib/Lib/ExecutionContext.hpp" -#include +#include "lib/AST/SymbolFilter.hpp" +#include "lib/AST/ClangHelpers.hpp" +#include +#include +#include #include +#include +#include -namespace clang { -namespace mrdocs { +namespace clang::mrdocs { -/** Return a factory used to create our visitor. +class TypeInfoBuilder; +class NameInfoBuilder; +template +class TerminalTypeVisitor; + +/** Convert AST to metadata representation. + + This class is responsible for converting AST nodes + in a `clang::ASTContext` into metadata representation + for the MrDocs Corpus. + + It's instantiated by the `ASTVisitorConsumer` class + created by the `ASTAction` class. The `ASTVisitorConsumer` + only handles translation units represented by a + `clang::ASTContext` by creating an instance of this + class and calling the `build` method, which recursively + traverses the `clang::Decl` representing the + translation unit. + + As it traverse nodes, the `ASTVisitor` class will + create MrDocs `Info` objects for each declaration that + passes the filter configurations. */ -std::unique_ptr -makeFrontendActionFactory( - ExecutionContext& ex, - ConfigImpl const& config); +class ASTVisitor +{ + friend TypeInfoBuilder; + friend NameInfoBuilder; + template + friend class TerminalTypeVisitor; + + // MrDocs configuration + const ConfigImpl& config_; + + // MrDocs diagnostics + Diagnostics diags_; + + // The compiler instance + CompilerInstance& compiler_; + + // The AST context + ASTContext& context_; + + // The source files in memory + SourceManager& source_; + + // Semantic analysis + Sema& sema_; + + // An unordered set of all extracted Info declarations + InfoSet info_; + + /* The set of dependencies found during extraction + + The metadata for these dependencies will be extracted + after the initial extraction pass, if the configuration + option `referencedDeclarations` is not set to `never`. + + @see @ref buildDependencies + */ + std::unordered_set dependencies_; + + /* Struct to hold pre-processed file information. + + This struct stores information about a file, including its full path, + short path, and file kind. + + The short path is the path of the file relative to a search directory + used by the compiler. + */ + struct FileInfo + { + static + FileInfo + build( + std::span> search_dirs, + std::string_view file_path, + std::string_view sourceRoot); + + // The full path of the file. + std::string full_path; + + // The file path relative to a search directory. + std::string short_path; + + // The kind of the file. + FileKind kind; + }; + + /* A map of Clang FileEntry objects to Visitor FileInfo objects + + This map is used to store information about files + that have been identified through the translation + unit AST, including the full path to the file, the + short path to the file relative to a search path, + and the kind of file (source, system, etc.). + + This map is later used by the @ref buildFileInfo + function to retrieve a pointer to a FileInfo + object. This can be used later on to determine + if a file should be extracted or to add the + SourceInfo to an Info object. + */ + std::unordered_map files_; + + /* The filter for symbols to extract + + Whenever traversing a declaration, the ASTVisitor + will check if the declaration passes the filter + before extracting metadata for it. + */ + SymbolFilter symbolFilter_; + + enum class ExtractMode + { + // Extraction of declarations which pass all filters + Normal, + // Extraction of declarations as direct dependencies + DirectDependency, + // Extraction of declarations as indirect dependencies + IndirectDependency, + }; + + // The current extraction mode + ExtractMode mode = ExtractMode::Normal; + + struct [[nodiscard]] ExtractionScope + { + ASTVisitor& visitor_; + ExtractMode previous_; + + public: + ExtractionScope( + ASTVisitor& visitor, + ExtractMode mode) noexcept + : visitor_(visitor) + , previous_(visitor.mode) + { + visitor_.mode = mode; + } + + ~ExtractionScope() + { + visitor_.mode = previous_; + } + }; + + ExtractionScope + enterMode(ExtractMode new_mode) noexcept + { + return {*this, new_mode}; + } + + ExtractMode + currentMode() const noexcept + { + return mode; + } + + struct SFINAEInfo + { + TemplateDecl* Template = nullptr; + const IdentifierInfo* Member = nullptr; + ArrayRef Arguments; + }; + + template + struct upsertResult { + InfoTy& I; + bool created; + }; + +public: + /** Constructor for ASTVisitor. + + This constructor initializes the ASTVisitor with the given configuration, + diagnostics, compiler instance, AST context, and Sema object. + + It also initializes clang custom documentation commands and + populates `files_` with the FileInfo for files in the + translation unit. + + @param config The configuration object. + @param diags The diagnostics object. + @param compiler The compiler instance. + @param context The AST context. + @param sema The Sema object. + */ + ASTVisitor( + const ConfigImpl& config, + Diagnostics& diags, + CompilerInstance& compiler, + ASTContext& context, + Sema& sema) noexcept; + + /** Build the metadata representation from the AST. + + This function initiates the process of converting AST nodes + in the `clang::ASTContext` into metadata representation for + the MrDocs Corpus. It recursively traverses the AST and + extracts relevant information based on the filter configurations. + + It's the main entry point for the ASTVisitor class, and is + called by the ASTVisitorConsumer class when the ASTs for + the entire translation unit have been parsed. + + The translation unit declaration is processed with + `traverseAny`, which will traverse all scopes and declarations + recursively. + + If the configuration option `referencedDeclarations` + is not set to `never`, a second pass is made to extract + referenced declarations. The `buildDependencies` function + is responsible for this second pass. + */ + void + build(); + + /** Get the set of extracted Info declarations. + + This function returns a reference to the set of Info + declarations that have been extracted by the ASTVisitor. + + @return A reference to the InfoSet containing the extracted Info declarations. + */ + InfoSet& + results() + { + return info_; + } + +private: + /* Build metadata representation for all dependencies in the AST + + If the option `referencedDeclarations` is not set to `never`, + this function will be called after the initial extraction + pass to extract metadata for all referenced declarations + in the main symbols extracted. + + It calls `traverseAny` for each declaration in the set + of dependencies, which will recursively traverse the + declaration and extract the relevant information. + + If this pass finds new dependencies, it will call itself + recursively to extract the metadata for those dependencies + until no new dependencies are found. + */ + void + buildDependencies(); + + // ================================================= + // AST Traversal + // ================================================= + + /* Traverse a declaration context + + This function is called to traverse any declaration. + The Decl element is converted to its derived type, + and the appropriate `traverse` overload is called. + + The build() function will call this function with + context_.getTranslationUnitDecl() to initiate + the traversal of the entire AST, while + `buildDependencies()` will call it for each dependency. + + @param DC The declaration context to traverse. + @param args The arguments to forward to the `traverse` function. + */ + template + void + traverseAny(Decl* D, Args&&... args); + + /* Traverse an unspecified declaration + + Catch-all function to traverse any declaration. + The function will attempt to convert the declaration + to a DeclContext and call traverse it if successful. + */ + template + void + traverse(Decl* D, Args&&...); + + /* Traverse declaration contexts + + This function is called to traverse the members of a declaration + that is also a context with other members. + + The function will call traverseAny for each member of the + declaration context. + */ + void + traverse(DeclContext* DC); + + /* The default implementation for the traverse function + + This function defines the usual behavior for the + traverse function for a concrete declaration type. + + It creates a new corresponding Info object for the + declaration, populates it with the necessary information, + and optionally traverses the members of the declaration. + + The Traverse template parameter is used to determine + whether the function should traverse the members of + the declaration context. + + @param D The declaration to traverse + */ + template + requires + std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as + void + traverse(DeclTy* D); + + /* Traverse a C++ struct, union, or class + + This function is called by traverseAny to traverse a + C++ struct, union, or class. `D` can be a CXXRecordDecl + or a templated CXXRecordDecl. + + If `CTD` is not null, the function will populate + the template parameters of the Record object. + `CTD` might represent a partial specialization, + an explicit specialization, or the primary template. + */ + template CXXRecordDeclTy> + void + traverse(CXXRecordDeclTy*, ClassTemplateDecl* CTD = nullptr); + + /* Traverse a C++ function + + This function is called by traverseAny to traverse a + C++ function. `D` can be a FunctionDecl + or a templated FunctionDecl. + + If `CTD` is not null, the function will populate + the template parameters of the function. + `CTD` might represent a partial specialization, + an explicit specialization, or the primary template. + */ + template FunctionDeclTy> + void + traverse(FunctionDeclTy* D, FunctionTemplateDecl* CTD = nullptr); + + /* Traverse a typedef declaration + + This function is called by traverseAny to traverse a + typedef declaration. `D` can be a TypedefNameDecl + or a templated TypedefNameDecl. + + If `ATD` is not null, the function will populate + the template parameters of the Typedef object. + `ATD` might represent a partial specialization, + an explicit specialization, or the primary template. + */ + template TypedefNameDeclTy> + void + traverse(TypedefNameDeclTy * D, TypeAliasTemplateDecl * ATD = nullptr); + + /* Traverse a variable declaration or definition + + This function is called by traverseAny to traverse a + variable declaration or definition. `D` can be a VarDecl + or a templated VarDecl. + + If `VTD` is not null, the function will populate + the template parameters of the Variable object. + `VTD` might represent a partial specialization, + an explicit specialization, or the primary template. + */ + template VarDeclTy> + void + traverse(VarDeclTy* D, VarTemplateDecl* VTD = nullptr); + + /* Traverse a deduction guide + + This function is called by traverseAny to traverse a + C++ deduction guide. `D` can be a CXXDeductionGuideDecl + or a templated CXXDeductionGuideDecl. + + If `FTD` is not null, the function will populate + the template parameters of the Guide object. + */ + void + traverse( + CXXDeductionGuideDecl* D, + FunctionTemplateDecl const* FTD = nullptr); + + /* Traverse a using directive + + This function is called to traverse a using directive + such as `using namespace std;`. + + The parent declaration is extracted and included + in the dependencies. + */ + void + traverse(UsingDirectiveDecl*); + + /* Traverse a concept definition + + This function is called by traverseAny to traverse a + C++ concept definition. + */ + void + traverse(ConceptDecl*); + + /* Traverse a member of an anonymous union. + */ + void + traverse(IndirectFieldDecl*); + + // ================================================= + // AST Traversal Helpers + // ================================================= + + /* Generates a Unified Symbol Resolution value for a declaration. + + USRs are strings that provide an unambiguous reference to a symbol. + + This function determines the underlying Decl type and + generates a USR for it. + + @returns true if USR generation succeeded. + */ + Expected> + generateUSR(const Decl* D) const; + + /* Generate the symbol ID for a declaration. + + This function will extract the symbol ID for a + declaration, and store it in the `id` input + parameter. + + As USRs (Unified Symbol Resolution) could be + large, especially for functions with long type + arguments, we use 160-bits SHA1(USR) values. + + To guarantee the uniqueness of symbols while using + a relatively small amount of memory (vs storing + USRs directly), this function hashes the Decl + USR value with SHA1. + + @return true if the symbol ID could be extracted. + */ + bool + generateID(const Decl* D, SymbolID& id) const; + + /* Extracts the symbol ID for a declaration. + + This function will extract the symbol ID for a + declaration, and return it. + + If the `generateID` function fails, the function + will return SymbolID::invalid. + + @return the symbol ID for the declaration. + */ + SymbolID + generateID(const Decl* D) const; + + // ================================================= + // Populate functions + // ================================================= + void + populate(NamespaceInfo& I, bool created, NamespaceDecl* D); + + void + populate(RecordInfo& I, bool created, CXXRecordDecl* D); + + template DeclTy> + void + populate(FunctionInfo& I, bool created, DeclTy* D); + + void + populate(EnumInfo& I, bool created, EnumDecl* D); + + void + populate(EnumConstantInfo& I, bool created, EnumConstantDecl* D); + + void + populate(TypedefInfo& I, bool created, TypedefNameDecl* D); + + void + populate(VariableInfo& I, bool created, VarDecl* D); + + void + populate(FieldInfo& I, bool created, FieldDecl* D); + + void + populate(SpecializationInfo& I, bool created, ClassTemplateSpecializationDecl* D); + + void + populate(FriendInfo& I, bool created, FriendDecl* D); + + void + populate(GuideInfo& I, bool created, CXXDeductionGuideDecl* D); + + void + populate(NamespaceAliasInfo& I, bool created, NamespaceAliasDecl* D); + + void + populate(UsingInfo& I, bool created, UsingDecl* D); + + void + populate(ConceptInfo& I, bool created, ConceptDecl* D); + + void + populate(SourceInfo& I, clang::SourceLocation loc, bool definition, bool documented); + + void + populate(NoexceptInfo& I, const FunctionProtoType* FPT); + + void + populate(ExplicitInfo& I, const ExplicitSpecifier& ES); + + void + populate(ExprInfo& I, const Expr* E); + + template + void + populate(ConstantExprInfo& I, const Expr* E); + + template + void + populate(ConstantExprInfo& I, const Expr* E, const llvm::APInt& V); + + void + populate(std::unique_ptr& I, const NamedDecl* N); + + void + populate(TemplateInfo& TI, const TemplateParameterList* TPL); + + template + void + populate( + std::vector>& result, + Range&& args); + + void + populate( + std::vector>& result, + const ASTTemplateArgumentListInfo* args); + + // ================================================= + // Populate function helpers + // ================================================= + // Extract the name of a declaration + std::string + extractName(NamedDecl const* D); + + // Extract the name of a declaration + std::string + extractName(DeclarationName N); + + /* Populate the Info with its parent namespaces + + Given a Decl `D`, this function will populate + the `Info` `I` with the SymbolID of each parent namespace + of `D`. The SymbolID of the global namespace is always + included as the first element of `I.Namespace`. + */ + void + populateNamespaces(Info& I, Decl* D); + + /* Emplace a member Info into a ScopeInfo + + Given a ScopeInfo `P` and an Info `C`, this function will + add the SymbolID of `C` to `P.Members` and `P.Lookups[C.Name]`. + + @param P The parent ScopeInfo + @param C The child Info + */ + static + void + addMember( + ScopeInfo& P, + Info& C); + + /* Parse the comments above a declaration as Javadoc + + This function will parse the comments above a declaration + as Javadoc, and store the results in the `javadoc` input + parameter. + + @return true if the comments were successfully parsed as + Javadoc, and false otherwise. + */ + bool + generateJavadoc( + std::unique_ptr& javadoc, + Decl const* D); + + std::unique_ptr + toTypeInfo( + QualType qt, + ExtractMode extract_mode = ExtractMode::IndirectDependency); + + std::unique_ptr + toNameInfo( + NestedNameSpecifier const* NNS, + ExtractMode extract_mode = ExtractMode::IndirectDependency); + + template > + std::unique_ptr + toNameInfo( + DeclarationName Name, + std::optional TArgs = std::nullopt, + const NestedNameSpecifier* NNS = nullptr, + ExtractMode extract_mode = ExtractMode::IndirectDependency); + + template > + std::unique_ptr + toNameInfo( + const Decl* D, + std::optional TArgs = std::nullopt, + const NestedNameSpecifier* NNS = nullptr, + ExtractMode extract_mode = ExtractMode::IndirectDependency); + + std::unique_ptr + toTArg(const TemplateArgument& A); + + // Pretty-print an expression + std::string + toString(const Expr* E); + + // Pretty-print a type + std::string + toString(const Type* T); + + template + Integer + toInteger(const llvm::APInt& V); + + /* Get the original source code in a range + + This function is used to populate the information in + Info types that contain expressions as written + in the source code, such as the value of default + arguments. + */ + std::string + getSourceCode(SourceRange const& R) const; + + // Determine if a type is a SFINAE type + std::optional>> + isSFINAEType(QualType T); + + // Determine if a type is a SFINAE type + std::optional>> + isSFINAEType(Type const* T) + { + return isSFINAEType(QualType(T, 0)); + } + + std::optional< + std::tuple> + isSFINAETemplate(TemplateDecl* TD, IdentifierInfo const* Member); + + static std::optional + getSFINAETemplate(QualType T, bool AllowDependentNames); + + static + std::optional + tryGetTemplateArgument( + TemplateParameterList* Parameters, + ArrayRef Arguments, + unsigned Index); + + // ================================================= + // Filters + // ================================================= + + /* Determine if a declaration should be extracted + + This function will determine whether a declaration + should be extracted based on the current extraction + mode, and the current symbol filter state. + + The function filters private symbols, symbols outside + the input files, and symbols in files that do not match + the input file patterns. + + Any configuration options that affect extraction + will be checked here. + + @param D the declaration to check + @param access the access specifier of the declaration + + @return true if the declaration should be extracted, + and false otherwise. + */ + bool + shouldExtract(const Decl* D, AccessSpecifier access); + + // Determine if a declaration passes the symbol filter + bool + checkSymbolFilter(const NamedDecl* ND); + + // Determine if a declaration is in a file that should be extracted + bool + inExtractedFile(const Decl* D); + + /* Determine if the declaration is in a special namespace + + This function will determine if a declaration is in a + special namespace based on the current symbol filter + state and the special namespace patterns. + + The function will check if the declaration is in a + special namespace, and if so, it will return true. + + @param D the declaration to check + @param Patterns the special namespace patterns + + @return true if the declaration is in a special namespace, + and false otherwise. + */ + static + bool + isInSpecialNamespace( + const Decl* D, + std::span Patterns); + + // @overload isInSpecialNamespace + static + bool + isInSpecialNamespace( + const NestedNameSpecifier* NNS, + std::span Patterns); + + /* Check if a declaration is in a special namespace + + This function will check if a declaration is in a special + namespace based on the current symbol filter state and + the special namespace patterns. + + If the declaration is in a special namespace, the function + will set the NameInfo `I` to the appropriate special + namespace name (see-below or implementation-defined), and + return true. + + @param I The NameInfo to set if the declaration is in a + special namespace + @param NNS the nested name specifier of the declaration + @param D the declaration to check + + @return true if the declaration is in a special namespace, + and false otherwise. + */ + bool + checkSpecialNamespace( + std::unique_ptr& I, + const NestedNameSpecifier* NNS, + const Decl* D) const; + + // @overload checkSpecialNamespace + bool + checkSpecialNamespace( + std::unique_ptr& I, + const NestedNameSpecifier* NNS, + const Decl* D) const; + + // ================================================= + // Element access + // ================================================= + + /* Get Info from ASTVisitor InfoSet + */ + Info* + find(SymbolID const& id); + + /* Get FileInfo from ASTVisitor files + + This function will return a pointer to a FileInfo + object for a given Clang FileEntry object. + + If the FileInfo object does not exist, the function + will construct a new FileInfo object and add it to + the files_ map. + + The map of files is created during the object + construction, and is populated with the FileInfo + for each file in the translation unit. + + @param file The Clang FileEntry object to get the FileInfo for. + + @return a pointer to the FileInfo object. + */ + FileInfo* + findFileInfo(clang::SourceLocation loc); + + /* Get or construct an empty Info with a specified id. + + If an Info object for the declaration has already + been extracted, this function will return a reference + to the Info object. + + Otherwise, it will construct a new Info object of type + `InfoTy` with the given `id`, and return a reference + to the new Info object. + */ + template InfoTy> + upsertResult + upsert(SymbolID const& id); + + /* Get or construct an empty Info for a declaration. + + The function will determine if the declaration should + be extracted, determine the symbol ID for the declaration, + and, if there are no errors, call `upsert` with the + symbol ID. + + The return type for the result is inferred from the + declaration type. + + @param D The declaration to extract + */ + template DeclType> + Expected>> + upsert(DeclType* D); + + /* Get or construct an empty Info for a dependency declaration. + + The function will determine the symbol ID for the + declaration, store it in the `id` input parameter, + and add it to the set of dependencies. + + The dependencies are processed after the initial + extraction pass, if the configuration option + `referencedDeclarations` is not set to `never`. + */ + void + upsertDependency(Decl* D, SymbolID& id); + + // @overload upsertDependency + void + upsertDependency(Decl const* D, SymbolID& id) + { + return upsertDependency(const_cast(D), id); + } +}; -} // mrdocs -} // clang +} // clang::mrdocs #endif diff --git a/src/lib/AST/ASTVisitorConsumer.cpp b/src/lib/AST/ASTVisitorConsumer.cpp new file mode 100644 index 0000000000..bb4fd64509 --- /dev/null +++ b/src/lib/AST/ASTVisitorConsumer.cpp @@ -0,0 +1,38 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#include "lib/AST/ASTVisitorConsumer.hpp" +#include "lib/AST/ASTVisitor.hpp" +#include "lib/Support/Path.hpp" + +namespace clang { +namespace mrdocs { + +void +ASTVisitorConsumer:: +HandleTranslationUnit(ASTContext& Context) +{ + MRDOCS_ASSERT(sema_); + Diagnostics diags; + ASTVisitor visitor( + config_, + diags, + compiler_, + Context, + *sema_); + visitor.build(); + ex_.report(std::move(visitor.results()), std::move(diags)); +} + +} // mrdocs +} // clang diff --git a/src/lib/AST/ASTVisitorConsumer.hpp b/src/lib/AST/ASTVisitorConsumer.hpp new file mode 100644 index 0000000000..3130836e70 --- /dev/null +++ b/src/lib/AST/ASTVisitorConsumer.hpp @@ -0,0 +1,266 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#ifndef MRDOCS_LIB_AST_ASTVISITORCONSUMER_HPP +#define MRDOCS_LIB_AST_ASTVISITORCONSUMER_HPP + +#include "lib/Lib/ConfigImpl.hpp" +#include "lib/Lib/ExecutionContext.hpp" +#include +#include +#include + +namespace clang { +namespace mrdocs { + +/** A consumer for visiting AST nodes and performing semantic analysis. + + The @ref ASTAction class uses the ASTVisitor to perform semantic + analysis on the AST and convert AST nodes into Info types for + the MrDocs Corpus. This class is derived from `clang::SemaConsumer` + and is used to visit AST nodes and perform various semantic + analyses and transformations. + + This is done by overriding the virtual functions of the + `clang::SemaConsumer` class. The main function this class + overrides is `HandleTranslationUnit`, which is called + when the translation unit is complete. + */ +class ASTVisitorConsumer + : public SemaConsumer +{ + const ConfigImpl& config_; + ExecutionContext& ex_; + CompilerInstance& compiler_; + Sema* sema_ = nullptr; + +public: + ASTVisitorConsumer( + const ConfigImpl& config, + ExecutionContext& ex, + CompilerInstance& compiler) noexcept + : config_(config) + , ex_(ex) + , compiler_(compiler) + { + } + + /** Initialize the semantic consumer + + Initialize the semantic consumer with the Sema instance + being used to perform semantic analysis on the abstract syntax + tree. + */ + void + InitializeSema(Sema& S) override + { + // Sema should not have been initialized yet + MRDOCS_ASSERT(!sema_); + sema_ = &S; + } + + /** Inform the semantic consumer that Sema is no longer available. + + This is called when the Sema instance is no longer available. + */ + void + ForgetSema() override + { + sema_ = nullptr; + } + + /** Handle a TranslationUnit + + This method is called when the ASTs for entire + translation unit have been parsed. + + It's the main entry point for the ASTVisitorConsumer. + It initializes the diagnostics reporter, loads and caches + source files into memory, and then creates an ASTVisitor + to traverse the translation unit. + + All other `Handle*` methods called by when a specific type + of declaration or definition is found is left as an empty stub. + */ + void + HandleTranslationUnit(ASTContext& Context) override; + + /** Handle the specified top-level declaration. + + This is called by the parser to process every + top-level Decl*. + + @returns `true` to always continue parsing + */ + bool + HandleTopLevelDecl(DeclGroupRef) override + { + return true; + } + + /** Handle a static member variable instantiation. + + This is called by the parser to process a static + member variable instantiation. + + The default implementation is an empty stub. + This implementation sets the declaration as implicit + because implicitly instantiated definitions of non-inline + static data members of class templates are added to + the end of the TU DeclContext. As a result, Decl::isImplicit + returns false for these VarDecls, so we manually set it here. + + @param D The declaration of the static member variable + */ + void + HandleCXXStaticMemberVarInstantiation(VarDecl* D) override + { + D->setImplicit(); + } + + /** Handle an implicit function instantiation. + + This is called by the parser to process an implicit + function instantiation. + + At this point, the function does not have a body. Its body is + instantiated at the end of the translation unit and passed to + HandleTopLevelDecl. + + The default implementation is an empty stub. + This implementation sets the declaration as implicit + because implicitly instantiated definitions of member + functions of class templates are added to the end of the + TU DeclContext. As a result, Decl::isImplicit returns + false for these FunctionDecls, so we manually set it here. + + @param D The declaration of the function + */ + void + HandleCXXImplicitFunctionInstantiation(FunctionDecl* D) override + { + D->setImplicit(); + } + + /** Handle an inline function definition. + + This is called by the parser to process an inline + function definition. + + The implementation is an empty stub. + + @param D The declaration of the function + */ + void HandleInlineFunctionDefinition(FunctionDecl*) override { } + + /** Handle a tag declaration definition. + + This is called by the parser to process a tag declaration + definition. + + The implementation is an empty stub. + + @param D The declaration of the tag + */ + void HandleTagDeclDefinition(TagDecl*) override { } + + /** Handle a tag declaration required definition. + + This is called by the parser to process a tag declaration + required definition. + + The implementation is an empty stub. + + @param D The declaration of the tag + */ + void HandleTagDeclRequiredDefinition(const TagDecl*) override { } + + /** Handle an interesting declaration. + + This is called by the AST reader when deserializing things + that might interest the consumer. + + The default implementation forwards to HandleTopLevelDecl. + + The implementation is an empty stub. + + @param D The declaration + */ + void HandleInterestingDecl(DeclGroupRef) override { } + + /** Handle a tentative definition. + + This is called by the parser to process a tentative definition. + + The implementation is an empty stub. + + @param D The declaration + */ + void CompleteTentativeDefinition(VarDecl*) override { } + + /** Handle a tentative definition. + + This is called by the parser to process a tentative definition. + + The implementation is an empty stub. + + @param D The declaration + */ + void CompleteExternalDeclaration(DeclaratorDecl*) override { } + + /** Handle a vtable. + + This is called by the parser to process a vtable. + + The implementation is an empty stub. + + @param D The declaration + */ + void AssignInheritanceModel(CXXRecordDecl*) override { } + + /** Handle an implicit import declaration. + + This is called by the parser to process an implicit import declaration. + + The implementation is an empty stub. + + @param D The declaration + */ + void HandleVTable(CXXRecordDecl*) override { } + + /** Handle an implicit import declaration. + + This is called by the parser to process an implicit import declaration. + + The implementation is an empty stub. + + @param D The declaration + */ + void HandleImplicitImportDecl(ImportDecl*) override { } + + /** Handle a top-level declaration in an Objective-C container. + + This is called by the parser to process a top-level declaration + in an Objective-C container. + + The implementation is an empty stub. + + @param D The declaration + */ + void HandleTopLevelDeclInObjCContainer(DeclGroupRef) override { } +}; + +} // mrdocs +} // clang + +#endif diff --git a/src/lib/AST/ASTVisitorHelpers.hpp b/src/lib/AST/ASTVisitorHelpers.hpp deleted file mode 100644 index fd81637e72..0000000000 --- a/src/lib/AST/ASTVisitorHelpers.hpp +++ /dev/null @@ -1,550 +0,0 @@ -// 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) 2023 Krystian Stasiowski (sdkrystian@gmail.com) -// -// Official repository: https://github.com/cppalliance/mrdocs -// - -#ifndef MRDOCS_LIB_AST_ASTVISITORHELPERS_HPP -#define MRDOCS_LIB_AST_ASTVISITORHELPERS_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace clang { -namespace mrdocs { -namespace { - -/** Determine the MrDocs Info type for a Clang DeclType - - This trait associates a Clang Decl type with the corresponding - MrDocs Info type, when there is a direct correspondence. - - This is used to determine what type of Info object to create - and return when @ref ASTVisitor::upsertMrDocsInfoFor is called - to create or update an Info object for a Decl. - - Not all Info types have a direct correspondence with a Decl type. - In this case, the objects are created and updated by the visitor - at other steps in the traversal. - - When there's no direct correspondence, this trait returns - the base Info type. - - */ -template -struct MrDocsType : std::type_identity {}; - -template DeclType> -struct MrDocsType : std::type_identity {}; - -template VarTy> -struct MrDocsType : std::type_identity {}; - -template FunctionTy> -struct MrDocsType : std::type_identity {}; - -template TypedefNameTy> -struct MrDocsType : std::type_identity {}; - -template <> -struct MrDocsType : std::type_identity {}; - -template <> -struct MrDocsType : std::type_identity {}; - -template <> -struct MrDocsType : std::type_identity {}; - -template <> -struct MrDocsType : std::type_identity {}; - -template <> -struct MrDocsType : std::type_identity {}; - -template <> -struct MrDocsType : std::type_identity {}; - -template <> -struct MrDocsType : std::type_identity {}; - -template <> -struct MrDocsType : std::type_identity{}; - -template <> -struct MrDocsType : std::type_identity{}; - -/// @copydoc MrDocsType -template -using MrDocsType_t = typename MrDocsType::type; - -/** Convert a Clang AccessSpecifier into a MrDocs AccessKind - */ -AccessKind -convertToAccessKind( - AccessSpecifier spec) -{ - using OldKind = AccessSpecifier; - using NewKind = AccessKind; - switch(spec) - { - case OldKind::AS_public: return NewKind::Public; - case OldKind::AS_protected: return NewKind::Protected; - case OldKind::AS_private: return NewKind::Private; - case OldKind::AS_none: return NewKind::None; - default: - MRDOCS_UNREACHABLE(); - } -} - -/** Convert a Clang StorageClass into a MrDocs StorageClassKind - */ -StorageClassKind -convertToStorageClassKind( - StorageClass spec) -{ - using OldKind = StorageClass; - using NewKind = StorageClassKind; - switch(spec) - { - case OldKind::SC_None: return NewKind::None; - case OldKind::SC_Extern: return NewKind::Extern; - case OldKind::SC_Static: return NewKind::Static; - case OldKind::SC_Auto: return NewKind::Auto; - case OldKind::SC_Register: return NewKind::Register; - default: - // SC_PrivateExtern (__private_extern__) - // is a C only Apple extension - MRDOCS_UNREACHABLE(); - } -} - -/** Convert a Clang ConstexprSpecKind into a MrDocs ConstexprKind - */ -ConstexprKind -convertToConstexprKind( - ConstexprSpecKind spec) -{ - using OldKind = ConstexprSpecKind; - using NewKind = ConstexprKind; - switch(spec) - { - case OldKind::Unspecified: return NewKind::None; - case OldKind::Constexpr: return NewKind::Constexpr; - case OldKind::Consteval: return NewKind::Consteval; - // KRYSTIAN NOTE: ConstexprSpecKind::Constinit exists, - // but I don't think it's ever used because a variable - // can be declared both constexpr and constinit - // (but not both in the same declaration) - case OldKind::Constinit: - default: - MRDOCS_UNREACHABLE(); - } -} - -/** Convert a Clang ExplicitSpecKind into a MrDocs ExplicitKind - */ -ExplicitKind -convertToExplicitKind( - const ExplicitSpecifier& spec) -{ - using OldKind = ExplicitSpecKind; - using NewKind = ExplicitKind; - - // no explicit-specifier - if(! spec.isSpecified()) - return NewKind::False; - - switch(spec.getKind()) - { - case OldKind::ResolvedFalse: - return NewKind::False; - case OldKind::ResolvedTrue: - return NewKind::True; - case OldKind::Unresolved: - return NewKind::Dependent; - default: - MRDOCS_UNREACHABLE(); - } -} - -/** Convert a Clang ExceptionSpecificationType into a MrDocs NoexceptKind - */ -NoexceptKind -convertToNoexceptKind( - ExceptionSpecificationType spec) -{ - using OldKind = ExceptionSpecificationType; - using NewKind = NoexceptKind; - // KRYSTIAN TODO: right now we convert pre-C++17 dynamic exception - // specifications to an (roughly) equivalent noexcept-specifier - switch(spec) - { - case OldKind::EST_None: - case OldKind::EST_MSAny: - case OldKind::EST_Unevaluated: - case OldKind::EST_Uninstantiated: - // we *shouldn't* ever encounter an unparsed exception specification, - // assuming that clang is working correctly... - case OldKind::EST_Unparsed: - case OldKind::EST_Dynamic: - case OldKind::EST_NoexceptFalse: - return NewKind::False; - case OldKind::EST_NoThrow: - case OldKind::EST_BasicNoexcept: - case OldKind::EST_NoexceptTrue: - case OldKind::EST_DynamicNone: - return NewKind::True; - case OldKind::EST_DependentNoexcept: - return NewKind::Dependent; - default: - MRDOCS_UNREACHABLE(); - } -} - -/** Convert a Clang OverloadedOperatorKind into a MrDocs OperatorKind - */ -OperatorKind -convertToOperatorKind( - OverloadedOperatorKind kind) -{ - using OldKind = OverloadedOperatorKind; - using NewKind = OperatorKind; - switch(kind) - { - case OldKind::OO_None: return NewKind::None; - case OldKind::OO_New: return NewKind::New; - case OldKind::OO_Delete: return NewKind::Delete; - case OldKind::OO_Array_New: return NewKind::ArrayNew; - case OldKind::OO_Array_Delete: return NewKind::ArrayDelete; - case OldKind::OO_Plus: return NewKind::Plus; - case OldKind::OO_Minus: return NewKind::Minus; - case OldKind::OO_Star: return NewKind::Star; - case OldKind::OO_Slash: return NewKind::Slash; - case OldKind::OO_Percent: return NewKind::Percent; - case OldKind::OO_Caret: return NewKind::Caret; - case OldKind::OO_Amp: return NewKind::Amp; - case OldKind::OO_Pipe: return NewKind::Pipe; - case OldKind::OO_Tilde: return NewKind::Tilde; - case OldKind::OO_Exclaim: return NewKind::Exclaim; - case OldKind::OO_Equal: return NewKind::Equal; - case OldKind::OO_Less: return NewKind::Less; - case OldKind::OO_Greater: return NewKind::Greater; - case OldKind::OO_PlusEqual: return NewKind::PlusEqual; - case OldKind::OO_MinusEqual: return NewKind::MinusEqual; - case OldKind::OO_StarEqual: return NewKind::StarEqual; - case OldKind::OO_SlashEqual: return NewKind::SlashEqual; - case OldKind::OO_PercentEqual: return NewKind::PercentEqual; - case OldKind::OO_CaretEqual: return NewKind::CaretEqual; - case OldKind::OO_AmpEqual: return NewKind::AmpEqual; - case OldKind::OO_PipeEqual: return NewKind::PipeEqual; - case OldKind::OO_LessLess: return NewKind::LessLess; - case OldKind::OO_GreaterGreater: return NewKind::GreaterGreater; - case OldKind::OO_LessLessEqual: return NewKind::LessLessEqual; - case OldKind::OO_GreaterGreaterEqual: return NewKind::GreaterGreaterEqual; - case OldKind::OO_EqualEqual: return NewKind::EqualEqual; - case OldKind::OO_ExclaimEqual: return NewKind::ExclaimEqual; - case OldKind::OO_LessEqual: return NewKind::LessEqual; - case OldKind::OO_GreaterEqual: return NewKind::GreaterEqual; - case OldKind::OO_Spaceship: return NewKind::Spaceship; - case OldKind::OO_AmpAmp: return NewKind::AmpAmp; - case OldKind::OO_PipePipe: return NewKind::PipePipe; - case OldKind::OO_PlusPlus: return NewKind::PlusPlus; - case OldKind::OO_MinusMinus: return NewKind::MinusMinus; - case OldKind::OO_Comma: return NewKind::Comma; - case OldKind::OO_ArrowStar: return NewKind::ArrowStar; - case OldKind::OO_Arrow: return NewKind::Arrow; - case OldKind::OO_Call: return NewKind::Call; - case OldKind::OO_Subscript: return NewKind::Subscript; - case OldKind::OO_Conditional: return NewKind::Conditional; - case OldKind::OO_Coawait: return NewKind::Coawait; - default: - MRDOCS_UNREACHABLE(); - } -} - -/** Convert a Clang ReferenceKind into a MrDocs ReferenceKind - */ -ReferenceKind -convertToReferenceKind( - RefQualifierKind kind) -{ - using OldKind = RefQualifierKind; - using NewKind = ReferenceKind; - switch(kind) - { - case OldKind::RQ_None: return NewKind::None; - case OldKind::RQ_LValue: return NewKind::LValue; - case OldKind::RQ_RValue: return NewKind::RValue; - default: - MRDOCS_UNREACHABLE(); - } -} - -/** Convert a Clang TagTypeKind into a MrDocs RecordKeyKind - */ -RecordKeyKind -convertToRecordKeyKind( - TagTypeKind kind) -{ - using OldKind = TagTypeKind; - using NewKind = RecordKeyKind; - switch(kind) - { - case OldKind::Struct: return NewKind::Struct; - case OldKind::Class: return NewKind::Class; - case OldKind::Union: return NewKind::Union; - default: - // unsupported TagTypeKind (Interface, or Enum) - MRDOCS_UNREACHABLE(); - } -} - -/** Convert a Clang unsigned qualifier kind into a MrDocs QualifierKind - */ -QualifierKind -convertToQualifierKind( - unsigned quals) -{ - std::underlying_type_t< - QualifierKind> result = QualifierKind::None; - if(quals & Qualifiers::Const) - result |= QualifierKind::Const; - if(quals & Qualifiers::Volatile) - result |= QualifierKind::Volatile; - return static_cast(result); - -} - -/** Convert a Clang Decl::Kind into a MrDocs FunctionClass - */ -FunctionClass -convertToFunctionClass( - Decl::Kind kind) -{ - using OldKind = Decl::Kind; - using NewKind = FunctionClass; - switch(kind) - { - case OldKind::Function: return NewKind::Normal; - case OldKind::CXXMethod: return NewKind::Normal; - case OldKind::CXXConstructor: return NewKind::Constructor; - case OldKind::CXXConversion: return NewKind::Conversion; - case OldKind::CXXDestructor: return NewKind::Destructor; - default: - MRDOCS_UNREACHABLE(); - } -} - -/** Convert a Clang AutoTypeKeyword into a MrDocs AutoKind - */ -AutoKind -convertToAutoKind( - AutoTypeKeyword kind) -{ - using OldKind = AutoTypeKeyword; - using NewKind = AutoKind; - switch(kind) - { - case OldKind::Auto: - case OldKind::GNUAutoType: - return NewKind::Auto; - case OldKind::DecltypeAuto: - return NewKind::DecltypeAuto; - default: - MRDOCS_UNREACHABLE(); - } -} - -// ---------------------------------------------------------------- - -/** Visit a Decl and call the appropriate visitor function. - */ -template< - typename DeclTy, - typename Visitor, - typename... Args> - requires std::derived_from -decltype(auto) -visit( - DeclTy* D, - Visitor&& visitor, - Args&&... args) -{ - MRDOCS_ASSERT(D); - switch(D->getKind()) - { - #define ABSTRACT_DECL(TYPE) - #define DECL(DERIVED, BASE) \ - case Decl::DERIVED: \ - if constexpr(std::derived_from) \ - return std::forward(visitor)( \ - static_cast*>(D), \ - std::forward(args)...); \ - else \ - MRDOCS_UNREACHABLE(); - - #include - - default: - MRDOCS_UNREACHABLE(); - } -} - -template -consteval -Decl::Kind -DeclToKindImpl() = delete; - -#define ABSTRACT_DECL(TYPE) -#define DECL(DERIVED, BASE) \ - template<> \ - consteval \ - Decl::Kind \ - DeclToKindImpl() { return Decl::DERIVED; } - -#include - -/** Get the Decl::Kind for a type DeclTy derived from Decl. - */ -template -consteval -Decl::Kind -DeclToKind() -{ - return DeclToKindImpl< - std::remove_cvref_t>(); -} - -// ---------------------------------------------------------------- - -/** Visit a Type and call the appropriate visitor function. - */ -template< - typename TypeTy, - typename Visitor, - typename... Args> - requires std::derived_from -decltype(auto) -visit( - TypeTy* T, - Visitor&& visitor, - Args&&... args) -{ - #define ABSTRACT_TYPE(DERIVED, BASE) - switch(T->getTypeClass()) - { - #define TYPE(DERIVED, BASE) \ - case Type::DERIVED: \ - if constexpr(std::derived_from) \ - return std::forward(visitor)( \ - static_cast*>(T), \ - std::forward(args)...); \ - else \ - MRDOCS_UNREACHABLE(); - - #include - - default: - MRDOCS_UNREACHABLE(); - } -} - -template -consteval -Type::TypeClass -TypeToKindImpl() = delete; - -#define ABSTRACT_TYPE(DERIVED, BASE) -#define TYPE(DERIVED, BASE) \ - template<> \ - consteval \ - Type::TypeClass \ - TypeToKindImpl() { return Type::DERIVED; } - -#include - -template -consteval -Type::TypeClass -TypeToKind() -{ - return TypeToKindImpl< - std::remove_cvref_t>(); -} - -// ---------------------------------------------------------------- - -/** Visit a TypeLoc and call the appropriate visitor function. - */ -template< - typename TypeLocTy, - typename Visitor, - typename... Args> - requires std::derived_from -decltype(auto) -visit( - TypeLocTy* T, - Visitor&& visitor, - Args&&... args) -{ - switch(T->getTypeLocClass()) - { - #define ABSTRACT_TYPELOC(DERIVED, BASE) - #define TYPELOC(DERIVED, BASE) \ - case TypeLoc::DERIVED: \ - if constexpr(std::derived_from) \ - return std::forward(visitor)( \ - static_cast*>(T), \ - std::forward(args)...); \ - else \ - MRDOCS_UNREACHABLE(); - - #include - - default: - MRDOCS_UNREACHABLE(); - } -} - -template -consteval -TypeLoc::TypeLocClass -TypeLocToKindImpl() = delete; - -#define ABSTRACT_TYPELOC(DERIVED, BASE) -#define TYPELOC(DERIVED, BASE) \ - template<> \ - consteval \ - TypeLoc::TypeLocClass \ - TypeLocToKindImpl() { return TypeLoc::DERIVED; } - -#include - -/** Get the TypeLoc::TypeLocClass for a type TypeLocTy derived from TypeLoc. - */ -template -consteval -TypeLoc::TypeLocClass -TypeLocToKind() -{ - return TypeLocToKindImpl< - std::remove_cvref_t>(); -} - -} // (anon) -} // mrdocs -} // clang - -#endif diff --git a/src/lib/AST/ClangHelpers.cpp b/src/lib/AST/ClangHelpers.cpp new file mode 100644 index 0000000000..3b4a7f8335 --- /dev/null +++ b/src/lib/AST/ClangHelpers.cpp @@ -0,0 +1,245 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#include "lib/AST/ClangHelpers.hpp" +#include +#include +#include + +namespace clang::mrdocs { + +const Expr* +SubstituteConstraintExpressionWithoutSatisfaction( + Sema &S, const Sema::TemplateCompareNewDeclInfo &DeclInfo, + const Expr *ConstrExpr) +{ + MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs( + DeclInfo.getDecl(), DeclInfo.getLexicalDeclContext(), /*Final=*/false, + /*Innermost=*/std::nullopt, + /*RelativeToPrimary=*/true, + /*Pattern=*/nullptr, /*ForConstraintInstantiation=*/true, + /*SkipForSpecialization*/ false); + + if (MLTAL.getNumSubstitutedLevels() == 0) + { + return ConstrExpr; + } + + Sema::SFINAETrap const SFINAE(S, /*AccessCheckingSFINAE=*/false); + + Sema::InstantiatingTemplate Inst( + S, DeclInfo.getLocation(), + Sema::InstantiatingTemplate::ConstraintNormalization{}, + const_cast(DeclInfo.getDecl()), SourceRange{}); + if (Inst.isInvalid()) + { + return nullptr; + } + + // Set up a dummy 'instantiation' scope in the case of reference to function + // parameters that the surrounding function hasn't been instantiated yet. Note + // this may happen while we're comparing two templates' constraint + // equivalence. + LocalInstantiationScope ScopeForParameters(S); + if (auto *FD = DeclInfo.getDecl()->getAsFunction()) + { + for (auto *PVD : FD->parameters()) + { + ScopeForParameters.InstantiatedLocal(PVD, PVD); + } + } + + std::optional ThisScope; + + // See TreeTransform::RebuildTemplateSpecializationType. A context scope is + // essential for having an injected class as the canonical type for a template + // specialization type at the rebuilding stage. This guarantees that, for + // out-of-line definitions, injected class name types and their equivalent + // template specializations can be profiled to the same value, which makes it + // possible that e.g. constraints involving C> and C are + // perceived identical. + std::optional ContextScope; + if (auto *RD = dyn_cast(DeclInfo.getDeclContext())) + { + ThisScope.emplace(S, const_cast(RD), Qualifiers()); + ContextScope.emplace(S, const_cast(cast(RD)), + /*NewThisContext=*/false); + } + ExprResult SubstConstr = S.SubstConstraintExprWithoutSatisfaction( + const_cast(ConstrExpr), MLTAL); + if (SFINAE.hasErrorOccurred() || !SubstConstr.isUsable()) + { + return nullptr; + } + return SubstConstr.get(); +} + +AccessSpecifier +getAccess(const Decl* D) +{ + // First, get the declaration this was instantiated from + D = getInstantiatedFrom(D); + + // If this is the template declaration of a template, + // use the access of the template + if (TemplateDecl const* TD = D->getDescribedTemplate()) + { + return TD->getAccessUnsafe(); + } + + // For class/variable template partial/explicit specializations, + // we want to use the access of the primary template + if (auto const* CTSD = dyn_cast(D)) + { + return CTSD->getSpecializedTemplate()->getAccessUnsafe(); + } + + if (auto const* VTSD = dyn_cast(D)) + { + return VTSD->getSpecializedTemplate()->getAccessUnsafe(); + } + + // For function template specializations, use the access of the + // primary template if it has been resolved + if(auto const* FD = dyn_cast(D)) + { + if (auto const* FTD = FD->getPrimaryTemplate()) + { + return FTD->getAccessUnsafe(); + } + } + + // Since friend declarations are not members, this hack computes + // their access based on the default access for the tag they + // appear in, and any AccessSpecDecls which appears lexically + // before them + if(auto const* FD = dyn_cast(D)) + { + auto const* RD = dyn_cast( + FD->getLexicalDeclContext()); + // RD should never be null in well-formed code, + // but clang error recovery may build an AST + // where the assumption will not hold + if (!RD) + { + return AccessSpecifier::AS_public; + } + auto access = RD->isClass() ? + AccessSpecifier::AS_private : + AccessSpecifier::AS_public; + for(auto* M : RD->decls()) + { + if (auto* AD = dyn_cast(M)) + { + access = AD->getAccessUnsafe(); + } else if (M == FD) + { + return access; + } + } + // KRYSTIAN FIXME: will this ever be hit? + // it would require a friend declaration that is + // not in the lexical traversal of its lexical context + MRDOCS_UNREACHABLE(); + } + + // In all other cases, use the access of this declaration + return D->getAccessUnsafe(); +} + +QualType +getDeclaratorType(const DeclaratorDecl* DD) +{ + if (auto* TSI = DD->getTypeSourceInfo(); + TSI && !TSI->getType().isNull()) + { + return TSI->getType(); + } + return DD->getType(); +} + +NonTypeTemplateParmDecl const* +getNTTPFromExpr(const Expr* E, unsigned const Depth) +{ + while(true) + { + if(const auto* ICE = dyn_cast(E)) + { + E = ICE->getSubExpr(); + continue; + } + if(const auto* CE = dyn_cast(E)) + { + E = CE->getSubExpr(); + continue; + } + if(const auto* SNTTPE = dyn_cast(E)) + { + E = SNTTPE->getReplacement(); + continue; + } + if(const auto* CCE = dyn_cast(E); + CCE && CCE->getParenOrBraceRange().isInvalid()) + { + // look through implicit copy construction from an lvalue of the same type + E = CCE->getArg(0); + continue; + } + break; + } + + const auto* DRE = dyn_cast(E); + if (!DRE) + { + return nullptr; + } + + const auto* NTTPD = dyn_cast(DRE->getDecl()); + if (!NTTPD || NTTPD->getDepth() != Depth) + { + return nullptr; + } + + return NTTPD; +} + +Decl* +getParentDecl(Decl* D) +{ + while((D = cast_if_present< + Decl>(D->getDeclContext()))) + { + switch(D->getKind()) + { + case Decl::CXXRecord: + // we treat anonymous unions as "transparent" + if (auto const* RD = cast(D); + RD->isAnonymousStructOrUnion()) + { + break; + } + [[fallthrough]]; + case Decl::TranslationUnit: + case Decl::Namespace: + case Decl::Enum: + case Decl::ClassTemplateSpecialization: + case Decl::ClassTemplatePartialSpecialization: + return D; + // we consider all other DeclContexts to be "transparent" + default: + break; + } + } + return nullptr; +} + +} // clang::mrdocs diff --git a/src/lib/AST/ClangHelpers.hpp b/src/lib/AST/ClangHelpers.hpp new file mode 100644 index 0000000000..76fc6219e0 --- /dev/null +++ b/src/lib/AST/ClangHelpers.hpp @@ -0,0 +1,697 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#ifndef MRDOCS_LIB_AST_CLANGHELPERS_HPP +#define MRDOCS_LIB_AST_CLANGHELPERS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace clang::mrdocs { + +/** Substitute the constraint expression without satisfaction. + + This function substitutes the constraint expression without + checking for satisfaction. + + It uses the provided Sema object and template comparison information + to perform the substitution. + + @param S The Sema object used for semantic analysis. + @param DeclInfo The template comparison information for the new declaration. + @param ConstrExpr The constraint expression to be substituted. + @return The substituted constraint expression, or nullptr if an error occurs. + */ +const Expr* +SubstituteConstraintExpressionWithoutSatisfaction( + Sema &S, + const Sema::TemplateCompareNewDeclInfo &DeclInfo, + const Expr *ConstrExpr); + +/** Determine the MrDocs Info type for a Clang DeclType + + This trait associates a Clang Decl type with the corresponding + MrDocs Info type, when there is a direct correspondence. + + This is used to determine what type of Info object to create + and return when @ref ASTVisitor::upsertMrDocsInfoFor is called + to create or update an Info object for a Decl. + + Not all Info types have a direct correspondence with a Decl type. + In this case, the objects are created and updated by the visitor + at other steps in the traversal. + + When there's no direct correspondence, this trait returns + the base Info type. + + */ +template +struct MrDocsType : std::type_identity {}; + +template DeclType> +struct MrDocsType : std::type_identity {}; + +template VarTy> +struct MrDocsType : std::type_identity {}; + +template FunctionTy> +struct MrDocsType : std::type_identity {}; + +template TypedefNameTy> +struct MrDocsType : std::type_identity {}; + +template <> +struct MrDocsType : std::type_identity {}; + +template <> +struct MrDocsType : std::type_identity {}; + +template <> +struct MrDocsType : std::type_identity {}; + +template <> +struct MrDocsType : std::type_identity {}; + +template <> +struct MrDocsType : std::type_identity {}; + +template <> +struct MrDocsType : std::type_identity {}; + +template <> +struct MrDocsType : std::type_identity {}; + +template <> +struct MrDocsType : std::type_identity{}; + +template <> +struct MrDocsType : std::type_identity{}; + +/// @copydoc MrDocsType +template +using MrDocsType_t = typename MrDocsType::type; + +/** Convert a Clang AccessSpecifier into a MrDocs AccessKind + */ +inline +AccessKind +convertToAccessKind(AccessSpecifier const spec) +{ + switch(spec) + { + case AccessSpecifier::AS_public: return AccessKind::Public; + case AccessSpecifier::AS_protected: return AccessKind::Protected; + case AccessSpecifier::AS_private: return AccessKind::Private; + case AccessSpecifier::AS_none: return AccessKind::None; + default: + MRDOCS_UNREACHABLE(); + } +} + +/** Convert a Clang StorageClass into a MrDocs StorageClassKind + */ +inline +StorageClassKind +toStorageClassKind(StorageClass const spec) +{ + switch(spec) + { + case StorageClass::SC_None: return StorageClassKind::None; + case StorageClass::SC_Extern: return StorageClassKind::Extern; + case StorageClass::SC_Static: return StorageClassKind::Static; + case StorageClass::SC_Auto: return StorageClassKind::Auto; + case StorageClass::SC_Register: return StorageClassKind::Register; + default: + // SC_PrivateExtern (__private_extern__) + // is a C only Apple extension + MRDOCS_UNREACHABLE(); + } +} + +/** Convert a Clang ConstexprSpecKind into a MrDocs ConstexprKind + */ +inline +ConstexprKind +toConstexprKind(ConstexprSpecKind const spec) +{ + switch(spec) + { + case ConstexprSpecKind::Unspecified: return ConstexprKind::None; + case ConstexprSpecKind::Constexpr: return ConstexprKind::Constexpr; + case ConstexprSpecKind::Consteval: return ConstexprKind::Consteval; + // KRYSTIAN NOTE: ConstexprSpecKind::Constinit exists, + // but I don't think it's ever used because a variable + // can be declared both constexpr and constinit + // (but not both in the same declaration) + case ConstexprSpecKind::Constinit: + default: + MRDOCS_UNREACHABLE(); + } +} + +/** Convert a Clang ExplicitSpecKind into a MrDocs ExplicitKind + */ +inline +ExplicitKind +convertToExplicitKind(ExplicitSpecifier const& spec) +{ + // no explicit-specifier + if (!spec.isSpecified()) + { + return ExplicitKind::False; + } + + switch(spec.getKind()) + { + case ExplicitSpecKind::ResolvedFalse: return ExplicitKind::False; + case ExplicitSpecKind::ResolvedTrue: return ExplicitKind::True; + case ExplicitSpecKind::Unresolved: return ExplicitKind::Dependent; + default: + MRDOCS_UNREACHABLE(); + } +} + +/** Convert a Clang ExceptionSpecificationType into a MrDocs NoexceptKind + */ +inline +NoexceptKind +convertToNoexceptKind(ExceptionSpecificationType const spec) +{ + // KRYSTIAN TODO: right now we convert pre-C++17 dynamic exception + // specifications to an (roughly) equivalent noexcept-specifier + switch(spec) + { + case ExceptionSpecificationType::EST_None: + case ExceptionSpecificationType::EST_MSAny: + case ExceptionSpecificationType::EST_Unevaluated: + case ExceptionSpecificationType::EST_Uninstantiated: + // we *shouldn't* ever encounter an unparsed exception specification, + // assuming that clang is working correctly... + case ExceptionSpecificationType::EST_Unparsed: + case ExceptionSpecificationType::EST_Dynamic: + case ExceptionSpecificationType::EST_NoexceptFalse: + return NoexceptKind::False; + case ExceptionSpecificationType::EST_NoThrow: + case ExceptionSpecificationType::EST_BasicNoexcept: + case ExceptionSpecificationType::EST_NoexceptTrue: + case ExceptionSpecificationType::EST_DynamicNone: + return NoexceptKind::True; + case ExceptionSpecificationType::EST_DependentNoexcept: + return NoexceptKind::Dependent; + default: + MRDOCS_UNREACHABLE(); + } +} + +/** Convert a Clang OverloadedOperatorKind into a MrDocs OperatorKind + */ +inline +OperatorKind +convertToOperatorKind(OverloadedOperatorKind const kind) +{ + switch(kind) + { + case OverloadedOperatorKind::OO_None: + return OperatorKind::None; + case OverloadedOperatorKind::OO_New: + return OperatorKind::New; + case OverloadedOperatorKind::OO_Delete: + return OperatorKind::Delete; + case OverloadedOperatorKind::OO_Array_New: + return OperatorKind::ArrayNew; + case OverloadedOperatorKind::OO_Array_Delete: + return OperatorKind::ArrayDelete; + case OverloadedOperatorKind::OO_Plus: + return OperatorKind::Plus; + case OverloadedOperatorKind::OO_Minus: + return OperatorKind::Minus; + case OverloadedOperatorKind::OO_Star: + return OperatorKind::Star; + case OverloadedOperatorKind::OO_Slash: + return OperatorKind::Slash; + case OverloadedOperatorKind::OO_Percent: + return OperatorKind::Percent; + case OverloadedOperatorKind::OO_Caret: + return OperatorKind::Caret; + case OverloadedOperatorKind::OO_Amp: + return OperatorKind::Amp; + case OverloadedOperatorKind::OO_Pipe: + return OperatorKind::Pipe; + case OverloadedOperatorKind::OO_Tilde: + return OperatorKind::Tilde; + case OverloadedOperatorKind::OO_Exclaim: + return OperatorKind::Exclaim; + case OverloadedOperatorKind::OO_Equal: + return OperatorKind::Equal; + case OverloadedOperatorKind::OO_Less: + return OperatorKind::Less; + case OverloadedOperatorKind::OO_Greater: + return OperatorKind::Greater; + case OverloadedOperatorKind::OO_PlusEqual: + return OperatorKind::PlusEqual; + case OverloadedOperatorKind::OO_MinusEqual: + return OperatorKind::MinusEqual; + case OverloadedOperatorKind::OO_StarEqual: + return OperatorKind::StarEqual; + case OverloadedOperatorKind::OO_SlashEqual: + return OperatorKind::SlashEqual; + case OverloadedOperatorKind::OO_PercentEqual: + return OperatorKind::PercentEqual; + case OverloadedOperatorKind::OO_CaretEqual: + return OperatorKind::CaretEqual; + case OverloadedOperatorKind::OO_AmpEqual: + return OperatorKind::AmpEqual; + case OverloadedOperatorKind::OO_PipeEqual: + return OperatorKind::PipeEqual; + case OverloadedOperatorKind::OO_LessLess: + return OperatorKind::LessLess; + case OverloadedOperatorKind::OO_GreaterGreater: + return OperatorKind::GreaterGreater; + case OverloadedOperatorKind::OO_LessLessEqual: + return OperatorKind::LessLessEqual; + case OverloadedOperatorKind::OO_GreaterGreaterEqual: + return OperatorKind::GreaterGreaterEqual; + case OverloadedOperatorKind::OO_EqualEqual: + return OperatorKind::EqualEqual; + case OverloadedOperatorKind::OO_ExclaimEqual: + return OperatorKind::ExclaimEqual; + case OverloadedOperatorKind::OO_LessEqual: + return OperatorKind::LessEqual; + case OverloadedOperatorKind::OO_GreaterEqual: + return OperatorKind::GreaterEqual; + case OverloadedOperatorKind::OO_Spaceship: + return OperatorKind::Spaceship; + case OverloadedOperatorKind::OO_AmpAmp: + return OperatorKind::AmpAmp; + case OverloadedOperatorKind::OO_PipePipe: + return OperatorKind::PipePipe; + case OverloadedOperatorKind::OO_PlusPlus: + return OperatorKind::PlusPlus; + case OverloadedOperatorKind::OO_MinusMinus: + return OperatorKind::MinusMinus; + case OverloadedOperatorKind::OO_Comma: + return OperatorKind::Comma; + case OverloadedOperatorKind::OO_ArrowStar: + return OperatorKind::ArrowStar; + case OverloadedOperatorKind::OO_Arrow: + return OperatorKind::Arrow; + case OverloadedOperatorKind::OO_Call: + return OperatorKind::Call; + case OverloadedOperatorKind::OO_Subscript: + return OperatorKind::Subscript; + case OverloadedOperatorKind::OO_Conditional: + return OperatorKind::Conditional; + case OverloadedOperatorKind::OO_Coawait: + return OperatorKind::Coawait; + default: + MRDOCS_UNREACHABLE(); + } +} + +/** Convert a Clang ReferenceKind into a MrDocs ReferenceKind + */ +inline +ReferenceKind +convertToReferenceKind(RefQualifierKind const kind) +{ + switch(kind) + { + case RefQualifierKind::RQ_None: + return ReferenceKind::None; + case RefQualifierKind::RQ_LValue: + return ReferenceKind::LValue; + case RefQualifierKind::RQ_RValue: + return ReferenceKind::RValue; + default: + MRDOCS_UNREACHABLE(); + } +} + +/** Convert a Clang TagTypeKind into a MrDocs RecordKeyKind + */ +inline +RecordKeyKind +toRecordKeyKind(TagTypeKind const kind) +{ + switch(kind) + { + case TagTypeKind::Struct: return RecordKeyKind::Struct; + case TagTypeKind::Class: return RecordKeyKind::Class; + case TagTypeKind::Union: return RecordKeyKind::Union; + default: + // unsupported TagTypeKind (Interface, or Enum) + MRDOCS_UNREACHABLE(); + } +} + +/** Convert a Clang unsigned qualifier kind into a MrDocs QualifierKind + */ +inline +QualifierKind +convertToQualifierKind(unsigned const quals) +{ + std::underlying_type_t result = QualifierKind::None; + if (quals & Qualifiers::Const) + { + result |= QualifierKind::Const; + } + if (quals & Qualifiers::Volatile) + { + result |= QualifierKind::Volatile; + } + return static_cast(result); + +} + +/** Convert a Clang Decl::Kind into a MrDocs FunctionClass + */ +inline +FunctionClass +toFunctionClass(Decl::Kind const kind) +{ + switch(kind) + { + case Decl::Kind::Function: + case Decl::Kind::CXXMethod: return FunctionClass::Normal; + case Decl::Kind::CXXConstructor: return FunctionClass::Constructor; + case Decl::Kind::CXXConversion: return FunctionClass::Conversion; + case Decl::Kind::CXXDestructor: return FunctionClass::Destructor; + default: + MRDOCS_UNREACHABLE(); + } +} + +/** Convert a Clang AutoTypeKeyword into a MrDocs AutoKind + */ +inline +AutoKind +convertToAutoKind(AutoTypeKeyword const kind) +{ + switch(kind) + { + case AutoTypeKeyword::Auto: + case AutoTypeKeyword::GNUAutoType: + return AutoKind::Auto; + case AutoTypeKeyword::DecltypeAuto: + return AutoKind::DecltypeAuto; + default: + MRDOCS_UNREACHABLE(); + } +} + +// ---------------------------------------------------------------- + +/** Visit a Decl and call the appropriate visitor function. + */ +template< + typename DeclTy, + typename Visitor, + typename... Args> + requires std::derived_from +decltype(auto) +visit( + DeclTy* D, + Visitor&& visitor, + Args&&... args) +{ + MRDOCS_ASSERT(D); + switch(D->getKind()) + { + #define ABSTRACT_DECL(TYPE) + #define DECL(DERIVED, BASE) \ + case Decl::DERIVED: \ + if constexpr(std::derived_from) \ + return std::forward(visitor)( \ + static_cast*>(D), \ + std::forward(args)...); \ + else \ + MRDOCS_UNREACHABLE(); + + #include + + default: + MRDOCS_UNREACHABLE(); + } +} + +template +consteval +Decl::Kind +DeclToKindImpl() = delete; + +#define ABSTRACT_DECL(TYPE) +#define DECL(DERIVED, BASE) \ + template<> \ + consteval \ + Decl::Kind \ + DeclToKindImpl() { return Decl::DERIVED; } + +#include + +/** Get the Decl::Kind for a type DeclTy derived from Decl. + */ +template +consteval +Decl::Kind +DeclToKind() +{ + return DeclToKindImpl< + std::remove_cvref_t>(); +} + +// ---------------------------------------------------------------- + +/** Visit a Type and call the appropriate visitor function. + */ +template< + typename TypeTy, + typename Visitor, + typename... Args> + requires std::derived_from +decltype(auto) +visit( + TypeTy* T, + Visitor&& visitor, + Args&&... args) +{ + #define ABSTRACT_TYPE(DERIVED, BASE) + switch(T->getTypeClass()) + { + #define TYPE(DERIVED, BASE) \ + case Type::DERIVED: \ + if constexpr(std::derived_from) \ + return std::forward(visitor)( \ + static_cast*>(T), \ + std::forward(args)...); \ + else \ + MRDOCS_UNREACHABLE(); + + #include + + default: + MRDOCS_UNREACHABLE(); + } +} + +template +consteval +Type::TypeClass +TypeToKindImpl() = delete; + +#define ABSTRACT_TYPE(DERIVED, BASE) +#define TYPE(DERIVED, BASE) \ + template<> \ + consteval \ + Type::TypeClass \ + TypeToKindImpl() { return Type::DERIVED; } + +#include + +template +consteval +Type::TypeClass +TypeToKind() +{ + return TypeToKindImpl< + std::remove_cvref_t>(); +} + +// ---------------------------------------------------------------- + +/** Visit a TypeLoc and call the appropriate visitor function. + */ +template< + typename TypeLocTy, + typename Visitor, + typename... Args> + requires std::derived_from +decltype(auto) +visit( + TypeLocTy* T, + Visitor&& visitor, + Args&&... args) +{ + switch(T->getTypeLocClass()) + { + #define ABSTRACT_TYPELOC(DERIVED, BASE) + #define TYPELOC(DERIVED, BASE) \ + case TypeLoc::DERIVED: \ + if constexpr(std::derived_from) \ + return std::forward(visitor)( \ + static_cast*>(T), \ + std::forward(args)...); \ + else \ + MRDOCS_UNREACHABLE(); + + #include + + default: + MRDOCS_UNREACHABLE(); + } +} + +template +consteval +TypeLoc::TypeLocClass +TypeLocToKindImpl() = delete; + +#define ABSTRACT_TYPELOC(DERIVED, BASE) +#define TYPELOC(DERIVED, BASE) \ + template<> \ + consteval \ + TypeLoc::TypeLocClass \ + TypeLocToKindImpl() { return TypeLoc::DERIVED; } + +#include + +/** Get the TypeLoc::TypeLocClass for a type TypeLocTy derived from TypeLoc. + */ +template +consteval +TypeLoc::TypeLocClass +TypeLocToKind() +{ + return TypeLocToKindImpl< + std::remove_cvref_t>(); +} + +/** Get the user-written `Decl` for a `Decl` + + Given a `Decl` `D`, `getInstantiatedFrom` will return the + user-written `Decl` corresponding to `D`. For specializations + which were implicitly instantiated, this will be whichever `Decl` + was used as the pattern for instantiation. +*/ +template +DeclTy* +getInstantiatedFrom(DeclTy* D) +{ + if (!D) + { + return nullptr; + } + auto* decayedD = const_cast(static_cast(D)); + Decl* resultDecl = InstantiatedFromVisitor().Visit(decayedD); + return cast(resultDecl); +} + +template +requires + std::derived_from || + std::same_as> +FunctionDecl* +getInstantiatedFrom(DeclTy* D) +{ + return dyn_cast_if_present( + getInstantiatedFrom(D)); +} + +template +requires + std::derived_from || + std::same_as> +CXXRecordDecl* +getInstantiatedFrom(DeclTy* D) +{ + return dyn_cast_if_present( + getInstantiatedFrom(D)); +} + +template +requires + std::derived_from || + std::same_as> +VarDecl* +getInstantiatedFrom(DeclTy* D) +{ + return dyn_cast_if_present( + getInstantiatedFrom(D)); +} + +template +requires + std::derived_from || + std::same_as> +TypedefNameDecl* +getInstantiatedFrom(DeclTy* D) +{ + return dyn_cast_if_present( + getInstantiatedFrom(D)); +} + +/** Get the access specifier for a `Decl` + + Given a `Decl`, this function will analyze the parent + context and return the access specifier for the declaration. + */ +MRDOCS_DECL +AccessSpecifier +getAccess(const Decl* D); + +MRDOCS_DECL +QualType +getDeclaratorType(const DeclaratorDecl* DD); + +MRDOCS_DECL +NonTypeTemplateParmDecl const* +getNTTPFromExpr(const Expr* E, unsigned Depth); + +// Get the parent declaration of a declaration +MRDOCS_DECL +Decl* +getParentDecl(Decl* D); + +// Get the parent declaration of a declaration +inline +Decl const* +getParentDecl(Decl const* D) { + return getParentDecl(const_cast(D)); +} + + +} // clang::mrdocs + +#endif diff --git a/src/lib/AST/FrontendActionFactory.cpp b/src/lib/AST/FrontendActionFactory.cpp new file mode 100644 index 0000000000..4c1a3981fc --- /dev/null +++ b/src/lib/AST/FrontendActionFactory.cpp @@ -0,0 +1,50 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#include "FrontendActionFactory.hpp" +#include "lib/AST/ASTAction.hpp" + +namespace clang { +namespace mrdocs { + +std::unique_ptr +makeFrontendActionFactory( + ExecutionContext& ex, + ConfigImpl const& config) +{ + class ASTActionFactory : + public tooling::FrontendActionFactory + { + ExecutionContext& ex_; + ConfigImpl const& config_; + public: + ASTActionFactory( + ExecutionContext& ex, + ConfigImpl const& config) noexcept + : ex_(ex) + , config_(config) + { + } + + std::unique_ptr + create() override + { + return std::make_unique(ex_, config_); + } + }; + + return std::make_unique(ex, config); +} + +} // mrdocs +} // clang diff --git a/src/lib/AST/FrontendActionFactory.hpp b/src/lib/AST/FrontendActionFactory.hpp new file mode 100644 index 0000000000..3b009b647e --- /dev/null +++ b/src/lib/AST/FrontendActionFactory.hpp @@ -0,0 +1,45 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#ifndef MRDOCS_LIB_AST_FRONTENDACTIONFACTORY_HPP +#define MRDOCS_LIB_AST_FRONTENDACTIONFACTORY_HPP + +#include "lib/Lib/ConfigImpl.hpp" +#include "lib/Lib/ExecutionContext.hpp" +#include + +namespace clang { +namespace mrdocs { + +/** Return a factory of MrDocs actions for the Clang Frontend + + This function returns an implementation of + `clang::tooling::FrontendActionFactory` that allows + one action to be created for each translation unit. + + The `create` method of this factory returns a new instance + of @ref clang::mrdocs::ASTAction for each translation unit. + + A `tooling::ClangTool`, with access to the compilation database, + can receive this factory action via `tooling::ClangTool::run()`. + This is the entry point for the AST traversal in `CorpusImpl::build`. + */ +std::unique_ptr +makeFrontendActionFactory( + ExecutionContext& ex, + ConfigImpl const& config); + +} // mrdocs +} // clang + +#endif diff --git a/src/lib/AST/InstantiatedFromVisitor.hpp b/src/lib/AST/InstantiatedFromVisitor.hpp new file mode 100644 index 0000000000..4872997d6a --- /dev/null +++ b/src/lib/AST/InstantiatedFromVisitor.hpp @@ -0,0 +1,270 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#ifndef MRDOCS_LIB_AST_INSTANTIATEDFROMVISITOR_HPP +#define MRDOCS_LIB_AST_INSTANTIATEDFROMVISITOR_HPP + +#include + +namespace clang::mrdocs { + +/** A visitor class for handling instantiations from templates. + + This class provides methods to visit various template declarations + and retrieve the original declarations from which they were instantiated. + */ +class InstantiatedFromVisitor + : public DeclVisitor +{ +public: + Decl* + VisitDecl(Decl* D) + { + return D; + } + + FunctionDecl* + VisitFunctionTemplateDecl(FunctionTemplateDecl const* D) + { + while(auto* MT = D->getInstantiatedFromMemberTemplate()) + { + if (D->isMemberSpecialization()) + { + break; + } + D = MT; + } + return D->getTemplatedDecl(); + } + + CXXRecordDecl* + VisitClassTemplateDecl(ClassTemplateDecl const* D) + { + while (auto* MT = D->getInstantiatedFromMemberTemplate()) + { + if (D->isMemberSpecialization()) + { + break; + } + D = MT; + } + return D->getTemplatedDecl(); + } + + VarDecl* + VisitVarTemplateDecl(VarTemplateDecl const* D) + { + while(auto* MT = D->getInstantiatedFromMemberTemplate()) + { + if (D->isMemberSpecialization()) + { + break; + } + D = MT; + } + return D->getTemplatedDecl(); + } + + TypedefNameDecl* + VisitTypeAliasTemplateDecl(TypeAliasTemplateDecl const* D) + { + if(auto* MT = D->getInstantiatedFromMemberTemplate()) + { + // KRYSTIAN NOTE: we don't really need to check this + if (!D->isMemberSpecialization()) + { + D = MT; + } + } + return VisitTypedefNameDecl(D->getTemplatedDecl()); + } + + FunctionDecl* + VisitFunctionDecl(FunctionDecl* D) + { + if (FunctionDecl const* DD = nullptr; + D->isDefined(DD, false)) + { + D = const_cast(DD); + } + + if (MemberSpecializationInfo const* MSI = D->getMemberSpecializationInfo()) + { + if (!MSI->isExplicitSpecialization()) + { + D = cast(MSI->getInstantiatedFrom()); + } + } + else if(D->getTemplateSpecializationKind() != + TSK_ExplicitSpecialization) + { + D = D->getFirstDecl(); + if (auto* FTD = D->getPrimaryTemplate()) + { + D = VisitFunctionTemplateDecl(FTD); + } + } + return D; + } + + CXXRecordDecl* + VisitClassTemplatePartialSpecializationDecl(ClassTemplatePartialSpecializationDecl* D) + { + while (auto* MT = D->getInstantiatedFromMember()) + { + if (D->isMemberSpecialization()) + { + break; + } + D = MT; + } + return VisitClassTemplateSpecializationDecl(D); + } + + CXXRecordDecl* + VisitClassTemplateSpecializationDecl(ClassTemplateSpecializationDecl* D) + { + if (!D->isExplicitSpecialization()) + { + auto const inst_from = D->getSpecializedTemplateOrPartial(); + if(auto* CTPSD = inst_from.dyn_cast< + ClassTemplatePartialSpecializationDecl*>()) + { + MRDOCS_ASSERT(D != CTPSD); + return VisitClassTemplatePartialSpecializationDecl(CTPSD); + } + // Explicit instantiation declaration/definition + else if(auto* CTD = inst_from.dyn_cast()) + { + return VisitClassTemplateDecl(CTD); + } + } + return VisitCXXRecordDecl(D); + } + + CXXRecordDecl* + VisitCXXRecordDecl(CXXRecordDecl* D) + { + while (MemberSpecializationInfo const* MSI = + D->getMemberSpecializationInfo()) + { + // if this is a member of an explicit specialization, + // then we have the correct declaration + if (MSI->isExplicitSpecialization()) + { + break; + } + D = cast(MSI->getInstantiatedFrom()); + } + return D; + } + + VarDecl* + VisitVarTemplatePartialSpecializationDecl(VarTemplatePartialSpecializationDecl* D) + { + while(auto* MT = D->getInstantiatedFromMember()) + { + if (D->isMemberSpecialization()) + { + break; + } + D = MT; + } + return VisitVarTemplateSpecializationDecl(D); + } + + VarDecl* + VisitVarTemplateSpecializationDecl(VarTemplateSpecializationDecl* D) + { + if(! D->isExplicitSpecialization()) + { + auto const inst_from = D->getSpecializedTemplateOrPartial(); + if(auto* VTPSD = inst_from.dyn_cast< + VarTemplatePartialSpecializationDecl*>()) + { + MRDOCS_ASSERT(D != VTPSD); + return VisitVarTemplatePartialSpecializationDecl(VTPSD); + } + // explicit instantiation declaration/definition + else if(auto* VTD = inst_from.dyn_cast< + VarTemplateDecl*>()) + { + return VisitVarTemplateDecl(VTD); + } + } + return VisitVarDecl(D); + } + + VarDecl* + VisitVarDecl(VarDecl* D) + { + while(MemberSpecializationInfo* MSI = + D->getMemberSpecializationInfo()) + { + if (MSI->isExplicitSpecialization()) + { + break; + } + D = cast(MSI->getInstantiatedFrom()); + } + return D; + } + + EnumDecl* + VisitEnumDecl(EnumDecl* D) + { + while(MemberSpecializationInfo* MSI = + D->getMemberSpecializationInfo()) + { + if (MSI->isExplicitSpecialization()) + { + break; + } + D = cast(MSI->getInstantiatedFrom()); + } + return D; + } + + TypedefNameDecl* + VisitTypedefNameDecl(TypedefNameDecl* D) + { + DeclContext* Context = D->getNonTransparentDeclContext(); + if (Context->isFileContext()) + { + return D; + } + auto const* ContextPattern = + cast(Visit(cast(Context))); + if (Context == ContextPattern) + { + return D; + } + for (auto lookup = ContextPattern->lookup(D->getDeclName()); + NamedDecl * ND : lookup) + { + if (auto* TND = dyn_cast(ND)) + { + return TND; + } + if (auto const* TATD = dyn_cast(ND)) + { + return TATD->getTemplatedDecl(); + } + } + return D; + } +}; + +} // clang::mrdocs + +#endif diff --git a/src/lib/AST/NameInfoBuilder.cpp b/src/lib/AST/NameInfoBuilder.cpp new file mode 100644 index 0000000000..d64298122f --- /dev/null +++ b/src/lib/AST/NameInfoBuilder.cpp @@ -0,0 +1,135 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#include "lib/AST/NameInfoBuilder.hpp" + +namespace clang::mrdocs { + +void +NameInfoBuilder:: +buildDecltype( + const DecltypeType* T, + unsigned quals, + bool pack) +{ + // KRYSTIAN TODO: support decltype in names + // (e.g. within nested-name-specifiers). +} + +void +NameInfoBuilder:: +buildTerminal( + const NestedNameSpecifier* NNS, + const Type* T, + unsigned quals, + bool pack) +{ + if (getASTVisitor().checkSpecialNamespace(Result, NNS, nullptr)) + { + return; + } + + auto I = std::make_unique(); + I->Name = getASTVisitor().toString(T); + Result = std::move(I); + if (NNS) + { + Result->Prefix = getASTVisitor().toNameInfo(NNS); + } +} + +void +NameInfoBuilder:: +buildTerminal( + const NestedNameSpecifier* NNS, + const IdentifierInfo* II, + std::optional> TArgs, + unsigned quals, + bool pack) +{ + ASTVisitor& V = getASTVisitor(); + if (V.checkSpecialNamespace(Result, NNS, nullptr)) + { + return; + } + + if(TArgs) + { + auto I = std::make_unique(); + if (II) + { + I->Name = II->getName(); + } + V.populate(I->TemplateArgs, *TArgs); + Result = std::move(I); + } + else + { + auto I = std::make_unique(); + if (II) + { + I->Name = II->getName(); + } + Result = std::move(I); + } + if (NNS) + { + Result->Prefix = V.toNameInfo(NNS); + } +} + +void +NameInfoBuilder:: +buildTerminal( + const NestedNameSpecifier* NNS, + const NamedDecl* D, + std::optional> const& TArgs, + unsigned quals, + bool pack) +{ + ASTVisitor& V = getASTVisitor(); + if (V.checkSpecialNamespace(Result, NNS, D)) + { + return; + } + + const IdentifierInfo* II = D->getIdentifier(); + if(TArgs) + { + auto I = std::make_unique(); + if (II) + { + I->Name = II->getName(); + } + V.upsertDependency(getInstantiatedFrom(D), I->id); + V.populate(I->TemplateArgs, *TArgs); + Result = std::move(I); + } + else + { + auto I = std::make_unique(); + if (II) + { + I->Name = II->getName(); + } + V.upsertDependency(getInstantiatedFrom(D), I->id); + Result = std::move(I); + } + if (NNS) + { + Result->Prefix = V.toNameInfo(NNS); + } +} + + +} // clang::mrdocs diff --git a/src/lib/AST/NameInfoBuilder.hpp b/src/lib/AST/NameInfoBuilder.hpp new file mode 100644 index 0000000000..f80a740623 --- /dev/null +++ b/src/lib/AST/NameInfoBuilder.hpp @@ -0,0 +1,67 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#ifndef MRDOCS_LIB_AST_NAMEINFOBUILDER_HPP +#define MRDOCS_LIB_AST_NAMEINFOBUILDER_HPP + +#include +#include + +namespace clang::mrdocs { + +class NameInfoBuilder + : public TerminalTypeVisitor +{ + std::unique_ptr Result; + +public: + using TerminalTypeVisitor::TerminalTypeVisitor; + + std::unique_ptr result() + { + return std::move(Result); + } + + void + buildDecltype( + const DecltypeType* T, + unsigned quals, + bool pack); + + void + buildTerminal( + const NestedNameSpecifier* NNS, + const Type* T, + unsigned quals, + bool pack); + + void + buildTerminal( + const NestedNameSpecifier* NNS, + const IdentifierInfo* II, + std::optional> TArgs, + unsigned quals, + bool pack); + + void + buildTerminal( + const NestedNameSpecifier* NNS, + const NamedDecl* D, + std::optional> const& TArgs, + unsigned quals, + bool pack); +}; + +} // clang::mrdocs + +#endif diff --git a/src/lib/AST/ParseJavadoc.cpp b/src/lib/AST/ParseJavadoc.cpp index 94032e5c8f..8b417bdec9 100644 --- a/src/lib/AST/ParseJavadoc.cpp +++ b/src/lib/AST/ParseJavadoc.cpp @@ -1448,7 +1448,7 @@ initCustomCommentCommands(ASTContext& context) void parseJavadoc( std::unique_ptr& jd, - FullComment* FC, + FullComment const* FC, Decl const* D, Config const& config, Diagnostics& diags) diff --git a/src/lib/AST/ParseJavadoc.hpp b/src/lib/AST/ParseJavadoc.hpp index 22a05aa2aa..2dfd3d0728 100644 --- a/src/lib/AST/ParseJavadoc.hpp +++ b/src/lib/AST/ParseJavadoc.hpp @@ -52,7 +52,7 @@ initCustomCommentCommands( void parseJavadoc( std::unique_ptr& jd, - comments::FullComment* FC, + comments::FullComment const* FC, Decl const* D, Config const& config, Diagnostics& diags); diff --git a/src/lib/AST/SymbolFilter.hpp b/src/lib/AST/SymbolFilter.hpp new file mode 100644 index 0000000000..b5ede4e113 --- /dev/null +++ b/src/lib/AST/SymbolFilter.hpp @@ -0,0 +1,93 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#ifndef MRDOCS_LIB_AST_SYMBOLFILTER_HPP +#define MRDOCS_LIB_AST_SYMBOLFILTER_HPP + +#include "lib/Lib/Filters.hpp" + +namespace clang { +namespace mrdocs { + +/** Filter for symbols + + This class is used to filter symbols based on the + configuration provided by the user. + + */ +struct SymbolFilter +{ + const FilterNode& root; + const FilterNode* current = nullptr; + const FilterNode* last_explicit = nullptr; + bool detached = false; + + SymbolFilter(const FilterNode& root_node) + : root(root_node) + { + setCurrent(&root, false); + } + + SymbolFilter(const SymbolFilter&) = delete; + SymbolFilter(SymbolFilter&&) = delete; + + void + setCurrent( + const FilterNode* node, + bool node_detached) + { + current = node; + detached = node_detached; + if(node && node->Explicit) + last_explicit = node; + } +}; + +/** Scope for symbol filtering + + This class is used to scope the symbol filter state + during the traversal of the AST. + + It stores the state of the filter before entering + a scope and restores it when leaving the scope, after + the traversal of that scope is complete. + */ +class FilterScope +{ + SymbolFilter& filter_; + FilterNode const* current_prev_; + FilterNode const* last_explicit_prev_; + bool detached_prev_; + +public: + explicit + FilterScope(SymbolFilter& filter) + : filter_(filter) + , current_prev_(filter.current) + , last_explicit_prev_(filter.last_explicit) + , detached_prev_(filter.detached) + { + } + + ~FilterScope() + { + filter_.current = current_prev_; + filter_.last_explicit = last_explicit_prev_; + filter_.detached = detached_prev_; + } +}; + +} // mrdocs +} // clang + +#endif diff --git a/src/lib/AST/SymbolFilterScope.hpp b/src/lib/AST/SymbolFilterScope.hpp new file mode 100644 index 0000000000..fb737666c5 --- /dev/null +++ b/src/lib/AST/SymbolFilterScope.hpp @@ -0,0 +1,59 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#ifndef MRDOCS_LIB_AST_SYMBOLFILTERSCOPE_HPP +#define MRDOCS_LIB_AST_SYMBOLFILTERSCOPE_HPP + +#include "lib/AST/SymbolFilter.hpp" + +namespace clang { +namespace mrdocs { + +/** Scope for symbol filtering + + This class is used to scope the symbol filter state + during the traversal of the AST. + + It stores the state of the filter before entering + a scope and restores it when leaving the scope, after + the traversal of that scope is complete. + */ +class SymbolFilterScope +{ + SymbolFilter& filter_; + FilterNode const* current_prev_; + FilterNode const* last_explicit_prev_; + bool detached_prev_; + +public: + explicit + SymbolFilterScope(SymbolFilter& filter) + : filter_(filter) + , current_prev_(filter.current) + , last_explicit_prev_(filter.last_explicit) + , detached_prev_(filter.detached) + { + } + + ~SymbolFilterScope() + { + filter_.current = current_prev_; + filter_.last_explicit = last_explicit_prev_; + filter_.detached = detached_prev_; + } +}; + +} // mrdocs +} // clang + +#endif diff --git a/src/lib/AST/TerminalTypeVisitor.hpp b/src/lib/AST/TerminalTypeVisitor.hpp new file mode 100644 index 0000000000..c35abf6aa1 --- /dev/null +++ b/src/lib/AST/TerminalTypeVisitor.hpp @@ -0,0 +1,476 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#ifndef MRDOCS_LIB_AST_TERMINALTYPEVISITOR_HPP +#define MRDOCS_LIB_AST_TERMINALTYPEVISITOR_HPP + +#include +#include + +namespace clang::mrdocs { + +/** A visitor class for terminal types. + + This class is used to visit various terminal types in the AST. + It derives from the TypeVisitor class and provides specific + implementations for visiting different types. + + @tparam Derived The derived class type. + */ +template +class TerminalTypeVisitor + : public TypeVisitor, bool> +{ + friend class TerminalTypeVisitor::TypeVisitor; + + ASTVisitor& Visitor_; + + unsigned Quals_ = 0; + bool IsPack_ = false; + const NestedNameSpecifier* NNS_; + +public: + /** Constructor for TerminalTypeVisitor. + + This constructor initializes the TerminalTypeVisitor with the given ASTVisitor + and an optional NestedNameSpecifier. + + @param Visitor The ASTVisitor instance. + @param NNS The optional NestedNameSpecifier. + */ + explicit + TerminalTypeVisitor( + ASTVisitor& Visitor, + const NestedNameSpecifier* NNS = nullptr) + : Visitor_(Visitor) + , NNS_(NNS) + { + } + + /** Get the ASTVisitor instance. + + This function returns a reference to the ASTVisitor instance. + + @return A reference to the ASTVisitor instance. + */ + ASTVisitor& + getASTVisitor() + { + return Visitor_; + } + + using TerminalTypeVisitor::TypeVisitor::Visit; + + bool + Visit(QualType const QT) + { + Quals_ |= QT.getLocalFastQualifiers(); + return Visit(QT.getTypePtrOrNull()); + } + + void + buildPointer + (const PointerType* T, + unsigned quals) + { + } + + void + buildLValueReference( + const LValueReferenceType* T) + { + } + + void + buildRValueReference( + const RValueReferenceType* T) + { + } + + void + buildMemberPointer( + const MemberPointerType* T, unsigned quals) + { + } + + void + buildArray( + const ArrayType* T) + { + } + + void + populate( + const FunctionType* T) + { + } + + void + buildDecltype( + const DecltypeType* T, + unsigned quals, + bool pack) + { + } + + void + buildAuto( + const AutoType* T, + unsigned quals, + bool pack) + { + } + + void + buildTerminal( + const NestedNameSpecifier* NNS, + const Type* T, + unsigned quals, + bool pack) + { + } + + void + buildTerminal( + const NestedNameSpecifier* NNS, + const IdentifierInfo* II, + std::optional> TArgs, + unsigned quals, + bool pack) + { + } + + void + buildTerminal( + const NestedNameSpecifier* NNS, + const NamedDecl* D, + std::optional> TArgs, + unsigned quals, + bool pack) + { + } + + +private: + /** Get the derived class instance. + + This function casts the current instance to the derived class type. + + @return A reference to the derived class instance. + */ + Derived& + getDerived() + { + return static_cast(*this); + } + + bool + VisitParenType( + const ParenType* T) + { + return Visit(T->getInnerType()); + } + + bool + VisitMacroQualified( + const MacroQualifiedType* T) + { + return Visit(T->getUnderlyingType()); + } + + bool + VisitAttributedType( + const AttributedType* T) + { + return Visit(T->getModifiedType()); + } + + bool + VisitAdjustedType( + const AdjustedType* T) + { + return Visit(T->getOriginalType()); + } + + bool + VisitUsingType( + const UsingType* T) + { + return Visit(T->getUnderlyingType()); + } + + bool + VisitSubstTemplateTypeParmType( + const SubstTemplateTypeParmType* T) + { + return Visit(T->getReplacementType()); + } + + // ---------------------------------------------------------------- + + bool + VisitElaboratedType( + const ElaboratedType* T) + { + NNS_ = T->getQualifier(); + return Visit(T->getNamedType()); + } + + bool + VisitPackExpansionType( + const PackExpansionType* T) + { + IsPack_ = true; + return Visit(T->getPattern()); + } + + // ---------------------------------------------------------------- + + bool + VisitPointerType( + const PointerType* T) + { + getDerived().buildPointer(T, std::exchange(Quals_, 0)); + return Visit(T->getPointeeType()); + } + + bool + VisitLValueReferenceType( + const LValueReferenceType* T) + { + getDerived().buildLValueReference(T); + Quals_ = 0; + return Visit(T->getPointeeType()); + } + + bool + VisitRValueReferenceType( + const RValueReferenceType* T) + { + getDerived().buildRValueReference(T); + Quals_ = 0; + return Visit(T->getPointeeType()); + } + + bool + VisitMemberPointerType( + const MemberPointerType* T) + { + getDerived().buildMemberPointer(T, std::exchange(Quals_, 0)); + return Visit(T->getPointeeType()); + } + + bool + VisitFunctionType( + const FunctionType* T) + { + getDerived().populate(T); + return Visit(T->getReturnType()); + } + + bool + VisitArrayType( + const ArrayType* T) + { + getDerived().buildArray(T); + return Visit(T->getElementType()); + } + + // ---------------------------------------------------------------- + + bool + VisitDecltypeType( + const DecltypeType* T) + { + getDerived().buildDecltype(T, Quals_, IsPack_); + return true; + } + + bool + VisitAutoType( + const AutoType* T) + { + // KRYSTIAN NOTE: we don't use isDeduced because it will + // return true if the type is dependent + // if the type has been deduced, use the deduced type + getDerived().buildAuto(T, Quals_, IsPack_); + return true; + } + + bool + VisitDeducedTemplateSpecializationType( + const DeducedTemplateSpecializationType* T) + { + // KRYSTIAN TODO: we should probably add a TypeInfo + // to represent deduced types that also stores what + // it was deduced as. + if (QualType DT = T->getDeducedType(); !DT.isNull()) + { + return Visit(DT); + } + TemplateName const TN = T->getTemplateName(); + MRDOCS_ASSERT(! TN.isNull()); + NamedDecl* ND = TN.getAsTemplateDecl(); + getDerived().buildTerminal(NNS_, ND, + std::nullopt, Quals_, IsPack_); + return true; + } + + bool + VisitDependentNameType( + const DependentNameType* T) + { + if (auto SFINAE = getASTVisitor().isSFINAEType(T); SFINAE.has_value()) + { + return getDerived().Visit(SFINAE->first); + } + + if (auto const* NNS = T->getQualifier()) + { + NNS_ = NNS; + } + getDerived().buildTerminal(NNS_, T->getIdentifier(), + std::nullopt, Quals_, IsPack_); + return true; + } + + bool + VisitDependentTemplateSpecializationType( + const DependentTemplateSpecializationType* T) + { + if (auto const* NNS = T->getQualifier()) + { + NNS_ = NNS; + } + getDerived().buildTerminal(NNS_, T->getIdentifier(), + T->template_arguments(), Quals_, IsPack_); + return true; + } + + bool + VisitTemplateSpecializationType( + const TemplateSpecializationType* T) + { + if (auto SFINAE = getASTVisitor().isSFINAEType(T); SFINAE.has_value()) + { + return getDerived().Visit(SFINAE->first); + } + + TemplateName const TN = T->getTemplateName(); + MRDOCS_ASSERT(! TN.isNull()); + NamedDecl* ND = TN.getAsTemplateDecl(); + if(! T->isTypeAlias()) + { + auto* CT = T->getCanonicalTypeInternal().getTypePtrOrNull(); + if (auto* ICT = dyn_cast_or_null(CT)) + { + ND = ICT->getDecl(); + } + else if (auto* RT = dyn_cast_or_null(CT)) + { + ND = RT->getDecl(); + } + } + getDerived().buildTerminal(NNS_, ND, + T->template_arguments(), Quals_, IsPack_); + return true; + } + + bool + VisitRecordType( + const RecordType* T) + { + RecordDecl* RD = T->getDecl(); + // if this is an instantiation of a class template, + // create a SpecializationTypeInfo & extract the template arguments + std::optional> TArgs = std::nullopt; + if (auto const* CTSD = dyn_cast(RD)) + { + TArgs = CTSD->getTemplateArgs().asArray(); + } + getDerived().buildTerminal(NNS_, RD, + TArgs, Quals_, IsPack_); + return true; + } + + bool + VisitInjectedClassNameType( + const InjectedClassNameType* T) + { + getDerived().buildTerminal(NNS_, T->getDecl(), + std::nullopt, Quals_, IsPack_); + return true; + } + + bool + VisitEnumType( + const EnumType* T) + { + getDerived().buildTerminal(NNS_, T->getDecl(), + std::nullopt, Quals_, IsPack_); + return true; + } + + bool + VisitTypedefType( + const TypedefType* T) + { + getDerived().buildTerminal(NNS_, T->getDecl(), + std::nullopt, Quals_, IsPack_); + return true; + } + + bool + VisitTemplateTypeParmType( + const TemplateTypeParmType* T) + { + const IdentifierInfo* II = nullptr; + if (TemplateTypeParmDecl const* D = T->getDecl()) + { + if(D->isImplicit()) + { + // special case for implicit template parameters + // resulting from abbreviated function templates + getDerived().buildTerminal( + NNS_, T, Quals_, IsPack_); + return true; + } + II = D->getIdentifier(); + } + getDerived().buildTerminal(NNS_, II, + std::nullopt, Quals_, IsPack_); + return true; + } + + bool + VisitSubstTemplateTypeParmPackType( + const SubstTemplateTypeParmPackType* T) + { + getDerived().buildTerminal(NNS_, T->getIdentifier(), + std::nullopt, Quals_, IsPack_); + return true; + } + + bool + VisitType(const Type* T) + { + getDerived().buildTerminal( + NNS_, T, Quals_, IsPack_); + return true; + } + +}; + +} // clang::mrdocs + +#endif diff --git a/src/lib/AST/TypeInfoBuilder.cpp b/src/lib/AST/TypeInfoBuilder.cpp new file mode 100644 index 0000000000..b4a9030959 --- /dev/null +++ b/src/lib/AST/TypeInfoBuilder.cpp @@ -0,0 +1,257 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#include "lib/AST/TypeInfoBuilder.hpp" + +namespace clang::mrdocs { + +void +TypeInfoBuilder:: +buildPointer(const PointerType* T, unsigned quals) +{ + auto I = std::make_unique(); + I->CVQualifiers = convertToQualifierKind(quals); + *std::exchange(Inner, &I->PointeeType) = std::move(I); +} + +void +TypeInfoBuilder:: +buildLValueReference(const LValueReferenceType* T) +{ + auto I = std::make_unique(); + *std::exchange(Inner, &I->PointeeType) = std::move(I); +} + +void +TypeInfoBuilder:: +buildRValueReference(const RValueReferenceType* T) +{ + auto I = std::make_unique(); + *std::exchange(Inner, &I->PointeeType) = std::move(I); +} + +void +TypeInfoBuilder:: +buildMemberPointer(const MemberPointerType* T, unsigned quals) +{ + auto I = std::make_unique(); + I->CVQualifiers = convertToQualifierKind(quals); + // do not set NNS because the parent type is *not* + // a nested-name-specifier which qualifies the pointee type + I->ParentType = getASTVisitor().toTypeInfo( + QualType(T->getClass(), 0)); + *std::exchange(Inner, &I->PointeeType) = std::move(I); +} + +void +TypeInfoBuilder:: +buildArray(const ArrayType* T) +{ + auto I = std::make_unique(); + if(auto* CAT = dyn_cast(T)) + { + getASTVisitor().populate( + I->Bounds, CAT->getSizeExpr(), CAT->getSize()); + } + else if(auto* DAT = dyn_cast(T)) + { + getASTVisitor().populate( + I->Bounds, DAT->getSizeExpr()); + } + *std::exchange(Inner, &I->ElementType) = std::move(I); +} + +void +TypeInfoBuilder:: +populate(const FunctionType* T) +{ + auto* FPT = cast(T); + auto I = std::make_unique(); + for(QualType PT : FPT->getParamTypes()) + { + I->ParamTypes.emplace_back( + getASTVisitor().toTypeInfo(PT)); + } + I->RefQualifier = convertToReferenceKind( + FPT->getRefQualifier()); + I->CVQualifiers = convertToQualifierKind( + FPT->getMethodQuals().getFastQualifiers()); + I->IsVariadic = FPT->isVariadic(); + getASTVisitor().populate(I->ExceptionSpec, FPT); + *std::exchange(Inner, &I->ReturnType) = std::move(I); +} + +void +TypeInfoBuilder:: +buildDecltype( + const DecltypeType* T, + unsigned quals, + bool pack) +{ + auto I = std::make_unique(); + getASTVisitor().populate( + I->Operand, T->getUnderlyingExpr()); + I->CVQualifiers = convertToQualifierKind(quals); + *Inner = std::move(I); + Result->IsPackExpansion = pack; +} + +void +TypeInfoBuilder:: +buildAuto( + const AutoType* T, + unsigned const quals, + bool const pack) +{ + auto I = std::make_unique(); + I->CVQualifiers = convertToQualifierKind(quals); + I->Keyword = convertToAutoKind(T->getKeyword()); + if(T->isConstrained()) + { + std::optional> TArgs; + if(auto Args = T->getTypeConstraintArguments(); + ! Args.empty()) + { + TArgs.emplace(Args); + } + I->Constraint = getASTVisitor().toNameInfo( + T->getTypeConstraintConcept(), TArgs); + // Constraint->Prefix = getASTVisitor().buildNameInfo( + // cast(CD->getDeclContext())); + } + *Inner = std::move(I); + Result->IsPackExpansion = pack; +} + + + +void +TypeInfoBuilder:: +buildTerminal( + const NestedNameSpecifier* NNS, + const Type* T, + unsigned quals, + bool pack) +{ + if(getASTVisitor().checkSpecialNamespace(*Inner, NNS, nullptr)) + { + return; + } + + auto I = std::make_unique(); + I->CVQualifiers = convertToQualifierKind(quals); + + auto Name = std::make_unique(); + Name->Name = getASTVisitor().toString(T); + Name->Prefix = getASTVisitor().toNameInfo(NNS); + I->Name = std::move(Name); + *Inner = std::move(I); + Result->IsPackExpansion = pack; +} + +void +TypeInfoBuilder:: +buildTerminal( + const NestedNameSpecifier* NNS, + const IdentifierInfo* II, + std::optional> TArgs, + unsigned quals, + bool pack) +{ + ASTVisitor& V = getASTVisitor(); + if(V.checkSpecialNamespace(*Inner, NNS, nullptr)) + { + return; + } + + auto I = std::make_unique(); + I->CVQualifiers = convertToQualifierKind(quals); + + if(TArgs) + { + auto Name = std::make_unique(); + if(II) + { + Name->Name = II->getName(); + } + Name->Prefix = getASTVisitor().toNameInfo(NNS); + V.populate(Name->TemplateArgs, *TArgs); + I->Name = std::move(Name); + } + else + { + auto Name = std::make_unique(); + if(II) + { + Name->Name = II->getName(); + } + Name->Prefix = getASTVisitor().toNameInfo(NNS); + I->Name = std::move(Name); + } + *Inner = std::move(I); + Result->IsPackExpansion = pack; +} + +void +TypeInfoBuilder:: +buildTerminal( + const NestedNameSpecifier* NNS, + const NamedDecl* D, + std::optional> TArgs, + unsigned quals, + bool pack) +{ + ASTVisitor& V = getASTVisitor(); + if(V.checkSpecialNamespace(*Inner, NNS, D)) + { + return; + } + + auto I = std::make_unique(); + I->CVQualifiers = convertToQualifierKind(quals); + + if(TArgs) + { + auto Name = std::make_unique(); + if(const IdentifierInfo* II = D->getIdentifier()) + { + Name->Name = II->getName(); + } + V.upsertDependency(getInstantiatedFrom(D), Name->id); + if(NNS) + { + Name->Prefix = V.toNameInfo(NNS); + } + + V.populate(Name->TemplateArgs, *TArgs); + I->Name = std::move(Name); + } + else + { + auto Name = std::make_unique(); + if(const IdentifierInfo* II = D->getIdentifier()) + { + Name->Name = II->getName(); + } + V.upsertDependency(getInstantiatedFrom(D), Name->id); + if(NNS) + { + Name->Prefix = V.toNameInfo(NNS); + } + I->Name = std::move(Name); + } + *Inner = std::move(I); + Result->IsPackExpansion = pack; +} + +} // clang::mrdocs diff --git a/src/lib/AST/TypeInfoBuilder.hpp b/src/lib/AST/TypeInfoBuilder.hpp new file mode 100644 index 0000000000..88c6acc741 --- /dev/null +++ b/src/lib/AST/TypeInfoBuilder.hpp @@ -0,0 +1,101 @@ +// +// This is a derivative work. originally part of the LLVM Project. +// 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) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#ifndef MRDOCS_LIB_AST_TYPEINFOBUILDER_HPP +#define MRDOCS_LIB_AST_TYPEINFOBUILDER_HPP + +#include + +namespace clang::mrdocs { + +/** A builder class for type information. + + This class is used to build type information by visiting + various terminal types. + + It derives from the TerminalTypeVisitor class and provides + specific implementations for building different types of + type information. + */ +class TypeInfoBuilder + : public TerminalTypeVisitor +{ + std::unique_ptr Result; + std::unique_ptr* Inner = &Result; + +public: + using TerminalTypeVisitor::TerminalTypeVisitor; + + std::unique_ptr + result() + { + return std::move(Result); + } + + void + buildPointer(const PointerType* T, unsigned quals); + + void + buildLValueReference(const LValueReferenceType* T); + + void + buildRValueReference(const RValueReferenceType* T); + + void + buildMemberPointer(const MemberPointerType* T, unsigned quals); + + void + buildArray(const ArrayType* T); + + void + populate(const FunctionType* T); + + void + buildDecltype( + const DecltypeType* T, + unsigned quals, + bool pack); + + void + buildAuto( + const AutoType* T, + unsigned const quals, + bool const pack); + + void + buildTerminal( + const NestedNameSpecifier* NNS, + const Type* T, + unsigned quals, + bool pack); + + void + buildTerminal( + const NestedNameSpecifier* NNS, + const IdentifierInfo* II, + std::optional> TArgs, + unsigned quals, + bool pack); + + void + buildTerminal( + const NestedNameSpecifier* NNS, + const NamedDecl* D, + std::optional> TArgs, + unsigned quals, + bool pack); +}; + +} // clang::mrdocs + +#endif diff --git a/src/lib/Gen/hbs/Builder.cpp b/src/lib/Gen/hbs/Builder.cpp index 37a2199547..3d9f6d6c81 100644 --- a/src/lib/Gen/hbs/Builder.cpp +++ b/src/lib/Gen/hbs/Builder.cpp @@ -114,11 +114,11 @@ relativize_fn(dom::Value to0, dom::Value from0, dom::Value options) { return to; } - std::string_view from = from0.getString().get(); + std::string_view const from = from0.getString().get(); // Find anchor in URL std::string_view hash; - std::size_t hashIdx = to.find('#'); + std::size_t const hashIdx = to.find('#'); if (hashIdx != std::string_view::npos) { hash = to.substr(hashIdx); @@ -132,7 +132,7 @@ relativize_fn(dom::Value to0, dom::Value from0, dom::Value options) { return hash; } - else if (files::isDirsy(to)) + if (files::isDirsy(to)) { return "./"; } @@ -140,7 +140,7 @@ relativize_fn(dom::Value to0, dom::Value from0, dom::Value options) } // Handle the general case - std::string fromDir = files::getParentDir(from); + std::string const fromDir = files::getParentDir(from); std::string relativePath = std::filesystem::path(to).lexically_relative(fromDir).generic_string(); if (relativePath.empty()) { @@ -164,8 +164,8 @@ Builder:: Builder( HandlebarsCorpus const& corpus, std::function escapeFn) - : domCorpus(corpus) - , escapeFn_(std::move(escapeFn)) + : escapeFn_(std::move(escapeFn)) + , domCorpus(corpus) { namespace fs = std::filesystem; @@ -196,39 +196,45 @@ Builder( dom::makeInvocable([](dom::Value const& v) -> dom::Value { - dom::Value src_loc = v.get("loc"); - if(! src_loc) + dom::Value const sourceInfo = v.get("loc"); + if (!sourceInfo) + { return nullptr; - dom::Value decls = src_loc.get("decl"); - if(dom::Value def = src_loc.get("def")) + } + dom::Value decls = sourceInfo.get("decl"); + if(dom::Value def = sourceInfo.get("def")) { // for classes/enums, prefer the definition - dom::Value kind = v.get("kind"); - if(kind == "record" || kind == "enum") + if (dom::Value const kind = v.get("kind"); + kind == "record" || kind == "enum") + { return def; - - // we only every want to use the definition + } + // We only want to use the definition // for non-tag types when no other declaration // exists - if(! decls) + if (!decls) + { return def; + } } - if(! decls.isArray()) + if (!decls.isArray() || + decls.getArray().empty()) + { return nullptr; - dom::Value first; - // otherwise, use whatever declaration had docs. - // if no declaration had docs, fallback to the - // first declaration - for(const dom::Value& loc : decls.getArray()) + } + // Use whatever declaration had docs. + for (const dom::Value& loc : decls.getArray()) { - if(loc.get("documented")) + if (loc.get("documented")) + { return loc; - else if(! first) - first = loc; + } } - return first; + // if no declaration had docs, fallback to the + // first declaration + return decls.getArray().get(0); })); - helpers::registerConstructorHelpers(hbs_); helpers::registerStringHelpers(hbs_); helpers::registerAntoraHelpers(hbs_); diff --git a/src/lib/Gen/xml/CXXTags.cpp b/src/lib/Gen/xml/CXXTags.cpp index a88579d69a..364cf427c2 100644 --- a/src/lib/Gen/xml/CXXTags.cpp +++ b/src/lib/Gen/xml/CXXTags.cpp @@ -24,7 +24,7 @@ getDefaultTagName(Info const& I) noexcept #define INFO(PascalName, LowerName) \ case InfoKind::PascalName: \ return LowerName##TagName; -#include +#include default: break; } @@ -49,7 +49,7 @@ getTagName(Info const& I) noexcept break; case InfoKind::Typedef: if(static_cast(I).IsUsing) - return aliasTagName; + return namespaceAliasTagName; else return typedefTagName; default: diff --git a/src/lib/Gen/xml/CXXTags.hpp b/src/lib/Gen/xml/CXXTags.hpp index 64b0c30c79..501aebed6e 100644 --- a/src/lib/Gen/xml/CXXTags.hpp +++ b/src/lib/Gen/xml/CXXTags.hpp @@ -31,9 +31,9 @@ namespace clang { namespace mrdocs { namespace xml { -#define INFO(LowerName) \ -constexpr auto LowerName##TagName = #LowerName; -#include +#define INFO(camelName, LowerName) \ +constexpr auto camelName##TagName = #LowerName; +#include constexpr auto accessTagName = "access"; constexpr auto attributeTagName = "attr"; diff --git a/src/lib/Gen/xml/XMLWriter.cpp b/src/lib/Gen/xml/XMLWriter.cpp index 2b1ce74c31..5e2caddfcd 100644 --- a/src/lib/Gen/xml/XMLWriter.cpp +++ b/src/lib/Gen/xml/XMLWriter.cpp @@ -244,14 +244,14 @@ writeEnum( void XMLWriter:: -writeEnumerator( - EnumeratorInfo const& I) +writeEnumConstant( + EnumConstantInfo const& I) { std::string val = I.Initializer.Value ? std::to_string(*I.Initializer.Value) : I.Initializer.Written; - tags_.open(enumeratorTagName, { + tags_.open(enumConstantTagName, { { "name", I.Name }, { "initializer", val }, { I.Access }, @@ -262,7 +262,7 @@ writeEnumerator( writeJavadoc(I.javadoc); - tags_.close(enumeratorTagName); + tags_.close(enumConstantTagName); } void @@ -405,10 +405,10 @@ writeConcept( void XMLWriter:: -writeAlias( - AliasInfo const& I) +writeNamespaceAlias( + NamespaceAliasInfo const& I) { - tags_.open(aliasTagName, { + tags_.open(namespaceAliasTagName, { { "name", I.Name }, { I.Access }, { I.id } @@ -422,7 +422,7 @@ writeAlias( {"name", toString(*I.AliasedSymbol)}, { I.AliasedSymbol->id } }); - tags_.close(aliasTagName); + tags_.close(namespaceAliasTagName); } void @@ -516,7 +516,7 @@ writeTypedef( llvm::StringRef tag; if(I.IsUsing) - tag = aliasTagName; + tag = namespaceAliasTagName; else tag = typedefTagName; tags_.open(tag, { diff --git a/src/lib/Lib/ConfigOptions.json b/src/lib/Lib/ConfigOptions.json index e8f2bb9d41..214b1c1a05 100644 --- a/src/lib/Lib/ConfigOptions.json +++ b/src/lib/Lib/ConfigOptions.json @@ -170,7 +170,7 @@ { "name": "referenced-declarations", "brief": "Extraction policy for references to external declarations", - "details": "Determine whether external declarations should be extracted when they are referenced in the source code. When set to `always`, external declarations are always extracted. When set to `dependency`, external declarations are extracted only if they are referenced by the source code. When set to `never`, external declarations are never extracted.", + "details": "Determine whether external declarations should be extracted when they are referenced in the source code. If this option is not `never`, a second pass happens in the extraction process to extract dependencies in the Corpus. When set to `always`, external declarations are always extracted. When set to `dependency`, external declarations are extracted only if they are referenced by the source code. When set to `never`, external declarations are never extracted.", "type": "enum", "values": [ "always", @@ -194,7 +194,7 @@ { "name": "inaccessible-members", "brief": "Extraction policy for inaccessible members", - "details": "Determine whether inaccessible members should be extracted. When set to `always`, inaccessible members are always extracted. When set to `dependency`, inaccessible members are extracted only if they are referenced by the source code. When set to `never`, inaccessible members are never extracted.", + "details": "Determine whether inaccessible members, such as private members of records, should be extracted. When set to `always`, inaccessible members are always extracted. When set to `dependency`, inaccessible members are extracted only if they are referenced by the source code. When set to `never`, inaccessible members are never extracted.", "type": "enum", "values": [ "always", @@ -218,14 +218,14 @@ { "name": "see-below", "brief": "Namespaces for symbols rendered as \"see-below\"", - "details": "Namespaces for symbols rendered as \"see-below\". Symbols in these namespaces are not extracted and are rendered as \"see-below\" in the documentation. This option is used to exclude symbols from the documentation that are considered part of the private API of the project.", + "details": "Namespaces for symbols rendered as \"see-below\". Symbols in these namespaces are rendered as \"see-below\" in the documentation. This option is used to exclude details about symbols from the documentation that are considered part of the private API of the project. In the documentation page for this symbol, the synopsis of the implementation of a \"see-below\" symbol is rendered as \"see-below\". When the symbol is a scope (such as a namespace or record), its members are not listed. The rest of the documentation is rendered as usual.", "type": "list", "default": [] }, { "name": "implementation-defined", "brief": "Namespaces for symbols rendered as \"implementation-defined\"", - "details": "Namespaces for symbols rendered as \"implementation-defined\". Symbols in these namespaces are not extracted and are rendered as \"implementation-defined\" in the documentation. This option is used to exclude symbols from the documentation that are considered part of the private API of the project.", + "details": "Namespaces for symbols rendered as \"implementation-defined\". Symbols in these are rendered as \"implementation-defined\" in the documentation. This option is used to exclude symbols from the documentation that are considered part of the private API of the project. An \"implementation-defined\" symbol has no documentation page in the output. If any other symbol refers to it, the reference is rendered as \"implementation-defined\".", "type": "list", "default": [] }, diff --git a/src/lib/Lib/CorpusImpl.cpp b/src/lib/Lib/CorpusImpl.cpp index 7ac7ef9bb5..ed7724c06e 100644 --- a/src/lib/Lib/CorpusImpl.cpp +++ b/src/lib/Lib/CorpusImpl.cpp @@ -11,7 +11,7 @@ // #include "CorpusImpl.hpp" -#include "lib/AST/ASTVisitor.hpp" +#include "lib/AST/FrontendActionFactory.hpp" #include "lib/Metadata/Finalize.hpp" #include "lib/Lib/Lookup.hpp" #include "lib/Support/Error.hpp" diff --git a/src/lib/Lib/MrDocsCompilationDatabase.cpp b/src/lib/Lib/MrDocsCompilationDatabase.cpp index 2f42a6442d..43b3c3f83d 100644 --- a/src/lib/Lib/MrDocsCompilationDatabase.cpp +++ b/src/lib/Lib/MrDocsCompilationDatabase.cpp @@ -244,14 +244,14 @@ isValidMrDocsOption( static std::vector adjustCommandLine( - llvm::StringRef workingDir, + StringRef const workingDir, std::vector const& cmdline, std::vector const& additional_defines, std::unordered_map> const& implicitIncludeDirectories, std::vector const& stdlibIncludes, std::vector const& systemIncludes, std::vector const& includes, - bool useSystemStdlib) + bool const useSystemStdlib) { if (cmdline.empty()) { @@ -281,7 +281,7 @@ adjustCommandLine( // command line option formats. The value is deduced from // the `-drive-mode` option or from `progName`. // Common values are "gcc", "g++", "cpp", "cl" and "flang". - StringRef driver_mode = driver::getDriverMode(progName, cmdLineCStrs); + StringRef const driver_mode = driver::getDriverMode(progName, cmdLineCStrs); // Identify if we should use "msvc/clang-cl" or "clang/gcc" format // for options. bool const is_clang_cl = driver::IsClangCL(driver_mode); @@ -406,10 +406,11 @@ adjustCommandLine( { new_cmdline.emplace_back(fmt::format("-isystem{}", inc)); } - for (auto const& inc : stdlibIncludes) + for (auto const& inc: stdlibIncludes) { new_cmdline.emplace_back(fmt::format("-isystem{}", inc)); } + // new_cmdline.emplace_back("-nostdinc"); new_cmdline.emplace_back("-nostdinc++"); new_cmdline.emplace_back("-nostdlib++"); } @@ -438,7 +439,7 @@ adjustCommandLine( { // Parse one argument as a Clang option // ParseOneArg updates Index to the next argument to be parsed. - unsigned idx0 = idx; + unsigned const idx0 = idx; std::unique_ptr arg = opts_table.ParseOneArg(args, idx, visibility); if (!isValidMrDocsOption(workingDir, arg)) diff --git a/src/lib/Metadata/Finalize.cpp b/src/lib/Metadata/Finalize.cpp index 1036e6705f..e183769396 100644 --- a/src/lib/Metadata/Finalize.cpp +++ b/src/lib/Metadata/Finalize.cpp @@ -352,7 +352,7 @@ class Finalizer finalize(I.FriendType); } - void operator()(AliasInfo& I) + void operator()(NamespaceAliasInfo& I) { check(I.Namespace); finalize(I.javadoc); @@ -367,7 +367,7 @@ class Finalizer finalize(I.UsingSymbols); } - void operator()(EnumeratorInfo& I) + void operator()(EnumConstantInfo& I) { check(I.Namespace); finalize(I.javadoc); diff --git a/src/lib/Metadata/Function.cpp b/src/lib/Metadata/Function.cpp index e111930b6d..e2acbee167 100644 --- a/src/lib/Metadata/Function.cpp +++ b/src/lib/Metadata/Function.cpp @@ -93,7 +93,7 @@ static constinit Item const Table[] = { std::string_view getOperatorName( - OperatorKind kind, + OperatorKind const kind, bool include_keyword) noexcept { MRDOCS_ASSERT(Table[to_underlying(kind)].kind == kind); diff --git a/src/lib/Metadata/Info.cpp b/src/lib/Metadata/Info.cpp index f3cf33181d..9616cae506 100644 --- a/src/lib/Metadata/Info.cpp +++ b/src/lib/Metadata/Info.cpp @@ -225,7 +225,7 @@ tag_invoke( io.map("type", I.FriendType); } } - if constexpr (T::isAlias()) + if constexpr (T::isNamespaceAlias()) { MRDOCS_ASSERT(I.AliasedSymbol); io.map("aliasedSymbol", I.AliasedSymbol); @@ -236,7 +236,7 @@ tag_invoke( io.map("shadows", dom::LazyArray(I.UsingSymbols, domCorpus)); io.map("qualifier", I.Qualifier); } - if constexpr (T::isEnumerator()) + if constexpr (T::isEnumConstant()) { if (!I.Initializer.Written.empty()) { diff --git a/src/lib/Metadata/InfoNodes.json b/src/lib/Metadata/InfoNodes.json index 7eb3dddd0e..79b889e490 100644 --- a/src/lib/Metadata/InfoNodes.json +++ b/src/lib/Metadata/InfoNodes.json @@ -15,6 +15,10 @@ "name": "enum", "brief": "The symbol is an enum" }, + { + "name": "enum-constant", + "brief": "The symbol is an enum-constant" + }, { "name": "typedef", "brief": "The symbol is a typedef" @@ -35,16 +39,12 @@ "name": "friend", "brief": "The symbol is a friend declaration" }, - { - "name": "enumerator", - "brief": "The symbol is an enumerator" - }, { "name": "guide", "brief": "The symbol is a deduction guide" }, { - "name": "alias", + "name": "namespace-alias", "brief": "The symbol is a namespace alias" }, { diff --git a/src/lib/Metadata/Interface.cpp b/src/lib/Metadata/Interface.cpp index 5204b752aa..12339d801d 100644 --- a/src/lib/Metadata/Interface.cpp +++ b/src/lib/Metadata/Interface.cpp @@ -268,10 +268,10 @@ class TrancheBuilder } void operator()( - AliasInfo const& I, + NamespaceAliasInfo const& I, AccessKind access) { - push(&Tranche::Aliases, access, I); + push(&Tranche::NamespaceAliases, access, I); } void operator()( @@ -282,7 +282,7 @@ class TrancheBuilder } void operator()( - const EnumeratorInfo& I, + const EnumConstantInfo& I, AccessKind access) { // KRYSTIAN FIXME: currently unimplemented diff --git a/src/lib/Metadata/Reduce.cpp b/src/lib/Metadata/Reduce.cpp index c0cefc450c..2e4d7fe5b5 100644 --- a/src/lib/Metadata/Reduce.cpp +++ b/src/lib/Metadata/Reduce.cpp @@ -342,7 +342,7 @@ void merge(FriendInfo& I, FriendInfo&& Other) I.FriendType = std::move(Other.FriendType); } -void merge(AliasInfo& I, AliasInfo&& Other) +void merge(NamespaceAliasInfo& I, NamespaceAliasInfo&& Other) { MRDOCS_ASSERT(canMerge(I, Other)); if (! I.AliasedSymbol) @@ -364,7 +364,7 @@ void merge(UsingInfo& I, UsingInfo&& Other) mergeInfo(I, std::move(Other)); } -void merge(EnumeratorInfo& I, EnumeratorInfo&& Other) +void merge(EnumConstantInfo& I, EnumConstantInfo&& Other) { MRDOCS_ASSERT(canMerge(I, Other)); if(I.Initializer.Written.empty()) diff --git a/src/lib/Metadata/Reduce.hpp b/src/lib/Metadata/Reduce.hpp index ab123f2f3f..e946f6058b 100644 --- a/src/lib/Metadata/Reduce.hpp +++ b/src/lib/Metadata/Reduce.hpp @@ -30,9 +30,9 @@ void merge(FieldInfo& I, FieldInfo&& Other); void merge(VariableInfo& I, VariableInfo&& Other); void merge(SpecializationInfo& I, SpecializationInfo&& Other); void merge(FriendInfo& I, FriendInfo&& Other); -void merge(EnumeratorInfo& I, EnumeratorInfo&& Other); +void merge(EnumConstantInfo& I, EnumConstantInfo&& Other); void merge(GuideInfo& I, GuideInfo&& Other); -void merge(AliasInfo& I, AliasInfo&& Other); +void merge(NamespaceAliasInfo& I, NamespaceAliasInfo&& Other); void merge(UsingInfo& I, UsingInfo&& Other); void merge(ConceptInfo& I, ConceptInfo&& Other); diff --git a/src/lib/Support/LegibleNames.cpp b/src/lib/Support/LegibleNames.cpp index 19607e4faf..07c03f476e 100644 --- a/src/lib/Support/LegibleNames.cpp +++ b/src/lib/Support/LegibleNames.cpp @@ -69,10 +69,10 @@ class LegibleNames::Impl "05variable", "06field", "07specialization", + "09enum-constant", "08friend", - "09enumerator", "10guide", - "11alias", + "11namespace-alias", "12using", "13concept", }; @@ -203,7 +203,7 @@ class LegibleNames::Impl return getReserved(t); } - if constexpr(T::isAlias()) + if constexpr(T::isNamespaceAlias()) { MRDOCS_ASSERT(! t.Name.empty()); return t.Name; @@ -215,7 +215,7 @@ class LegibleNames::Impl return t.Name; } - if constexpr(T::isEnumerator()) + if constexpr(T::isEnumConstant()) { MRDOCS_ASSERT(! t.Name.empty()); return t.Name; diff --git a/src/lib/Support/Path.cpp b/src/lib/Support/Path.cpp index 4e6eaeeeef..6edd6ad477 100644 --- a/src/lib/Support/Path.cpp +++ b/src/lib/Support/Path.cpp @@ -29,8 +29,10 @@ convert_to_slash( llvm::sys::path::Style style) { if (! llvm::sys::path::is_style_posix(style)) + { std::replace(path.begin(), path.end(), '\\', '/'); - return llvm::StringRef(path.data(), path.size()); + } + return {path.data(), path.size()}; } //------------------------------------------------ diff --git a/src/test/TestRunner.cpp b/src/test/TestRunner.cpp index ecb3459685..70c2b3ccb5 100644 --- a/src/test/TestRunner.cpp +++ b/src/test/TestRunner.cpp @@ -148,7 +148,8 @@ handleFile( testArgs.action == Action::update) { // Create expected documentation file - if(auto exp = writeFile(expectedPath, generatedDocs)) + if(auto exp = writeFile(expectedPath, generatedDocs); + !exp) { return report::error("{}: \"{}\"", exp.error(), expectedPath); } @@ -227,26 +228,31 @@ handleDir( namespace fs = llvm::sys::fs; namespace path = llvm::sys::path; - results.numberOfDirs++; + ++results.numberOfDirs; // Visit each file in the directory std::error_code ec; fs::directory_iterator const end{}; fs::directory_iterator iter(dirPath, ec, false); - if(ec) + if (ec) + { return report::error("{}: \"{}\"", dirPath, Error(ec)); + } while(iter != end) { - auto const& entry = *iter; - if(entry.type() == fs::file_type::directory_file) + if (auto const& entry = *iter; + entry.type() == fs::file_type::directory_file) { // Check for a subdirectory-wide config Config::Settings subdirSettings = dirSettings; - std::string const& configPath = files::appendPath( - files::appendPath(dirPath, entry.path()), "mrdocs.yml"); - if (files::exists(configPath)) { + std::string const& configPath = files::appendPath(entry.path(), "mrdocs.yml"); + if (files::exists(configPath)) + { Config::Settings::load_file(subdirSettings, configPath, dirs_).value(); + auto prev = dirs_.configDir; + dirs_.configDir = entry.path(); subdirSettings.normalize(dirs_); + dirs_.configDir = std::move(prev); } handleDir(entry.path(), subdirSettings); } @@ -261,9 +267,10 @@ handleDir( }); } iter.increment(ec); - if(ec) - return report::error("{}: \"{}\"", - Error(ec), dirPath); + if (ec) + { + return report::error("{}: \"{}\"", Error(ec), dirPath); + } } } diff --git a/test-files/golden-tests/alias-template.xml b/test-files/golden-tests/alias-template.xml index f9bc8c14bb..2d5bd53b01 100644 --- a/test-files/golden-tests/alias-template.xml +++ b/test-files/golden-tests/alias-template.xml @@ -17,10 +17,10 @@ diff --git a/test-files/golden-tests/dependency-propagation.xml b/test-files/golden-tests/dependency-propagation.xml index b6b337115c..3cbcbf0525 100644 --- a/test-files/golden-tests/dependency-propagation.xml +++ b/test-files/golden-tests/dependency-propagation.xml @@ -11,17 +11,17 @@ - + - - + + - + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + diff --git a/test-files/golden-tests/namespace-alias-1.xml b/test-files/golden-tests/namespace-alias-1.xml index 173bc174d8..9d11b38327 100644 --- a/test-files/golden-tests/namespace-alias-1.xml +++ b/test-files/golden-tests/namespace-alias-1.xml @@ -4,9 +4,9 @@ - + - + diff --git a/test-files/golden-tests/namespace-alias-2.xml b/test-files/golden-tests/namespace-alias-2.xml index 86cd0b9874..3e66c4e88d 100644 --- a/test-files/golden-tests/namespace-alias-2.xml +++ b/test-files/golden-tests/namespace-alias-2.xml @@ -4,13 +4,13 @@ - + - - + + - + diff --git a/test-files/golden-tests/namespace-alias-3.xml b/test-files/golden-tests/namespace-alias-3.xml index 0342faf59b..1e7b2058b1 100644 --- a/test-files/golden-tests/namespace-alias-3.xml +++ b/test-files/golden-tests/namespace-alias-3.xml @@ -4,13 +4,13 @@ - + - - + + - + diff --git a/test-files/golden-tests/record-1.xml b/test-files/golden-tests/record-1.xml index 857193b32b..f22454dbb1 100644 --- a/test-files/golden-tests/record-1.xml +++ b/test-files/golden-tests/record-1.xml @@ -4,10 +4,10 @@ - + - + diff --git a/test-files/golden-tests/record-data.xml b/test-files/golden-tests/record-data.xml index 1f024bfb92..a712f86f67 100644 --- a/test-files/golden-tests/record-data.xml +++ b/test-files/golden-tests/record-data.xml @@ -62,10 +62,10 @@ - + - + diff --git a/test-files/golden-tests/snippets/distance.adoc b/test-files/golden-tests/snippets/distance.adoc new file mode 100644 index 0000000000..ececb9beca --- /dev/null +++ b/test-files/golden-tests/snippets/distance.adoc @@ -0,0 +1,77 @@ += Reference +:mrdocs: + +[#index] +== Global namespace + +=== Functions +[cols=2] +|=== +| Name | Description + +| <<#distance,`distance`>> +| +Return the distance between two points + + +|=== + +[#distance] +== distance + + +Return the distance between two points + + +=== Synopsis + +Declared in `` +[source,cpp,subs="verbatim,macros,-callouts"] +---- +double +distance( + double x0, + double y0, + double x1, + double y1); +---- + +=== Description + + +This function returns the distance between two points +according to the Euclidean distance formula. + + +=== Return Value + + +The distance between the two points + + +=== Parameters + +|=== +| Name | Description + +| *x0* +| pass:[ +The x-coordinate of the first point +] +| *y0* +| pass:[ +The y-coordinate of the first point +] +| *x1* +| pass:[ +The x-coordinate of the second point +] +| *y1* +| pass:[ +The y-coordinate of the second point +] +|=== + + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/snippets/distance.cpp b/test-files/golden-tests/snippets/distance.cpp new file mode 100644 index 0000000000..8983087c6d --- /dev/null +++ b/test-files/golden-tests/snippets/distance.cpp @@ -0,0 +1,13 @@ +/** Return the distance between two points + + This function returns the distance between two points + according to the Euclidean distance formula. + + @param x0 The x-coordinate of the first point + @param y0 The y-coordinate of the first point + @param x1 The x-coordinate of the second point + @param y1 The y-coordinate of the second point + @return The distance between the two points +*/ +double +distance(double x0, double y0, double x1, double y1); \ No newline at end of file diff --git a/test-files/golden-tests/snippets/distance.html b/test-files/golden-tests/snippets/distance.html new file mode 100644 index 0000000000..ebca525307 --- /dev/null +++ b/test-files/golden-tests/snippets/distance.html @@ -0,0 +1,96 @@ + + +Reference + + +
+

Reference

+
+
+

Global namespace

+
+

Functions

+ + + + + + + + + + +
NameDescription
distance

Return the distance between two points

+ + +
+
+
+
+

distance

+
+

Return the distance between two points

+ + + +
+
+
+

Synopsis

+
+Declared in <distance.cpp>
+
+
+double
+distance(
+    double x0,
+    double y0,
+    double x1,
+    double y1);
+
+
+
+
+

Description

+

This function returns the distance between two points according to the Euclidean distance formula.

+ + + +
+
+

Parameters

+ + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescription
x0
y0
x1
y1
+
+
+ +
+
+

Created with MrDocs

+
+ + \ No newline at end of file diff --git a/test-files/golden-tests/snippets/distance.xml b/test-files/golden-tests/snippets/distance.xml new file mode 100644 index 0000000000..d130a2e831 --- /dev/null +++ b/test-files/golden-tests/snippets/distance.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + Return the distance between two points + + + This function returns the distance between two points + according to the Euclidean distance formula. + + + The x-coordinate of the first point + + + The y-coordinate of the first point + + + The x-coordinate of the second point + + + The y-coordinate of the second point + + + The distance between the two points + + + + + diff --git a/test-files/golden-tests/snippets/is_prime.adoc b/test-files/golden-tests/snippets/is_prime.adoc new file mode 100644 index 0000000000..5cefb0ce2c --- /dev/null +++ b/test-files/golden-tests/snippets/is_prime.adoc @@ -0,0 +1,60 @@ += Reference +:mrdocs: + +[#index] +== Global namespace + +=== Functions +[cols=2] +|=== +| Name | Description + +| <<#is_prime,`pass:[is_prime]`>> +| +Return true if a number is prime. + + +|=== + +[#is_prime] +== pass:[is_prime] + + +Return true if a number is prime. + + +=== Synopsis + +Declared in `` +[source,cpp,subs="verbatim,macros,-callouts"] +---- +bool +pass:[is_prime](unsigned long long n) noexcept; +---- + +=== Description + + +Linear in n. + + +=== Return Value + + +Whether or not n is prime. + + +=== Parameters + +|=== +| Name | Description + +| *n* +| +The number to test + +|=== + + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/snippets/is_prime.cpp b/test-files/golden-tests/snippets/is_prime.cpp new file mode 100644 index 0000000000..b13810c013 --- /dev/null +++ b/test-files/golden-tests/snippets/is_prime.cpp @@ -0,0 +1,12 @@ +/** Return true if a number is prime. + + @par Complexity + + Linear in n. + + @return Whether or not n is prime. + @param n The number to test + +*/ +bool +is_prime(unsigned long long n) noexcept; \ No newline at end of file diff --git a/test-files/golden-tests/snippets/is_prime.html b/test-files/golden-tests/snippets/is_prime.html new file mode 100644 index 0000000000..d8987eb7b6 --- /dev/null +++ b/test-files/golden-tests/snippets/is_prime.html @@ -0,0 +1,80 @@ + + +Reference + + +
+

Reference

+
+
+

Global namespace

+
+

Functions

+ + + + + + + + + + +
NameDescription
is_prime

Return true if a number is prime.

+ + +
+
+
+
+

is_prime

+
+

Return true if a number is prime.

+ + + +
+
+
+

Synopsis

+
+Declared in <is_prime.cpp>
+
+
+bool
+is_prime(unsigned long long n) noexcept;
+
+
+
+
+

Description

+

Linear in n.

+ + + +
+
+

Parameters

+ + + + + + + + + + + + + +
NameDescription
n
+
+
+ +
+
+

Created with MrDocs

+
+ + \ No newline at end of file diff --git a/test-files/golden-tests/snippets/is_prime.xml b/test-files/golden-tests/snippets/is_prime.xml new file mode 100644 index 0000000000..a35fa58c87 --- /dev/null +++ b/test-files/golden-tests/snippets/is_prime.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + Return true if a number is prime. + + + Linear in n. + + + Whether or not n is prime. + + + The number to test + + + + + diff --git a/test-files/golden-tests/snippets/mrdocs.yml b/test-files/golden-tests/snippets/mrdocs.yml new file mode 100644 index 0000000000..e7067b0574 --- /dev/null +++ b/test-files/golden-tests/snippets/mrdocs.yml @@ -0,0 +1,4 @@ +source-root: . +input: + include: + - . diff --git a/test-files/golden-tests/snippets/sqrt.adoc b/test-files/golden-tests/snippets/sqrt.adoc new file mode 100644 index 0000000000..cd832cdf05 --- /dev/null +++ b/test-files/golden-tests/snippets/sqrt.adoc @@ -0,0 +1,84 @@ += Reference +:mrdocs: + +[#index] +== Global namespace + +=== Functions +[cols=2] +|=== +| Name | Description + +| <<#sqrt,`sqrt`>> +| +Computes the square root of an integral value. + + +|=== + +[#sqrt] +== sqrt + + +Computes the square root of an integral value. + + +=== Synopsis + +Declared in `` +[source,cpp,subs="verbatim,macros,-callouts"] +---- +template +std::T +sqrt(T value); +---- + +=== Description + + +This function calculates the square root of a +given integral value using bit manipulation. + + +=== Exceptions + +|=== +| Name | Thrown on + +| `if` +| pass:[ +the input value is negative. +] +|=== + +=== Return Value + + +The square root of the input value. + + +=== Template Parameters + +|=== +| Name | Description + +| *T* +| pass:[ +The type of the input value. Must be an integral type. +] +|=== + +=== Parameters + +|=== +| Name | Description + +| *value* +| pass:[ +The integral value to compute the square root of. +] +|=== + + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/snippets/sqrt.cpp b/test-files/golden-tests/snippets/sqrt.cpp new file mode 100644 index 0000000000..24d30487a4 --- /dev/null +++ b/test-files/golden-tests/snippets/sqrt.cpp @@ -0,0 +1,34 @@ +#include +#include + +/** Computes the square root of an integral value. + + This function calculates the square root of a + given integral value using bit manipulation. + + @throws std::invalid_argument if the input value is negative. + + @tparam T The type of the input value. Must be an integral type. + @param value The integral value to compute the square root of. + @return The square root of the input value. + */ +template +std::enable_if_t, T> sqrt(T value) { + if (value < 0) { + throw std::invalid_argument( + "Cannot compute square root of a negative number"); + } + T result = 0; + // The second-to-top bit is set + T bit = 1 << (sizeof(T) * 8 - 2); + while (bit > value) bit >>= 2; + while (bit != 0) { + if (value >= result + bit) { + value -= result + bit; + result += bit << 1; + } + result >>= 1; + bit >>= 2; + } + return result; +} diff --git a/test-files/golden-tests/snippets/sqrt.html b/test-files/golden-tests/snippets/sqrt.html new file mode 100644 index 0000000000..dd7658f668 --- /dev/null +++ b/test-files/golden-tests/snippets/sqrt.html @@ -0,0 +1,115 @@ + + +Reference + + +
+

Reference

+
+
+

Global namespace

+
+

Functions

+ + + + + + + + + + +
NameDescription
sqrt

Computes the square root of an integral value.

+ + +
+
+
+
+

sqrt

+
+

Computes the square root of an integral value.

+ + + +
+
+
+

Synopsis

+
+Declared in <sqrt.cpp>
+
+
+template<typename T>
+std::T
+sqrt(T value);
+
+
+
+
+

Description

+

This function calculates the square root of a given integral value using bit manipulation.

+ + + +
+
+

Exceptions

+ + + + + + + + + + + + + +
NameThrown on
if
+
+
+

Template Parameters

+ + + + + + + + + + + + + +
NameDescription
T
+
+
+

Parameters

+ + + + + + + + + + + + + +
NameDescription
value
+
+
+ +
+
+

Created with MrDocs

+
+ + \ No newline at end of file diff --git a/test-files/golden-tests/snippets/sqrt.xml b/test-files/golden-tests/snippets/sqrt.xml new file mode 100644 index 0000000000..afbbada2d2 --- /dev/null +++ b/test-files/golden-tests/snippets/sqrt.xml @@ -0,0 +1,39 @@ + + + + + + diff --git a/test-files/golden-tests/snippets/terminate.adoc b/test-files/golden-tests/snippets/terminate.adoc new file mode 100644 index 0000000000..9883c427a7 --- /dev/null +++ b/test-files/golden-tests/snippets/terminate.adoc @@ -0,0 +1,46 @@ += Reference +:mrdocs: + +[#index] +== Global namespace + +=== Functions +[cols=2] +|=== +| Name | Description + +| <<#terminate,`terminate`>> +| +Exit the program. + + +|=== + +[#terminate] +== terminate + + +Exit the program. + + +=== Synopsis + +Declared in `` +[source,cpp,subs="verbatim,macros,-callouts"] +---- +void +terminate() noexcept; +---- + +=== Description + + +The program will end immediately. +[NOTE] + +This function does not return. + + + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/snippets/terminate.cpp b/test-files/golden-tests/snippets/terminate.cpp new file mode 100644 index 0000000000..b3e291bc5d --- /dev/null +++ b/test-files/golden-tests/snippets/terminate.cpp @@ -0,0 +1,9 @@ +/** Exit the program. + + The program will end immediately. + + @note This function does not return. +*/ +[[noreturn]] +void +terminate() noexcept; \ No newline at end of file diff --git a/test-files/golden-tests/snippets/terminate.html b/test-files/golden-tests/snippets/terminate.html new file mode 100644 index 0000000000..0830d02603 --- /dev/null +++ b/test-files/golden-tests/snippets/terminate.html @@ -0,0 +1,68 @@ + + +Reference + + +
+

Reference

+
+
+

Global namespace

+
+

Functions

+ + + + + + + + + + +
NameDescription
terminate

Exit the program.

+ + +
+
+
+
+

terminate

+
+

Exit the program.

+ + + +
+
+
+

Synopsis

+
+Declared in <terminate.cpp>
+
+
+void
+terminate() noexcept;
+
+
+
+
+

Description

+

The program will end immediately.

+ +
+

NOTE

+

This function does not return.

+ +
+ + +
+
+ +
+
+

Created with MrDocs

+
+ + \ No newline at end of file diff --git a/test-files/golden-tests/snippets/terminate.xml b/test-files/golden-tests/snippets/terminate.xml new file mode 100644 index 0000000000..031f4ba434 --- /dev/null +++ b/test-files/golden-tests/snippets/terminate.xml @@ -0,0 +1,21 @@ + + + + + + + + + Exit the program. + + + The program will end immediately. + + + This function does not return. + + + + + diff --git a/test-files/golden-tests/type-resolution.xml b/test-files/golden-tests/type-resolution.xml index f873b08d0d..bb3dc12586 100644 --- a/test-files/golden-tests/type-resolution.xml +++ b/test-files/golden-tests/type-resolution.xml @@ -12,20 +12,20 @@
- + - - + + - + diff --git a/test-files/golden-tests/variadic-function.xml b/test-files/golden-tests/variadic-function.xml index e324d8f968..8e1d62cd35 100644 --- a/test-files/golden-tests/variadic-function.xml +++ b/test-files/golden-tests/variadic-function.xml @@ -2,19 +2,19 @@ - + - - + + - + diff --git a/util/generate-info-files.py b/util/generate-info-files.py index d52c567fd3..26043b1229 100644 --- a/util/generate-info-files.py +++ b/util/generate-info-files.py @@ -73,6 +73,16 @@ def generate_info_nodes_lowercase_inc(info_nodes): contents += f'\n#undef INFO\n' return contents +def generate_info_nodes_camelcase_inc(info_nodes): + contents = generate_header_comment() + contents += f'#ifndef INFO\n' + contents += f'#define INFO(CamelName) \n' + contents += f'#endif\n\n' + for info_node in info_nodes: + contents += f'INFO({to_camel_case(info_node["name"])})\n' + contents += f'\n#undef INFO\n' + return contents + def generate_info_nodes_pascal_and_lowercase_inc(info_nodes): contents = generate_header_comment() @@ -84,6 +94,25 @@ def generate_info_nodes_pascal_and_lowercase_inc(info_nodes): contents += f'\n#undef INFO\n' return contents +def generate_info_nodes_pascal_and_camelcase_inc(info_nodes): + contents = generate_header_comment() + contents += f'#ifndef INFO\n' + contents += f'#define INFO(PascalName, CamelName) \n' + contents += f'#endif\n\n' + for info_node in info_nodes: + contents += f'INFO({to_pascal_case(info_node["name"])}, {to_camel_case(info_node["name"])})\n' + contents += f'\n#undef INFO\n' + return contents + +def generate_info_nodes_camel_and_lowercase_inc(info_nodes): + contents = generate_header_comment() + contents += f'#ifndef INFO\n' + contents += f'#define INFO(CamelName, LowerName) \n' + contents += f'#endif\n\n' + for info_node in info_nodes: + contents += f'INFO({to_camel_case(info_node["name"])}, {info_node["name"].lower()})\n' + contents += f'\n#undef INFO\n' + return contents def to_plural(name): if name.endswith('y'): @@ -127,7 +156,10 @@ def generate(info_nodes, output_dir): files_content = { 'InfoNodesPascal.inc': generate_info_nodes_pascal_inc(info_nodes), 'InfoNodesPascalAndLower.inc': generate_info_nodes_pascal_and_lowercase_inc(info_nodes), + 'InfoNodesPascalAndCamel.inc': generate_info_nodes_pascal_and_camelcase_inc(info_nodes), + 'InfoNodesCamelAndLower.inc': generate_info_nodes_camel_and_lowercase_inc(info_nodes), 'InfoNodesLower.inc': generate_info_nodes_lowercase_inc(info_nodes), + 'InfoNodesCamel.inc': generate_info_nodes_camelcase_inc(info_nodes), 'InfoNodesPascalPlural.inc': generate_info_nodes_pascal_plural_inc(info_nodes), 'InfoNodesPascalPluralAndLowerPlural.inc': generate_info_nodes_pascal_plural_and_lowercase_plural_inc(info_nodes) }