From 3e1c279a0bbd743a01b8e6dbb8e61be7b0a643ab Mon Sep 17 00:00:00 2001 From: alandefreitas Date: Thu, 30 Nov 2023 21:16:34 -0300 Subject: [PATCH] refactor: tool uses Expected --- include/mrdocs/Support/Error.hpp | 116 +++++++++++++++++++++++++++++-- include/mrdocs/Support/Path.hpp | 2 + src/lib/Support/Path.cpp | 4 +- src/tool/Addons.cpp | 74 +++++++++----------- src/tool/Addons.hpp | 4 +- src/tool/GenerateAction.cpp | 88 ++++++++++++----------- src/tool/ToolMain.cpp | 51 ++++++++------ 7 files changed, 220 insertions(+), 119 deletions(-) diff --git a/include/mrdocs/Support/Error.hpp b/include/mrdocs/Support/Error.hpp index b864e7e14..c7ffb2fbd 100644 --- a/include/mrdocs/Support/Error.hpp +++ b/include/mrdocs/Support/Error.hpp @@ -515,25 +515,97 @@ class Unexpected template Unexpected(E) -> Unexpected; +namespace detail +{ + template + constexpr + bool + failed(T const&t) + { + if constexpr (isExpected>) + { + return !t; + } + else if constexpr (std::same_as, Error>) + { + return t.failed(); + } + else if constexpr (requires (T const& t0) { t0.empty(); }) + { + return t.empty(); + } + else if constexpr (std::constructible_from) + { + return !t; + } + } + + template + constexpr + decltype(auto) + error(T const& t) + { + if constexpr (isExpected>) + { + return t.error(); + } + else if constexpr (std::same_as, Error>) + { + return t; + } + else if constexpr (requires (T const& t0) { t0.empty(); }) + { + return Error("Empty value"); + } + else if constexpr (std::constructible_from) + { + return Error("Invalid value"); + } + } +} + #ifndef MRDOCS_TRY # define MRDOCS_MERGE_(a, b) a##b # define MRDOCS_LABEL_(a) MRDOCS_MERGE_(expected_result_, a) # define MRDOCS_UNIQUE_NAME MRDOCS_LABEL_(__LINE__) + +/// Try to retrive expected-like type # define MRDOCS_TRY_VOID(expr) \ auto MRDOCS_UNIQUE_NAME = expr; \ - if (!MRDOCS_UNIQUE_NAME) { \ - return Unexpected(MRDOCS_UNIQUE_NAME.error()); \ + if (detail::failed(MRDOCS_UNIQUE_NAME)) { \ + return Unexpected(detail::error(MRDOCS_UNIQUE_NAME)); \ } \ void(0) # define MRDOCS_TRY_VAR(var, expr) \ auto MRDOCS_UNIQUE_NAME = expr; \ - if (!MRDOCS_UNIQUE_NAME) { \ - return Unexpected(MRDOCS_UNIQUE_NAME.error()); \ - } \ + if (detail::failed(MRDOCS_UNIQUE_NAME)) { \ + return Unexpected(detail::error(MRDOCS_UNIQUE_NAME)); \ + } \ var = *std::move(MRDOCS_UNIQUE_NAME) -# define GET_MACRO(_1, _2, NAME, ...) NAME +# define MRDOCS_TRY_MSG(var, expr, msg) \ + auto MRDOCS_UNIQUE_NAME = expr; \ + if (detail::failed(MRDOCS_UNIQUE_NAME)) { \ + return Unexpected(Error(msg)); \ + } \ + var = *std::move(MRDOCS_UNIQUE_NAME) +# define MRDOCS_TRY_GET_MACRO(_1, _2, _3, NAME, ...) NAME # define MRDOCS_TRY(...) \ - GET_MACRO(__VA_ARGS__, MRDOCS_TRY_VAR, MRDOCS_TRY_VOID)(__VA_ARGS__) + MRDOCS_TRY_GET_MACRO(__VA_ARGS__, MRDOCS_TRY_MSG, MRDOCS_TRY_VAR, MRDOCS_TRY_VOID)(__VA_ARGS__) + +/// Check existing expected-like type +# define MRDOCS_CHECK_VOID(var) \ + if (!detail::failed(var)) { \ + return Unexpected(detail::error(var)); \ + } \ + void(0) +# define MRDOCS_CHECK_MSG(var, msg) \ + if (detail::failed(var)) { \ + return Unexpected(Error(msg)); \ + } \ + void(0) +# define MRDOCS_CHECK_GET_MACRO(_1, _2, NAME, ...) NAME +# define MRDOCS_CHECK(...) \ + MRDOCS_CHECK_GET_MACRO(__VA_ARGS__, MRDOCS_CHECK_MSG, MRDOCS_CHECK_VOID)(__VA_ARGS__) #endif @@ -1854,6 +1926,36 @@ class Expected : unex_(std::move(u).error()), has_value_(false) { } +// The following constructors are extensions that allow +// Expected to be constructed directly from an error type +// if this is not ambiguous with the value type. This +// is not part of std::expected. MRDOCS_EXPECTED_FROM_ERROR +// can be defined to disable this behavior. +#ifndef MRDOCS_EXPECTED_FROM_ERROR + template + requires + std::is_constructible_v && + (!std::is_constructible_v) + constexpr + explicit(!std::is_convertible_v) + Expected(const G& u) + noexcept(std::is_nothrow_constructible_v) + : unex_(u) + , has_value_(false) + { } + + template + requires + std::is_constructible_v && + (!std::is_constructible_v) + constexpr + explicit(!std::is_convertible_v) + Expected(G&& u) + noexcept(std::is_nothrow_constructible_v) + : unex_(std::move(u)), has_value_(false) + { } +#endif + constexpr explicit Expected(std::in_place_t) noexcept : Expected() diff --git a/include/mrdocs/Support/Path.hpp b/include/mrdocs/Support/Path.hpp index 6690272f9..719b9eff9 100644 --- a/include/mrdocs/Support/Path.hpp +++ b/include/mrdocs/Support/Path.hpp @@ -175,6 +175,8 @@ isDirsy( @li Separators made uniform + @li Separators are replaced with the native separator + @return The normalized path. @param pathName The relative or absolute path. diff --git a/src/lib/Support/Path.cpp b/src/lib/Support/Path.cpp index 1a4585c4e..1d057f540 100644 --- a/src/lib/Support/Path.cpp +++ b/src/lib/Support/Path.cpp @@ -212,8 +212,8 @@ makeDirsy( namespace path = llvm::sys::path; std::string result = static_cast(pathName); - if( ! result.empty() && - ! path::is_separator( + if (!result.empty() && + !path::is_separator( result.back(), path::Style::windows_slash)) { diff --git a/src/tool/Addons.cpp b/src/tool/Addons.cpp index 57ff83f73..02a2e3a7a 100644 --- a/src/tool/Addons.cpp +++ b/src/tool/Addons.cpp @@ -17,59 +17,47 @@ namespace clang { namespace mrdocs { -Error +Expected setupAddonsDir( llvm::cl::opt& addonsDirArg, char const* argv0, void* addressOfMain) { - namespace fs = llvm::sys::fs; - using Process = llvm::sys::Process; - - std::string addonsDir; - - // Set addons dir - if(! addonsDirArg.getValue().empty()) + // Set addons dir from command line argument + if (!addonsDirArg.getValue().empty()) { - // from command line - auto absPath = files::makeAbsolute( - addonsDirArg.getValue()); - if(! absPath) - return absPath.error(); - addonsDir = files::makeDirsy(files::normalizePath(*absPath)); - if(auto err = files::requireDirectory(addonsDir)) - return err; + MRDOCS_TRY( + std::string absPath, + files::makeAbsolute(addonsDirArg.getValue())); + std::string addonsDir = files::makeDirsy( + files::normalizePath(absPath)); + MRDOCS_TRY(files::requireDirectory(addonsDir)); addonsDirArg.getValue() = addonsDir; + return {}; } - else - { - // check process working directory - addonsDir = fs::getMainExecutable(argv0, addressOfMain); - if(addonsDir.empty()) - return Error("getMainExecutable failed"); - addonsDir = files::makeDirsy(files::appendPath( - files::getParentDir(addonsDir), "addons")); - if(! files::requireDirectory(addonsDir).failed()) - { - // from directory containing the process executable - addonsDirArg.getValue() = addonsDir; - } - else - { - auto addonsEnvVar = Process::GetEnv("MRDOCS_ADDONS_DIR"); - if(! addonsEnvVar.has_value()) - return Error("no MRDOCS_ADDONS_DIR in environment"); - // from environment variable - addonsDir = files::makeDirsy(files::normalizePath(*addonsEnvVar)); - if(auto err = files::requireAbsolute(addonsDir)) - return err; - if(auto err = files::requireDirectory(addonsDir)) - return err; - addonsDirArg.getValue() = addonsDir; - } + // Set addons dir from process working directory + std::string addonsDir = llvm::sys::fs::getMainExecutable(argv0, addressOfMain); + MRDOCS_CHECK(addonsDir, "getMainExecutable failed"); + addonsDir = files::makeDirsy(files::appendPath( + files::getParentDir(addonsDir), "addons")); + Error err = files::requireDirectory(addonsDir); + if (!err.failed()) + { + addonsDirArg.getValue() = addonsDir; + return {}; } - return Error::success(); + + // Set addons dir from environment variable + MRDOCS_TRY( + std::string addonsEnvVar, + llvm::sys::Process::GetEnv("MRDOCS_ADDONS_DIR"), + "no MRDOCS_ADDONS_DIR in environment"); + addonsDir = files::makeDirsy(files::normalizePath(addonsEnvVar)); + MRDOCS_TRY(files::requireAbsolute(addonsDir)); + MRDOCS_TRY(files::requireDirectory(addonsDir)); + addonsDirArg.getValue() = addonsDir; + return {}; } } // mrdocs diff --git a/src/tool/Addons.hpp b/src/tool/Addons.hpp index 8070c7452..405b759bd 100644 --- a/src/tool/Addons.hpp +++ b/src/tool/Addons.hpp @@ -20,9 +20,11 @@ namespace mrdocs { /** Set the addons directory using the argument as a hint. + + @return The error if any occurred. */ -Error +Expected setupAddonsDir( llvm::cl::opt& addonsDirArg, char const* argv0, diff --git a/src/tool/GenerateAction.cpp b/src/tool/GenerateAction.cpp index 63e85b377..edfc8a061 100644 --- a/src/tool/GenerateAction.cpp +++ b/src/tool/GenerateAction.cpp @@ -22,83 +22,81 @@ namespace clang { namespace mrdocs { -Error +Expected DoGenerateAction() { - auto& generators = getGenerators(); - - ThreadPool threadPool(toolArgs.concurrency); - - // Calculate additional YAML settings from command line options. + // Get additional YAML settings from command line std::string extraYaml; { llvm::raw_string_ostream os(extraYaml); - if(toolArgs.ignoreMappingFailures.getValue()) + if (toolArgs.ignoreMappingFailures.getValue()) + { os << "ignore-failures: true\n"; + } } + // Load YAML configuration file std::shared_ptr config; + ThreadPool threadPool(toolArgs.concurrency); { - // Load configuration file - if(toolArgs.configPath.empty()) - return formatError("the config path argument is missing"); - auto configFile = loadConfigFile( + MRDOCS_CHECK(toolArgs.configPath, "The config path argument is missing"); + MRDOCS_TRY(auto configFile, loadConfigFile( toolArgs.configPath, toolArgs.addonsDir, extraYaml, nullptr, - threadPool); - if(! configFile) - return configFile.error(); - - config = std::move(configFile.value()); + threadPool)); + config = std::move(configFile); } - - // Create the generator - auto generator = generators.find(config->settings().generate); - if(! generator) - return formatError("the Generator \"{}\" was not found", - config->settings().generate); + // Get the generator + MRDOCS_TRY( + Generator const& generator, + getGenerators().find(config->settings().generate), + formatError( + "the Generator \"{}\" was not found", + config->settings().generate)); // Load the compilation database - if(toolArgs.inputPaths.empty()) - return formatError("the compilation database path argument is missing"); - if(toolArgs.inputPaths.size() > 1) - return formatError("got {} input paths where 1 was expected", toolArgs.inputPaths.size()); + MRDOCS_CHECK(toolArgs.inputPaths, "The compilation database path argument is missing"); + MRDOCS_CHECK(toolArgs.inputPaths.size() == 1, + formatError( + "got {} input paths where 1 was expected", + toolArgs.inputPaths.size())); auto compilationsPath = files::normalizePath(toolArgs.inputPaths.front()); std::string errorMessage; - auto jsonCompilations = tooling::JSONCompilationDatabase::loadFromFile( - compilationsPath, errorMessage, tooling::JSONCommandLineSyntax::AutoDetect); - if(! jsonCompilations) - return Error(std::move(errorMessage)); + MRDOCS_TRY_MSG( + auto& jsonCompilations, + tooling::JSONCompilationDatabase::loadFromFile( + compilationsPath, + errorMessage, + tooling::JSONCommandLineSyntax::AutoDetect), + std::move(errorMessage)); // Calculate the working directory - auto absPath = files::makeAbsolute(compilationsPath); - if(! absPath) - return absPath.error(); - auto workingDir = files::getParentDir(*absPath); + MRDOCS_TRY(auto absPath, files::makeAbsolute(compilationsPath)); + auto workingDir = files::getParentDir(absPath); - // normalize outputPath - if( toolArgs.outputPath.empty()) - return formatError("output path is empty"); + // Normalize outputPath + MRDOCS_CHECK(toolArgs.outputPath, "The output path argument is missing"); toolArgs.outputPath = files::normalizePath( files::makeAbsolute(toolArgs.outputPath, (*config)->workingDir)); // Convert relative paths to absolute AbsoluteCompilationDatabase compilations( - workingDir, *jsonCompilations, config); + workingDir, jsonCompilations, config); - // Run the tool, this can take a while - auto corpus = CorpusImpl::build( - report::Level::info, config, compilations); - if(! corpus) - return formatError("CorpusImpl::build returned \"{}\"", corpus.error()); + // Run the tool: this can take a while + MRDOCS_TRY( + auto corpus, + CorpusImpl::build( + report::Level::info, config, compilations)); - // Run the generator. + // Run the generator report::info("Generating docs\n"); - return generator->build(toolArgs.outputPath.getValue(), **corpus); + MRDOCS_TRY(generator.build(toolArgs.outputPath.getValue(), *corpus)); + return {}; } } // mrdocs diff --git a/src/tool/ToolMain.cpp b/src/tool/ToolMain.cpp index 5704e9df3..47e93ca04 100644 --- a/src/tool/ToolMain.cpp +++ b/src/tool/ToolMain.cpp @@ -19,7 +19,7 @@ #include #include #include -#include +#include extern int main(int argc, char const** argv); @@ -27,7 +27,7 @@ namespace clang { namespace mrdocs { extern int DoTestAction(); -extern Error DoGenerateAction(); +extern Expected DoGenerateAction(); void print_version(llvm::raw_ostream& os) @@ -38,29 +38,31 @@ print_version(llvm::raw_ostream& os) << "\n built with LLVM " << LLVM_VERSION_STRING; } -int mrdocs_main(int argc, char const** argv) +int +mrdocs_main(int argc, char const** argv) { - namespace fs = llvm::sys::fs; - - // VFALCO this heap checking is too strong for - // a clang tool's model of what is actually a leak. - // debugEnableHeapChecking(); - + // Enable stack traces llvm::EnablePrettyStackTrace(); llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); - llvm::cl::SetVersionPrinter(&print_version); + // Parse command line options + llvm::cl::SetVersionPrinter(&print_version); toolArgs.hideForeignOptions(); - if(! llvm::cl::ParseCommandLineOptions( + if (!llvm::cl::ParseCommandLineOptions( argc, argv, toolArgs.usageText)) + { return EXIT_FAILURE; + } // Apply reportLevel report::setMinimumLevel(report::getLevel( toolArgs.reportLevel.getValue())); - if(auto err = setupAddonsDir(toolArgs.addonsDir, - argv[0], reinterpret_cast(&main))) + // Set up addons directory + void* addressOfMain = reinterpret_cast(&main); + auto exp = setupAddonsDir( + toolArgs.addonsDir,argv[0], addressOfMain); + if (!exp) { report::error( "{}: \"{}\"\n" @@ -69,21 +71,27 @@ int mrdocs_main(int argc, char const** argv) "no valid addons location was specified on the command line, " "and no addons directory exists in the same directory as " "the executable.", - err, toolArgs.addonsDir); + exp.error(), toolArgs.addonsDir); return EXIT_FAILURE; } // Generate - if(auto err = DoGenerateAction()) - report::error("Generating reference failed: {}", err.message()); - - if( report::results.errorCount > 0 || + exp = DoGenerateAction(); + if (!exp) + { + report::error("Generating reference failed: {}", exp.error().message()); + } + if (report::results.errorCount > 0 || report::results.fatalCount > 0) + { return EXIT_FAILURE; + } return EXIT_SUCCESS; } -static void reportUnhandledException( +static +void +reportUnhandledException( std::exception const& ex) { namespace sys = llvm::sys; @@ -95,7 +103,8 @@ static void reportUnhandledException( } // mrdocs } // clang -int main(int argc, char const** argv) +int +main(int argc, char const** argv) { try { @@ -103,7 +112,7 @@ int main(int argc, char const** argv) } catch(clang::mrdocs::Exception const& ex) { - // thrown Exception should never get here. + // Thrown Exception should never get here. clang::mrdocs::reportUnhandledException(ex); } catch(std::exception const& ex)