From 1e4a744398bdaf13ce51391d9a35e73b591ecb32 Mon Sep 17 00:00:00 2001 From: Andrii Semkiv Date: Thu, 17 Oct 2024 12:01:51 +0200 Subject: [PATCH] Debugger: tl::expected and Utils::Result dumpers Added dumpers for `tl::expected` and `Utils::Result` utility classes. Added a basic test. Useless `enum` prefix from CDB enum types (e.g. `enum MyEnum`) will be discarded in tests (credits to @hjk). Note that the include path for the newly added test executable is based on `__FILE__` macro value so it might easily break if the sources are reorganized. Also creating a test for `Utils::Result` is practically impossible within current setup as it is not a header only class, so the test binary must actually link against the correct version of Utils library target. Fixes: QTCREATORBUG-31795 Change-Id: I7f9ccb92d0c59333a2dca4ba928ac991f1e5238b Reviewed-by: hjk --- share/qtcreator/debugger/creatortypes.py | 12 ++++ share/qtcreator/debugger/dumper.py | 6 +- share/qtcreator/debugger/misctypes.py | 34 ++++++++- tests/auto/debugger/tst_dumpers.cpp | 88 ++++++++++++++++++++++-- 4 files changed, 131 insertions(+), 9 deletions(-) diff --git a/share/qtcreator/debugger/creatortypes.py b/share/qtcreator/debugger/creatortypes.py index fe78253c039..968eb26a51c 100644 --- a/share/qtcreator/debugger/creatortypes.py +++ b/share/qtcreator/debugger/creatortypes.py @@ -369,3 +369,15 @@ def qdump__QmakeProjectManager__QmakePriFileNode(d, value): def qdump__QmakeProjectManager__QmakeProFileNode(d, value): qdump__ProjectExplorer__FolderNode(d, value) + + +def qdump__Utils__Result(d, value): + error, _pad, has_err = value.split('{QString}@b') + if has_err: + d.putExpandable() + d.putValue('Error') + if d.isExpanded(): + with Children(d): + d.putSubItem('message', error) + else: + d.putValue('Ok') diff --git a/share/qtcreator/debugger/dumper.py b/share/qtcreator/debugger/dumper.py index d4241160db6..ffd48af5f95 100644 --- a/share/qtcreator/debugger/dumper.py +++ b/share/qtcreator/debugger/dumper.py @@ -4052,7 +4052,7 @@ typename)) #self.warn('SEARCHING FOR MEMBER: %s IN %s' % (name, value.type.name)) members = self.value_members(value, True) #self.warn('MEMBERS: %s' % ', '.join(str(m.name) for m in members)) - base = None + bases = [] for member in members: #self.warn('CHECKING FIELD %s' % member.name) if member.type.code == TypeCode.Typedef: @@ -4061,9 +4061,9 @@ typename)) #self.warn('FOUND MEMBER 1: %s IN %s' % (name, value.type.name)) return member if member.isBaseClass: - base = member + bases.append(member) if self.isCdb: - if base is not None: + for base in bases: # self.warn("CHECKING BASE CLASS '%s' for '%s'" % (base.type.name, name)) res = self.value_member_by_name(base, name) if res is not None: diff --git a/share/qtcreator/debugger/misctypes.py b/share/qtcreator/debugger/misctypes.py index 9099f19a2f7..a07feea4319 100644 --- a/share/qtcreator/debugger/misctypes.py +++ b/share/qtcreator/debugger/misctypes.py @@ -1,7 +1,7 @@ # Copyright (C) 2016 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -from dumper import Children, SubItem +from dumper import Children, SubItem, DumperBase from utils import TypeCode, DisplayFormat import re @@ -586,3 +586,35 @@ def qdump__QtcDumperTest_String(d, value): second = d.hexdecode(d.putSubItem('second', value['second']).value) third = d.hexdecode(d.putSubItem('third', value['third']).value)[:-1] d.putValue(first + ', ' + second + ', ' + third) + + +def qdump__tl__expected(d: DumperBase, value: DumperBase.Value): + # There are issues with correct type handling for enums and pointer in CDB + # preventing type cast from working as expected in these cases + + has_value = False + val = {} + + try: + has_value = value["m_has_val"].integer() != 0 + val = value["m_val"] if has_value else value["m_unexpect"]["m_val"] + except: + okType = value.type[0] + errType = value.type[1] + + # Result and error (and a initialized flag) are packed into a union storage + largerType = max(okType, errType, key=lambda t: t.size()) + storage, has_value = value.split(f'{{{largerType.name}}}b') + val = storage.cast(okType.name) if has_value else storage.cast(errType.name) + + if has_value: + if val.type.name != 'void': + d.putExpandable() + d.putValue('Expected') + else: + d.putExpandable() + d.putValue('Unexpected') + + if d.isExpanded(): + with Children(d): + d.putSubItem('inner', val) diff --git a/tests/auto/debugger/tst_dumpers.cpp b/tests/auto/debugger/tst_dumpers.cpp index 02bed53c5a4..bf110533899 100644 --- a/tests/auto/debugger/tst_dumpers.cpp +++ b/tests/auto/debugger/tst_dumpers.cpp @@ -411,6 +411,7 @@ struct Type } } QString actualType = simplifyType(actualType0); + actualType.replace(QRegularExpression("\\benum\\b"), ""); actualType.replace(' ', ""); actualType.replace("const", ""); QString expectedType; @@ -418,6 +419,7 @@ struct Type expectedType = type; else expectedType = aliasName; + expectedType.replace(QRegularExpression("\\benum\\b"), ""); expectedType.replace(' ', ""); expectedType.replace("const", ""); expectedType.replace('@', context.nameSpace); @@ -770,14 +772,16 @@ struct XmlProfile {}; struct NimProfile {}; struct BigArrayProfile {}; +struct InternalProfile +{}; class Data { public: Data() {} - Data(const QString &includes, const QString &code, const QString &unused) - : includes(includes), code(code), unused(unused) + Data(const QString &preamble, const QString &code, const QString &unused) + : preamble(preamble), code(code), unused(unused) {} const Data &operator+(const Check &check) const @@ -824,7 +828,7 @@ public: const Data &operator+(const Profile &profile) const { profileExtra += QString::fromUtf8(profile.contents); - includes += QString::fromUtf8(profile.includes); + preamble += QString::fromUtf8(profile.includes); return *this; } @@ -1059,6 +1063,13 @@ public: return *this; } + const Data &operator+(InternalProfile) const + { + const auto parentDir = Utils::FilePath::fromUserInput(__FILE__).parentDir().path(); + profileExtra += "INCLUDEPATH += " + parentDir + "/../../../src/libs/3rdparty\n"; + return *this; + } + const Data &operator+(const ForceC &) const { language = Language::C; @@ -1108,7 +1119,7 @@ public: mutable QString dumperOptions; mutable QString profileExtra; mutable QString cmakelistsExtra; - mutable QString includes; + mutable QString preamble; mutable QString code; mutable QString unused; @@ -1594,7 +1605,7 @@ void tst_Dumpers::dumper() "\n}\n" "\n#endif" "\n" - "\n\n" + data.includes + + "\n\n" + data.preamble + "\n\n" + (data.useQHash ? "\n#include " "\n#include " @@ -8563,6 +8574,73 @@ void tst_Dumpers::dumper_data() + Check("dir.entryInfoList.1", "[1]", quoted(tempDir + "/.."), "@QFileInfo") % NoCdbEngine + Check("dir.entryList.0", "[0]", "\".\"", "@QString") % NoCdbEngine + Check("dir.entryList.1", "[1]", "\"..\"", "@QString") % NoCdbEngine; + + // clang-format off + QTest::newRow("tl__expected") << Data{ + R"( + #include + + #include + + enum class Test { + One, + Two, + }; + + enum class ErrCode : std::int8_t { + Good, + Bad, + }; + + class MyClass { + public: + MyClass(int x) : m_x{x} {} + + private: + int m_x = 42; + }; + + static constexpr auto global = 42; + )", + + R"( + const auto ok_void = tl::expected{}; + const auto ok_primitive = tl::expected{42}; + const auto ok_pointer = tl::expected{&global}; + const auto ok_enum = tl::expected{Test::Two}; + const auto ok_class = tl::expected{MyClass{10}}; + + const auto err_primitive = tl::expected{tl::make_unexpected(42)}; + const auto err_pointer = tl::expected{tl::make_unexpected(&global)}; + const auto err_enum = tl::expected{tl::make_unexpected(ErrCode::Bad)}; + const auto err_class = tl::expected{tl::make_unexpected(MyClass{10})}; + )", + + "&ok_void, &ok_primitive, &ok_pointer, &ok_enum, &ok_class, &err_primitive, " + "&err_pointer, &err_enum, &err_class" + } + + CoreProfile{} + + InternalProfile{} + + Check{"ok_void", "Expected", "tl::expected"} + + Check{"ok_primitive", "Expected", "tl::expected"} + + Check{"ok_primitive.inner", "42", "int"} + + Check{"ok_pointer", "Expected", "tl::expected"} + + Check{"ok_pointer.inner", "42", "int"} + + Check{"ok_enum", "Expected", "tl::expected"} + + Check{"ok_enum.inner", "Test::Two (1)", "Test"} % GdbEngine + + Check{"ok_enum.inner", "Two (1)", "Test"} % NoGdbEngine + + Check{"ok_class", "Expected", "tl::expected"} + + Check{"ok_class.inner.m_x", "10", "int"} + + Check{"err_primitive", "Unexpected", "tl::expected"} + + Check{"err_primitive.inner", "42", "int"} + + Check{"err_pointer", "Unexpected", "tl::expected"} + + Check{"err_pointer.inner", "42", "int"} + + Check{"err_enum", "Unexpected", "tl::expected"} + + Check{"err_enum.inner", "ErrCode::Bad (1)", "ErrCode"} % GdbEngine + + Check{"err_enum.inner", "Bad (1)", "ErrCode"} % NoGdbEngine + + Check{"err_class", "Unexpected", "tl::expected"} + + Check{"err_class.inner.m_x", "10", "int"}; + //clang-format on } int main(int argc, char *argv[])