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 <hjk@qt.io>
This commit is contained in:
Andrii Semkiv
2024-10-17 12:01:51 +02:00
parent 6f6754fbd3
commit 1e4a744398
4 changed files with 131 additions and 9 deletions

View File

@@ -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')

View File

@@ -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:

View File

@@ -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)

View File

@@ -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 <QByteArray>"
"\n#include <QtGlobal>"
@@ -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 <tl_expected/include/tl/expected.hpp>
#include <cstdint>
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<void, ErrCode>{};
const auto ok_primitive = tl::expected<int, ErrCode>{42};
const auto ok_pointer = tl::expected<const int*, ErrCode>{&global};
const auto ok_enum = tl::expected<Test, ErrCode>{Test::Two};
const auto ok_class = tl::expected<MyClass, ErrCode>{MyClass{10}};
const auto err_primitive = tl::expected<ErrCode, int>{tl::make_unexpected(42)};
const auto err_pointer = tl::expected<ErrCode, const int*>{tl::make_unexpected(&global)};
const auto err_enum = tl::expected<ErrCode, ErrCode>{tl::make_unexpected(ErrCode::Bad)};
const auto err_class = tl::expected<ErrCode, MyClass>{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<void, ErrCode>"}
+ Check{"ok_primitive", "Expected", "tl::expected<int, ErrCode>"}
+ Check{"ok_primitive.inner", "42", "int"}
+ Check{"ok_pointer", "Expected", "tl::expected<const int*, ErrCode>"}
+ Check{"ok_pointer.inner", "42", "int"}
+ Check{"ok_enum", "Expected", "tl::expected<Test, ErrCode>"}
+ Check{"ok_enum.inner", "Test::Two (1)", "Test"} % GdbEngine
+ Check{"ok_enum.inner", "Two (1)", "Test"} % NoGdbEngine
+ Check{"ok_class", "Expected", "tl::expected<MyClass, ErrCode>"}
+ Check{"ok_class.inner.m_x", "10", "int"}
+ Check{"err_primitive", "Unexpected", "tl::expected<ErrCode, int>"}
+ Check{"err_primitive.inner", "42", "int"}
+ Check{"err_pointer", "Unexpected", "tl::expected<ErrCode, const int*>"}
+ Check{"err_pointer.inner", "42", "int"}
+ Check{"err_enum", "Unexpected", "tl::expected<ErrCode, ErrCode>"}
+ Check{"err_enum.inner", "ErrCode::Bad (1)", "ErrCode"} % GdbEngine
+ Check{"err_enum.inner", "Bad (1)", "ErrCode"} % NoGdbEngine
+ Check{"err_class", "Unexpected", "tl::expected<ErrCode, MyClass>"}
+ Check{"err_class.inner.m_x", "10", "int"};
//clang-format on
}
int main(int argc, char *argv[])