CppEditor: Consider #pragma once when inserting includes

Fixes: QTCREATORBUG-30808
Change-Id: Ib9f2ed1e428abfaa608b9dc42bc09dd2d403ee56
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Christian Kandeler
2024-06-05 17:32:36 +02:00
parent d49934604b
commit ccae4fc93c
13 changed files with 128 additions and 15 deletions

View File

@@ -356,6 +356,15 @@ void Document::addUndefinedMacroUse(const QByteArray &name,
_undefinedMacroUses.append(use);
}
int Document::pragmaOnceLine() const
{
for (const Pragma &p : _pragmas) {
if (p.tokens.size() == 1 && p.tokens.first() == "once")
return p.line;
}
return -1;
}
/*!
\class Document::MacroUse
\brief The MacroUse class represents the usage of a macro in a

View File

@@ -11,15 +11,14 @@
#include <utils/filepath.h>
#include <QSharedPointer>
#include <QDateTime>
#include <QHash>
#include <QFuture>
#include <QAtomicInt>
#include <QByteArrayList>
#include <QDateTime>
#include <QFuture>
#include <QHash>
#include <QSharedPointer>
namespace CPlusPlus {
class Macro;
class MacroArgumentReference;
class LookupContext;
@@ -55,6 +54,9 @@ public:
void addUndefinedMacroUse(const QByteArray &name,
int bytesOffset, int utf16charsOffset);
void appendPragma(const Pragma &pragma) { _pragmas << pragma; }
int pragmaOnceLine() const;
Control *control() const { return _control; }
Control *swapControl(Control *newControl);
TranslationUnit *translationUnit() const { return _translationUnit; }
@@ -353,6 +355,8 @@ private:
/// the macro name of the include guard, if there is one.
QByteArray _includeGuardMacroName;
QList<Pragma> _pragmas;
QByteArray m_fingerprint;
QByteArray _source;

View File

@@ -85,6 +85,12 @@ void FastPreprocessor::macroAdded(const Macro &macro)
_currentDoc->appendMacro(macro);
}
void FastPreprocessor::pragmaAdded(const Pragma &pragma)
{
Q_ASSERT(_currentDoc);
_currentDoc->appendPragma(pragma);
}
static const Macro revision(const Snapshot &s, const Macro &m)
{
if (Document::Ptr d = s.document(m.filePath())) {

View File

@@ -40,7 +40,7 @@ public:
const Utils::FilePaths &initialIncludes = {});
virtual void macroAdded(const Macro &);
virtual void pragmaAdded(const Pragma &pragma);
virtual void passedMacroDefinitionCheck(int, int, int, const Macro &);
virtual void failedMacroDefinitionCheck(int, int, const ByteArrayRef &);

View File

@@ -157,4 +157,11 @@ private:
};
};
class CPLUSPLUS_EXPORT Pragma
{
public:
QByteArrayList tokens;
int line;
};
} // CPlusPlus

View File

@@ -14,6 +14,7 @@ namespace CPlusPlus {
class ByteArrayRef;
class Macro;
class Pragma;
class CPLUSPLUS_EXPORT MacroArgumentReference
{
@@ -61,6 +62,7 @@ public:
virtual ~Client() = 0;
virtual void macroAdded(const Macro &macro) = 0;
virtual void pragmaAdded(const Pragma &pragma) = 0;
virtual void passedMacroDefinitionCheck(int bytesOffset, int utf16charsOffset,
int line, const Macro &macro) = 0;

View File

@@ -1626,10 +1626,10 @@ void Preprocessor::handlePreprocessorDirective(PPToken *tk)
static const QByteArray ppInclude("include");
static const QByteArray ppIncludeNext("include_next");
static const QByteArray ppImport("import");
static const QByteArray ppPragma("pragma");
//### TODO:
// line
// error
// pragma
if (tk->is(T_IDENTIFIER)) {
const ByteArrayRef directive = tk->asByteArrayRef();
@@ -1640,6 +1640,8 @@ void Preprocessor::handlePreprocessorDirective(PPToken *tk)
handleIfDefDirective(true, tk);
} else if (directive == ppEndIf) {
handleEndIfDirective(tk, poundToken);
} else if (directive == ppPragma) {
handlePragmaDirective(tk);
} else {
m_state.updateIncludeGuardState(State::IncludeGuardStateHint_OtherToken);
@@ -1866,6 +1868,23 @@ void Preprocessor::handleDefineDirective(PPToken *tk)
m_client->macroAdded(macro);
}
void Preprocessor::handlePragmaDirective(PPToken *tk)
{
Pragma pragma;
pragma.line = tk->lineno;
lex(tk); // consume "pragma" token
while (isContinuationToken(*tk)) {
if (!consumeComments(tk))
return;
pragma.tokens << tk->asByteArrayRef().toByteArray();
lex(tk);
}
if (m_client)
m_client->pragmaAdded(pragma);
}
QByteArray Preprocessor::expand(PPToken *tk, PPToken *lastConditionToken)
{
unsigned line = tk->lineno;

View File

@@ -210,6 +210,7 @@ private:
void handlePreprocessorDirective(PPToken *tk);
void handleIncludeDirective(PPToken *tk, bool includeNext);
void handleDefineDirective(PPToken *tk);
void handlePragmaDirective(PPToken *tk);
QByteArray expand(PPToken *tk, PPToken *lastConditionToken = nullptr);
const Internal::PPToken evalExpression(PPToken *tk, Value &result);
void handleIfDirective(PPToken *tk);

View File

@@ -298,6 +298,13 @@ void CppSourceProcessor::macroAdded(const CPlusPlus::Macro &macro)
m_currentDoc->appendMacro(macro);
}
void CppSourceProcessor::pragmaAdded(const CPlusPlus::Pragma &pragma)
{
if (!m_currentDoc)
return;
m_currentDoc->appendPragma(pragma);
}
void CppSourceProcessor::passedMacroDefinitionCheck(int bytesOffset, int utf16charsOffset,
int line, const CPlusPlus::Macro &macro)
{

View File

@@ -68,6 +68,7 @@ private:
// Client interface
void macroAdded(const CPlusPlus::Macro &macro) override;
void pragmaAdded(const CPlusPlus::Pragma &pragma) override;
void passedMacroDefinitionCheck(int bytesOffset, int utf16charsOffset,
int line, const CPlusPlus::Macro &macro) override;
void failedMacroDefinitionCheck(int bytesOffset, int utf16charOffset,

View File

@@ -216,15 +216,22 @@ int LineForNewIncludeDirective::findInsertLineForVeryFirstInclude(unsigned *newL
{
int insertLine = 1;
// If there is an include guard, insert right after that one
const QByteArray includeGuardMacroName = m_cppDocument->includeGuardMacroName();
if (!includeGuardMacroName.isEmpty()) {
for (const Macro &definedMacro : m_cppDocument->definedMacros()) {
if (definedMacro.name() == includeGuardMacroName) {
const auto appendAndPrependNewline = [&] {
if (newLinesToPrepend)
*newLinesToPrepend = 1;
if (newLinesToAppend)
*newLinesToAppend += 1;
};
// If there is an include guard or a "#pragma once", insert right after that one
if (const int pragmaOnceLine = m_cppDocument->pragmaOnceLine(); pragmaOnceLine != -1) {
appendAndPrependNewline();
insertLine = pragmaOnceLine + 1;
} else if (const QByteArray includeGuardMacroName = m_cppDocument->includeGuardMacroName();
!includeGuardMacroName.isEmpty()) {
for (const Macro &definedMacro : m_cppDocument->definedMacros()) {
if (definedMacro.name() == includeGuardMacroName) {
appendAndPrependNewline();
insertLine = definedMacro.line() + 1;
}
}

View File

@@ -1145,6 +1145,25 @@ private slots:
// -------------------------------------------------------------------------------------------
original =
"#pragma once\n"
"void @f();\n"
;
expected =
"#pragma once\n"
"\n"
"#include \"file.h\"\n"
"\n"
"void f();\n"
;
testDocuments << CppTestDocument::create("file.cpp", original, expected);
QTest::newRow("inserting_onlyPragmaOnce")
<< TestIncludePaths::globalIncludePath()
<< testDocuments << firstRefactoringOperation << "\"file.h\"";
testDocuments.clear();
// -------------------------------------------------------------------------------------------
original =
"\n"
"// comment\n"

View File

@@ -108,6 +108,11 @@ public:
m_definedMacrosLine.append(macro.line());
}
void pragmaAdded(const Pragma &pragma) override
{
m_pragmas.append(pragma);
}
void passedMacroDefinitionCheck(int /*bytesOffset*/,
int /*utf16charsOffset*/,
int line,
@@ -264,6 +269,8 @@ public:
const QMap<QByteArray, QVector<MacroArgumentReference >> usedMacros() const
{ return m_usedMacros; }
const QList<Pragma> &pragmas() const { return m_pragmas; }
private:
Environment *m_env;
QByteArray *m_output;
@@ -282,6 +289,7 @@ private:
QSet<QByteArray> m_unresolvedDefines;
QList<int> m_macroArgsCount;
QMap<QByteArray, QVector<MacroArgumentReference >> m_usedMacros;
QList<Pragma> m_pragmas;
};
QT_BEGIN_NAMESPACE
@@ -381,6 +389,7 @@ private slots:
void trigraph();
void nested_arguments_expansion();
void preprocessorSymbolsAsMacroArguments();
void pragmas();
};
// Remove all #... lines, and 'simplify' string, to allow easily comparing the result
@@ -2090,6 +2099,28 @@ void tst_Preprocessor::preprocessorSymbolsAsMacroArguments()
QVERIFY(preprocess.run(QLatin1String("<stdin>"), input).startsWith("# 1 \"<stdin>\"\n"));
}
void tst_Preprocessor::pragmas()
{
Environment env;
QByteArray output;
MockClient client(&env, &output);
Preprocessor preprocess(&client, &env);
QByteArray input =
"#pragma once\n"
"#include <iostream>\n"
"#pragma pack(/*distraction*/push)\n"
"struct S { bool b1; int i; short s; bool b2; };\n"
"#pragma pack(pop)\n";
preprocess.run(QLatin1String("<stdin>"), input);
QCOMPARE(client.pragmas().size(), 3);
QCOMPARE(client.pragmas().at(0).line, 1);
QCOMPARE(client.pragmas().at(0).tokens, QByteArrayList{"once"});
QCOMPARE(client.pragmas().at(1).line, 3);
QCOMPARE(client.pragmas().at(1).tokens, (QByteArrayList{"pack", "(", "push", ")"}));
QCOMPARE(client.pragmas().at(2).line, 5);
QCOMPARE(client.pragmas().at(2).tokens, (QByteArrayList{"pack", "(", "pop", ")"}));
}
void tst_Preprocessor::excessive_nesting()
{
Environment env;