From 1a426d9f0146a11f40686be4b929ccd3f8399b61 Mon Sep 17 00:00:00 2001
From: Nikolai Kosjar <nikolai.kosjar@qt.io>
Date: Tue, 13 Sep 2016 10:41:22 +0200
Subject: [PATCH] Clang: Support second translation unit

A TranslationUnit is owned by TranslationUnits now. TranslationUnits
allows to add another TranslationUnit and to update/query the recently
and previously parsed translation unit.

This does not change any behavior yet.

Change-Id: I8a2f0cc05d3e51bf739dd5d7c4da14b54147f3ab
Reviewed-by: David Schulz <david.schulz@qt.io>
---
 .../ipcsource/clangbackend_global.h           |  36 +++++
 .../ipcsource/clangbackendclangipc-source.pri |   3 +
 .../clangbackend/ipcsource/clangdocument.cpp  |  30 +++-
 .../clangbackend/ipcsource/clangdocument.h    |   6 +-
 .../ipcsource/clangexceptions.cpp             |   7 +
 .../clangbackend/ipcsource/clangexceptions.h  |   6 +
 .../ipcsource/clangtranslationunit.cpp        |  19 ++-
 .../ipcsource/clangtranslationunit.h          |   6 +-
 .../ipcsource/clangtranslationunits.cpp       | 135 +++++++++++++++++
 .../ipcsource/clangtranslationunits.h         |  80 ++++++++++
 .../ipcsource/clangtranslationunitupdater.cpp |   4 +-
 .../ipcsource/clangtranslationunitupdater.h   |   6 +-
 tests/unit/unittest/clangdocument-test.cpp    |  25 +++
 .../unittest/clangtranslationunits-test.cpp   | 143 ++++++++++++++++++
 tests/unit/unittest/cursor-test.cpp           |   1 +
 .../unit/unittest/highlightingmarks-test.cpp  |   1 +
 .../unittest/skippedsourceranges-test.cpp     |   1 +
 tests/unit/unittest/sourcerange-test.cpp      |   1 +
 .../unittest/translationunitupdater-test.cpp  |  19 ++-
 tests/unit/unittest/unittest.pro              |   1 +
 20 files changed, 508 insertions(+), 22 deletions(-)
 create mode 100644 src/tools/clangbackend/ipcsource/clangbackend_global.h
 create mode 100644 src/tools/clangbackend/ipcsource/clangtranslationunits.cpp
 create mode 100644 src/tools/clangbackend/ipcsource/clangtranslationunits.h
 create mode 100644 tests/unit/unittest/clangtranslationunits-test.cpp

diff --git a/src/tools/clangbackend/ipcsource/clangbackend_global.h b/src/tools/clangbackend/ipcsource/clangbackend_global.h
new file mode 100644
index 0000000000..ff17d1c104
--- /dev/null
+++ b/src/tools/clangbackend/ipcsource/clangbackend_global.h
@@ -0,0 +1,36 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+namespace ClangBackEnd {
+
+enum class PreferredTranslationUnit
+{
+    RecentlyParsed,
+    PreviouslyParsed,
+};
+
+} // namespace ClangBackEnd
diff --git a/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri b/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri
index dc468470f5..f79c1dcab8 100644
--- a/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri
+++ b/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri
@@ -29,6 +29,7 @@ HEADERS += $$PWD/clangcodemodelserver.h \
     $$PWD/highlightingmarksiterator.h \
     $$PWD/utf8positionfromlinecolumn.h \
     $$PWD/clangasyncjob.h \
+    $$PWD/clangbackend_global.h \
     $$PWD/clangcompletecodejob.h \
     $$PWD/clangcreateinitialdocumentpreamblejob.h \
     $$PWD/clangfilepath.h \
@@ -44,6 +45,7 @@ HEADERS += $$PWD/clangcodemodelserver.h \
     $$PWD/clangexceptions.h \
     $$PWD/clangdocumentprocessor.h \
     $$PWD/clangdocumentprocessors.h \
+    $$PWD/clangtranslationunits.h
 
 SOURCES += $$PWD/clangcodemodelserver.cpp \
     $$PWD/codecompleter.cpp \
@@ -85,3 +87,4 @@ SOURCES += $$PWD/clangcodemodelserver.cpp \
     $$PWD/clangexceptions.cpp \
     $$PWD/clangdocumentprocessor.cpp \
     $$PWD/clangdocumentprocessors.cpp \
+    $$PWD/clangtranslationunits.cpp
diff --git a/src/tools/clangbackend/ipcsource/clangdocument.cpp b/src/tools/clangbackend/ipcsource/clangdocument.cpp
index 3e9b6971ab..af45cc43f5 100644
--- a/src/tools/clangbackend/ipcsource/clangdocument.cpp
+++ b/src/tools/clangbackend/ipcsource/clangdocument.cpp
@@ -32,6 +32,7 @@
 #include "projectpart.h"
 #include "clangexceptions.h"
 #include "clangtranslationunit.h"
+#include "clangtranslationunits.h"
 #include "clangtranslationunitupdater.h"
 #include "unsavedfiles.h"
 #include "unsavedfile.h"
@@ -64,8 +65,7 @@ public:
     ProjectPart projectPart;
     time_point lastProjectPartChangeTimePoint;
 
-    CXTranslationUnit translationUnit = nullptr;
-    CXIndex index = nullptr;
+    TranslationUnits translationUnits;
 
     QSet<Utf8String> dependedFilePaths;
 
@@ -86,15 +86,15 @@ DocumentData::DocumentData(const Utf8String &filePath,
       fileArguments(fileArguments),
       projectPart(projectPart),
       lastProjectPartChangeTimePoint(std::chrono::steady_clock::now()),
+      translationUnits(filePath),
       needsToBeReparsedChangeTimePoint(lastProjectPartChangeTimePoint)
 {
     dependedFilePaths.insert(filePath);
+    translationUnits.createAndAppend();
 }
 
 DocumentData::~DocumentData()
 {
-    clang_disposeTranslationUnit(translationUnit);
-    clang_disposeIndex(index);
 }
 
 Document::Document(const Utf8String &filePath,
@@ -282,8 +282,13 @@ TranslationUnitUpdateInput Document::createUpdateInput() const
 
 TranslationUnitUpdater Document::createUpdater() const
 {
+    TranslationUnit unit = translationUnit();
+
     const TranslationUnitUpdateInput updateInput = createUpdateInput();
-    TranslationUnitUpdater updater(d->index, d->translationUnit, updateInput);
+    TranslationUnitUpdater updater(unit.id(),
+                                   unit.cxIndex(),
+                                   unit.cxTranslationUnit(),
+                                   updateInput);
 
     return updater;
 }
@@ -304,9 +309,13 @@ void Document::incorporateUpdaterResult(const TranslationUnitUpdateResult &resul
     if (result.hasParsed())
         d->lastProjectPartChangeTimePoint = result.parseTimePoint;
 
-    if (result.hasParsed() || result.hasReparsed())
+    if (result.hasParsed() || result.hasReparsed()) {
         d->dependedFilePaths = result.dependedOnFilePaths;
 
+        const time_point timePoint = qMax(result.parseTimePoint, result.reparseTimePoint);
+        d->translationUnits.updateParseTimePoint(result.translationUnitId, timePoint);
+    }
+
     d->documents.addWatchedFiles(d->dependedFilePaths);
 
     if (result.hasReparsed()
@@ -315,11 +324,16 @@ void Document::incorporateUpdaterResult(const TranslationUnitUpdateResult &resul
     }
 }
 
-TranslationUnit Document::translationUnit() const
+TranslationUnit Document::translationUnit(PreferredTranslationUnit preferredTranslationUnit) const
 {
     checkIfNull();
 
-    return TranslationUnit(d->filePath, d->index, d->translationUnit);
+    return d->translationUnits.get(preferredTranslationUnit);
+}
+
+TranslationUnits &Document::translationUnits() const
+{
+    return d->translationUnits;
 }
 
 void Document::parse() const
diff --git a/src/tools/clangbackend/ipcsource/clangdocument.h b/src/tools/clangbackend/ipcsource/clangdocument.h
index 55c1bbb4d4..712b10d6de 100644
--- a/src/tools/clangbackend/ipcsource/clangdocument.h
+++ b/src/tools/clangbackend/ipcsource/clangdocument.h
@@ -27,6 +27,7 @@
 
 #include "clangtranslationunitupdater.h"
 
+#include "clangbackend_global.h"
 #include "clangtranslationunit.h"
 
 #include <utf8stringvector.h>
@@ -44,6 +45,7 @@ class Utf8String;
 namespace ClangBackEnd {
 
 class TranslationUnit;
+class TranslationUnits;
 class DocumentData;
 class TranslationUnitUpdateResult;
 class ProjectPart;
@@ -104,7 +106,9 @@ public:
     TranslationUnitUpdateInput createUpdateInput() const;
     void incorporateUpdaterResult(const TranslationUnitUpdateResult &result) const;
 
-    TranslationUnit translationUnit() const;
+    TranslationUnit translationUnit(PreferredTranslationUnit preferredTranslationUnit
+                                        = PreferredTranslationUnit::RecentlyParsed) const;
+    TranslationUnits &translationUnits() const;
 
 public: // for tests
     void parse() const;
diff --git a/src/tools/clangbackend/ipcsource/clangexceptions.cpp b/src/tools/clangbackend/ipcsource/clangexceptions.cpp
index 4a85df4129..187ce98d1a 100644
--- a/src/tools/clangbackend/ipcsource/clangexceptions.cpp
+++ b/src/tools/clangbackend/ipcsource/clangexceptions.cpp
@@ -94,4 +94,11 @@ DocumentProcessorDoesNotExist::DocumentProcessorDoesNotExist(const Utf8String &f
            + Utf8StringLiteral("' does not exist!");
 }
 
+TranslationUnitDoesNotExist::TranslationUnitDoesNotExist(const Utf8String &filePath)
+{
+    m_info += Utf8StringLiteral("TranslationUnit for file '")
+            + filePath
+            + Utf8StringLiteral("' does not exist.");
+}
+
 } // namespace ClangBackEnd
diff --git a/src/tools/clangbackend/ipcsource/clangexceptions.h b/src/tools/clangbackend/ipcsource/clangexceptions.h
index 1fbedccb38..ffc1c09da1 100644
--- a/src/tools/clangbackend/ipcsource/clangexceptions.h
+++ b/src/tools/clangbackend/ipcsource/clangexceptions.h
@@ -88,4 +88,10 @@ public:
                                   const Utf8String &projectPartId);
 };
 
+class TranslationUnitDoesNotExist : public ClangBaseException
+{
+public:
+    TranslationUnitDoesNotExist(const Utf8String &filePath);
+};
+
 } // namespace ClangBackEnd
diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp b/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp
index 37bdf08891..2b963b21ca 100644
--- a/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp
+++ b/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp
@@ -38,10 +38,12 @@
 
 namespace ClangBackEnd {
 
-TranslationUnit::TranslationUnit(const Utf8String &filepath,
+TranslationUnit::TranslationUnit(const Utf8String &id,
+                                 const Utf8String &filepath,
                                  CXIndex &cxIndex,
                                  CXTranslationUnit &cxTranslationUnit)
-    : m_filePath(filepath)
+    : m_id(id)
+    , m_filePath(filepath)
     , m_cxIndex(cxIndex)
     , m_cxTranslationUnit(cxTranslationUnit)
 {
@@ -49,7 +51,12 @@ TranslationUnit::TranslationUnit(const Utf8String &filepath,
 
 bool TranslationUnit::isNull() const
 {
-    return !m_cxTranslationUnit || !m_cxIndex || m_filePath.isEmpty();
+    return !m_cxTranslationUnit || !m_cxIndex || m_filePath.isEmpty() || m_id.isEmpty();
+}
+
+Utf8String TranslationUnit::id() const
+{
+    return m_id;
 }
 
 Utf8String TranslationUnit::filePath() const
@@ -70,7 +77,7 @@ CXTranslationUnit &TranslationUnit::cxTranslationUnit() const
 TranslationUnitUpdateResult TranslationUnit::update(
         const TranslationUnitUpdateInput &parseInput) const
 {
-    TranslationUnitUpdater updater(cxIndex(), cxTranslationUnit(), parseInput);
+    TranslationUnitUpdater updater(id(), cxIndex(), cxTranslationUnit(), parseInput);
 
     return updater.update(TranslationUnitUpdater::UpdateMode::AsNeeded);
 }
@@ -78,7 +85,7 @@ TranslationUnitUpdateResult TranslationUnit::update(
 TranslationUnitUpdateResult TranslationUnit::parse(
         const TranslationUnitUpdateInput &parseInput) const
 {
-    TranslationUnitUpdater updater(cxIndex(), cxTranslationUnit(), parseInput);
+    TranslationUnitUpdater updater(id(), cxIndex(), cxTranslationUnit(), parseInput);
 
     return updater.update(TranslationUnitUpdater::UpdateMode::ParseIfNeeded);
 }
@@ -86,7 +93,7 @@ TranslationUnitUpdateResult TranslationUnit::parse(
 TranslationUnitUpdateResult TranslationUnit::reparse(
         const TranslationUnitUpdateInput &parseInput) const
 {
-    TranslationUnitUpdater updater(cxIndex(), cxTranslationUnit(), parseInput);
+    TranslationUnitUpdater updater(id(), cxIndex(), cxTranslationUnit(), parseInput);
 
     return updater.update(TranslationUnitUpdater::UpdateMode::ForceReparse);
 }
diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunit.h b/src/tools/clangbackend/ipcsource/clangtranslationunit.h
index 3b7e81f618..cf65dddc3a 100644
--- a/src/tools/clangbackend/ipcsource/clangtranslationunit.h
+++ b/src/tools/clangbackend/ipcsource/clangtranslationunit.h
@@ -57,12 +57,15 @@ public:
     };
 
 public:
-    TranslationUnit(const Utf8String &filePath,
+    TranslationUnit(const Utf8String &id,
+                    const Utf8String &filePath,
                     CXIndex &cxIndex,
                     CXTranslationUnit &cxTranslationUnit);
 
     bool isNull() const;
 
+    Utf8String id() const;
+
     Utf8String filePath() const;
     CXIndex &cxIndex() const;
     CXTranslationUnit &cxTranslationUnit() const;
@@ -94,6 +97,7 @@ public:
     SkippedSourceRanges skippedSourceRanges() const;
 
 private:
+    const Utf8String m_id;
     const Utf8String m_filePath;
     CXIndex &m_cxIndex;
     CXTranslationUnit &m_cxTranslationUnit;
diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunits.cpp b/src/tools/clangbackend/ipcsource/clangtranslationunits.cpp
new file mode 100644
index 0000000000..6992dbd1a6
--- /dev/null
+++ b/src/tools/clangbackend/ipcsource/clangtranslationunits.cpp
@@ -0,0 +1,135 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "clangtranslationunits.h"
+
+#include "clangexceptions.h"
+#include "clangtranslationunit.h"
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+
+#include <QFileInfo>
+#include <QLoggingCategory>
+#include <QUuid>
+
+#include <algorithm>
+
+Q_LOGGING_CATEGORY(tuLog, "qtc.clangbackend.translationunits");
+
+namespace ClangBackEnd {
+
+TranslationUnits::TranslationUnits(const Utf8String &filePath)
+    : m_filePath(filePath)
+{
+}
+
+TranslationUnits::~TranslationUnits()
+{
+    foreach (const TranslationUnitData &unit, m_tuDatas) {
+        clang_disposeTranslationUnit(unit.cxTranslationUnit);
+        clang_disposeIndex(unit.cxIndex);
+    }
+}
+
+TranslationUnit TranslationUnits::createAndAppend()
+{
+    const Utf8String id = Utf8String::fromByteArray(QUuid::createUuid().toByteArray());
+    qCDebug(tuLog) << "Creating TranslationUnit" << id << "for" << QFileInfo(m_filePath).fileName();
+
+    m_tuDatas.append(TranslationUnitData(id));
+    TranslationUnitData &translationUnitData = m_tuDatas.last();
+
+    return toTranslationUnit(translationUnitData);
+}
+
+TranslationUnit TranslationUnits::get(PreferredTranslationUnit type)
+{
+    if (m_tuDatas.isEmpty())
+        throw TranslationUnitDoesNotExist(m_filePath);
+
+    if (m_tuDatas.size() == 1 || !areAllTranslationUnitsParsed())
+        return toTranslationUnit(m_tuDatas.first());
+
+    return getPreferredTranslationUnit(type);
+}
+
+void TranslationUnits::updateParseTimePoint(const Utf8String &translationUnitId,
+                                            time_point timePoint)
+{
+    TranslationUnitData &unit = findUnit(translationUnitId);
+
+    QTC_CHECK(timePoint != time_point());
+    unit.parseTimePoint = timePoint;
+
+    qCDebug(tuLog) << "Updated" << translationUnitId << "for" << QFileInfo(m_filePath).fileName()
+        << "RecentlyParsed:" << get(PreferredTranslationUnit::RecentlyParsed).id()
+        << "PreviouslyParsed:" << get(PreferredTranslationUnit::PreviouslyParsed).id();
+}
+
+bool TranslationUnits::areAllTranslationUnitsParsed() const
+{
+    return Utils::allOf(m_tuDatas, [](const TranslationUnitData &unit) {
+        return unit.parseTimePoint != time_point();
+    });
+}
+
+TranslationUnit TranslationUnits::getPreferredTranslationUnit(PreferredTranslationUnit type)
+{
+    using TuData = TranslationUnitData;
+
+    const auto lessThan = [](const TuData &a, const TuData &b) {
+        return a.parseTimePoint < b.parseTimePoint;
+    };
+    auto translationUnitData = type == PreferredTranslationUnit::RecentlyParsed
+            ? std::max_element(m_tuDatas.begin(), m_tuDatas.end(), lessThan)
+            : std::min_element(m_tuDatas.begin(), m_tuDatas.end(), lessThan);
+
+    if (translationUnitData == m_tuDatas.end())
+        throw TranslationUnitDoesNotExist(m_filePath);
+
+    return toTranslationUnit(*translationUnitData);
+}
+
+TranslationUnits::TranslationUnitData &TranslationUnits::findUnit(
+        const Utf8String &translationUnitId)
+{
+    for (TranslationUnitData &unit : m_tuDatas) {
+        if (translationUnitId == unit.id)
+            return unit;
+    }
+
+    throw TranslationUnitDoesNotExist(m_filePath);
+}
+
+TranslationUnit TranslationUnits::toTranslationUnit(TranslationUnits::TranslationUnitData &unit)
+{
+    return TranslationUnit(unit.id,
+                           m_filePath,
+                           unit.cxIndex,
+                           unit.cxTranslationUnit);
+}
+
+} // namespace ClangBackEnd
diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunits.h b/src/tools/clangbackend/ipcsource/clangtranslationunits.h
new file mode 100644
index 0000000000..b5bd1dc3d1
--- /dev/null
+++ b/src/tools/clangbackend/ipcsource/clangtranslationunits.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "clangbackend_global.h"
+
+#include <utf8string.h>
+
+#include <clang-c/Index.h>
+
+#include <QList>
+
+#include <chrono>
+
+namespace ClangBackEnd {
+
+using time_point = std::chrono::steady_clock::time_point;
+
+class TranslationUnit;
+
+class TranslationUnits
+{
+public:
+    class TranslationUnitData {
+    public:
+        TranslationUnitData(const Utf8String &id)
+            : id(id)
+        {}
+
+        Utf8String id;
+
+        CXTranslationUnit cxTranslationUnit = nullptr;
+        CXIndex cxIndex = nullptr;
+
+        time_point parseTimePoint;
+    };
+
+public:
+    TranslationUnits(const Utf8String &filePath);
+    ~TranslationUnits();
+
+    TranslationUnit createAndAppend();
+    TranslationUnit get(PreferredTranslationUnit type = PreferredTranslationUnit::RecentlyParsed);
+    void updateParseTimePoint(const Utf8String &translationUnitId, time_point timePoint);
+
+private:
+    bool areAllTranslationUnitsParsed() const;
+    TranslationUnit getPreferredTranslationUnit(PreferredTranslationUnit type);
+    TranslationUnitData &findUnit(const Utf8String &translationUnitId);
+    TranslationUnit toTranslationUnit(TranslationUnitData &unit);
+
+private:
+    Utf8String m_filePath;
+    QList<TranslationUnitData> m_tuDatas;
+};
+
+} // namespace ClangBackEnd
diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.cpp b/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.cpp
index 51e34dc53c..dec3334be4 100644
--- a/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.cpp
+++ b/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.cpp
@@ -40,13 +40,15 @@ static bool isVerboseModeEnabled()
 
 namespace ClangBackEnd {
 
-TranslationUnitUpdater::TranslationUnitUpdater(CXIndex &index,
+TranslationUnitUpdater::TranslationUnitUpdater(const Utf8String translationUnitId,
+                                               CXIndex &index,
                                                CXTranslationUnit &cxTranslationUnit,
                                                const TranslationUnitUpdateInput &updateData)
     : m_cxIndex(index)
     , m_cxTranslationUnit(cxTranslationUnit)
     , m_in(updateData)
 {
+    m_out.translationUnitId = translationUnitId;
 }
 
 TranslationUnitUpdateResult TranslationUnitUpdater::update(UpdateMode mode)
diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.h b/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.h
index 6802daf7eb..8f34339111 100644
--- a/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.h
+++ b/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.h
@@ -63,8 +63,9 @@ public:
     { return reparseTimePoint != time_point(); }
 
 public:
-    bool hasParseOrReparseFailed = false;
+    Utf8String translationUnitId;
 
+    bool hasParseOrReparseFailed = false;
     time_point parseTimePoint;
     time_point reparseTimePoint;
     time_point needsToBeReparsedChangeTimePoint;
@@ -81,7 +82,8 @@ public:
     };
 
 public:
-    TranslationUnitUpdater(CXIndex &index,
+    TranslationUnitUpdater(const Utf8String translationUnitId,
+                           CXIndex &index,
                            CXTranslationUnit &cxTranslationUnit,
                            const TranslationUnitUpdateInput &in);
 
diff --git a/tests/unit/unittest/clangdocument-test.cpp b/tests/unit/unittest/clangdocument-test.cpp
index 010964fb2f..8ec6a608cc 100644
--- a/tests/unit/unittest/clangdocument-test.cpp
+++ b/tests/unit/unittest/clangdocument-test.cpp
@@ -27,6 +27,8 @@
 
 #include <clangfilepath.h>
 #include <clangtranslationunitupdater.h>
+#include <clangtranslationunits.h>
+#include <clangtranslationunit.h>
 #include <commandlinearguments.h>
 #include <diagnosticset.h>
 #include <highlightingmarks.h>
@@ -56,6 +58,8 @@ using ClangBackEnd::ProjectPart;
 using ClangBackEnd::ProjectPartContainer;
 using ClangBackEnd::Documents;
 using ClangBackEnd::TranslationUnitUpdateResult;
+using ClangBackEnd::TranslationUnit;
+using ClangBackEnd::TranslationUnits;
 
 using testing::IsNull;
 using testing::NotNull;
@@ -332,6 +336,7 @@ TEST_F(Document, IncorporateUpdaterResultResetsDirtyness)
     TranslationUnitUpdateResult result;
     result.reparseTimePoint = std::chrono::steady_clock::now();
     result.needsToBeReparsedChangeTimePoint = document.isNeededReparseChangeTimePoint();
+    result.translationUnitId = document.translationUnit().id();
 
     document.incorporateUpdaterResult(result);
 
@@ -343,6 +348,7 @@ TEST_F(Document, IncorporateUpdaterResultDoesNotResetDirtynessIfItWasChanged)
     TranslationUnitUpdateResult result;
     result.reparseTimePoint = std::chrono::steady_clock::now();
     result.needsToBeReparsedChangeTimePoint = std::chrono::steady_clock::now();
+    result.translationUnitId = document.translationUnit().id();
     document.setDirtyIfDependencyIsMet(document.filePath());
 
     document.incorporateUpdaterResult(result);
@@ -350,6 +356,25 @@ TEST_F(Document, IncorporateUpdaterResultDoesNotResetDirtynessIfItWasChanged)
     ASSERT_TRUE(document.isNeedingReparse());
 }
 
+TEST_F(Document, IncorporateUpdaterResultUpdatesTranslationUnitsReparseTimePoint)
+{
+    TranslationUnits &translationUnits = document.translationUnits();
+    const TranslationUnit initialTranslationUnit = translationUnits.get();
+    translationUnits.updateParseTimePoint(initialTranslationUnit.id(), std::chrono::steady_clock::now());
+    const TranslationUnit alternativeTranslationUnit = translationUnits.createAndAppend();
+    translationUnits.updateParseTimePoint(alternativeTranslationUnit.id(), std::chrono::steady_clock::now());
+    TranslationUnitUpdateResult result;
+    result.reparseTimePoint = std::chrono::steady_clock::now();
+    result.needsToBeReparsedChangeTimePoint = std::chrono::steady_clock::now();
+    result.translationUnitId = initialTranslationUnit.id();
+    document.setDirtyIfDependencyIsMet(document.filePath());
+    ASSERT_THAT(translationUnits.get().id(), Eq(alternativeTranslationUnit.id()));
+
+    document.incorporateUpdaterResult(result);
+
+    ASSERT_THAT(translationUnits.get().id(), Eq(initialTranslationUnit.id()));
+}
+
 void Document::SetUp()
 {
     projects.createOrUpdate({ProjectPartContainer(projectPartId)});
diff --git a/tests/unit/unittest/clangtranslationunits-test.cpp b/tests/unit/unittest/clangtranslationunits-test.cpp
new file mode 100644
index 0000000000..bf039338aa
--- /dev/null
+++ b/tests/unit/unittest/clangtranslationunits-test.cpp
@@ -0,0 +1,143 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include <clangbackend_global.h>
+#include <clangexceptions.h>
+#include <clangtranslationunit.h>
+#include <clangtranslationunits.h>
+#include <utf8string.h>
+
+#include <clang-c/Index.h>
+
+#include <chrono>
+
+#include <gmock/gmock.h>
+#include <gmock/gmock-matchers.h>
+#include <gtest/gtest.h>
+#include "gtest-qt-printing.h"
+
+using ClangBackEnd::TranslationUnit;
+using ClangBackEnd::TranslationUnits;
+using ClangBackEnd::TranslationUnitDoesNotExist;
+using ClangBackEnd::PreferredTranslationUnit;
+
+using testing::Eq;
+
+namespace {
+
+class TranslationUnits : public ::testing::Test
+{
+protected:
+    Utf8String someFilePath = Utf8StringLiteral("someFilePath");
+    ClangBackEnd::TranslationUnits translationUnits{someFilePath};
+};
+
+TEST_F(TranslationUnits, CreatedUnitIsNull)
+{
+    TranslationUnit translationUnit = translationUnits.createAndAppend();
+
+    ASSERT_TRUE(translationUnit.isNull());
+}
+
+TEST_F(TranslationUnits, GetThrowsForNotExisting)
+{
+    ASSERT_THROW(translationUnits.get(), TranslationUnitDoesNotExist);
+}
+
+TEST_F(TranslationUnits, GetForSingleTranslationUnit)
+{
+    const TranslationUnit created = translationUnits.createAndAppend();
+
+    const TranslationUnit queried = translationUnits.get();
+
+    ASSERT_THAT(queried.id(), Eq(created.id()));
+}
+
+TEST_F(TranslationUnits, GetFirstForMultipleTranslationUnits)
+{
+    const TranslationUnit created1 = translationUnits.createAndAppend();
+    translationUnits.createAndAppend();
+
+    const TranslationUnit queried = translationUnits.get();
+
+    ASSERT_THAT(queried.id(), Eq(created1.id()));
+}
+
+TEST_F(TranslationUnits, GetFirstForMultipleTranslationUnitsAndOnlyFirstParsed)
+{
+    const TranslationUnit created1 = translationUnits.createAndAppend();
+    translationUnits.updateParseTimePoint(created1.id(), std::chrono::steady_clock::now());
+    translationUnits.createAndAppend();
+
+    const TranslationUnit queried = translationUnits.get();
+
+    ASSERT_THAT(queried.id(), Eq(created1.id()));
+}
+
+TEST_F(TranslationUnits, GetFirstForMultipleTranslationUnitsAndOnlySecondParsed)
+{
+    const TranslationUnit created1 = translationUnits.createAndAppend();
+    const TranslationUnit created2 = translationUnits.createAndAppend();
+    translationUnits.updateParseTimePoint(created2.id(), std::chrono::steady_clock::now());
+
+    const TranslationUnit queried = translationUnits.get();
+
+    ASSERT_THAT(queried.id(), Eq(created1.id()));
+}
+
+TEST_F(TranslationUnits, GetRecentForMultipleTranslationUnits)
+{
+    const TranslationUnit created1 = translationUnits.createAndAppend();
+    translationUnits.updateParseTimePoint(created1.id(), std::chrono::steady_clock::now());
+    const TranslationUnit created2 = translationUnits.createAndAppend();
+    translationUnits.updateParseTimePoint(created2.id(), std::chrono::steady_clock::now());
+
+    const TranslationUnit queried = translationUnits.get(PreferredTranslationUnit::RecentlyParsed);
+
+    ASSERT_THAT(queried.id(), Eq(created2.id()));
+}
+
+TEST_F(TranslationUnits, GetPreviousForMultipleTranslationUnits)
+{
+    const TranslationUnit created1 = translationUnits.createAndAppend();
+    translationUnits.updateParseTimePoint(created1.id(), std::chrono::steady_clock::now());
+    const TranslationUnit created2 = translationUnits.createAndAppend();
+    translationUnits.updateParseTimePoint(created2.id(), std::chrono::steady_clock::now());
+
+    const TranslationUnit queried = translationUnits.get(PreferredTranslationUnit::PreviouslyParsed);
+
+    ASSERT_THAT(queried.id(), Eq(created1.id()));
+}
+
+TEST_F(TranslationUnits, UpdateThrowsForNotExisting)
+{
+    ClangBackEnd::TranslationUnits otherTranslationUnits{someFilePath};
+    const TranslationUnit translationUnit = otherTranslationUnits.createAndAppend();
+
+    ASSERT_THROW(translationUnits.updateParseTimePoint(translationUnit.id(), std::chrono::steady_clock::now()),
+                 TranslationUnitDoesNotExist);
+}
+
+} // anonymous namespace
diff --git a/tests/unit/unittest/cursor-test.cpp b/tests/unit/unittest/cursor-test.cpp
index 6fa4249c72..bba963be86 100644
--- a/tests/unit/unittest/cursor-test.cpp
+++ b/tests/unit/unittest/cursor-test.cpp
@@ -68,6 +68,7 @@ struct Data {
                       {},
                       documents};
     TranslationUnit translationUnit{filePath,
+                                    filePath,
                                     document.translationUnit().cxIndex(),
                                     document.translationUnit().cxTranslationUnit()};
 };
diff --git a/tests/unit/unittest/highlightingmarks-test.cpp b/tests/unit/unittest/highlightingmarks-test.cpp
index de79d379fd..577fa66fdd 100644
--- a/tests/unit/unittest/highlightingmarks-test.cpp
+++ b/tests/unit/unittest/highlightingmarks-test.cpp
@@ -111,6 +111,7 @@ struct Data {
                       {},
                       documents};
     TranslationUnit translationUnit{filePath,
+                                    filePath,
                                     document.translationUnit().cxIndex(),
                                     document.translationUnit().cxTranslationUnit()};
 };
diff --git a/tests/unit/unittest/skippedsourceranges-test.cpp b/tests/unit/unittest/skippedsourceranges-test.cpp
index e775961a84..1e1d9d4ee6 100644
--- a/tests/unit/unittest/skippedsourceranges-test.cpp
+++ b/tests/unit/unittest/skippedsourceranges-test.cpp
@@ -99,6 +99,7 @@ struct Data {
                       {},
                       documents};
     TranslationUnit translationUnit{filePath,
+                                    filePath,
                                     document.translationUnit().cxIndex(),
                                     document.translationUnit().cxTranslationUnit()};
 };
diff --git a/tests/unit/unittest/sourcerange-test.cpp b/tests/unit/unittest/sourcerange-test.cpp
index a9adee6413..c50bb98dc2 100644
--- a/tests/unit/unittest/sourcerange-test.cpp
+++ b/tests/unit/unittest/sourcerange-test.cpp
@@ -103,6 +103,7 @@ struct Data {
                       Utf8StringVector(),
                       documents};
     TranslationUnit translationUnit{filePath,
+                                    filePath,
                                     document.translationUnit().cxIndex(),
                                     document.translationUnit().cxTranslationUnit()};
 
diff --git a/tests/unit/unittest/translationunitupdater-test.cpp b/tests/unit/unittest/translationunitupdater-test.cpp
index 40ca890971..8a4e8ad0f6 100644
--- a/tests/unit/unittest/translationunitupdater-test.cpp
+++ b/tests/unit/unittest/translationunitupdater-test.cpp
@@ -33,6 +33,7 @@ using ClangBackEnd::TranslationUnitUpdater;
 using ClangBackEnd::TranslationUnitUpdateInput;
 using ClangBackEnd::TranslationUnitUpdateResult;
 
+using testing::Eq;
 using testing::Gt;
 
 namespace {
@@ -42,7 +43,8 @@ class TranslationUnitUpdater : public ::testing::Test
 protected:
     void TearDown() override;
 
-    ::TranslationUnitUpdater createUpdater(const TranslationUnitUpdateInput &input);
+    ::TranslationUnitUpdater createUpdater(const TranslationUnitUpdateInput &input,
+                                           const Utf8String &translationUnitId = Utf8String());
 
     enum ReparseMode { SetReparseNeeded, DoNotSetReparseNeeded };
     TranslationUnitUpdateInput createInput(ReparseMode reparseMode = DoNotSetReparseNeeded);
@@ -73,6 +75,16 @@ TEST_F(TranslationUnitUpdater, ReparsesIfNeeded)
     ASSERT_TRUE(result.hasReparsed());
 }
 
+TEST_F(TranslationUnitUpdater, PropagatesTranslationUnitId)
+{
+    const Utf8String translationUnitId = Utf8StringLiteral("myId");
+    ::TranslationUnitUpdater updater = createUpdater(createInput(SetReparseNeeded), translationUnitId);
+
+    TranslationUnitUpdateResult result = updater.update(::TranslationUnitUpdater::UpdateMode::AsNeeded);
+
+    ASSERT_THAT(result.translationUnitId, Eq(translationUnitId));
+}
+
 TEST_F(TranslationUnitUpdater, UpdatesParseTimePoint)
 {
     ::TranslationUnitUpdater updater = createUpdater(createInput());
@@ -111,9 +123,10 @@ void TranslationUnitUpdater::TearDown()
 }
 
 ::TranslationUnitUpdater
-TranslationUnitUpdater::createUpdater(const TranslationUnitUpdateInput &input)
+TranslationUnitUpdater::createUpdater(const TranslationUnitUpdateInput &input,
+                                      const Utf8String &translationUnitId)
 {
-    return ::TranslationUnitUpdater(cxIndex, cxTranslationUnit, input);
+    return ::TranslationUnitUpdater(translationUnitId, cxIndex, cxTranslationUnit, input);
 }
 
 TranslationUnitUpdateInput
diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro
index 48bd0a59fa..168047cca5 100644
--- a/tests/unit/unittest/unittest.pro
+++ b/tests/unit/unittest/unittest.pro
@@ -58,6 +58,7 @@ SOURCES += \
     clangjobs-test.cpp \
     clangrequestdocumentannotationsjob-test.cpp \
     clangstring-test.cpp \
+    clangtranslationunits-test.cpp \
     clangupdatedocumentannotationsjob-test.cpp \
     codecompletionsextractor-test.cpp \
     codecompletion-test.cpp \
-- 
GitLab