From ccae4fc93c3bdddc37e8cf68d9f0923fb64c1e7c Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Wed, 5 Jun 2024 17:32:36 +0200 Subject: [PATCH] CppEditor: Consider #pragma once when inserting includes Fixes: QTCREATORBUG-30808 Change-Id: Ib9f2ed1e428abfaa608b9dc42bc09dd2d403ee56 Reviewed-by: Christian Stenger --- src/libs/cplusplus/CppDocument.cpp | 9 ++++++ src/libs/cplusplus/CppDocument.h | 16 ++++++---- src/libs/cplusplus/FastPreprocessor.cpp | 6 ++++ src/libs/cplusplus/FastPreprocessor.h | 2 +- src/libs/cplusplus/Macro.h | 7 +++++ src/libs/cplusplus/PreprocessorClient.h | 2 ++ src/libs/cplusplus/pp-engine.cpp | 21 ++++++++++++- src/libs/cplusplus/pp-engine.h | 1 + src/plugins/cppeditor/cppsourceprocessor.cpp | 7 +++++ src/plugins/cppeditor/cppsourceprocessor.h | 1 + src/plugins/cppeditor/includeutils.cpp | 21 ++++++++----- .../quickfixes/bringidentifierintoscope.cpp | 19 ++++++++++++ .../preprocessor/tst_preprocessor.cpp | 31 +++++++++++++++++++ 13 files changed, 128 insertions(+), 15 deletions(-) diff --git a/src/libs/cplusplus/CppDocument.cpp b/src/libs/cplusplus/CppDocument.cpp index cdc036f1777..0b0d3669250 100644 --- a/src/libs/cplusplus/CppDocument.cpp +++ b/src/libs/cplusplus/CppDocument.cpp @@ -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 diff --git a/src/libs/cplusplus/CppDocument.h b/src/libs/cplusplus/CppDocument.h index 8834c7538a7..ca32807309f 100644 --- a/src/libs/cplusplus/CppDocument.h +++ b/src/libs/cplusplus/CppDocument.h @@ -11,15 +11,14 @@ #include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include 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 _pragmas; + QByteArray m_fingerprint; QByteArray _source; diff --git a/src/libs/cplusplus/FastPreprocessor.cpp b/src/libs/cplusplus/FastPreprocessor.cpp index 8dcc8509b15..88acd386e4c 100644 --- a/src/libs/cplusplus/FastPreprocessor.cpp +++ b/src/libs/cplusplus/FastPreprocessor.cpp @@ -85,6 +85,12 @@ void FastPreprocessor::macroAdded(const Macro ¯o) _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())) { diff --git a/src/libs/cplusplus/FastPreprocessor.h b/src/libs/cplusplus/FastPreprocessor.h index 641a0164cf2..cf1ac86cdf2 100644 --- a/src/libs/cplusplus/FastPreprocessor.h +++ b/src/libs/cplusplus/FastPreprocessor.h @@ -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 &); diff --git a/src/libs/cplusplus/Macro.h b/src/libs/cplusplus/Macro.h index 26a33ea8490..d5288dadf1b 100644 --- a/src/libs/cplusplus/Macro.h +++ b/src/libs/cplusplus/Macro.h @@ -157,4 +157,11 @@ private: }; }; +class CPLUSPLUS_EXPORT Pragma +{ +public: + QByteArrayList tokens; + int line; +}; + } // CPlusPlus diff --git a/src/libs/cplusplus/PreprocessorClient.h b/src/libs/cplusplus/PreprocessorClient.h index 2d0df7faf98..2c7ad0a42c3 100644 --- a/src/libs/cplusplus/PreprocessorClient.h +++ b/src/libs/cplusplus/PreprocessorClient.h @@ -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 ¯o) = 0; + virtual void pragmaAdded(const Pragma &pragma) = 0; virtual void passedMacroDefinitionCheck(int bytesOffset, int utf16charsOffset, int line, const Macro ¯o) = 0; diff --git a/src/libs/cplusplus/pp-engine.cpp b/src/libs/cplusplus/pp-engine.cpp index 74ca1ceb516..a1343e54713 100644 --- a/src/libs/cplusplus/pp-engine.cpp +++ b/src/libs/cplusplus/pp-engine.cpp @@ -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; diff --git a/src/libs/cplusplus/pp-engine.h b/src/libs/cplusplus/pp-engine.h index 2163380dea2..537f505b952 100644 --- a/src/libs/cplusplus/pp-engine.h +++ b/src/libs/cplusplus/pp-engine.h @@ -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); diff --git a/src/plugins/cppeditor/cppsourceprocessor.cpp b/src/plugins/cppeditor/cppsourceprocessor.cpp index 7796c5d2ab1..19a11a8e4ae 100644 --- a/src/plugins/cppeditor/cppsourceprocessor.cpp +++ b/src/plugins/cppeditor/cppsourceprocessor.cpp @@ -298,6 +298,13 @@ void CppSourceProcessor::macroAdded(const CPlusPlus::Macro ¯o) 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 ¯o) { diff --git a/src/plugins/cppeditor/cppsourceprocessor.h b/src/plugins/cppeditor/cppsourceprocessor.h index d5a2763e30d..1fccbbf74d7 100644 --- a/src/plugins/cppeditor/cppsourceprocessor.h +++ b/src/plugins/cppeditor/cppsourceprocessor.h @@ -68,6 +68,7 @@ private: // Client interface void macroAdded(const CPlusPlus::Macro ¯o) override; + void pragmaAdded(const CPlusPlus::Pragma &pragma) override; void passedMacroDefinitionCheck(int bytesOffset, int utf16charsOffset, int line, const CPlusPlus::Macro ¯o) override; void failedMacroDefinitionCheck(int bytesOffset, int utf16charOffset, diff --git a/src/plugins/cppeditor/includeutils.cpp b/src/plugins/cppeditor/includeutils.cpp index 8728e732f09..cd972de3516 100644 --- a/src/plugins/cppeditor/includeutils.cpp +++ b/src/plugins/cppeditor/includeutils.cpp @@ -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()) { + 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) { - if (newLinesToPrepend) - *newLinesToPrepend = 1; - if (newLinesToAppend) - *newLinesToAppend += 1; + appendAndPrependNewline(); insertLine = definedMacro.line() + 1; } } diff --git a/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.cpp b/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.cpp index 3c4df4cb76e..6a85c95dfc9 100644 --- a/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.cpp +++ b/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.cpp @@ -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" diff --git a/tests/auto/cplusplus/preprocessor/tst_preprocessor.cpp b/tests/auto/cplusplus/preprocessor/tst_preprocessor.cpp index 03cf4d930bd..2a1c1323d4a 100644 --- a/tests/auto/cplusplus/preprocessor/tst_preprocessor.cpp +++ b/tests/auto/cplusplus/preprocessor/tst_preprocessor.cpp @@ -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> usedMacros() const { return m_usedMacros; } + const QList &pragmas() const { return m_pragmas; } + private: Environment *m_env; QByteArray *m_output; @@ -282,6 +289,7 @@ private: QSet m_unresolvedDefines; QList m_macroArgsCount; QMap> m_usedMacros; + QList 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(""), input).startsWith("# 1 \"\"\n")); } +void tst_Preprocessor::pragmas() +{ + Environment env; + QByteArray output; + MockClient client(&env, &output); + Preprocessor preprocess(&client, &env); + QByteArray input = + "#pragma once\n" + "#include \n" + "#pragma pack(/*distraction*/push)\n" + "struct S { bool b1; int i; short s; bool b2; };\n" + "#pragma pack(pop)\n"; + preprocess.run(QLatin1String(""), 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;