diff --git a/include/vcpkg/base/diagnostics.h b/include/vcpkg/base/diagnostics.h new file mode 100644 index 0000000000..666de6eaa6 --- /dev/null +++ b/include/vcpkg/base/diagnostics.h @@ -0,0 +1,158 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace vcpkg +{ + enum class DiagKind + { + None, // foo.h: localized + Message, // foo.h: message: localized + Error, // foo.h: error: localized + Warning, // foo.h: warning: localized + Note // foo.h: note: localized + }; + + struct TextPosition + { + // '0' indicates uninitialized; '1' is the first row/column + int row = 0; + int column = 0; + }; + + struct DiagnosticLine + { + DiagnosticLine(DiagKind kind, LocalizedString&& message) + : m_kind(kind), m_origin(), m_position(), m_message(std::move(message)) + { + } + + DiagnosticLine(DiagKind kind, StringView origin, LocalizedString&& message) + : m_kind(kind), m_origin(origin.to_string()), m_position(), m_message(std::move(message)) + { + } + + DiagnosticLine(DiagKind kind, StringView origin, TextPosition position, LocalizedString&& message) + : m_kind(kind), m_origin(origin.to_string()), m_position(position), m_message(std::move(message)) + { + } + + // Prints this diagnostic to the terminal. + // Not thread safe: The console DiagnosticContext must apply its own synchronization. + void print(MessageSink& sink) const; + // Converts this message into a string + // Prefer print() if possible because it applies color + std::string to_string() const; + void to_string(std::string& target) const; + + private: + DiagKind m_kind; + Optional m_origin; + TextPosition m_position; + LocalizedString m_message; + }; + + struct DiagnosticContext + { + // Records a diagnostic. Implementations must make simultaneous calls of report() safe from multiple threads + // and print entire DiagnosticLines as atomic units. Implementations are not required to synchronize with + // other machinery like msg::print and friends. + // + // This serves to make multithreaded code that reports only via this mechanism safe. + virtual void report(const DiagnosticLine& line) = 0; + virtual void report(DiagnosticLine&& line) { report(line); } + + protected: + ~DiagnosticContext() = default; + }; + + struct BufferedDiagnosticContext final : DiagnosticContext + { + virtual void report(const DiagnosticLine& line) override; + virtual void report(DiagnosticLine&& line) override; + + std::vector lines; + + // Prints all diagnostics to the terminal. + // Not safe to use in the face of concurrent calls to report() + void print(MessageSink& sink) const; + // Converts this message into a string + // Prefer print() if possible because it applies color + // Not safe to use in the face of concurrent calls to report() + std::string to_string() const; + void to_string(std::string& target) const; + + private: + std::mutex m_mtx; + }; + + // If T Ty is an rvalue Optional, typename UnwrapOptional::type is the type necessary to forward U + // Otherwise, there is no member UnwrapOptional::type + template + struct UnwrapOptional + { + // no member ::type, SFINAEs out when the input type is: + // * not Optional + // * not an rvalue + // * volatile + }; + + template + struct UnwrapOptional> + { + // prvalue + using type = Wrapped; + using fwd = Wrapped&&; + }; + + template + struct UnwrapOptional> + { + // const prvalue + using type = Wrapped; + using fwd = const Wrapped&&; + }; + + template + struct UnwrapOptional&&> + { + // xvalue + using type = Wrapped&&; + using fwd = Wrapped&&; + }; + + template + struct UnwrapOptional&&> + { + // const xvalue + using type = const Wrapped&&; + using fwd = Wrapped&&; + }; + + template + auto adapt_context_to_expected(Fn functor, Args&&... args) + -> ExpectedL>::type> + { + using Contained = typename UnwrapOptional>::type; + BufferedDiagnosticContext bdc; + auto maybe_result = functor(bdc, std::forward(args)...); + if (auto result = maybe_result.get()) + { + // N.B.: This may be a move + return ExpectedL{ + static_cast< + typename UnwrapOptional>::fwd>( + *result), + expected_left_tag}; + } + + return ExpectedL{LocalizedString::from_raw(bdc.to_string()), expected_right_tag}; + } +} diff --git a/include/vcpkg/base/parse.h b/include/vcpkg/base/parse.h index bbdd7097f3..87ca180417 100644 --- a/include/vcpkg/base/parse.h +++ b/include/vcpkg/base/parse.h @@ -2,13 +2,12 @@ #include +#include #include #include #include #include -#include - #include #include @@ -75,7 +74,7 @@ namespace vcpkg struct ParserBase { - ParserBase(StringView text, StringView origin, TextRowCol init_rowcol = {}); + ParserBase(StringView text, StringView origin, TextPosition init_rowcol = {1, 1}); static constexpr bool is_whitespace(char32_t ch) { return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; } static constexpr bool is_lower_alpha(char32_t ch) { return ch >= 'a' && ch <= 'z'; } @@ -131,7 +130,7 @@ namespace vcpkg Unicode::Utf8Decoder it() const { return m_it; } char32_t cur() const { return m_it == m_it.end() ? Unicode::end_of_file : *m_it; } SourceLoc cur_loc() const { return {m_it, m_start_of_line, m_row, m_column}; } - TextRowCol cur_rowcol() const { return {m_row, m_column}; } + TextPosition cur_rowcol() const { return {m_row, m_column}; } char32_t next(); bool at_eof() const { return m_it == m_it.end(); } diff --git a/include/vcpkg/paragraphparser.h b/include/vcpkg/paragraphparser.h index 96c188940d..0ea11c71bc 100644 --- a/include/vcpkg/paragraphparser.h +++ b/include/vcpkg/paragraphparser.h @@ -2,12 +2,12 @@ #include +#include #include #include #include #include -#include #include #include @@ -16,7 +16,7 @@ namespace vcpkg { - using Paragraph = std::map, std::less<>>; + using Paragraph = std::map, std::less<>>; struct ParagraphParser { @@ -28,9 +28,9 @@ namespace vcpkg std::string required_field(StringLiteral fieldname); std::string optional_field(StringLiteral fieldname); - std::string optional_field(StringLiteral fieldname, TextRowCol& position); + std::string optional_field(StringLiteral fieldname, TextPosition& position); - void add_error(TextRowCol position, msg::MessageT<> error_content); + void add_error(TextPosition position, msg::MessageT<> error_content); Optional error() const; @@ -42,8 +42,8 @@ namespace vcpkg ExpectedL> parse_default_features_list(const std::string& str, StringView origin = "", - TextRowCol textrowcol = {}); + TextPosition position = {1, 1}); ExpectedL> parse_qualified_specifier_list(const std::string& str, StringView origin = "", - TextRowCol textrowcol = {}); + TextPosition position = {1, 1}); } diff --git a/include/vcpkg/sourceparagraph.h b/include/vcpkg/sourceparagraph.h index 45bb97e118..80206f6d10 100644 --- a/include/vcpkg/sourceparagraph.h +++ b/include/vcpkg/sourceparagraph.h @@ -223,5 +223,5 @@ namespace vcpkg // Exposed for testing ExpectedL> parse_dependencies_list(const std::string& str, StringView origin, - TextRowCol textrowcol = {}); + TextPosition position = {1, 1}); } diff --git a/include/vcpkg/textrowcol.h b/include/vcpkg/textrowcol.h deleted file mode 100644 index d7e216d852..0000000000 --- a/include/vcpkg/textrowcol.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -namespace vcpkg -{ - struct TextRowCol - { - constexpr TextRowCol() noexcept = default; - constexpr TextRowCol(int row, int column) noexcept : row(row), column(column) { } - /// '0' indicates uninitialized; '1' is the first row. - int row = 0; - /// '0' indicates uninitialized; '1' is the first column. - int column = 0; - - constexpr int row_or(int def) const noexcept { return row ? row : def; } - constexpr int column_or(int def) const noexcept { return column ? column : def; } - }; -} diff --git a/src/vcpkg-test/messages.cpp b/src/vcpkg-test/messages.cpp index 842b354e08..18c3db24d2 100644 --- a/src/vcpkg-test/messages.cpp +++ b/src/vcpkg-test/messages.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -99,3 +100,84 @@ TEST_CASE ("generate message get_format_arg_mismatches", "[messages]") CHECK(res.arguments_without_comment == std::vector{"go", "ho"}); CHECK(res.comments_without_argument == std::vector{"blah"}); } + +namespace +{ + struct OnlyMoveOnce + { + bool& m_moved; + + explicit OnlyMoveOnce(bool& moved) : m_moved(moved) { } + OnlyMoveOnce(const OnlyMoveOnce&) = delete; + OnlyMoveOnce(OnlyMoveOnce&& other) : m_moved(other.m_moved) + { + REQUIRE(!m_moved); + m_moved = true; + } + + OnlyMoveOnce& operator=(const OnlyMoveOnce&) = delete; + OnlyMoveOnce& operator=(OnlyMoveOnce&&) = delete; + }; + + int returns_int(DiagnosticContext&) { return 42; } + std::unique_ptr returns_unique_ptr(DiagnosticContext&) { return std::unique_ptr{new int{42}}; } + Optional returns_optional_prvalue(DiagnosticContext&, int val) { return val; } + const Optional returns_optional_const_prvalue(DiagnosticContext&, int val) { return val; } + Optional&& returns_optional_xvalue(DiagnosticContext&, Optional&& val) { return std::move(val); } + const Optional&& returns_optional_const_xvalue(DiagnosticContext&, Optional&& val) + { + return std::move(val); + } + Optional returns_optional_prvalue_fail(DiagnosticContext& context) + { + context.report(DiagnosticLine{DiagKind::Error, LocalizedString::from_raw("something bad happened")}); + return nullopt; + } + const Optional returns_optional_const_prvalue_fail(DiagnosticContext& context) + { + context.report(DiagnosticLine{DiagKind::Error, LocalizedString::from_raw("something bad happened")}); + return nullopt; + } + Optional&& returns_optional_xvalue_fail(DiagnosticContext& context, Optional&& val) + { + val.clear(); + context.report(DiagnosticLine{DiagKind::Error, LocalizedString::from_raw("something bad happened")}); + return std::move(val); + } + + const Optional&& returns_optional_const_xvalue_fail(DiagnosticContext& context, Optional&& val) + { + val.clear(); + context.report(DiagnosticLine{DiagKind::Error, LocalizedString::from_raw("something bad happened")}); + return std::move(val); + } +} // unnamed namespace + +TEST_CASE ("adapt DiagnosticContext to ExpectedL", "[diagnostics]") +{ + // adapt_context_to_expected(returns_int); // should not compile + // adapt_context_to_expected(returns_unique_ptr); // should not compile + + static_assert(std::is_same_v, decltype(adapt_context_to_expected(returns_optional_prvalue, 42))>, + "boom"); + static_assert( + std::is_same_v, decltype(adapt_context_to_expected(returns_optional_const_prvalue, 42))>, + "boom"); + static_assert( + std::is_same_v, + decltype(adapt_context_to_expected(returns_optional_xvalue, std::declval>()))>, + "boom"); + static_assert(std::is_same_v, + decltype(adapt_context_to_expected(returns_optional_const_xvalue, + std::declval>()))>, + "boom"); + { + auto adapted = adapt_context_to_expected(returns_optional_prvalue, 42); + REQUIRE(adapted.value_or_exit(VCPKG_LINE_INFO) == 42); + } + { + auto adapted = adapt_context_to_expected(returns_optional_prvalue_fail); + REQUIRE(!adapted.has_value()); + REQUIRE(adapted.error() == LocalizedString::from_raw("error: something bad happened")); + } +} diff --git a/src/vcpkg-test/paragraph.cpp b/src/vcpkg-test/paragraph.cpp index 6c74c1d7c1..3f29fb14c6 100644 --- a/src/vcpkg-test/paragraph.cpp +++ b/src/vcpkg-test/paragraph.cpp @@ -15,7 +15,7 @@ namespace { pghs.emplace_back(); for (auto&& kv : p) - pghs.back().emplace(kv.first, std::make_pair(kv.second, vcpkg::TextRowCol{})); + pghs.back().emplace(kv.first, std::make_pair(kv.second, vcpkg::TextPosition{})); } return vcpkg::SourceControlFile::parse_control_file("", std::move(pghs)); } @@ -24,7 +24,7 @@ namespace { Paragraph pgh; for (auto&& kv : v) - pgh.emplace(kv.first, std::make_pair(kv.second, vcpkg::TextRowCol{})); + pgh.emplace(kv.first, std::make_pair(kv.second, vcpkg::TextPosition{})); return vcpkg::BinaryParagraph("test", std::move(pgh)); } diff --git a/src/vcpkg-test/util.cpp b/src/vcpkg-test/util.cpp index e86e2bf06b..1c414bde5f 100644 --- a/src/vcpkg-test/util.cpp +++ b/src/vcpkg-test/util.cpp @@ -63,7 +63,7 @@ namespace vcpkg::Test pghs.emplace_back(); for (auto&& kv : p) { - pghs.back().emplace(kv.first, std::make_pair(kv.second, vcpkg::TextRowCol{})); + pghs.back().emplace(kv.first, std::make_pair(kv.second, vcpkg::TextPosition{})); } } return vcpkg::SourceControlFile::parse_control_file("", std::move(pghs)); diff --git a/src/vcpkg/base/diagnostics.cpp b/src/vcpkg/base/diagnostics.cpp new file mode 100644 index 0000000000..7bf4a92d5a --- /dev/null +++ b/src/vcpkg/base/diagnostics.cpp @@ -0,0 +1,58 @@ +#include +#include + +using namespace vcpkg; + +namespace vcpkg +{ + void DiagnosticLine::print(MessageSink& sink) const { sink.println(LocalizedString::from_raw("hello")); } + std::string DiagnosticLine::to_string() const + { + std::string result; + this->to_string(result); + return result; + } + void DiagnosticLine::to_string(std::string& target) const { target.append("hello"); } + + void BufferedDiagnosticContext::report(const DiagnosticLine& line) + { + std::lock_guard lck{m_mtx}; + lines.push_back(line); + } + + void BufferedDiagnosticContext::report(DiagnosticLine&& line) + { + std::lock_guard lck{m_mtx}; + lines.push_back(std::move(line)); + } + void BufferedDiagnosticContext::print(MessageSink& sink) const { sink.println(LocalizedString::from_raw("hello")); } + // Converts this message into a string + // Prefer print() if possible because it applies color + // Not safe to use in the face of concurrent calls to report() + std::string BufferedDiagnosticContext::to_string() const + { + std::string result; + this->to_string(result); + return result; + } + void BufferedDiagnosticContext::to_string(std::string& target) const + { + auto first = lines.begin(); + const auto last = lines.end(); + if (first == last) + { + return; + } + + for (;;) + { + first->to_string(target); + if (++first == last) + { + return; + } + + target.push_back('\n'); + } + } +} diff --git a/src/vcpkg/base/parse.cpp b/src/vcpkg/base/parse.cpp index bdadabf937..7f40e34e4f 100644 --- a/src/vcpkg/base/parse.cpp +++ b/src/vcpkg/base/parse.cpp @@ -90,11 +90,11 @@ namespace vcpkg } } - ParserBase::ParserBase(StringView text, StringView origin, TextRowCol init_rowcol) + ParserBase::ParserBase(StringView text, StringView origin, TextPosition init_rowcol) : m_it(text.begin(), text.end()) , m_start_of_line(m_it) - , m_row(init_rowcol.row_or(1)) - , m_column(init_rowcol.column_or(1)) + , m_row(init_rowcol.row) + , m_column(init_rowcol.column) , m_text(text) , m_origin(origin) { diff --git a/src/vcpkg/binaryparagraph.cpp b/src/vcpkg/binaryparagraph.cpp index 5ac0d7a5b0..12757105e9 100644 --- a/src/vcpkg/binaryparagraph.cpp +++ b/src/vcpkg/binaryparagraph.cpp @@ -35,7 +35,7 @@ namespace vcpkg // one or the other this->version.text = parser.optional_field(Fields::VERSION); - TextRowCol pv_position; + TextPosition pv_position; auto pv_str = parser.optional_field(Fields::PORT_VERSION, pv_position); this->version.port_version = 0; if (!pv_str.empty()) diff --git a/src/vcpkg/paragraphs.cpp b/src/vcpkg/paragraphs.cpp index a8826fc736..697e1822aa 100644 --- a/src/vcpkg/paragraphs.cpp +++ b/src/vcpkg/paragraphs.cpp @@ -20,7 +20,7 @@ static std::atomic g_load_ports_stats(0); namespace vcpkg { - static Optional> remove_field(Paragraph* fields, StringView fieldname) + static Optional> remove_field(Paragraph* fields, StringView fieldname) { auto it = fields->find(fieldname.to_string()); if (it == fields->end()) @@ -33,7 +33,7 @@ namespace vcpkg return value; } - std::string ParagraphParser::optional_field(StringLiteral fieldname, TextRowCol& position) + std::string ParagraphParser::optional_field(StringLiteral fieldname, TextPosition& position) { auto maybe_field = remove_field(&fields, fieldname); if (auto field = maybe_field.get()) @@ -47,7 +47,7 @@ namespace vcpkg std::string ParagraphParser::optional_field(StringLiteral fieldname) { - TextRowCol ignore; + TextPosition ignore; return optional_field(fieldname, ignore); } @@ -66,7 +66,7 @@ namespace vcpkg return std::string(); } - void ParagraphParser::add_error(TextRowCol position, msg::MessageT<> error_content) + void ParagraphParser::add_error(TextPosition position, msg::MessageT<> error_content) { errors.emplace_back(LocalizedString::from_raw(origin) .append_raw(fmt::format("{}:{}: ", position.row, position.column)) @@ -167,18 +167,18 @@ namespace vcpkg ExpectedL> parse_default_features_list(const std::string& str, StringView origin, - TextRowCol textrowcol) + TextPosition position) { - auto parser = ParserBase(str, origin, textrowcol); + auto parser = ParserBase(str, origin, position); auto opt = parse_list_until_eof(msgExpectedDefaultFeaturesList, parser, &parse_feature_name); if (!opt) return {LocalizedString::from_raw(parser.get_error()->to_string()), expected_right_tag}; return {std::move(opt).value_or_exit(VCPKG_LINE_INFO), expected_left_tag}; } ExpectedL> parse_qualified_specifier_list(const std::string& str, StringView origin, - TextRowCol textrowcol) + TextPosition position) { - auto parser = ParserBase(str, origin, textrowcol); + auto parser = ParserBase(str, origin, position); auto opt = parse_list_until_eof( msgExpectedDependenciesList, parser, [](ParserBase& parser) { return parse_qualified_specifier(parser); }); if (!opt) return {LocalizedString::from_raw(parser.get_error()->to_string()), expected_right_tag}; @@ -187,9 +187,9 @@ namespace vcpkg } ExpectedL> parse_dependencies_list(const std::string& str, StringView origin, - TextRowCol textrowcol) + TextPosition position) { - auto parser = ParserBase(str, origin, textrowcol); + auto parser = ParserBase(str, origin, position); auto opt = parse_list_until_eof(msgExpectedDependenciesList, parser, [](ParserBase& parser) { auto loc = parser.cur_loc(); return parse_qualified_specifier(parser).then([&](ParsedQualifiedSpecifier&& pqs) -> Optional { diff --git a/src/vcpkg/sourceparagraph.cpp b/src/vcpkg/sourceparagraph.cpp index e5761523ab..fdf0c92a67 100644 --- a/src/vcpkg/sourceparagraph.cpp +++ b/src/vcpkg/sourceparagraph.cpp @@ -300,7 +300,7 @@ namespace vcpkg spgh->name = parser.required_field(SourceParagraphFields::NAME); spgh->version.text = parser.required_field(SourceParagraphFields::VERSION); - TextRowCol pv_position; + TextPosition pv_position; auto pv_str = parser.optional_field(SourceParagraphFields::PORT_VERSION, pv_position); if (!pv_str.empty()) { @@ -322,10 +322,10 @@ namespace vcpkg trim_all(spgh->maintainers); spgh->homepage = parser.optional_field(SourceParagraphFields::HOMEPAGE); - TextRowCol textrowcol; - std::string buf = parser.optional_field(SourceParagraphFields::BUILD_DEPENDS, textrowcol); + TextPosition build_depends_position; + std::string buf = parser.optional_field(SourceParagraphFields::BUILD_DEPENDS, build_depends_position); - auto maybe_dependencies = parse_dependencies_list(buf, origin, textrowcol); + auto maybe_dependencies = parse_dependencies_list(buf, origin, build_depends_position); if (const auto dependencies = maybe_dependencies.get()) { spgh->dependencies = *dependencies; @@ -335,9 +335,10 @@ namespace vcpkg return std::move(maybe_dependencies).error(); } - buf = parser.optional_field(SourceParagraphFields::DEFAULT_FEATURES, textrowcol); + TextPosition default_features_position; + buf = parser.optional_field(SourceParagraphFields::DEFAULT_FEATURES, default_features_position); - auto maybe_default_features = parse_default_features_list(buf, origin, textrowcol); + auto maybe_default_features = parse_default_features_list(buf, origin, default_features_position); if (const auto default_features = maybe_default_features.get()) { for (auto&& default_feature : *default_features) @@ -368,7 +369,7 @@ namespace vcpkg return std::move(maybe_default_features).error(); } - TextRowCol supports_position; + TextPosition supports_position; auto supports_text = parser.optional_field(SourceParagraphFields::SUPPORTS, supports_position); if (!supports_text.empty()) {