[M1] add util::Error, Result<T>, format()

This commit is contained in:
2026-05-08 00:00:21 +00:00
parent 6e922b7249
commit fba725e192
8 changed files with 183 additions and 2 deletions

View File

@@ -12,3 +12,10 @@ All notable changes to cargoxx will be documented in this file.
builds an empty `cargoxx` binary.
- `.clang-format` (LLVM, 100-column) and `.gitignore`.
- `SPEC.md`, `TECH_SPEC.md`.
- M1 foundation in `cargoxx.util`: `ErrorCode` (numbers per `TECH_SPEC.md` §4),
`Error`, `Result<T> = std::expected<T, Error>`, and `format(Error)` rendering
`error[Ennnn]: ...` with optional `--> path:line:col` and `hint:` lines.
`format` lives in `src/util/error.cpp` as a module implementation unit.
- `Catch2 v3` wired through `flake.nix` (libc++-built override) and registered
in CMake; `tests/util_error.cpp` covers six format cases via
`catch_discover_tests`.

View File

@@ -13,7 +13,7 @@ project(cargoxx LANGUAGES CXX VERSION 0.1.0)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_EXTENSIONS ON)
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
@@ -29,9 +29,11 @@ if(CARGOXX_WERROR)
add_compile_options(-Werror)
endif()
# ----- cargoxx library: all module units -----
# ----- cargoxx library: module units + implementation units -----
add_library(cargoxx STATIC)
target_sources(cargoxx
PRIVATE
src/util/error.cpp
PUBLIC
FILE_SET CXX_MODULES FILES
src/lib.cppm
@@ -50,3 +52,10 @@ target_sources(cargoxx
add_executable(cargoxx_bin src/main.cpp)
set_target_properties(cargoxx_bin PROPERTIES OUTPUT_NAME cargoxx)
target_link_libraries(cargoxx_bin PRIVATE cargoxx)
# ----- tests -----
option(CARGOXX_BUILD_TESTS "Build cargoxx tests" ON)
if(CARGOXX_BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()

View File

@@ -11,6 +11,11 @@
let
pkgs = import nixpkgs { inherit system; };
llvmPkgs = pkgs.llvmPackages;
libcxxPkgs = {
catch2_3 = pkgs.catch2_3.override {
stdenv = llvmPkgs.libcxxStdenv;
};
};
in {
devShells.default = llvmPkgs.libcxxStdenv.mkDerivation {
name = "cargoxx-dev";
@@ -25,6 +30,7 @@
buildInputs = [
pkgs.sqlite
pkgs.reproc
libcxxPkgs.catch2_3
];
env.NIX_CFLAGS_COMPILE = toString [
"-stdlib=libc++"

View File

@@ -1,5 +1,7 @@
import cargoxx;
import std;
int main(int /*argc*/, char** /*argv*/) {
std::println("Hello, cargoxx!");
return 0;
}

29
src/util/error.cpp Normal file
View File

@@ -0,0 +1,29 @@
module cargoxx.util;
import std;
namespace cargoxx::util {
auto format(const Error& e) -> std::string {
std::string out;
out += std::format("error[E{:04}]: {}\n", static_cast<int>(e.code), e.message);
if (e.location) {
if (e.line_col) {
out += std::format(" --> {}:{}:{}\n",
e.location->string(),
e.line_col->first,
e.line_col->second);
} else {
out += std::format(" --> {}\n", e.location->string());
}
}
if (!e.hint.empty()) {
out += std::format(" hint: {}\n", e.hint);
}
return out;
}
} // namespace cargoxx::util

View File

@@ -1 +1,50 @@
export module cargoxx.util;
import std;
export namespace cargoxx::util {
// Error code numbering follows TECH_SPEC.md §4.
enum class ErrorCode {
ManifestNotFound = 1,
ManifestParseError,
ManifestInvalidField,
ManifestUnknownField,
ManifestVersionInvalid,
LayoutNoTarget = 20,
LayoutAmbiguousLib,
LayoutInvalidName,
ResolutionUnknownPackage = 40,
ResolutionNetworkError,
ResolutionUnsatisfiable,
ResolutionVersionNotFound,
LinkdbUnknownPackage = 60,
LinkdbCorrupt,
LinkdbComponentNotSupported,
ExecCommandFailed = 80,
ExecToolNotFound,
BuildCmakeFailed,
BuildNixFailed,
Internal = 100,
NotImplemented,
};
struct Error {
ErrorCode code;
std::string message;
std::string hint;
std::optional<std::filesystem::path> location;
std::optional<std::pair<int, int>> line_col;
};
template <typename T>
using Result = std::expected<T, Error>;
auto format(const Error& e) -> std::string;
} // namespace cargoxx::util

10
tests/CMakeLists.txt Normal file
View File

@@ -0,0 +1,10 @@
find_package(Catch2 3 REQUIRED CONFIG)
include(Catch)
function(cargoxx_add_test name)
add_executable(${name} ${name}.cpp)
target_link_libraries(${name} PRIVATE cargoxx Catch2::Catch2WithMain)
catch_discover_tests(${name})
endfunction()
cargoxx_add_test(util_error)

69
tests/util_error.cpp Normal file
View File

@@ -0,0 +1,69 @@
#include <catch2/catch_test_macros.hpp>
import cargoxx.util;
import std;
using cargoxx::util::Error;
using cargoxx::util::ErrorCode;
using cargoxx::util::format;
TEST_CASE("format renders code, message, and trailing newline", "[util][format]") {
Error e{ErrorCode::ManifestNotFound, "manifest not found", "", std::nullopt, std::nullopt};
REQUIRE(format(e) == "error[E0001]: manifest not found\n");
}
TEST_CASE("format pads error codes to four digits", "[util][format]") {
Error e{ErrorCode::Internal, "boom", "", std::nullopt, std::nullopt};
REQUIRE(format(e) == "error[E0100]: boom\n");
}
TEST_CASE("format includes location with line:col when present", "[util][format]") {
Error e{
ErrorCode::ManifestParseError,
"syntax error",
"",
std::filesystem::path{"Cargoxx.toml"},
std::pair{7, 1},
};
REQUIRE(format(e) == "error[E0002]: syntax error\n"
" --> Cargoxx.toml:7:1\n");
}
TEST_CASE("format includes location without line:col when only path is set", "[util][format]") {
Error e{
ErrorCode::LayoutNoTarget,
"no target found",
"",
std::filesystem::path{"./"},
std::nullopt,
};
REQUIRE(format(e) == "error[E0020]: no target found\n"
" --> ./\n");
}
TEST_CASE("format appends a hint line when hint is non-empty", "[util][format]") {
Error e{
ErrorCode::LayoutNoTarget,
"no target found",
"run `cargoxx new --lib <name>` to create a library project",
std::nullopt,
std::nullopt,
};
REQUIRE(format(e) ==
"error[E0020]: no target found\n"
" hint: run `cargoxx new --lib <name>` to create a library project\n");
}
TEST_CASE("format combines location and hint", "[util][format]") {
Error e{
ErrorCode::LinkdbUnknownPackage,
"package not in link database",
"file an issue or supply a manual recipe",
std::filesystem::path{"Cargoxx.toml"},
std::pair{7, 1},
};
REQUIRE(format(e) ==
"error[E0060]: package not in link database\n"
" --> Cargoxx.toml:7:1\n"
" hint: file an issue or supply a manual recipe\n");
}