forked from qt-creator/qt-creator
C++: Add star binding to TypePrettyPrinter
Now we can specify if we want to print a whitespace before and/or after '*'/'&' when printing pointer and reference types. Task-number: QTCREATORBUG-6169 Change-Id: Ida1b035aa4fd79be9108934b75f236db9f7238af Reviewed-by: Erik Verbruggen <erik.verbruggen@digia.com>
This commit is contained in:
committed by
Erik Verbruggen
parent
b6a9d58f69
commit
8c3794f9d1
@@ -38,14 +38,15 @@
|
||||
using namespace CPlusPlus;
|
||||
|
||||
Overview::Overview()
|
||||
: markedArgument(0),
|
||||
markedArgumentBegin(0),
|
||||
markedArgumentEnd(0),
|
||||
: starBindFlags(BindToIdentifier), // default to "Qt Style"
|
||||
showArgumentNames(false),
|
||||
showReturnTypes(false),
|
||||
showFunctionSignatures(true),
|
||||
showDefaultArguments(true),
|
||||
showTemplateParameters(false)
|
||||
showTemplateParameters(false),
|
||||
markedArgument(0),
|
||||
markedArgumentBegin(0),
|
||||
markedArgumentEnd(0)
|
||||
{ }
|
||||
|
||||
QString Overview::prettyName(const Name *name) const
|
||||
|
@@ -37,6 +37,16 @@
|
||||
|
||||
namespace CPlusPlus {
|
||||
|
||||
/*!
|
||||
\class Overview
|
||||
|
||||
\brief Converts a FullySpecifiedType and/or any qualified name,
|
||||
to its string representation.
|
||||
|
||||
The public data members (except the ones starting with "marked")
|
||||
determine what exactly and how to print.
|
||||
*/
|
||||
|
||||
class CPLUSPLUS_EXPORT Overview
|
||||
{
|
||||
Overview(const Overview &other);
|
||||
@@ -60,14 +70,57 @@ public:
|
||||
QString prettyType(const FullySpecifiedType &type, const QString &name) const;
|
||||
|
||||
public:
|
||||
unsigned markedArgument;
|
||||
int markedArgumentBegin;
|
||||
int markedArgumentEnd;
|
||||
/*!
|
||||
\enum Overview::StarBindFlag
|
||||
|
||||
The StarBindFlags describe how the '*' and '&' in pointers/references
|
||||
should be bound in the string representation.
|
||||
|
||||
This also applies to rvalue references ('&&'), but not to
|
||||
pointers to functions or arrays like in
|
||||
|
||||
void (*p)()
|
||||
void (*p)[]
|
||||
|
||||
since it seems to be quite uncommon to use spaces there.
|
||||
|
||||
See the examples below. These assume that exactly one
|
||||
flag is set. That is, it may look different with
|
||||
flag combinations.
|
||||
|
||||
\value BindToIdentifier
|
||||
e.g. "char *foo", but not "char * foo"
|
||||
\value BindToTypeName
|
||||
e.g. "char*", but not "char *"
|
||||
\value BindToLeftSpecifier
|
||||
e.g. "char * const* const", but not "char * const * const"
|
||||
\value BindToRightSpecifier
|
||||
e.g. "char *const", but not "char * const"
|
||||
*/
|
||||
enum StarBindFlag {
|
||||
BindToIdentifier = 0x1,
|
||||
BindToTypeName = 0x2,
|
||||
BindToLeftSpecifier = 0x4,
|
||||
BindToRightSpecifier = 0x8
|
||||
};
|
||||
Q_DECLARE_FLAGS(StarBindFlags, StarBindFlag)
|
||||
|
||||
StarBindFlags starBindFlags;
|
||||
bool showArgumentNames: 1;
|
||||
bool showReturnTypes: 1;
|
||||
bool showFunctionSignatures: 1;
|
||||
bool showDefaultArguments: 1;
|
||||
bool showTemplateParameters: 1;
|
||||
|
||||
/*!
|
||||
You can get the start and end position of a function argument
|
||||
in the resulting string. Set "markedArgument" to the desired
|
||||
argument. After processing, "markedArgumentBegin" and
|
||||
"markedArgumentEnd" will contain the positions.
|
||||
*/
|
||||
unsigned markedArgument;
|
||||
int markedArgumentBegin;
|
||||
int markedArgumentEnd;
|
||||
};
|
||||
|
||||
} // namespace CPlusPlus
|
||||
|
@@ -44,6 +44,8 @@ using namespace CPlusPlus;
|
||||
TypePrettyPrinter::TypePrettyPrinter(const Overview *overview)
|
||||
: _overview(overview)
|
||||
, _needsParens(false)
|
||||
, _isIndirectionType(false)
|
||||
, _isIndirectionToArrayOrFunction(false)
|
||||
{ }
|
||||
|
||||
TypePrettyPrinter::~TypePrettyPrinter()
|
||||
@@ -91,13 +93,28 @@ QString TypePrettyPrinter::switchName(const QString &name)
|
||||
const QString previousName = _name;
|
||||
_name = name;
|
||||
return previousName;
|
||||
|
||||
}
|
||||
|
||||
QString TypePrettyPrinter::switchText(const QString &name)
|
||||
bool TypePrettyPrinter::switchIsIndirectionType(bool isIndirectionType)
|
||||
{
|
||||
QString previousName = _text;
|
||||
_text = name;
|
||||
return previousName;
|
||||
bool previousIsIndirectionType = _isIndirectionType;
|
||||
_isIndirectionType = isIndirectionType;
|
||||
return previousIsIndirectionType;
|
||||
}
|
||||
|
||||
bool TypePrettyPrinter::switchIsIndirectionToArrayOrFunction(bool isIndirectionToArrayOrFunction)
|
||||
{
|
||||
bool previousIsIndirectionToArrayOrFunction = _isIndirectionToArrayOrFunction;
|
||||
_isIndirectionToArrayOrFunction = isIndirectionToArrayOrFunction;
|
||||
return previousIsIndirectionToArrayOrFunction;
|
||||
}
|
||||
|
||||
QString TypePrettyPrinter::switchText(const QString &text)
|
||||
{
|
||||
QString previousText = _text;
|
||||
_text = text;
|
||||
return previousText;
|
||||
}
|
||||
|
||||
bool TypePrettyPrinter::switchNeedsParens(bool needsParens)
|
||||
@@ -175,6 +192,46 @@ void TypePrettyPrinter::visit(Enum *type)
|
||||
prependCv(_fullySpecifiedType);
|
||||
}
|
||||
|
||||
void TypePrettyPrinter::visitIndirectionType(
|
||||
const TypePrettyPrinter::IndirectionType indirectionType,
|
||||
const FullySpecifiedType &elementType,
|
||||
bool isIndirectionToArrayOrFunction)
|
||||
{
|
||||
QLatin1Char indirectionSign = indirectionType == aPointerType
|
||||
? QLatin1Char('*') : QLatin1Char('&');
|
||||
|
||||
const bool prevIsIndirectionType = switchIsIndirectionType(true);
|
||||
const bool hasName = ! _name.isEmpty();
|
||||
if (hasName) {
|
||||
_text.prepend(_name);
|
||||
_name.clear();
|
||||
}
|
||||
prependCv(_fullySpecifiedType);
|
||||
|
||||
if (_text.startsWith(QLatin1Char('&')) && indirectionType != aPointerType)
|
||||
_text.prepend(QLatin1Char(' '));
|
||||
|
||||
const bool prevIsIndirectionToArrayOrFunction
|
||||
= switchIsIndirectionToArrayOrFunction(isIndirectionToArrayOrFunction);
|
||||
|
||||
// Space after indirectionSign?
|
||||
prependSpaceAfterIndirection(hasName);
|
||||
|
||||
// Write indirectionSign or reference
|
||||
if (indirectionType == aRvalueReferenceType)
|
||||
_text.prepend(QLatin1String("&&"));
|
||||
else
|
||||
_text.prepend(indirectionSign);
|
||||
|
||||
// Space before indirectionSign?
|
||||
prependSpaceBeforeIndirection(elementType);
|
||||
|
||||
_needsParens = true;
|
||||
acceptType(elementType);
|
||||
(bool) switchIsIndirectionToArrayOrFunction(prevIsIndirectionToArrayOrFunction);
|
||||
(bool) switchIsIndirectionType(prevIsIndirectionType);
|
||||
}
|
||||
|
||||
void TypePrettyPrinter::visit(IntegerType *type)
|
||||
{
|
||||
prependSpaceUnlessBracket();
|
||||
@@ -246,35 +303,54 @@ void TypePrettyPrinter::visit(PointerToMemberType *type)
|
||||
acceptType(type->elementType());
|
||||
}
|
||||
|
||||
void TypePrettyPrinter::prependSpaceBeforeIndirection(const FullySpecifiedType &type)
|
||||
{
|
||||
const bool elementTypeIsPointerOrReference = type.type()->isPointerType()
|
||||
|| type.type()->isReferenceType();
|
||||
const bool elementIsConstPointerOrReference = elementTypeIsPointerOrReference && type.isConst();
|
||||
const bool shouldBindToLeftSpecifier = _overview->starBindFlags & Overview::BindToLeftSpecifier;
|
||||
if (elementIsConstPointerOrReference && ! shouldBindToLeftSpecifier)
|
||||
_text.prepend(QLatin1String(" "));
|
||||
}
|
||||
|
||||
void TypePrettyPrinter::prependSpaceAfterIndirection(bool hasName)
|
||||
{
|
||||
const bool hasCvSpecifier = _fullySpecifiedType.isConst() || _fullySpecifiedType.isVolatile();
|
||||
const bool shouldBindToIdentifier = _overview->starBindFlags & Overview::BindToIdentifier;
|
||||
const bool shouldBindToRightSpecifier =
|
||||
_overview->starBindFlags & Overview::BindToRightSpecifier;
|
||||
|
||||
const bool spaceBeforeNameNeeded = hasName && ! shouldBindToIdentifier
|
||||
&& ! _isIndirectionToArrayOrFunction;
|
||||
const bool spaceBeforeSpecifierNeeded = hasCvSpecifier && ! shouldBindToRightSpecifier;
|
||||
|
||||
const bool case1 = hasCvSpecifier && spaceBeforeSpecifierNeeded;
|
||||
const bool case2 = ! hasCvSpecifier && spaceBeforeNameNeeded;
|
||||
// case 3: In "char *argv[]", put a space between '*' and "argv" when requested
|
||||
const bool case3 = ! hasCvSpecifier && ! shouldBindToIdentifier
|
||||
&& ! _isIndirectionToArrayOrFunction && _text.size() && _text.at(0).isLetter();
|
||||
if (case1 || case2 || case3)
|
||||
_text.prepend(QLatin1String(" "));
|
||||
}
|
||||
|
||||
void TypePrettyPrinter::visit(PointerType *type)
|
||||
{
|
||||
if (! _name.isEmpty()) {
|
||||
_text.prepend(_name);
|
||||
_name.clear();
|
||||
}
|
||||
prependCv(_fullySpecifiedType);
|
||||
_text.prepend(QLatin1String("*"));
|
||||
_needsParens = true;
|
||||
acceptType(type->elementType());
|
||||
const bool isIndirectionToFunction = type->elementType().type()->isFunctionType();
|
||||
const bool isIndirectionToArray = type->elementType().type()->isArrayType();
|
||||
|
||||
visitIndirectionType(aPointerType, type->elementType(),
|
||||
isIndirectionToFunction || isIndirectionToArray);
|
||||
}
|
||||
|
||||
void TypePrettyPrinter::visit(ReferenceType *type)
|
||||
{
|
||||
if (! _name.isEmpty()) {
|
||||
_text.prepend(_name);
|
||||
_name.clear();
|
||||
}
|
||||
prependCv(_fullySpecifiedType);
|
||||
const bool isIndirectionToFunction = type->elementType().type()->isFunctionType();
|
||||
const bool isIndirectionToArray = type->elementType().type()->isArrayType();
|
||||
const IndirectionType indirectionType = type->isRvalueReference()
|
||||
? aRvalueReferenceType : aReferenceType;
|
||||
|
||||
if (_text.startsWith(QLatin1Char('&')))
|
||||
_text.prepend(QLatin1Char(' '));
|
||||
|
||||
if (type->isRvalueReference())
|
||||
_text.prepend(QLatin1String("&&"));
|
||||
else
|
||||
_text.prepend(QLatin1String("&"));
|
||||
_needsParens = true;
|
||||
acceptType(type->elementType());
|
||||
visitIndirectionType(indirectionType, type->elementType(),
|
||||
isIndirectionToFunction || isIndirectionToArray);
|
||||
}
|
||||
|
||||
void TypePrettyPrinter::visit(ArrayType *type)
|
||||
@@ -326,6 +402,7 @@ void TypePrettyPrinter::visit(Function *type)
|
||||
|
||||
if (_overview->showFunctionSignatures) {
|
||||
Overview argumentText;
|
||||
argumentText.starBindFlags = _overview->starBindFlags;
|
||||
argumentText.showReturnTypes = true;
|
||||
argumentText.showArgumentNames = false;
|
||||
argumentText.showFunctionSignatures = true;
|
||||
@@ -393,8 +470,16 @@ void TypePrettyPrinter::prependSpaceUnlessBracket()
|
||||
|
||||
const QChar ch = _text.at(0);
|
||||
|
||||
if (ch != QLatin1Char('['))
|
||||
if (ch != QLatin1Char('[')) {
|
||||
const bool shouldBindToTypeNam = _overview->starBindFlags & Overview::BindToTypeName;
|
||||
const bool caseNoIndirection = ! _isIndirectionType;
|
||||
const bool caseIndirectionToArrayOrFunction = _isIndirectionType
|
||||
&& _isIndirectionToArrayOrFunction;
|
||||
const bool casePointerNoBind = _isIndirectionType && ! _isIndirectionToArrayOrFunction
|
||||
&& ! shouldBindToTypeNam;
|
||||
if (caseNoIndirection || caseIndirectionToArrayOrFunction || casePointerNoBind)
|
||||
_text.prepend(QLatin1Char(' '));
|
||||
}
|
||||
}
|
||||
|
||||
void TypePrettyPrinter::prependWordSeparatorSpace()
|
||||
|
@@ -39,6 +39,14 @@ namespace CPlusPlus {
|
||||
class Overview;
|
||||
class FullySpecifiedType;
|
||||
|
||||
/*!
|
||||
\class TypePrettyPrinter
|
||||
|
||||
\brief Helper class for Overview. Does the main type conversation work.
|
||||
|
||||
Don't use this class directly, use Overview instead.
|
||||
*/
|
||||
|
||||
class CPLUSPLUS_EXPORT TypePrettyPrinter: protected TypeVisitor
|
||||
{
|
||||
public:
|
||||
@@ -50,11 +58,7 @@ public:
|
||||
QString operator()(const FullySpecifiedType &type);
|
||||
QString operator()(const FullySpecifiedType &type, const QString &name);
|
||||
|
||||
protected:
|
||||
QString switchText(const QString &text = QString());
|
||||
bool switchNeedsParens(bool needsParens);
|
||||
QString switchName(const QString &name);
|
||||
|
||||
private:
|
||||
void acceptType(const FullySpecifiedType &ty);
|
||||
|
||||
virtual void visit(UndefinedType *type);
|
||||
@@ -72,17 +76,30 @@ protected:
|
||||
virtual void visit(Class *type);
|
||||
virtual void visit(Enum *type);
|
||||
|
||||
QString switchName(const QString &name);
|
||||
QString switchText(const QString &text = QString());
|
||||
bool switchNeedsParens(bool needsParens);
|
||||
bool switchIsIndirectionType(bool isIndirectionType);
|
||||
bool switchIsIndirectionToArrayOrFunction(bool isIndirectionToArrayOrFunction);
|
||||
|
||||
void appendSpace();
|
||||
void prependSpaceUnlessBracket();
|
||||
void prependWordSeparatorSpace();
|
||||
void prependCv(const FullySpecifiedType &ty);
|
||||
void prependSpaceAfterIndirection(bool hasName);
|
||||
void prependSpaceBeforeIndirection(const FullySpecifiedType &type);
|
||||
|
||||
enum IndirectionType { aPointerType, aReferenceType, aRvalueReferenceType };
|
||||
void visitIndirectionType(const IndirectionType indirectionType,
|
||||
const FullySpecifiedType &elementType, bool isIndirectionToArrayOrFunction);
|
||||
|
||||
private:
|
||||
const Overview *_overview;
|
||||
QString _name;
|
||||
QString _text;
|
||||
FullySpecifiedType _fullySpecifiedType;
|
||||
bool _needsParens;
|
||||
bool _isIndirectionType;
|
||||
bool _isIndirectionToArrayOrFunction;
|
||||
};
|
||||
|
||||
} // namespace CPlusPlus
|
||||
|
@@ -27,25 +27,23 @@
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <QtTest>
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QtTest>
|
||||
|
||||
#include <FullySpecifiedType.h>
|
||||
#include <Type.h>
|
||||
#include <CoreTypes.h>
|
||||
#include <Symbols.h>
|
||||
#include <TranslationUnit.h>
|
||||
#include <Control.h>
|
||||
#include <Names.h>
|
||||
#include <FullySpecifiedType.h>
|
||||
#include <Literals.h>
|
||||
#include <Overview.h>
|
||||
#include <Scope.h>
|
||||
#include <Symbols.h>
|
||||
#include <Type.h>
|
||||
#include <TypePrettyPrinter.h>
|
||||
|
||||
//TESTED_COMPONENT=src/libs/cplusplus
|
||||
using namespace CPlusPlus;
|
||||
|
||||
Q_DECLARE_METATYPE(CPlusPlus::FullySpecifiedType)
|
||||
Q_DECLARE_METATYPE(Overview::StarBindFlags)
|
||||
|
||||
class tst_TypePrettyPrinter: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -55,160 +53,359 @@ private Q_SLOTS:
|
||||
void basic_data();
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(CPlusPlus::FullySpecifiedType);
|
||||
|
||||
TranslationUnit *unit;
|
||||
|
||||
const Identifier *nameId(const QString &name)
|
||||
static const Identifier *nameId(const QString &name)
|
||||
{ return new Identifier(name.toLatin1().constData(), name.toLatin1().size()); }
|
||||
|
||||
Argument *arg(const QString &name, const FullySpecifiedType &ty)
|
||||
static Argument *arg(const QString &name, const FullySpecifiedType &ty)
|
||||
{
|
||||
Argument *a = new Argument(unit, 0, nameId(name));
|
||||
Argument *a = new Argument(0, 0, nameId(name));
|
||||
a->setType(ty);
|
||||
return a;
|
||||
}
|
||||
|
||||
FullySpecifiedType voidTy()
|
||||
static FullySpecifiedType voidTy()
|
||||
{ return FullySpecifiedType(new VoidType); }
|
||||
|
||||
FullySpecifiedType intTy()
|
||||
static FullySpecifiedType intTy()
|
||||
{ return FullySpecifiedType(new IntegerType(IntegerType::Int)); }
|
||||
|
||||
FullySpecifiedType fnTy(const QString &name, const FullySpecifiedType &ret)
|
||||
static FullySpecifiedType fnTy(const QString &name, const FullySpecifiedType &ret)
|
||||
{
|
||||
Function *fn = new Function(unit, 0, nameId(name));
|
||||
Function *fn = new Function(0, 0, nameId(name));
|
||||
fn->setReturnType(ret);
|
||||
return FullySpecifiedType(fn);
|
||||
}
|
||||
|
||||
FullySpecifiedType fnTy(const QString &name, const FullySpecifiedType &ret, const FullySpecifiedType &a0)
|
||||
static FullySpecifiedType fnTy(const QString &name, const FullySpecifiedType &ret,
|
||||
const FullySpecifiedType &a0)
|
||||
{
|
||||
Function *fn = new Function(unit, 0, nameId(name));
|
||||
Function *fn = new Function(0, 0, nameId(name));
|
||||
fn->setReturnType(ret);
|
||||
fn->addMember(arg("a0", a0));
|
||||
return FullySpecifiedType(fn);
|
||||
}
|
||||
|
||||
FullySpecifiedType ptr(const FullySpecifiedType &el)
|
||||
static FullySpecifiedType fnTy(const QString &name, const FullySpecifiedType &ret,
|
||||
const FullySpecifiedType &a0, const FullySpecifiedType &a1,
|
||||
const FullySpecifiedType &a2)
|
||||
{
|
||||
Function *fn = new Function(0, 0, nameId(name));
|
||||
fn->setReturnType(ret);
|
||||
fn->addMember(arg("a0", a0));
|
||||
fn->addMember(arg("a1", a1));
|
||||
fn->addMember(arg("a1", a2));
|
||||
return FullySpecifiedType(fn);
|
||||
}
|
||||
|
||||
static FullySpecifiedType ptr(const FullySpecifiedType &el)
|
||||
{ return FullySpecifiedType(new PointerType(el)); }
|
||||
|
||||
FullySpecifiedType ref(const FullySpecifiedType &el)
|
||||
static FullySpecifiedType ref(const FullySpecifiedType &el)
|
||||
{ return FullySpecifiedType(new ReferenceType(el, false)); }
|
||||
|
||||
FullySpecifiedType rref(const FullySpecifiedType &el)
|
||||
static FullySpecifiedType rref(const FullySpecifiedType &el)
|
||||
{ return FullySpecifiedType(new ReferenceType(el, true)); }
|
||||
|
||||
FullySpecifiedType arr(const FullySpecifiedType &el)
|
||||
static FullySpecifiedType arr(const FullySpecifiedType &el)
|
||||
{ return FullySpecifiedType(new ArrayType(el, 4)); }
|
||||
|
||||
FullySpecifiedType cnst(const FullySpecifiedType &el)
|
||||
static FullySpecifiedType cnst(const FullySpecifiedType &el)
|
||||
{ FullySpecifiedType r(el); r.setConst(true); return r; }
|
||||
|
||||
|
||||
|
||||
void addRow(const FullySpecifiedType &f, QString result, QString name = QString())
|
||||
static QString toString(Overview::StarBindFlags starBindFlags)
|
||||
{
|
||||
QTest::newRow(result.toLatin1().constData()) << f << name << result;
|
||||
QString result;
|
||||
if (starBindFlags & Overview::BindToIdentifier)
|
||||
result += QLatin1Char('n');
|
||||
if (starBindFlags & Overview::BindToTypeName)
|
||||
result += QLatin1Char('t');
|
||||
if (starBindFlags & Overview::BindToLeftSpecifier)
|
||||
result += QLatin1String("Sl");
|
||||
if (starBindFlags & Overview::BindToRightSpecifier)
|
||||
result += QLatin1String("Sr");
|
||||
return result;
|
||||
}
|
||||
|
||||
static void addRow(const FullySpecifiedType &f, Overview::StarBindFlags starBindFlags,
|
||||
const QString &result, const QString &name = QString())
|
||||
{
|
||||
const QString dataTag
|
||||
= QString::fromLatin1("'%1' with star binding '%2'").arg(result, toString(starBindFlags));
|
||||
QTest::newRow(dataTag.toLatin1().constData()) << f << starBindFlags << name << result;
|
||||
}
|
||||
|
||||
void tst_TypePrettyPrinter::basic_data()
|
||||
{
|
||||
// seems it now works without a translation unit
|
||||
// Control c;
|
||||
// TranslationUnit t(&c, 0);
|
||||
// unit = 0;
|
||||
|
||||
QTest::addColumn<FullySpecifiedType>("type");
|
||||
QTest::addColumn<Overview::StarBindFlags>("starBindFlags");
|
||||
QTest::addColumn<QString>("name");
|
||||
QTest::addColumn<QString>("result");
|
||||
|
||||
addRow(voidTy(), "void");
|
||||
addRow(cnst(voidTy()), "const void");
|
||||
addRow(ptr(fnTy("foo", voidTy())), "void (*)()");
|
||||
addRow(ptr(fnTy("foo", voidTy())), "void (*foo)()", "foo");
|
||||
// Define some often used flag combinations.
|
||||
const Overview::StarBindFlags bindToNothing = 0;
|
||||
const Overview::StarBindFlags bindToBothSpecifiers
|
||||
= Overview::StarBindFlags(Overview::BindToLeftSpecifier | Overview::BindToRightSpecifier);
|
||||
const Overview::StarBindFlags bindToNameAndType
|
||||
= Overview::StarBindFlags(Overview::BindToIdentifier | Overview::BindToTypeName);
|
||||
const Overview::StarBindFlags bindToTypeAndRightSpecifier
|
||||
= Overview::StarBindFlags(Overview::BindToTypeName | Overview::BindToRightSpecifier);
|
||||
const Overview::StarBindFlags bindToAll = Overview::StarBindFlags(Overview::BindToIdentifier
|
||||
| Overview::BindToTypeName | Overview::BindToLeftSpecifier | Overview::BindToRightSpecifier);
|
||||
|
||||
// named types
|
||||
addRow(voidTy(), "void foo", "foo");
|
||||
addRow(ptr(voidTy()), "void *foo", "foo");
|
||||
addRow(cnst(ptr(voidTy())), "void *const foo", "foo");
|
||||
addRow(arr(voidTy()), "void foo[]", "foo");
|
||||
addRow(ptr(arr(voidTy())), "void (*foo)[]", "foo");
|
||||
// The star bindings should not affect declarations without a star or reference sign.
|
||||
addRow(voidTy(), bindToNothing, "void");
|
||||
addRow(voidTy(), bindToAll, "void");
|
||||
addRow(voidTy(), bindToAll, "void foo", "foo");
|
||||
addRow(voidTy(), bindToNothing, "void foo", "foo");
|
||||
|
||||
// pointers
|
||||
addRow(ptr(voidTy()), "void *");
|
||||
addRow(ptr(ptr(voidTy())), "void **");
|
||||
addRow(cnst(voidTy()), bindToNothing, "const void");
|
||||
addRow(cnst(voidTy()), bindToAll, "const void");
|
||||
addRow(cnst(voidTy()), bindToNothing, "const void foo", "foo");
|
||||
addRow(cnst(voidTy()), bindToAll, "const void foo", "foo");
|
||||
|
||||
addRow(ptr(cnst(voidTy())), "const void *");
|
||||
addRow(cnst(ptr(cnst(voidTy()))), "const void *const");
|
||||
addRow(cnst(ptr(voidTy())), "void *const");
|
||||
addRow(arr(voidTy()), bindToNothing, "void[]");
|
||||
addRow(arr(voidTy()), bindToAll, "void[]");
|
||||
addRow(arr(voidTy()), bindToNothing, "void foo[]", "foo");
|
||||
addRow(arr(voidTy()), bindToAll, "void foo[]", "foo");
|
||||
|
||||
addRow(ptr(ptr(cnst(voidTy()))), "const void **");
|
||||
addRow(ptr(cnst(ptr(cnst(voidTy())))), "const void *const*");
|
||||
addRow(cnst(ptr(ptr(cnst(voidTy())))), "const void **const");
|
||||
addRow(cnst(ptr(cnst(ptr(cnst(voidTy()))))), "const void *const*const");
|
||||
addRow(ptr(cnst(ptr(voidTy()))), "void *const*");
|
||||
addRow(cnst(ptr(ptr(voidTy()))), "void **const");
|
||||
addRow(cnst(ptr(cnst(ptr(voidTy())))), "void *const*const");
|
||||
addRow(fnTy("foo", voidTy(), intTy()), bindToNothing, "void foo(int)", "foo");
|
||||
addRow(fnTy("foo", voidTy(), intTy()), bindToAll, "void foo(int)", "foo");
|
||||
|
||||
addRow(arr(voidTy()), "void[]");
|
||||
addRow(arr(ptr(voidTy())), "void *[]");
|
||||
addRow(ptr(arr(voidTy())), "void (*)[]");
|
||||
addRow(ptr(arr(arr(voidTy()))), "void (*)[][]");
|
||||
addRow(ptr(arr(ptr(voidTy()))), "void *(*)[]");
|
||||
addRow(arr(ptr(arr(voidTy()))), "void (*[])[]");
|
||||
// Pointers to functions and arrays are also excluded. It seems to be quite unusal to have
|
||||
// a space there.
|
||||
addRow(ptr(fnTy("foo", voidTy())), bindToAll, "void (*)()");
|
||||
addRow(ptr(fnTy("foo", voidTy())), bindToNothing, "void (*)()");
|
||||
addRow(ptr(fnTy("foo", voidTy())), bindToAll, "void (*foo)()", "foo");
|
||||
addRow(ptr(fnTy("foo", voidTy())), bindToNothing, "void (*foo)()", "foo");
|
||||
|
||||
// references
|
||||
addRow(ref(voidTy()), "void &");
|
||||
addRow(ref(ref(voidTy())), "void & &");
|
||||
addRow(ptr(arr(voidTy())), bindToNothing, "void (*)[]");
|
||||
addRow(ptr(arr(voidTy())), bindToAll, "void (*)[]");
|
||||
|
||||
addRow(ref(cnst(voidTy())), "const void &");
|
||||
addRow(cnst(ref(cnst(voidTy()))), "const void &const");
|
||||
addRow(cnst(ref(voidTy())), "void &const");
|
||||
addRow(ptr(arr(arr(voidTy()))), bindToNothing, "void (*)[][]");
|
||||
addRow(ptr(arr(arr(voidTy()))), bindToAll, "void (*)[][]");
|
||||
|
||||
addRow(ref(ref(cnst(voidTy()))), "const void & &");
|
||||
addRow(ref(cnst(ref(cnst(voidTy())))), "const void &const&");
|
||||
addRow(cnst(ref(ref(cnst(voidTy())))), "const void & &const");
|
||||
addRow(cnst(ref(cnst(ref(cnst(voidTy()))))), "const void &const&const");
|
||||
addRow(ref(cnst(ref(voidTy()))), "void &const&");
|
||||
addRow(cnst(ref(ref(voidTy()))), "void & &const");
|
||||
addRow(cnst(ref(cnst(ref(voidTy())))), "void &const&const");
|
||||
// Pointers only
|
||||
addRow(ptr(voidTy()), Overview::BindToTypeName, "void*");
|
||||
addRow(ptr(voidTy()), bindToAll, "void*");
|
||||
addRow(ptr(voidTy()), bindToNothing, "void *");
|
||||
addRow(ptr(voidTy()), Overview::BindToIdentifier, "void *foo", "foo");
|
||||
addRow(ptr(voidTy()), Overview::BindToTypeName, "void* foo", "foo");
|
||||
addRow(ptr(voidTy()), bindToNameAndType, "void*foo", "foo");
|
||||
addRow(ptr(voidTy()), bindToAll, "void*foo", "foo");
|
||||
addRow(ptr(voidTy()), bindToNothing, "void * foo", "foo");
|
||||
|
||||
addRow(arr(voidTy()), "void[]");
|
||||
addRow(arr(ref(voidTy())), "void &[]");
|
||||
addRow(ref(arr(voidTy())), "void (&)[]");
|
||||
addRow(ref(arr(arr(voidTy()))), "void (&)[][]");
|
||||
addRow(ref(arr(ref(voidTy()))), "void &(&)[]");
|
||||
addRow(arr(ref(arr(voidTy()))), "void (&[])[]");
|
||||
addRow(ptr(ptr(voidTy())), Overview::BindToTypeName, "void**");
|
||||
addRow(ptr(ptr(voidTy())), bindToAll, "void**");
|
||||
addRow(ptr(ptr(voidTy())), bindToNothing, "void **");
|
||||
addRow(ptr(ptr(voidTy())), Overview::BindToIdentifier, "void **foo", "foo");
|
||||
addRow(ptr(ptr(voidTy())), Overview::BindToTypeName, "void** foo", "foo");
|
||||
addRow(ptr(ptr(voidTy())), bindToNameAndType, "void**foo", "foo");
|
||||
addRow(ptr(ptr(voidTy())), bindToAll, "void**foo", "foo");
|
||||
addRow(ptr(ptr(voidTy())), bindToNothing, "void ** foo", "foo");
|
||||
|
||||
// rvalue references
|
||||
addRow(rref(voidTy()), "void &&");
|
||||
addRow(rref(cnst(voidTy())), "const void &&");
|
||||
addRow(ptr(cnst(voidTy())), bindToAll, "const void*");
|
||||
addRow(ptr(cnst(voidTy())), bindToNothing, "const void *");
|
||||
|
||||
addRow(rref(arr(voidTy())), "void (&&)[]");
|
||||
addRow(rref(arr(arr(voidTy()))), "void (&&)[][]");
|
||||
addRow(cnst(ptr(voidTy())), Overview::BindToIdentifier, "void * const foo", "foo");
|
||||
addRow(cnst(ptr(voidTy())), Overview::BindToTypeName, "void* const foo", "foo");
|
||||
addRow(cnst(ptr(voidTy())), Overview::BindToRightSpecifier, "void *const foo", "foo");
|
||||
addRow(cnst(ptr(voidTy())), bindToTypeAndRightSpecifier, "void*const foo", "foo");
|
||||
addRow(cnst(ptr(voidTy())), bindToAll, "void*const foo", "foo");
|
||||
addRow(cnst(ptr(voidTy())), bindToNothing, "void * const foo", "foo");
|
||||
|
||||
// simple functions
|
||||
addRow(ptr(fnTy("foo", voidTy(), intTy())), "void (*foo)(int)", "foo");
|
||||
addRow(ptr(fnTy("foo", voidTy(), ptr(voidTy()))), "void (*foo)(void *)", "foo");
|
||||
addRow(fnTy("foo", voidTy(), intTy()), "void foo(int)", "foo");
|
||||
addRow(fnTy("foo", voidTy(), ptr(voidTy())), "void foo(void *)", "foo");
|
||||
addRow(cnst(ptr(cnst(voidTy()))), bindToAll, "const void*const");
|
||||
addRow(cnst(ptr(cnst(voidTy()))), Overview::BindToTypeName, "const void* const");
|
||||
addRow(cnst(ptr(cnst(voidTy()))), Overview::BindToRightSpecifier, "const void *const");
|
||||
addRow(cnst(ptr(cnst(voidTy()))), bindToNothing, "const void * const");
|
||||
|
||||
// functions with ptr or ref returns
|
||||
addRow(ptr(fnTy("foo", ptr(voidTy()), intTy())), "void *(*foo)(int)", "foo");
|
||||
addRow(ptr(fnTy("foo", ref(voidTy()), ptr(voidTy()))), "void &(*foo)(void *)", "foo");
|
||||
addRow(fnTy("foo", ptr(voidTy()), intTy()), "void *foo(int)", "foo");
|
||||
addRow(fnTy("foo", ref(voidTy()), ptr(voidTy())), "void &foo(void *)", "foo");
|
||||
addRow(cnst(ptr(voidTy())), Overview::BindToIdentifier, "void * const");
|
||||
addRow(cnst(ptr(voidTy())), Overview::BindToTypeName, "void* const");
|
||||
addRow(cnst(ptr(voidTy())), Overview::BindToRightSpecifier, "void *const");
|
||||
addRow(cnst(ptr(voidTy())), bindToTypeAndRightSpecifier, "void*const");
|
||||
addRow(cnst(ptr(voidTy())), bindToAll, "void*const");
|
||||
addRow(cnst(ptr(voidTy())), bindToNothing, "void * const");
|
||||
|
||||
addRow(ptr(ptr(cnst(voidTy()))), Overview::BindToIdentifier, "const void **");
|
||||
addRow(ptr(cnst(ptr(cnst(voidTy())))), Overview::BindToIdentifier, "const void * const *");
|
||||
addRow(cnst(ptr(ptr(cnst(voidTy())))), Overview::BindToIdentifier, "const void ** const");
|
||||
|
||||
addRow(ptr(cnst(ptr(voidTy()))), bindToNothing, "void * const *");
|
||||
addRow(ptr(cnst(ptr(voidTy()))), bindToAll, "void*const*");
|
||||
addRow(cnst(ptr(ptr(voidTy()))), bindToNothing, "void ** const");
|
||||
addRow(cnst(ptr(ptr(voidTy()))), bindToAll, "void**const");
|
||||
|
||||
addRow(cnst(ptr(cnst(ptr(voidTy())))), bindToNothing, "void * const * const");
|
||||
addRow(cnst(ptr(cnst(ptr(voidTy())))), bindToAll, "void*const*const");
|
||||
addRow(cnst(ptr(ptr(cnst(ptr(voidTy()))))), bindToNothing, "void * const ** const");
|
||||
addRow(cnst(ptr(ptr(cnst(ptr(voidTy()))))), bindToAll, "void*const**const");
|
||||
|
||||
addRow(cnst(ptr(cnst(ptr(cnst(voidTy()))))), Overview::BindToLeftSpecifier, "const void * const* const");
|
||||
addRow(cnst(ptr(cnst(ptr(cnst(voidTy()))))), Overview::BindToRightSpecifier, "const void *const *const");
|
||||
addRow(cnst(ptr(cnst(ptr(cnst(voidTy()))))), bindToBothSpecifiers, "const void *const*const");
|
||||
addRow(cnst(ptr(cnst(ptr(cnst(voidTy()))))), bindToNothing, "const void * const * const");
|
||||
addRow(cnst(ptr(cnst(ptr(cnst(voidTy()))))), bindToAll, "const void*const*const");
|
||||
|
||||
// Pointers and arrays
|
||||
addRow(arr(ptr(voidTy())), bindToNothing, "void * argv[]", "argv");
|
||||
addRow(arr(ptr(voidTy())), bindToAll, "void*argv[]", "argv");
|
||||
|
||||
addRow(arr(ptr(voidTy())), bindToNothing, "void *[]");
|
||||
addRow(arr(ptr(voidTy())), bindToAll, "void*[]");
|
||||
|
||||
addRow(ptr(arr(ptr(voidTy()))), bindToNothing, "void *(*)[]");
|
||||
addRow(ptr(arr(ptr(voidTy()))), bindToAll, "void*(*)[]");
|
||||
|
||||
addRow(arr(ptr(arr(voidTy()))), bindToNothing, "void (*[])[]");
|
||||
addRow(arr(ptr(arr(voidTy()))), bindToAll, "void (*[])[]");
|
||||
|
||||
addRow(ptr(arr(voidTy())), bindToAll, "void (*foo)[]", "foo");
|
||||
addRow(ptr(arr(voidTy())), bindToNothing, "void (*foo)[]", "foo");
|
||||
|
||||
// Pointers to functions
|
||||
addRow(ptr(fnTy("foo", voidTy(), intTy())), bindToNothing, "void (*foo)(int)", "foo");
|
||||
addRow(ptr(fnTy("foo", voidTy(), intTy())), bindToAll, "void (*foo)(int)", "foo");
|
||||
|
||||
addRow(ptr(fnTy("foo", voidTy(), ptr(voidTy()))), bindToNothing, "void (*foo)(void *)", "foo");
|
||||
addRow(ptr(fnTy("foo", voidTy(), ptr(voidTy()))), bindToAll, "void (*foo)(void*)", "foo");
|
||||
|
||||
// Pointers in more complex declarations
|
||||
FullySpecifiedType complexType1
|
||||
= ptr(fnTy("foo", voidTy(), intTy(), ptr(voidTy()), ptr(ptr(intTy()))));
|
||||
addRow(complexType1, bindToNothing, "void (*foo)(int, void *, int **)", "foo");
|
||||
addRow(complexType1, bindToAll, "void (*foo)(int, void*, int**)", "foo");
|
||||
|
||||
FullySpecifiedType complexType2 = ptr(fnTy("foo", voidTy(),
|
||||
intTy(), cnst(ptr(cnst(voidTy()))), cnst(ptr(cnst(ptr(intTy()))))));
|
||||
addRow(complexType2, Overview::BindToLeftSpecifier,
|
||||
"void (*foo)(int, const void * const, int * const* const)", "foo");
|
||||
addRow(complexType2, Overview::BindToRightSpecifier,
|
||||
"void (*foo)(int, const void *const, int *const *const)", "foo");
|
||||
addRow(complexType2, bindToBothSpecifiers,
|
||||
"void (*foo)(int, const void *const, int *const*const)", "foo");
|
||||
addRow(complexType2, bindToNothing,
|
||||
"void (*foo)(int, const void * const, int * const * const)", "foo");
|
||||
addRow(complexType2, bindToAll,
|
||||
"void (*foo)(int, const void*const, int*const*const)", "foo");
|
||||
|
||||
// References only
|
||||
addRow(ref(voidTy()), bindToNothing, "void &");
|
||||
addRow(ref(voidTy()), bindToAll, "void&");
|
||||
|
||||
addRow(ref(ref(voidTy())), bindToNothing, "void & &");
|
||||
addRow(ref(ref(voidTy())), bindToAll, "void& &");
|
||||
|
||||
addRow(ref(cnst(voidTy())), bindToNothing, "const void &");
|
||||
addRow(ref(cnst(voidTy())), bindToAll, "const void&");
|
||||
|
||||
addRow(cnst(ref(cnst(voidTy()))), Overview::BindToRightSpecifier, "const void &const");
|
||||
addRow(cnst(ref(cnst(voidTy()))), Overview::BindToTypeName, "const void& const");
|
||||
addRow(cnst(ref(cnst(voidTy()))), bindToNothing, "const void & const");
|
||||
addRow(cnst(ref(cnst(voidTy()))), bindToAll, "const void&const");
|
||||
|
||||
addRow(cnst(ref(voidTy())), Overview::BindToRightSpecifier, "void &const");
|
||||
addRow(cnst(ref(voidTy())), Overview::BindToTypeName, "void& const");
|
||||
addRow(cnst(ref(voidTy())), bindToNothing, "void & const");
|
||||
addRow(cnst(ref(voidTy())), bindToAll, "void&const");
|
||||
|
||||
addRow(ref(ref(cnst(voidTy()))), bindToNothing, "const void & &");
|
||||
addRow(ref(ref(cnst(voidTy()))), bindToAll, "const void& &");
|
||||
|
||||
addRow(ref(cnst(ref(cnst(voidTy())))), Overview::BindToTypeName, "const void& const &");
|
||||
addRow(ref(cnst(ref(cnst(voidTy())))), Overview::BindToLeftSpecifier, "const void & const&");
|
||||
addRow(ref(cnst(ref(cnst(voidTy())))), Overview::BindToRightSpecifier, "const void &const &");
|
||||
addRow(ref(cnst(ref(cnst(voidTy())))), bindToBothSpecifiers, "const void &const&");
|
||||
addRow(ref(cnst(ref(cnst(voidTy())))), bindToNothing, "const void & const &");
|
||||
addRow(ref(cnst(ref(cnst(voidTy())))), bindToAll, "const void&const&");
|
||||
|
||||
addRow(cnst(ref(ref(cnst(voidTy())))), bindToBothSpecifiers, "const void & &const");
|
||||
addRow(cnst(ref(ref(cnst(voidTy())))), bindToNothing, "const void & & const");
|
||||
addRow(cnst(ref(ref(cnst(voidTy())))), bindToAll, "const void& &const");
|
||||
|
||||
addRow(cnst(ref(cnst(ref(cnst(voidTy()))))), Overview::BindToLeftSpecifier, "const void & const& const");
|
||||
addRow(cnst(ref(cnst(ref(cnst(voidTy()))))), Overview::BindToRightSpecifier, "const void &const &const");
|
||||
addRow(cnst(ref(cnst(ref(cnst(voidTy()))))), bindToBothSpecifiers, "const void &const&const");
|
||||
addRow(cnst(ref(cnst(ref(cnst(voidTy()))))), bindToNothing, "const void & const & const");
|
||||
addRow(cnst(ref(cnst(ref(cnst(voidTy()))))), bindToAll, "const void&const&const");
|
||||
|
||||
addRow(ref(cnst(ref(voidTy()))), bindToNothing, "void & const &");
|
||||
addRow(ref(cnst(ref(voidTy()))), bindToAll, "void&const&");
|
||||
|
||||
addRow(cnst(ref(ref(voidTy()))), bindToNothing, "void & & const");
|
||||
addRow(cnst(ref(ref(voidTy()))), bindToAll, "void& &const");
|
||||
|
||||
addRow(cnst(ref(cnst(ref(voidTy())))), Overview::BindToLeftSpecifier, "void & const& const");
|
||||
addRow(cnst(ref(cnst(ref(voidTy())))), Overview::BindToRightSpecifier, "void &const &const");
|
||||
addRow(cnst(ref(cnst(ref(voidTy())))), bindToBothSpecifiers, "void &const&const");
|
||||
addRow(cnst(ref(cnst(ref(voidTy())))), bindToNothing, "void & const & const");
|
||||
addRow(cnst(ref(cnst(ref(voidTy())))), bindToAll, "void&const&const");
|
||||
|
||||
addRow(ref(fnTy("foo", voidTy())), bindToNothing, "void (&foo)()", "foo");
|
||||
addRow(ref(fnTy("foo", voidTy())), bindToAll, "void (&foo)()", "foo");
|
||||
|
||||
addRow(arr(ref(voidTy())), bindToNothing, "void &[]");
|
||||
addRow(arr(ref(voidTy())), bindToAll, "void&[]");
|
||||
|
||||
addRow(ref(arr(voidTy())), bindToNothing, "void (&)[]");
|
||||
addRow(ref(arr(voidTy())), bindToAll, "void (&)[]");
|
||||
|
||||
addRow(ref(arr(arr(voidTy()))), bindToNothing, "void (&)[][]");
|
||||
addRow(ref(arr(arr(voidTy()))), bindToAll, "void (&)[][]");
|
||||
|
||||
addRow(ref(arr(ref(voidTy()))), bindToNothing, "void &(&)[]");
|
||||
addRow(ref(arr(ref(voidTy()))), bindToAll, "void&(&)[]");
|
||||
|
||||
addRow(arr(ref(arr(voidTy()))), bindToNothing, "void (&[])[]");
|
||||
addRow(arr(ref(arr(voidTy()))), bindToAll, "void (&[])[]");
|
||||
|
||||
// Rvalue References
|
||||
addRow(rref(voidTy()), bindToNothing, "void &&");
|
||||
addRow(rref(voidTy()), bindToAll, "void&&");
|
||||
|
||||
addRow(rref(cnst(voidTy())), bindToNothing, "const void &&");
|
||||
addRow(rref(cnst(voidTy())), bindToAll, "const void&&");
|
||||
|
||||
addRow(rref(cnst(voidTy())), bindToNothing, "const void && foo", "foo");
|
||||
addRow(rref(cnst(voidTy())), bindToAll, "const void&&foo", "foo");
|
||||
|
||||
addRow(rref(arr(voidTy())), bindToNothing, "void (&&)[]");
|
||||
addRow(rref(arr(voidTy())), bindToAll, "void (&&)[]");
|
||||
|
||||
addRow(rref(arr(arr(voidTy()))), bindToNothing, "void (&&)[][]");
|
||||
addRow(rref(arr(arr(voidTy()))), bindToAll, "void (&&)[][]");
|
||||
|
||||
// Pointers and references mixed
|
||||
addRow(cnst(ref(ptr(voidTy()))), bindToBothSpecifiers, "void *&const");
|
||||
addRow(cnst(ref(ptr(voidTy()))), bindToNothing, "void *& const");
|
||||
addRow(cnst(ref(ptr(voidTy()))), bindToAll, "void*&const");
|
||||
|
||||
// Functions with pointer parameters
|
||||
addRow(fnTy("foo", voidTy(), ptr(voidTy())), bindToNothing, "void foo(void *)", "foo");
|
||||
addRow(fnTy("foo", voidTy(), ptr(voidTy())), bindToAll, "void foo(void*)", "foo");
|
||||
|
||||
// Functions with pointer or reference returns
|
||||
addRow(ptr(fnTy("foo", ptr(voidTy()), intTy())), bindToNothing, "void *(*foo)(int)", "foo");
|
||||
addRow(ptr(fnTy("foo", ptr(voidTy()), intTy())), bindToAll, "void*(*foo)(int)", "foo");
|
||||
|
||||
addRow(ptr(fnTy("foo", ref(voidTy()), ptr(voidTy()))), bindToNothing, "void &(*foo)(void *)", "foo");
|
||||
addRow(ptr(fnTy("foo", ref(voidTy()), ptr(voidTy()))), bindToAll, "void&(*foo)(void*)", "foo");
|
||||
|
||||
addRow(fnTy("foo", ptr(voidTy()), intTy()), bindToNothing, "void *foo(int)", "foo");
|
||||
addRow(fnTy("foo", ptr(voidTy()), intTy()), bindToAll, "void*foo(int)", "foo");
|
||||
|
||||
addRow(fnTy("foo", ref(voidTy()), ptr(voidTy())), bindToNothing, "void &foo(void *)", "foo");
|
||||
addRow(fnTy("foo", ref(voidTy()), ptr(voidTy())), bindToAll, "void&foo(void*)", "foo");
|
||||
}
|
||||
|
||||
void tst_TypePrettyPrinter::basic()
|
||||
{
|
||||
QFETCH(FullySpecifiedType, type);
|
||||
QFETCH(Overview::StarBindFlags, starBindFlags);
|
||||
QFETCH(QString, name);
|
||||
QFETCH(QString, result);
|
||||
|
||||
Overview o;
|
||||
o.showReturnTypes = true;
|
||||
o.starBindFlags = starBindFlags;
|
||||
TypePrettyPrinter pp(&o);
|
||||
QCOMPARE(pp(type, name), result);
|
||||
}
|
||||
|
Reference in New Issue
Block a user