Files
swift-mirror/tools/SourceKit/lib/SwiftLang/SwiftEditor.cpp
Ben Langmuir 209a7b3813 Revert "Merge pull request #32628 from benlangmuir/sema-repeat"
This reverts commit 35eab3e3a8, reversing
changes made to d5a549a9e4.

This caused an unexpected regression.

rdar://65554791
2020-07-14 17:00:02 -07:00

2611 lines
91 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//===--- SwiftEditor.cpp --------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "SwiftASTManager.h"
#include "SwiftEditorDiagConsumer.h"
#include "SwiftLangSupport.h"
#include "SourceKit/Core/Context.h"
#include "SourceKit/Core/NotificationCenter.h"
#include "SourceKit/Support/FileSystemProvider.h"
#include "SourceKit/Support/ImmutableTextBuffer.h"
#include "SourceKit/Support/Logging.h"
#include "SourceKit/Support/Tracing.h"
#include "SourceKit/Support/UIdent.h"
#include "swift/AST/ASTPrinter.h"
#include "swift/AST/ASTVisitor.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/DiagnosticsClangImporter.h"
#include "swift/AST/DiagnosticsParse.h"
#include "swift/AST/DiagnosticsFrontend.h"
#include "swift/Basic/SourceManager.h"
#include "swift/Demangling/ManglingUtils.h"
#include "swift/Frontend/Frontend.h"
#include "swift/Frontend/PrintingDiagnosticConsumer.h"
#include "swift/IDE/CodeCompletion.h"
#include "swift/IDE/CommentConversion.h"
#include "swift/IDE/Indenting.h"
#include "swift/IDE/SourceEntityWalker.h"
#include "swift/IDE/SyntaxModel.h"
#include "swift/Subsystems.h"
#include "swift/SyntaxParse/SyntaxTreeCreator.h"
#include "swift/Syntax/Serialization/SyntaxSerialization.h"
#include "swift/Syntax/SyntaxNodes.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Mutex.h"
using namespace SourceKit;
using namespace swift;
using namespace ide;
static std::vector<unsigned> getSortedBufferIDs(
const llvm::DenseMap<unsigned, std::vector<DiagnosticEntryInfo>> &Map) {
std::vector<unsigned> bufferIDs;
bufferIDs.reserve(Map.size());
for (auto I = Map.begin(), E = Map.end(); I != E; ++I) {
bufferIDs.push_back(I->getFirst());
}
llvm::array_pod_sort(bufferIDs.begin(), bufferIDs.end());
return bufferIDs;
}
void EditorDiagConsumer::getAllDiagnostics(
SmallVectorImpl<DiagnosticEntryInfo> &Result) {
Result.append(InvalidLocDiagnostics.begin(), InvalidLocDiagnostics.end());
// Note: we cannot reuse InputBufIds because there may be diagnostics outside
// the inputs. Instead, sort the extant buffers.
auto bufferIDs = getSortedBufferIDs(BufferDiagnostics);
for (unsigned bufferID : bufferIDs) {
const auto &diags = BufferDiagnostics[bufferID];
Result.append(diags.begin(), diags.end());
}
}
void EditorDiagConsumer::handleDiagnostic(SourceManager &SM,
const DiagnosticInfo &Info) {
if (Info.Kind == DiagnosticKind::Error) {
HadAnyError = true;
}
// Filter out benign diagnostics for editing.
if (Info.ID == diag::lex_editor_placeholder.ID)
return;
bool IsNote = (Info.Kind == DiagnosticKind::Note);
if (IsNote && !haveLastDiag())
// Is this possible?
return;
if (Info.Kind == DiagnosticKind::Remark) {
// FIXME: we may want to handle optimization remarks in sourcekitd.
LOG_WARN_FUNC("unhandled optimization remark");
return;
}
DiagnosticEntryInfo SKInfo;
// Actually substitute the diagnostic arguments into the diagnostic text.
llvm::SmallString<256> Text;
{
llvm::raw_svector_ostream Out(Text);
DiagnosticEngine::formatDiagnosticText(Out, Info.FormatString,
Info.FormatArgs);
}
SKInfo.Description = std::string(Text.str());
for (auto notePath : Info.EducationalNotePaths)
SKInfo.EducationalNotePaths.push_back(notePath);
Optional<unsigned> BufferIDOpt;
if (Info.Loc.isValid()) {
BufferIDOpt = SM.findBufferContainingLoc(Info.Loc);
}
if (BufferIDOpt && !isInputBufferID(*BufferIDOpt)) {
if (Info.ID == diag::error_from_clang.ID ||
Info.ID == diag::warning_from_clang.ID ||
Info.ID == diag::note_from_clang.ID ||
!IsNote) {
// Handle it as other diagnostics.
} else {
// FIXME: This is a note pointing to a synthesized declaration buffer for
// a declaration coming from a module.
// We should include the Decl* in the DiagnosticInfo and have a way for
// Xcode to handle this "points-at-a-decl-from-module" location.
//
// For now instead of ignoring it, pick up the declaration name from the
// buffer identifier and append it to the diagnostic message.
auto &LastDiag = getLastDiag();
SKInfo.Description += " (";
SKInfo.Description += SM.getIdentifierForBuffer(*BufferIDOpt);
SKInfo.Description += ")";
SKInfo.Offset = LastDiag.Offset;
SKInfo.Line = LastDiag.Line;
SKInfo.Column = LastDiag.Column;
SKInfo.Filename = LastDiag.Filename;
LastDiag.Notes.push_back(std::move(SKInfo));
return;
}
}
if (BufferIDOpt.hasValue()) {
unsigned BufferID = *BufferIDOpt;
SKInfo.Offset = SM.getLocOffsetInBuffer(Info.Loc, BufferID);
std::tie(SKInfo.Line, SKInfo.Column) =
SM.getPresumedLineAndColumnForLoc(Info.Loc, BufferID);
SKInfo.Filename = SM.getDisplayNameForLoc(Info.Loc).str();
for (auto R : Info.Ranges) {
if (R.isInvalid() || SM.findBufferContainingLoc(R.getStart()) != BufferID)
continue;
unsigned Offset = SM.getLocOffsetInBuffer(R.getStart(), BufferID);
unsigned Length = R.getByteLength();
SKInfo.Ranges.push_back({Offset, Length});
}
for (auto F : Info.FixIts) {
if (F.getRange().isInvalid() ||
SM.findBufferContainingLoc(F.getRange().getStart()) != BufferID)
continue;
unsigned Offset =
SM.getLocOffsetInBuffer(F.getRange().getStart(), BufferID);
unsigned Length = F.getRange().getByteLength();
SKInfo.Fixits.push_back({Offset, Length, F.getText().str()});
}
} else {
SKInfo.Filename = "<unknown>";
}
if (IsNote) {
getLastDiag().Notes.push_back(std::move(SKInfo));
return;
}
switch (Info.Kind) {
case DiagnosticKind::Error:
SKInfo.Severity = DiagnosticSeverityKind::Error;
break;
case DiagnosticKind::Warning:
SKInfo.Severity = DiagnosticSeverityKind::Warning;
break;
case DiagnosticKind::Note:
case DiagnosticKind::Remark:
llvm_unreachable("already covered");
}
if (!BufferIDOpt) {
InvalidLocDiagnostics.push_back(std::move(SKInfo));
clearLastDiag();
return;
}
unsigned BufferID = *BufferIDOpt;
DiagnosticsTy &Diagnostics = BufferDiagnostics[BufferID];
if (Diagnostics.empty() || Diagnostics.back().Offset <= SKInfo.Offset) {
Diagnostics.push_back(std::move(SKInfo));
LastDiagBufferID = BufferID;
LastDiagIndex = Diagnostics.size() - 1;
return;
}
// Keep the diagnostics array in source order.
auto Pos = std::lower_bound(Diagnostics.begin(), Diagnostics.end(), SKInfo.Offset,
[&](const DiagnosticEntryInfo &LHS, unsigned Offset) -> bool {
return LHS.Offset < Offset;
});
LastDiagBufferID = BufferID;
LastDiagIndex = Pos - Diagnostics.begin();
Diagnostics.insert(Pos, std::move(SKInfo));
}
SwiftEditorDocumentRef
SwiftEditorDocumentFileMap::getByUnresolvedName(StringRef FilePath) {
SwiftEditorDocumentRef EditorDoc;
Queue.dispatchSync([&]{
auto It = Docs.find(FilePath);
if (It != Docs.end())
EditorDoc = It->second.DocRef;
});
return EditorDoc;
}
SwiftEditorDocumentRef
SwiftEditorDocumentFileMap::findByPath(StringRef FilePath) {
SwiftEditorDocumentRef EditorDoc;
std::string ResolvedPath = SwiftLangSupport::resolvePathSymlinks(FilePath);
Queue.dispatchSync([&]{
for (auto &Entry : Docs) {
if (Entry.getKey() == FilePath ||
Entry.getValue().ResolvedPath == ResolvedPath) {
EditorDoc = Entry.getValue().DocRef;
break;
}
}
});
return EditorDoc;
}
bool SwiftEditorDocumentFileMap::getOrUpdate(
StringRef FilePath, SwiftLangSupport &LangSupport,
SwiftEditorDocumentRef &EditorDoc) {
bool found = false;
std::string ResolvedPath = SwiftLangSupport::resolvePathSymlinks(FilePath);
Queue.dispatchBarrierSync([&]{
DocInfo &Doc = Docs[FilePath];
if (!Doc.DocRef) {
Doc.DocRef = EditorDoc;
Doc.ResolvedPath = ResolvedPath;
} else {
EditorDoc = Doc.DocRef;
found = true;
}
});
return found;
}
SwiftEditorDocumentRef SwiftEditorDocumentFileMap::remove(StringRef FilePath) {
SwiftEditorDocumentRef Removed;
Queue.dispatchBarrierSync([&]{
auto I = Docs.find(FilePath);
if (I != Docs.end()) {
Removed = I->second.DocRef;
Docs.erase(I);
}
});
return Removed;
}
namespace {
/// Merges two overlapping ranges and splits the first range into two
/// ranges before and after the overlapping range.
void mergeSplitRanges(unsigned Off1, unsigned Len1, unsigned Off2, unsigned Len2,
std::function<void(unsigned BeforeOff, unsigned BeforeLen,
unsigned AfterOff,
unsigned AfterLen)> applier) {
unsigned End1 = Off1 + Len1;
unsigned End2 = Off2 + Len2;
if (End1 > Off2) {
// Overlapping. Split into before and after ranges.
unsigned BeforeOff = Off1;
unsigned BeforeLen = Off2 > Off1 ? Off2 - Off1 : 0;
unsigned AfterOff = End2;
unsigned AfterLen = End1 > End2 ? End1 - End2 : 0;
applier(BeforeOff, BeforeLen, AfterOff, AfterLen);
}
else {
// Not overlapping.
applier(Off1, Len1, 0, 0);
}
}
struct SwiftSyntaxToken {
unsigned Offset;
unsigned Length:24;
SyntaxNodeKind Kind:8;
static SwiftSyntaxToken createInvalid() {
return {0, 0, SyntaxNodeKind::AttributeBuiltin};
}
SwiftSyntaxToken(unsigned Offset, unsigned Length, SyntaxNodeKind Kind)
: Offset(Offset), Length(Length), Kind(Kind) {}
unsigned endOffset() const { return Offset + Length; }
bool isInvalid() const { return Length == 0; }
bool operator==(const SwiftSyntaxToken &Other) const {
return Offset == Other.Offset && Length == Other.Length &&
Kind == Other.Kind;
}
bool operator!=(const SwiftSyntaxToken &Other) const {
return Offset != Other.Offset || Length != Other.Length ||
Kind != Other.Kind;
}
};
struct SwiftEditorCharRange {
unsigned Offset;
unsigned EndOffset;
SwiftEditorCharRange(unsigned Offset, unsigned EndOffset) :
Offset(Offset), EndOffset(EndOffset) {}
SwiftEditorCharRange(SwiftSyntaxToken Token) :
Offset(Token.Offset), EndOffset(Token.endOffset()) {}
size_t length() const { return EndOffset - Offset; }
bool isEmpty() const { return Offset == EndOffset; }
bool intersects(const SwiftSyntaxToken &Token) const {
return this->Offset < (Token.endOffset()) && this->EndOffset > Token.Offset;
}
void extendToInclude(const SwiftEditorCharRange &Range) {
if (Range.Offset < Offset)
Offset = Range.Offset;
if (Range.EndOffset > EndOffset)
EndOffset = Range.EndOffset;
}
void extendToInclude(unsigned OtherOffset) {
extendToInclude({OtherOffset, OtherOffset});
}
};
/// Finds and represents the first mismatching tokens in two syntax maps,
/// ignoring invalidated tokens.
template <class Iter>
struct TokenMismatch {
/// The begin and end iterators of the previous syntax map
Iter PrevTok, PrevEnd;
/// The begin and end iterators of the current syntax map
Iter CurrTok, CurrEnd;
TokenMismatch(Iter CurrTok, Iter CurrEnd, Iter PrevTok, Iter PrevEnd) :
PrevTok(PrevTok), PrevEnd(PrevEnd), CurrTok(CurrTok), CurrEnd(CurrEnd) {
skipInvalid();
while(advance());
}
/// Returns true if a mismatch was found
bool foundMismatch() const {
return CurrTok != CurrEnd || PrevTok != PrevEnd;
}
/// Returns the smallest start offset of the mismatched token ranges
unsigned mismatchStart() const {
assert(foundMismatch());
if (CurrTok != CurrEnd) {
if (PrevTok != PrevEnd)
return std::min(CurrTok->Offset, PrevTok->Offset);
return CurrTok->Offset;
}
return PrevTok->Offset;
}
/// Returns the largest end offset of the mismatched token ranges
unsigned mismatchEnd() const {
assert(foundMismatch());
if (CurrTok != CurrEnd) {
if (PrevTok != PrevEnd)
return std::max(CurrTok->endOffset(), PrevTok->endOffset());
return CurrTok->endOffset();
}
return PrevTok->endOffset();
}
private:
void skipInvalid() {
while (PrevTok != PrevEnd && PrevTok->isInvalid())
++PrevTok;
}
bool advance() {
if (CurrTok == CurrEnd || PrevTok == PrevEnd || *CurrTok != *PrevTok)
return false;
++CurrTok;
++PrevTok;
skipInvalid();
return true;
}
};
/// Represents a the syntax highlighted token ranges in a source file
struct SwiftSyntaxMap {
std::vector<SwiftSyntaxToken> Tokens;
explicit SwiftSyntaxMap(unsigned Capacity = 0) {
if (Capacity)
Tokens.reserve(Capacity);
}
void addToken(const SwiftSyntaxToken &Token) {
assert(Tokens.empty() || Token.Offset >= Tokens.back().Offset);
Tokens.push_back(Token);
}
/// Merge this nested token into the last token that was added
void mergeToken(const SwiftSyntaxToken &Token) {
if (Tokens.empty()) {
Tokens.push_back(Token);
return;
}
auto &LastTok = Tokens.back();
assert(LastTok.Offset <= Token.Offset);
mergeSplitRanges(LastTok.Offset, LastTok.Length, Token.Offset, Token.Length,
[&](unsigned BeforeOff, unsigned BeforeLen,
unsigned AfterOff, unsigned AfterLen) {
auto LastKind = LastTok.Kind;
Tokens.pop_back();
if (BeforeLen)
Tokens.emplace_back(BeforeOff, BeforeLen, LastKind);
Tokens.push_back(Token);
if (AfterLen)
Tokens.emplace_back(AfterOff, AfterLen, LastKind);
});
}
/// Adjusts the token offsets and lengths in this syntax map to account for
/// replacing \p Len bytes at the given \p Offset with \p NewLen bytes. Tokens
/// before the replacement stay the same, tokens after it are shifted, and
/// tokens that intersect it are 'removed' (really just marked invalid).
/// Clients are expected to match this behavior.
///
/// Returns the union of the replaced range and the token ranges it
/// intersected, or nothing if no tokens were intersected.
llvm::Optional<SwiftEditorCharRange>
adjustForReplacement(unsigned Offset, unsigned Len, unsigned NewLen) {
unsigned ReplacedStart = Offset;
unsigned ReplacedEnd = Offset + Len;
bool TokenIntersected = false;
SwiftEditorCharRange Affected = { /*Offset=*/ReplacedStart,
/*EndOffset=*/ReplacedEnd};
// Adjust the tokens
auto Token = Tokens.begin();
while (Token != Tokens.end() && Token->endOffset() <= ReplacedStart) {
// Completely before the replaced range no change needed
++Token;
}
while (Token != Tokens.end() && Token->Offset < ReplacedEnd) {
// Intersecting the replaced range extend Affected and invalidate
TokenIntersected = true;
Affected.extendToInclude(*Token);
*Token = SwiftSyntaxToken::createInvalid();
++Token;
}
while (Token != Tokens.end()) {
// Completely after the replaced range - shift to account for NewLen
if (NewLen >= Len)
Token->Offset += NewLen - Len;
else
Token->Offset -= Len - NewLen;
++Token;
}
// If the replaced range didn't intersect with any existing tokens, there's
// no need to report an affected range
if (!TokenIntersected)
return None;
// Update the end of the affected range to account for NewLen
if (NewLen >= Len) {
Affected.EndOffset += NewLen - Len;
} else {
Affected.EndOffset -= Len - NewLen;
}
return Affected;
}
/// Passes each token in this SwiftSyntaxMap to the given \p Consumer
void forEach(EditorConsumer &Consumer) {
for (auto &Token: Tokens) {
auto Kind = SwiftLangSupport::getUIDForSyntaxNodeKind(Token.Kind);
Consumer.handleSyntaxMap(Token.Offset, Token.Length, Kind);
}
}
/// Finds the delta between the given SwiftSyntaxMap, \p Prev, and this one.
/// It passes each token not in \p Prev to the given \p Consumer and, if
/// needed, also expands or sets the given \p Affected range to cover all
/// non-matching tokens in the two lists.
///
/// Returns true if this SwiftSyntaxMap is different to \p Prev.
bool forEachChanged(const SwiftSyntaxMap &Prev,
llvm::Optional<SwiftEditorCharRange> &Affected,
EditorConsumer &Consumer) const {
typedef std::vector<SwiftSyntaxToken>::const_iterator ForwardIt;
typedef std::vector<SwiftSyntaxToken>::const_reverse_iterator ReverseIt;
// Find the first pair of tokens that don't match
TokenMismatch<ForwardIt>
Forward(Tokens.begin(), Tokens.end(), Prev.Tokens.begin(), Prev.Tokens.end());
// Exit early if there was no mismatch
if (!Forward.foundMismatch())
return Affected && !Affected->isEmpty();
// Find the last pair of tokens that don't match
TokenMismatch<ReverseIt>
Backward(Tokens.rbegin(), Tokens.rend(), Prev.Tokens.rbegin(), Prev.Tokens.rend());
assert(Backward.foundMismatch());
// Set or extend the affected range to include the mismatched range
SwiftEditorCharRange
MismatchRange = {Forward.mismatchStart(),Backward.mismatchEnd()};
if (!Affected) {
Affected = MismatchRange;
} else {
Affected->extendToInclude(MismatchRange);
}
// Report all tokens in the affected range to the EditorConsumer
auto From = Forward.CurrTok;
auto To = Backward.CurrTok;
while (From != Tokens.begin() && (From-1)->Offset >= Affected->Offset)
--From;
while (To != Tokens.rbegin() && (To-1)->endOffset() <= Affected->EndOffset)
--To;
for (; From < To.base(); ++From) {
auto Kind = SwiftLangSupport::getUIDForSyntaxNodeKind(From->Kind);
Consumer.handleSyntaxMap(From->Offset, From->Length, Kind);
}
return true;
}
};
struct EditorConsumerSyntaxMapEntry {
unsigned Offset;
unsigned Length;
UIdent Kind;
EditorConsumerSyntaxMapEntry(unsigned Offset, unsigned Length, UIdent Kind)
:Offset(Offset), Length(Length), Kind(Kind) { }
};
struct SwiftSemanticToken {
unsigned ByteOffset;
unsigned Length : 24;
// The code-completion kinds are a good match for the semantic kinds we want.
// FIXME: Maybe rename CodeCompletionDeclKind to a more general concept ?
CodeCompletionDeclKind Kind : 6;
unsigned IsRef : 1;
unsigned IsSystem : 1;
SwiftSemanticToken(CodeCompletionDeclKind Kind,
unsigned ByteOffset, unsigned Length,
bool IsRef, bool IsSystem)
: ByteOffset(ByteOffset), Length(Length), Kind(Kind),
IsRef(IsRef), IsSystem(IsSystem) { }
bool getIsRef() const { return static_cast<bool>(IsRef); }
bool getIsSystem() const { return static_cast<bool>(IsSystem); }
UIdent getUIdentForKind() const {
return SwiftLangSupport::getUIDForCodeCompletionDeclKind(Kind, getIsRef());
}
};
static_assert(sizeof(SwiftSemanticToken) == 8, "Too big");
class SwiftDocumentSemanticInfo :
public ThreadSafeRefCountedBase<SwiftDocumentSemanticInfo> {
const std::string Filename;
std::weak_ptr<SwiftASTManager> ASTMgr;
std::shared_ptr<NotificationCenter> NotificationCtr;
ThreadSafeRefCntPtr<SwiftInvocation> InvokRef;
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fileSystem;
std::string CompilerArgsError;
uint64_t ASTGeneration = 0;
ImmutableTextSnapshotRef TokSnapshot;
std::vector<SwiftSemanticToken> SemaToks;
ImmutableTextSnapshotRef DiagSnapshot;
std::vector<DiagnosticEntryInfo> SemaDiags;
mutable llvm::sys::Mutex Mtx;
public:
SwiftDocumentSemanticInfo(
StringRef Filename, std::weak_ptr<SwiftASTManager> ASTMgr,
std::shared_ptr<NotificationCenter> NotificationCtr,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fileSystem)
: Filename(Filename), ASTMgr(ASTMgr), NotificationCtr(NotificationCtr),
fileSystem(fileSystem) {}
SwiftInvocationRef getInvocation() const {
return InvokRef;
}
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> getFileSystem() const {
return fileSystem;
}
uint64_t getASTGeneration() const;
void setCompilerArgs(ArrayRef<const char *> Args) {
if (auto ASTMgr = this->ASTMgr.lock()) {
InvokRef =
ASTMgr->getInvocation(Args, Filename, CompilerArgsError);
}
}
void readSemanticInfo(ImmutableTextSnapshotRef NewSnapshot,
std::vector<SwiftSemanticToken> &Tokens,
Optional<std::vector<DiagnosticEntryInfo>> &Diags,
ArrayRef<DiagnosticEntryInfo> ParserDiags);
void processLatestSnapshotAsync(EditableTextBufferRef EditableBuffer);
void updateSemanticInfo(std::vector<SwiftSemanticToken> Toks,
std::vector<DiagnosticEntryInfo> Diags,
ImmutableTextSnapshotRef Snapshot,
uint64_t ASTGeneration);
void removeCachedAST() {
if (InvokRef) {
if (auto ASTMgr = this->ASTMgr.lock()) {
ASTMgr->removeCachedAST(InvokRef);
}
}
}
private:
std::vector<SwiftSemanticToken> takeSemanticTokens(
ImmutableTextSnapshotRef NewSnapshot);
Optional<std::vector<DiagnosticEntryInfo>> getSemanticDiagnostics(
ImmutableTextSnapshotRef NewSnapshot,
ArrayRef<DiagnosticEntryInfo> ParserDiags);
};
class SwiftDocumentSyntaxInfo {
SourceManager SM;
EditorDiagConsumer DiagConsumer;
std::shared_ptr<SyntaxTreeCreator> SynTreeCreator;
std::unique_ptr<ParserUnit> Parser;
unsigned BufferID;
std::vector<std::string> Args;
std::string PrimaryFile;
/// Whether or not the AST stored in the source file is up-to-date or just an
/// artifact of incremental syntax parsing
bool HasUpToDateAST;
public:
SwiftDocumentSyntaxInfo(const CompilerInvocation &CompInv,
ImmutableTextSnapshotRef Snapshot,
std::vector<std::string> &Args,
StringRef FilePath)
: Args(Args), PrimaryFile(FilePath) {
std::unique_ptr<llvm::MemoryBuffer> BufCopy =
llvm::MemoryBuffer::getMemBufferCopy(
Snapshot->getBuffer()->getText(), FilePath);
BufferID = SM.addNewSourceBuffer(std::move(BufCopy));
DiagConsumer.setInputBufferIDs(BufferID);
if (CompInv.getLangOptions().BuildSyntaxTree) {
RC<SyntaxArena> syntaxArena{new syntax::SyntaxArena()};
SynTreeCreator = std::make_shared<SyntaxTreeCreator>(
SM, BufferID, CompInv.getMainFileSyntaxParsingCache(), syntaxArena);
}
Parser.reset(
new ParserUnit(SM, SourceFileKind::Main, BufferID,
CompInv.getLangOptions(),
CompInv.getTypeCheckerOptions(),
CompInv.getModuleName(),
SynTreeCreator,
CompInv.getMainFileSyntaxParsingCache())
);
registerParseRequestFunctions(Parser->getParser().Context.evaluator);
registerTypeCheckerRequestFunctions(
Parser->getParser().Context.evaluator);
Parser->getDiagnosticEngine().addConsumer(DiagConsumer);
// If there is a syntax parsing cache, incremental syntax parsing is
// performed and thus the generated AST may not be up-to-date.
HasUpToDateAST = CompInv.getMainFileSyntaxParsingCache() == nullptr;
}
void parse() {
Parser->parse();
}
SourceFile &getSourceFile() {
return Parser->getSourceFile();
}
unsigned getBufferID() {
return BufferID;
}
const LangOptions &getLangOptions() {
return Parser->getLangOptions();
}
SourceManager &getSourceManager() {
return SM;
}
bool hasUpToDateAST() { return HasUpToDateAST; }
ArrayRef<DiagnosticEntryInfo> getDiagnostics() {
return DiagConsumer.getDiagnosticsForBuffer(BufferID);
}
};
} // anonymous namespace
uint64_t SwiftDocumentSemanticInfo::getASTGeneration() const {
llvm::sys::ScopedLock L(Mtx);
return ASTGeneration;
}
void SwiftDocumentSemanticInfo::readSemanticInfo(
ImmutableTextSnapshotRef NewSnapshot,
std::vector<SwiftSemanticToken> &Tokens,
Optional<std::vector<DiagnosticEntryInfo>> &Diags,
ArrayRef<DiagnosticEntryInfo> ParserDiags) {
llvm::sys::ScopedLock L(Mtx);
Tokens = takeSemanticTokens(NewSnapshot);
Diags = getSemanticDiagnostics(NewSnapshot, ParserDiags);
}
std::vector<SwiftSemanticToken>
SwiftDocumentSemanticInfo::takeSemanticTokens(
ImmutableTextSnapshotRef NewSnapshot) {
llvm::sys::ScopedLock L(Mtx);
if (SemaToks.empty())
return {};
// Adjust the position of the tokens.
TokSnapshot->foreachReplaceUntil(NewSnapshot,
[&](ReplaceImmutableTextUpdateRef Upd) -> bool {
if (SemaToks.empty())
return false;
auto ReplaceBegin = std::lower_bound(SemaToks.begin(), SemaToks.end(),
Upd->getByteOffset(),
[&](const SwiftSemanticToken &Tok, unsigned StartOffset) -> bool {
return Tok.ByteOffset+Tok.Length < StartOffset;
});
std::vector<SwiftSemanticToken>::iterator ReplaceEnd;
if (Upd->getLength() == 0) {
ReplaceEnd = ReplaceBegin;
} else {
ReplaceEnd = std::upper_bound(ReplaceBegin, SemaToks.end(),
Upd->getByteOffset() + Upd->getLength(),
[&](unsigned EndOffset, const SwiftSemanticToken &Tok) -> bool {
return EndOffset < Tok.ByteOffset;
});
}
unsigned InsertLen = Upd->getText().size();
int Delta = InsertLen - Upd->getLength();
if (Delta != 0) {
for (std::vector<SwiftSemanticToken>::iterator
I = ReplaceEnd, E = SemaToks.end(); I != E; ++I)
I->ByteOffset += Delta;
}
SemaToks.erase(ReplaceBegin, ReplaceEnd);
return true;
});
return std::move(SemaToks);
}
Optional<std::vector<DiagnosticEntryInfo>>
SwiftDocumentSemanticInfo::getSemanticDiagnostics(
ImmutableTextSnapshotRef NewSnapshot,
ArrayRef<DiagnosticEntryInfo> ParserDiags) {
std::vector<DiagnosticEntryInfo> curSemaDiags;
{
llvm::sys::ScopedLock L(Mtx);
if (!DiagSnapshot || DiagSnapshot->getStamp() != NewSnapshot->getStamp()) {
// The semantic diagnostics are out-of-date, ignore them.
return llvm::None;
}
curSemaDiags = SemaDiags;
}
// Diagnostics from the AST and diagnostics from the parser are based on the
// same source text snapshot. But diagnostics from the AST may have excluded
// the parser diagnostics due to a fatal error, e.g. if the source has a
// 'so such module' error, which will suppress other diagnostics.
// We don't want to turn off the suppression to avoid a flood of diagnostics
// when a module import fails, but we also don't want to lose the parser
// diagnostics in such a case, so merge the parser diagnostics with the sema
// ones.
auto orderDiagnosticEntryInfos = [](const DiagnosticEntryInfo &LHS,
const DiagnosticEntryInfo &RHS) -> bool {
if (LHS.Filename != RHS.Filename)
return LHS.Filename < RHS.Filename;
if (LHS.Offset != RHS.Offset)
return LHS.Offset < RHS.Offset;
return LHS.Description < RHS.Description;
};
std::vector<DiagnosticEntryInfo> sortedParserDiags;
sortedParserDiags.reserve(ParserDiags.size());
sortedParserDiags.insert(sortedParserDiags.end(), ParserDiags.begin(),
ParserDiags.end());
std::stable_sort(sortedParserDiags.begin(), sortedParserDiags.end(),
orderDiagnosticEntryInfos);
std::vector<DiagnosticEntryInfo> finalDiags;
finalDiags.reserve(sortedParserDiags.size()+curSemaDiags.size());
// Add sema diagnostics unless it is an existing parser diagnostic.
// Note that we want to merge and eliminate diagnostics from the 'sema' set
// that also show up in the 'parser' set, but we don't want to remove
// duplicate diagnostics from within the same set (e.g. duplicates existing in
// the 'sema' set). We want to report the diagnostics as the compiler reported
// them, even if there's some duplicate one. This is why we don't just do a
// simple append/sort/keep-uniques step.
for (const auto &curDE : curSemaDiags) {
bool existsAsParserDiag = std::binary_search(sortedParserDiags.begin(),
sortedParserDiags.end(),
curDE, orderDiagnosticEntryInfos);
if (!existsAsParserDiag) {
finalDiags.push_back(curDE);
}
}
finalDiags.insert(finalDiags.end(),
sortedParserDiags.begin(), sortedParserDiags.end());
std::stable_sort(finalDiags.begin(), finalDiags.end(),
orderDiagnosticEntryInfos);
return finalDiags;
}
void SwiftDocumentSemanticInfo::updateSemanticInfo(
std::vector<SwiftSemanticToken> Toks,
std::vector<DiagnosticEntryInfo> Diags,
ImmutableTextSnapshotRef Snapshot,
uint64_t ASTGeneration) {
{
llvm::sys::ScopedLock L(Mtx);
if (ASTGeneration > this->ASTGeneration) {
SemaToks = std::move(Toks);
SemaDiags = std::move(Diags);
TokSnapshot = DiagSnapshot = std::move(Snapshot);
this->ASTGeneration = ASTGeneration;
}
}
LOG_INFO_FUNC(High, "posted document update notification for: " << Filename);
NotificationCtr->postDocumentUpdateNotification(Filename);
}
namespace {
class SemanticAnnotator : public SourceEntityWalker {
SourceManager &SM;
unsigned BufferID;
public:
std::vector<SwiftSemanticToken> SemaToks;
SemanticAnnotator(SourceManager &SM, unsigned BufferID)
: SM(SM), BufferID(BufferID) {}
bool visitDeclReference(ValueDecl *D, CharSourceRange Range,
TypeDecl *CtorTyRef, ExtensionDecl *ExtTyRef, Type T,
ReferenceMetaData Data) override {
if (Data.isImplicit)
return true;
if (isa<VarDecl>(D) && D->hasName() &&
D->getName() == D->getASTContext().Id_self)
return true;
// Do not annotate references to unavailable decls.
if (AvailableAttr::isUnavailable(D))
return true;
if (CtorTyRef)
D = CtorTyRef;
annotate(D, /*IsRef=*/true, Range);
return true;
}
bool visitSubscriptReference(ValueDecl *D, CharSourceRange Range,
ReferenceMetaData Data,
bool IsOpenBracket) override {
// We should treat both open and close brackets equally
return visitDeclReference(D, Range, nullptr, nullptr, Type(), Data);
}
void annotate(const Decl *D, bool IsRef, CharSourceRange Range) {
unsigned ByteOffset = SM.getLocOffsetInBuffer(Range.getStart(), BufferID);
unsigned Length = Range.getByteLength();
auto Kind = CodeCompletionResult::getCodeCompletionDeclKind(D);
bool IsSystem = D->getModuleContext()->isSystemModule();
SemaToks.emplace_back(Kind, ByteOffset, Length, IsRef, IsSystem);
}
};
} // anonymous namespace
namespace {
class AnnotAndDiagASTConsumer : public SwiftASTConsumer {
EditableTextBufferRef EditableBuffer;
RefPtr<SwiftDocumentSemanticInfo> SemaInfoRef;
public:
std::vector<SwiftSemanticToken> SemaToks;
AnnotAndDiagASTConsumer(EditableTextBufferRef EditableBuffer,
RefPtr<SwiftDocumentSemanticInfo> SemaInfoRef)
: EditableBuffer(std::move(EditableBuffer)),
SemaInfoRef(std::move(SemaInfoRef)) { }
void failed(StringRef Error) override {
LOG_WARN_FUNC("sema annotations failed: " << Error);
}
void handlePrimaryAST(ASTUnitRef AstUnit) override {
auto Generation = AstUnit->getGeneration();
auto &CompIns = AstUnit->getCompilerInstance();
auto &Consumer = AstUnit->getEditorDiagConsumer();
assert(Generation);
if (Generation < SemaInfoRef->getASTGeneration()) {
// It may happen that this request was waiting in async queue for
// too long so another thread has already updated this sema with
// ast generation bigger than ASTGeneration
return;
}
ImmutableTextSnapshotRef DocSnapshot;
for (auto &Snap : AstUnit->getSnapshots()) {
if (Snap->getEditableBuffer() == EditableBuffer) {
DocSnapshot = Snap;
break;
}
}
if (!DocSnapshot) {
LOG_WARN_FUNC("did not find document snapshot when handling the AST");
return;
}
if (Generation == SemaInfoRef->getASTGeneration()) {
// Save time if we already know we processed this AST version.
if (DocSnapshot->getStamp() != EditableBuffer->getSnapshot()->getStamp()){
// Handle edits that occurred after we processed the AST.
SemaInfoRef->processLatestSnapshotAsync(EditableBuffer);
}
return;
}
if (!AstUnit->getPrimarySourceFile().getBufferID().hasValue()) {
LOG_WARN_FUNC("Primary SourceFile is expected to have a BufferID");
return;
}
unsigned BufferID = AstUnit->getPrimarySourceFile().getBufferID().getValue();
SemanticAnnotator Annotator(CompIns.getSourceMgr(), BufferID);
Annotator.walk(AstUnit->getPrimarySourceFile());
SemaToks = std::move(Annotator.SemaToks);
SemaInfoRef->
updateSemanticInfo(std::move(SemaToks),
std::move(Consumer.getDiagnosticsForBuffer(BufferID)),
DocSnapshot,
Generation);
if (DocSnapshot->getStamp() != EditableBuffer->getSnapshot()->getStamp()) {
// Handle edits that occurred after we processed the AST.
SemaInfoRef->processLatestSnapshotAsync(EditableBuffer);
}
}
};
} // anonymous namespace
void SwiftDocumentSemanticInfo::processLatestSnapshotAsync(
EditableTextBufferRef EditableBuffer) {
SwiftInvocationRef Invok = InvokRef;
if (!Invok)
return;
RefPtr<SwiftDocumentSemanticInfo> SemaInfoRef = this;
auto Consumer = std::make_shared<AnnotAndDiagASTConsumer>(EditableBuffer,
SemaInfoRef);
// Semantic annotation queries for a particular document should cancel
// previously queued queries for the same document. Each document has a
// SwiftDocumentSemanticInfo pointer so use that for the token.
const void *OncePerASTToken = SemaInfoRef.get();
if (auto ASTMgr = this->ASTMgr.lock()) {
ASTMgr->processASTAsync(Invok, std::move(Consumer), OncePerASTToken,
fileSystem);
}
}
struct SwiftEditorDocument::Implementation {
std::weak_ptr<SwiftASTManager> ASTMgr;
std::shared_ptr<NotificationCenter> NotificationCtr;
const std::string FilePath;
EditableTextBufferRef EditableBuffer;
/// The list of syntax highlighted token offsets and ranges in the document
SwiftSyntaxMap SyntaxMap;
/// The minimal range of syntax highlighted tokens affected by the last edit
llvm::Optional<SwiftEditorCharRange> AffectedRange;
/// Whether the last operation was an edit rather than a document open
bool Edited;
/// The syntax tree of the document
llvm::Optional<SourceFileSyntax> SyntaxTree;
std::vector<DiagnosticEntryInfo> ParserDiagnostics;
RefPtr<SwiftDocumentSemanticInfo> SemanticInfo;
CodeFormatOptions FormatOptions;
std::shared_ptr<SwiftDocumentSyntaxInfo> SyntaxInfo;
std::shared_ptr<SwiftDocumentSyntaxInfo> getSyntaxInfo() {
llvm::sys::ScopedLock L(AccessMtx);
return SyntaxInfo;
}
llvm::sys::Mutex AccessMtx;
Implementation(StringRef FilePath, SwiftLangSupport &LangSupport,
CodeFormatOptions options,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fileSystem)
: ASTMgr(LangSupport.getASTManager()),
NotificationCtr(LangSupport.getNotificationCenter()),
FilePath(FilePath), FormatOptions(options) {
assert(fileSystem);
// This instance of semantic info is used if a document is opened with
// `key.syntactic_only: 1`, but subsequently a semantic request such as
// cursor_info is made.
SemanticInfo = new SwiftDocumentSemanticInfo(
FilePath, ASTMgr, NotificationCtr, fileSystem);
}
};
namespace {
static UIdent getAccessLevelUID(AccessLevel Access) {
static UIdent AccessOpen("source.lang.swift.accessibility.open");
static UIdent AccessPublic("source.lang.swift.accessibility.public");
static UIdent AccessInternal("source.lang.swift.accessibility.internal");
static UIdent AccessFilePrivate("source.lang.swift.accessibility.fileprivate");
static UIdent AccessPrivate("source.lang.swift.accessibility.private");
switch (Access) {
case AccessLevel::Private:
return AccessPrivate;
case AccessLevel::FilePrivate:
return AccessFilePrivate;
case AccessLevel::Internal:
return AccessInternal;
case AccessLevel::Public:
return AccessPublic;
case AccessLevel::Open:
return AccessOpen;
}
llvm_unreachable("Unhandled access level in switch.");
}
static Optional<AccessLevel>
inferDefaultAccessSyntactically(const ExtensionDecl *ED) {
// Check if the extension has an explicit access control attribute.
if (auto *AA = ED->getAttrs().getAttribute<AccessControlAttr>())
return std::min(std::max(AA->getAccess(), AccessLevel::FilePrivate),
AccessLevel::Public);
return None;
}
/// Document structure is a purely syntactic request that shouldn't require name lookup
/// or type-checking, so this is a best-effort computation, particularly where extensions
/// are concerned.
static Optional<AccessLevel> inferAccessSyntactically(const ValueDecl *D) {
assert(D);
// Check if the decl has an explicit access control attribute.
if (auto *AA = D->getAttrs().getAttribute<AccessControlAttr>())
return AA->getAccess();
DeclContext *DC = D->getDeclContext();
if (D->getKind() == DeclKind::Destructor ||
D->getKind() == DeclKind::EnumElement) {
if (auto container = dyn_cast<NominalTypeDecl>(D->getDeclContext())) {
if (auto containerAccess = inferAccessSyntactically(container))
return std::max(containerAccess.getValue(), AccessLevel::Internal);
return None;
}
return AccessLevel::Private;
}
switch (DC->getContextKind()) {
case DeclContextKind::TopLevelCodeDecl:
return AccessLevel::FilePrivate;
case DeclContextKind::SerializedLocal:
case DeclContextKind::AbstractClosureExpr:
case DeclContextKind::EnumElementDecl:
case DeclContextKind::Initializer:
case DeclContextKind::AbstractFunctionDecl:
case DeclContextKind::SubscriptDecl:
return AccessLevel::Private;
case DeclContextKind::Module:
case DeclContextKind::FileUnit:
return AccessLevel::Internal;
case DeclContextKind::GenericTypeDecl: {
auto generic = cast<GenericTypeDecl>(DC);
AccessLevel access = AccessLevel::Internal;
if (isa<ProtocolDecl>(generic)) {
if (auto protoAccess = inferAccessSyntactically(generic))
access = std::max(AccessLevel::FilePrivate, protoAccess.getValue());
}
return access;
}
case DeclContextKind::ExtensionDecl:
auto *ED = cast<ExtensionDecl>(DC);
return inferDefaultAccessSyntactically(ED);
}
llvm_unreachable("Unhandled DeclContextKind in switch.");
}
/// Document structure is a purely syntactic request that shouldn't require name lookup
/// or type-checking, so this is a best-effort computation.
static bool inferIsSettableSyntactically(const AbstractStorageDecl *D) {
if (auto *VD = dyn_cast<VarDecl>(D)) {
if (VD->isLet())
return false;
}
if (D->hasParsedAccessors()) {
return D->getParsedAccessor(AccessorKind::Set) != nullptr ||
D->hasObservers();
} else {
return true;
}
}
static Optional<AccessLevel>
inferSetterAccessSyntactically(const AbstractStorageDecl *D) {
if (!inferIsSettableSyntactically(D))
return None;
if (auto *AA = D->getAttrs().getAttribute<SetterAccessAttr>())
return AA->getAccess();
return inferAccessSyntactically(D);
}
class SwiftDocumentStructureWalker: public ide::SyntaxModelWalker {
SourceManager &SrcManager;
EditorConsumer &Consumer;
unsigned BufferID;
public:
SwiftDocumentStructureWalker(SourceManager &SrcManager,
unsigned BufferID,
EditorConsumer &Consumer)
: SrcManager(SrcManager), Consumer(Consumer), BufferID(BufferID) { }
bool walkToSubStructurePre(SyntaxStructureNode Node) override {
unsigned StartOffset = SrcManager.getLocOffsetInBuffer(Node.Range.getStart(),
BufferID);
unsigned EndOffset = SrcManager.getLocOffsetInBuffer(Node.Range.getEnd(),
BufferID);
unsigned NameStart;
unsigned NameEnd;
if (Node.NameRange.isValid()) {
NameStart = SrcManager.getLocOffsetInBuffer(Node.NameRange.getStart(),
BufferID);
NameEnd = SrcManager.getLocOffsetInBuffer(Node.NameRange.getEnd(),
BufferID);
}
else {
NameStart = NameEnd = 0;
}
unsigned BodyOffset;
unsigned BodyEnd;
if (Node.BodyRange.isValid()) {
BodyOffset = SrcManager.getLocOffsetInBuffer(Node.BodyRange.getStart(),
BufferID);
BodyEnd = SrcManager.getLocOffsetInBuffer(Node.BodyRange.getEnd(),
BufferID);
}
else {
BodyOffset = BodyEnd = 0;
}
unsigned DocOffset = 0;
unsigned DocEnd = 0;
if (Node.DocRange.isValid()) {
DocOffset = SrcManager.getLocOffsetInBuffer(Node.DocRange.getStart(),
BufferID);
DocEnd = SrcManager.getLocOffsetInBuffer(Node.DocRange.getEnd(),
BufferID);
}
UIdent Kind = SwiftLangSupport::getUIDForSyntaxStructureKind(Node.Kind);
UIdent AccessLevel;
UIdent SetterAccessLevel;
if (Node.Kind != SyntaxStructureKind::Parameter &&
Node.Kind != SyntaxStructureKind::LocalVariable &&
Node.Kind != SyntaxStructureKind::GenericTypeParam) {
if (auto *VD = dyn_cast_or_null<ValueDecl>(Node.Dcl)) {
if (auto Access = inferAccessSyntactically(VD))
AccessLevel = getAccessLevelUID(Access.getValue());
} else if (auto *ED = dyn_cast_or_null<ExtensionDecl>(Node.Dcl)) {
if (auto DefaultAccess = inferDefaultAccessSyntactically(ED))
AccessLevel = getAccessLevelUID(DefaultAccess.getValue());
}
if (auto *ASD = dyn_cast_or_null<AbstractStorageDecl>(Node.Dcl)) {
if (auto SetAccess = inferSetterAccessSyntactically(ASD))
SetterAccessLevel = getAccessLevelUID(SetAccess.getValue());
}
}
SmallVector<StringRef, 4> InheritedNames;
if (!Node.InheritedTypeRanges.empty()) {
for (auto &TR : Node.InheritedTypeRanges) {
InheritedNames.push_back(SrcManager.extractText(TR));
}
}
StringRef TypeName;
if (Node.TypeRange.isValid()) {
TypeName = SrcManager.extractText(Node.TypeRange);
}
SmallString<64> DisplayNameBuf;
StringRef DisplayName;
if (auto ValueD = dyn_cast_or_null<ValueDecl>(Node.Dcl)) {
llvm::raw_svector_ostream OS(DisplayNameBuf);
if (!SwiftLangSupport::printDisplayName(ValueD, OS))
DisplayName = OS.str();
}
else if (Node.NameRange.isValid()) {
DisplayName = SrcManager.extractText(Node.NameRange);
}
SmallString<64> RuntimeNameBuf;
StringRef RuntimeName = getObjCRuntimeName(Node.Dcl, RuntimeNameBuf);
SmallString<64> SelectorNameBuf;
StringRef SelectorName = getObjCSelectorName(Node.Dcl, SelectorNameBuf);
std::vector<std::tuple<UIdent, unsigned, unsigned>> Attrs;
for (auto Attr : Node.Attrs) {
if (auto AttrUID = SwiftLangSupport::getUIDForDeclAttribute(Attr)) {
unsigned AttrOffset = 0;
unsigned AttrEnd = 0;
auto AttrRange = Attr->getRangeWithAt();
if (AttrRange.isValid()) {
auto CharRange = Lexer::getCharSourceRangeFromSourceRange(SrcManager,
AttrRange);
AttrOffset = SrcManager.getLocOffsetInBuffer(CharRange.getStart(),
BufferID);
AttrEnd = SrcManager.getLocOffsetInBuffer(CharRange.getEnd(),
BufferID);
}
auto AttrTuple = std::make_tuple(AttrUID.getValue(), AttrOffset,
AttrEnd - AttrOffset);
Attrs.push_back(AttrTuple);
}
}
Consumer.beginDocumentSubStructure(StartOffset, EndOffset - StartOffset,
Kind, AccessLevel, SetterAccessLevel,
NameStart, NameEnd - NameStart,
BodyOffset, BodyEnd - BodyOffset,
DocOffset, DocEnd - DocOffset,
DisplayName,
TypeName, RuntimeName,
SelectorName,
InheritedNames, Attrs);
for (const auto &Elem : Node.Elements) {
if (Elem.Range.isInvalid())
continue;
UIdent Kind = SwiftLangSupport::getUIDForSyntaxStructureElementKind(Elem.Kind);
unsigned Offset = SrcManager.getLocOffsetInBuffer(Elem.Range.getStart(),
BufferID);
unsigned Length = Elem.Range.getByteLength();
Consumer.handleDocumentSubStructureElement(Kind, Offset, Length);
}
return true;
}
StringRef getObjCRuntimeName(const Decl *D, SmallString<64> &Buf) {
// We only report runtime name for classes and protocols with an explicitly
// defined ObjC name, i.e. those that have @objc("SomeName")
if (D && (isa<ClassDecl>(D) || isa<ProtocolDecl>(D))) {
auto *ObjCNameAttr = D->getAttrs().getAttribute<ObjCAttr>();
if (ObjCNameAttr && ObjCNameAttr->hasName())
return ObjCNameAttr->getName()->getString(Buf);
}
return StringRef();
}
StringRef getObjCSelectorName(const Decl *D, SmallString<64> &Buf) {
// We only vend the selector name for @IBAction and @IBSegueAction methods.
if (auto FuncD = dyn_cast_or_null<FuncDecl>(D)) {
if (FuncD->getAttrs().hasAttribute<IBActionAttr>() ||
FuncD->getAttrs().hasAttribute<IBSegueActionAttr>()) {
return FuncD->getObjCSelector(DeclName(), /*skipIsObjCResolution*/true)
.getString(Buf);
}
}
return StringRef();
}
bool walkToSubStructurePost(SyntaxStructureNode Node) override {
Consumer.endDocumentSubStructure();
return true;
}
bool walkToNodePre(SyntaxNode Node) override {
if (Node.Kind != SyntaxNodeKind::CommentMarker)
return false;
unsigned StartOffset = SrcManager.getLocOffsetInBuffer(Node.Range.getStart(),
BufferID);
unsigned EndOffset = SrcManager.getLocOffsetInBuffer(Node.Range.getEnd(),
BufferID);
UIdent Kind = SwiftLangSupport::getUIDForSyntaxNodeKind(Node.Kind);
Consumer.beginDocumentSubStructure(StartOffset, EndOffset - StartOffset,
Kind, UIdent(), UIdent(), 0, 0,
0, 0, 0, 0,
StringRef(),
StringRef(), StringRef(),
StringRef(),
{}, {});
return true;
}
bool walkToNodePost(SyntaxNode Node) override {
if (Node.Kind != SyntaxNodeKind::CommentMarker)
return true;
Consumer.endDocumentSubStructure();
return true;
}
};
/// Walks the syntax model to populate a given SwiftSyntaxMap with the token
/// ranges to highlight and pass document structure information to the given
/// EditorConsumer.
class SwiftEditorSyntaxWalker: public ide::SyntaxModelWalker {
/// The syntax map to populate
SwiftSyntaxMap &SyntaxMap;
SourceManager &SrcManager;
unsigned BufferID;
SwiftDocumentStructureWalker DocStructureWalker;
/// The current token nesting level (e.g. for a field in a doc comment)
unsigned NestingLevel = 0;
public:
SwiftEditorSyntaxWalker(SwiftSyntaxMap &SyntaxMap,
SourceManager &SrcManager, EditorConsumer &Consumer,
unsigned BufferID)
: SyntaxMap(SyntaxMap), SrcManager(SrcManager), BufferID(BufferID),
DocStructureWalker(SrcManager, BufferID, Consumer) { }
bool walkToNodePre(SyntaxNode Node) override {
if (Node.Kind == SyntaxNodeKind::CommentMarker)
return DocStructureWalker.walkToNodePre(Node);
++NestingLevel;
auto End = SrcManager.getLocOffsetInBuffer(Node.Range.getEnd(), BufferID),
Start = SrcManager.getLocOffsetInBuffer(Node.Range.getStart(), BufferID);
if (NestingLevel > 1) {
// We're nested inside the previously reported token - merge
SyntaxMap.mergeToken({Start, End - Start, Node.Kind});
} else {
// We're a top-level token, add it after the previous one
SyntaxMap.addToken({Start, End - Start, Node.Kind});
}
return true;
}
bool walkToNodePost(SyntaxNode Node) override {
if (Node.Kind == SyntaxNodeKind::CommentMarker)
return DocStructureWalker.walkToNodePost(Node);
--NestingLevel;
return true;
}
bool walkToSubStructurePre(SyntaxStructureNode Node) override {
return DocStructureWalker.walkToSubStructurePre(Node);
}
bool walkToSubStructurePost(SyntaxStructureNode Node) override {
return DocStructureWalker.walkToSubStructurePost(Node);
}
};
class PlaceholderExpansionScanner {
public:
struct Param {
CharSourceRange NameRange;
CharSourceRange TypeRange;
Param(CharSourceRange NameRange, CharSourceRange TypeRange)
:NameRange(NameRange), TypeRange(TypeRange) { }
};
struct ClosureInfo {
std::vector<Param> Params;
CharSourceRange ReturnTypeRange;
};
private:
SourceManager &SM;
class PlaceholderFinder: public ASTWalker {
SourceLoc PlaceholderLoc;
EditorPlaceholderExpr *&Found;
public:
PlaceholderFinder(SourceLoc PlaceholderLoc,
EditorPlaceholderExpr *&Found)
: PlaceholderLoc(PlaceholderLoc), Found(Found) {
}
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
if (isa<EditorPlaceholderExpr>(E) && E->getStartLoc() == PlaceholderLoc) {
Found = cast<EditorPlaceholderExpr>(E);
return { false, nullptr };
}
return { true, E };
}
bool walkToDeclPre(Decl *D) override {
if (auto *ICD = dyn_cast<IfConfigDecl>(D)) {
// The base walker assumes the content of active IfConfigDecl clauses
// has been injected into the parent context and will be walked there.
// This doesn't hold for pre-typechecked ASTs and we need to find
// placeholders in inactive clauses anyway, so walk them here.
for (auto Clause: ICD->getClauses()) {
for (auto Elem: Clause.Elements) {
Elem.walk(*this);
}
}
return false;
}
return true;
}
};
class ClosureTypeWalker: public ASTWalker {
SourceManager &SM;
ClosureInfo &Info;
public:
bool FoundFunctionTypeRepr = false;
explicit ClosureTypeWalker(SourceManager &SM, ClosureInfo &Info) : SM(SM),
Info(Info) { }
bool walkToTypeReprPre(TypeRepr *T) override {
if (auto *FTR = dyn_cast<FunctionTypeRepr>(T)) {
FoundFunctionTypeRepr = true;
for (auto &ArgElt : FTR->getArgsTypeRepr()->getElements()) {
CharSourceRange NR;
CharSourceRange TR;
auto name = ArgElt.Name;
if (!name.empty()) {
NR = CharSourceRange(ArgElt.NameLoc,
name.getLength());
}
SourceLoc SRE = Lexer::getLocForEndOfToken(SM,
ArgElt.Type->getEndLoc());
TR = CharSourceRange(SM, ArgElt.Type->getStartLoc(), SRE);
Info.Params.emplace_back(NR, TR);
}
if (auto *RTR = FTR->getResultTypeRepr()) {
SourceLoc SRE = Lexer::getLocForEndOfToken(SM, RTR->getEndLoc());
Info.ReturnTypeRange = CharSourceRange(SM, RTR->getStartLoc(), SRE);
}
}
return !FoundFunctionTypeRepr;
}
bool walkToTypeReprPost(TypeRepr *T) override {
// If we just visited the FunctionTypeRepr, end traversal.
return !FoundFunctionTypeRepr;
}
};
bool containClosure(Expr *E) {
if (E->getStartLoc().isInvalid())
return false;
EditorPlaceholderExpr *Found = nullptr;
ClosureInfo Info;
ClosureTypeWalker ClosureWalker(SM, Info);
PlaceholderFinder Finder(E->getStartLoc(), Found);
E->walk(Finder);
if (Found) {
if (auto TR = Found->getPlaceholderTypeRepr()) {
TR->walk(ClosureWalker);
return ClosureWalker.FoundFunctionTypeRepr;
}
}
E->walk(ClosureWalker);
return ClosureWalker.FoundFunctionTypeRepr;
}
bool scanClosureType(EditorPlaceholderExpr *PHE,
ClosureInfo &TargetClosureInfo) {
TargetClosureInfo.Params.clear();
TargetClosureInfo.ReturnTypeRange = CharSourceRange();
if (!PHE->getTypeForExpansion())
return false;
ClosureTypeWalker PW(SM, TargetClosureInfo);
PHE->getTypeForExpansion()->walk(PW);
return PW.FoundFunctionTypeRepr;
}
/// Finds the enclosing CallExpr, and indicates whether it should be further
/// considered a candidate for application of trailing closure.
/// For example, if the CallExpr is enclosed in another expression or statement
/// such as "outer(inner(<#closure#>))", or "if inner(<#closure#>)", then trailing
/// closure should not be applied to the inner call.
std::pair<Expr*, bool> enclosingCallExprArg(SourceFile &SF, SourceLoc SL) {
class CallExprFinder : public SourceEntityWalker {
public:
const SourceManager &SM;
SourceLoc TargetLoc;
std::pair<Expr *, Expr*> EnclosingCallAndArg;
Expr *OuterExpr;
Stmt *OuterStmt;
explicit CallExprFinder(const SourceManager &SM)
:SM(SM) { }
bool checkCallExpr(Expr *E) {
Expr* Arg = nullptr;
if (auto *CE = dyn_cast<CallExpr>(E)) {
// Call expression can have argument.
Arg = CE->getArg();
} else if (auto UME = dyn_cast<UnresolvedMemberExpr>(E)) {
// Unresolved member can have argument too.
Arg = UME->getArgument();
}
if (!Arg)
return false;
if (EnclosingCallAndArg.first)
OuterExpr = EnclosingCallAndArg.first;
EnclosingCallAndArg = {E, Arg};
return true;
}
bool walkToExprPre(Expr *E) override {
auto SR = E->getSourceRange();
if (SR.isValid() && SM.rangeContainsTokenLoc(SR, TargetLoc) &&
!checkCallExpr(E) && !EnclosingCallAndArg.first) {
OuterExpr = E;
}
return true;
}
bool walkToExprPost(Expr *E) override {
if (E->getStartLoc() == TargetLoc)
return false; // found what we needed to find, stop walking.
return true;
}
/// Whether this statement body consists of only an implicit "return",
/// possibly within braces.
bool isImplicitReturnBody(Stmt *S) {
if (auto RS = dyn_cast<ReturnStmt>(S))
return RS->isImplicit() && RS->getSourceRange().Start == TargetLoc;
if (auto BS = dyn_cast<BraceStmt>(S)) {
if (BS->getNumElements() == 1) {
if (auto innerS = BS->getFirstElement().dyn_cast<Stmt *>())
return isImplicitReturnBody(innerS);
}
}
return false;
}
bool walkToStmtPre(Stmt *S) override {
auto SR = S->getSourceRange();
if (SR.isValid() && SM.rangeContainsTokenLoc(SR, TargetLoc) &&
!isImplicitReturnBody(S)) {
// A statement inside an expression - e.g. `foo({ if ... })` - resets
// the enclosing context.
//
// ... unless it's an implicit return.
OuterExpr = nullptr;
EnclosingCallAndArg = {nullptr, nullptr};
switch (S->getKind()) {
case StmtKind::Brace:
case StmtKind::Return:
case StmtKind::Yield:
case StmtKind::Throw:
// A trailing closure is allowed in these statements.
OuterStmt = nullptr;
break;
default:
OuterStmt = S;
break;
}
}
return true;
}
bool shouldWalkInactiveConfigRegion() override { return true; }
Expr *findEnclosingCallArg(SourceFile &SF, SourceLoc SL) {
EnclosingCallAndArg = {nullptr, nullptr};
OuterExpr = nullptr;
OuterStmt = nullptr;
TargetLoc = SL;
walk(SF);
return EnclosingCallAndArg.second;
}
};
CallExprFinder CEFinder(SM);
auto *CE = CEFinder.findEnclosingCallArg(SF, SL);
if (!CE)
return std::make_pair(CE, false);
if (CEFinder.OuterExpr)
return std::make_pair(CE, false);
if (CEFinder.OuterStmt)
return std::make_pair(CE, false);
return std::make_pair(CE, true);
}
struct ParamClosureInfo {
Optional<ClosureInfo> placeholderClosure;
bool isNonPlaceholderClosure = false;
bool isWrappedWithBraces = false;
};
/// Scan the given TupleExpr collecting parameter closure information and
/// returning the index of the given target placeholder (if found).
Optional<unsigned> scanTupleExpr(TupleExpr *TE, SourceLoc targetPlaceholderLoc,
std::vector<ParamClosureInfo> &outParams) {
if (TE->getElements().empty())
return llvm::None;
outParams.clear();
outParams.reserve(TE->getNumElements());
Optional<unsigned> targetPlaceholderIndex;
for (Expr *E : TE->getElements()) {
outParams.emplace_back();
auto &outParam = outParams.back();
if (auto CE = dyn_cast<ClosureExpr>(E)) {
if (CE->hasSingleExpressionBody() &&
CE->getSingleExpressionBody()->getStartLoc() ==
targetPlaceholderLoc) {
targetPlaceholderIndex = outParams.size() - 1;
if (auto *PHE = dyn_cast<EditorPlaceholderExpr>(
CE->getSingleExpressionBody())) {
outParam.isWrappedWithBraces = true;
ClosureInfo info;
if (scanClosureType(PHE, info))
outParam.placeholderClosure = info;
continue;
}
}
// else...
outParam.isNonPlaceholderClosure = true;
continue;
}
if (auto *PHE = dyn_cast<EditorPlaceholderExpr>(E)) {
ClosureInfo info;
if (scanClosureType(PHE, info))
outParam.placeholderClosure = info;
} else if (containClosure(E)) {
outParam.isNonPlaceholderClosure = true;
}
if (E->getStartLoc() == targetPlaceholderLoc) {
targetPlaceholderIndex = outParams.size() - 1;
}
}
return targetPlaceholderIndex;
}
public:
explicit PlaceholderExpansionScanner(SourceManager &SM) : SM(SM) { }
/// Retrieves the parameter list, return type and context info for
/// a typed completion placeholder in a function call.
/// For example: foo.bar(aaa, <#T##(Int, Int) -> Bool#>).
bool scan(SourceFile &SF, unsigned BufID, unsigned Offset, unsigned Length,
std::function<void(Expr *Args, bool UseTrailingClosure,
bool isWrappedWithBraces, const ClosureInfo &)>
OneClosureCallback,
std::function<void(TupleExpr *Args, unsigned FirstTrailingIndex,
ArrayRef<ClosureInfo> trailingClosures)>
MultiClosureCallback,
std::function<bool(EditorPlaceholderExpr *)> NonClosureCallback) {
SourceLoc PlaceholderStartLoc = SM.getLocForOffset(BufID, Offset);
// See if the placeholder is encapsulated with an EditorPlaceholderExpr
EditorPlaceholderExpr *PHE = nullptr;
PlaceholderFinder Finder(PlaceholderStartLoc, PHE);
SF.walk(Finder);
if (!PHE)
return NonClosureCallback(PHE);
// Retrieve parameter and return type ranges.
ClosureInfo TargetClosureInfo;
if (!scanClosureType(PHE, TargetClosureInfo))
return NonClosureCallback(PHE);
// Now we need to see if we can suggest trailing closure expansion,
// and if the call parens can be removed in that case.
// We'll first find the enclosing CallExpr, and then do further analysis.
std::vector<ParamClosureInfo> params;
Optional<unsigned> targetPlaceholderIndex;
auto ECE = enclosingCallExprArg(SF, PlaceholderStartLoc);
Expr *Args = ECE.first;
if (Args && ECE.second) {
if (isa<ParenExpr>(Args)) {
params.emplace_back();
params.back().placeholderClosure = TargetClosureInfo;
targetPlaceholderIndex = 0;
} else if (auto *TE = dyn_cast<TupleExpr>(Args)) {
targetPlaceholderIndex = scanTupleExpr(TE, PlaceholderStartLoc, params);
}
}
// If there was no appropriate parent call expression, it's non-trailing.
if (!targetPlaceholderIndex.hasValue()) {
OneClosureCallback(Args, /*useTrailingClosure=*/false,
/*isWrappedWithBraces=*/false, TargetClosureInfo);
return true;
}
const unsigned end = params.size();
unsigned firstTrailingIndex = end;
// Find the first parameter eligible to be trailing.
while (firstTrailingIndex != 0) {
unsigned i = firstTrailingIndex - 1;
if (params[i].isNonPlaceholderClosure ||
!params[i].placeholderClosure.hasValue())
break;
firstTrailingIndex = i;
}
if (firstTrailingIndex > targetPlaceholderIndex) {
// Target comes before the eligible trailing closures.
OneClosureCallback(Args, /*isTrailing=*/false,
params[*targetPlaceholderIndex].isWrappedWithBraces,
TargetClosureInfo);
return true;
} else if (targetPlaceholderIndex == end - 1 &&
firstTrailingIndex == end - 1) {
// Target is the only eligible trailing closure.
OneClosureCallback(Args, /*isTrailing=*/true,
params[*targetPlaceholderIndex].isWrappedWithBraces,
TargetClosureInfo);
return true;
}
// There are multiple trailing closures.
SmallVector<ClosureInfo, 4> trailingClosures;
trailingClosures.reserve(params.size() - firstTrailingIndex);
for (const auto &param :
llvm::makeArrayRef(params).slice(firstTrailingIndex)) {
trailingClosures.push_back(*param.placeholderClosure);
}
MultiClosureCallback(cast<TupleExpr>(Args), firstTrailingIndex,
trailingClosures);
return true;
}
};
} // anonymous namespace
SwiftEditorDocument::SwiftEditorDocument(
StringRef FilePath, SwiftLangSupport &LangSupport,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fileSystem,
CodeFormatOptions Options)
:Impl(*new Implementation(FilePath, LangSupport, Options, fileSystem)) { }
SwiftEditorDocument::~SwiftEditorDocument()
{
delete &Impl;
}
ImmutableTextSnapshotRef SwiftEditorDocument::initializeText(
llvm::MemoryBuffer *Buf, ArrayRef<const char *> Args,
bool ProvideSemanticInfo,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fileSystem) {
llvm::sys::ScopedLock L(Impl.AccessMtx);
Impl.Edited = false;
Impl.EditableBuffer =
new EditableTextBuffer(Impl.FilePath, Buf->getBuffer());
// Reset the syntax map data and affected range
Impl.SyntaxMap.Tokens.clear();
Impl.AffectedRange = {0, static_cast<unsigned>(Buf->getBufferSize())};
// Try to create a compiler invocation object if needing semantic info
// or it's syntactic-only but with passed-in compiler arguments.
if (ProvideSemanticInfo || !Args.empty()) {
Impl.SemanticInfo = new SwiftDocumentSemanticInfo(
Impl.FilePath, Impl.ASTMgr, Impl.NotificationCtr, fileSystem);
Impl.SemanticInfo->setCompilerArgs(Args);
}
return Impl.EditableBuffer->getSnapshot();
}
static void updateSemaInfo(RefPtr<SwiftDocumentSemanticInfo> SemanticInfo,
EditableTextBufferRef EditableBuffer) {
if (SemanticInfo) {
SemanticInfo->processLatestSnapshotAsync(EditableBuffer);
}
}
ImmutableTextSnapshotRef SwiftEditorDocument::replaceText(
unsigned Offset, unsigned Length, llvm::MemoryBuffer *Buf,
bool ProvideSemanticInfo, std::string &error) {
ImmutableTextSnapshotRef Snapshot;
EditableTextBufferRef EditableBuffer;
RefPtr<SwiftDocumentSemanticInfo> SemanticInfo;
{
llvm::sys::ScopedLock L(Impl.AccessMtx);
EditableBuffer = Impl.EditableBuffer;
SemanticInfo = Impl.SemanticInfo;
// Validate offset and length.
if ((Offset + Length) > EditableBuffer->getSize()) {
error = "'offset' + 'length' is out of range";
return nullptr;
}
Impl.Edited = true;
llvm::StringRef Str = Buf->getBuffer();
// Update the buffer itself
Snapshot = EditableBuffer->replace(Offset, Length, Str);
// Update the old syntax map offsets to account for the replaced range.
// Also set the initial AffectedRange to cover any tokens that
// the replaced range intersected. This allows for clients that split
// multi-line tokens at line boundaries, and ensure all parts of these tokens
// will be cleared.
Impl.AffectedRange =
Impl.SyntaxMap.adjustForReplacement(Offset, Length, Str.size());
// We need to release `AccessMtx` before calling into the ASTManager, since
// it may call back to the editor for document state.
}
if (ProvideSemanticInfo) {
// If this is not a no-op, update semantic info.
if (Length != 0 || Buf->getBufferSize() != 0) {
::updateSemaInfo(SemanticInfo, EditableBuffer);
// FIXME: we should also update any "interesting" ASTs that depend on this
// document here, e.g. any ASTs for files visible in an editor. However,
// because our API conflates this with any file with unsaved changes we do
// not update all open documents, since there could be too many of them.
}
}
return Snapshot;
}
void SwiftEditorDocument::updateSemaInfo() {
Impl.AccessMtx.lock();
auto EditableBuffer = Impl.EditableBuffer;
auto SemanticInfo = Impl.SemanticInfo;
// We need to release `AccessMtx` before calling into the ASTManager, since it
// may call back to the editor for document state.
Impl.AccessMtx.unlock();
::updateSemaInfo(SemanticInfo, EditableBuffer);
}
void SwiftEditorDocument::parse(ImmutableTextSnapshotRef Snapshot,
SwiftLangSupport &Lang, bool BuildSyntaxTree,
SyntaxParsingCache *SyntaxCache) {
llvm::sys::ScopedLock L(Impl.AccessMtx);
assert(Impl.SemanticInfo && "Impl.SemanticInfo must be set");
std::vector<std::string> Args;
std::string PrimaryFile; // Ignored, Impl.FilePath will be used
CompilerInvocation CompInv;
if (Impl.SemanticInfo->getInvocation()) {
Impl.SemanticInfo->getInvocation()->applyTo(CompInv);
Impl.SemanticInfo->getInvocation()->raw(Args, PrimaryFile);
} else {
// Use stdin as a .swift input to satisfy the driver. Note that we don't
// use Impl.FilePath here because it may be invalid filename for driver
// like "" or "-foobar".
SmallVector<const char *, 1> Args;
Args.push_back("-");
std::string Error;
// Ignore possible error(s)
Lang.getASTManager()->
initCompilerInvocation(CompInv, Args, StringRef(), Error);
}
CompInv.getLangOptions().BuildSyntaxTree = BuildSyntaxTree;
CompInv.setMainFileSyntaxParsingCache(SyntaxCache);
// When reuse parts of the syntax tree from a SyntaxParsingCache, not
// all tokens are visited and thus token collection is invalid
CompInv.getLangOptions().CollectParsedToken = (SyntaxCache == nullptr);
// Access to Impl.SyntaxInfo is guarded by Impl.AccessMtx
Impl.SyntaxInfo.reset(
new SwiftDocumentSyntaxInfo(CompInv, Snapshot, Args, Impl.FilePath));
Impl.SyntaxInfo->parse();
}
void SwiftEditorDocument::readSyntaxInfo(EditorConsumer &Consumer) {
llvm::sys::ScopedLock L(Impl.AccessMtx);
Impl.ParserDiagnostics = Impl.SyntaxInfo->getDiagnostics();
SwiftSyntaxMap NewMap = SwiftSyntaxMap(Impl.SyntaxMap.Tokens.size() + 16);
if (Consumer.syntaxTreeEnabled()) {
auto SyntaxTree = Impl.SyntaxInfo->getSourceFile().getSyntaxRoot();
Impl.SyntaxTree.emplace(SyntaxTree);
if (Consumer.syntaxMapEnabled()) {
Consumer.handleRequestError(
"Retrieving both a syntax map and a syntax tree at the same time is "
"not supported. Use the SyntaxClassifier in swiftSyntax to generate "
"the syntax map on the Swift side.");
}
if (Consumer.documentStructureEnabled()) {
Consumer.handleRequestError(
"Retrieving both the document structure and a syntax tree at the "
"same time is not supported. Use the syntax tree to compute the "
"document structure.");
}
} else {
ide::SyntaxModelContext ModelContext(Impl.SyntaxInfo->getSourceFile());
SwiftEditorSyntaxWalker SyntaxWalker(
NewMap, Impl.SyntaxInfo->getSourceManager(), Consumer,
Impl.SyntaxInfo->getBufferID());
ModelContext.walk(SyntaxWalker);
bool SawChanges = true;
if (Impl.Edited) {
// We're ansering an edit request. Report all highlighted token ranges not
// in the previous syntax map to the Consumer and extend the AffectedRange
// to contain all added/removed token ranges.
SawChanges =
NewMap.forEachChanged(Impl.SyntaxMap, Impl.AffectedRange, Consumer);
} else {
// The is an open/initialise. Report all highlighted token ranges to the
// Consumer.
NewMap.forEach(Consumer);
}
Impl.SyntaxMap = std::move(NewMap);
// Recording an affected length of 0 still results in the client updating
// its copy of the syntax map (by clearning all tokens on the line of the
// affected offset). We need to not record it at all to signal a no-op.
if (SawChanges)
Consumer.recordAffectedRange(Impl.AffectedRange->Offset,
Impl.AffectedRange->length());
}
}
void SwiftEditorDocument::readSemanticInfo(ImmutableTextSnapshotRef Snapshot,
EditorConsumer& Consumer) {
llvm::sys::ScopedLock L(Impl.AccessMtx);
std::vector<SwiftSemanticToken> SemaToks;
Optional<std::vector<DiagnosticEntryInfo>> SemaDiags;
Impl.SemanticInfo->readSemanticInfo(Snapshot, SemaToks, SemaDiags,
Impl.ParserDiagnostics);
for (auto SemaTok : SemaToks) {
unsigned Offset = SemaTok.ByteOffset;
unsigned Length = SemaTok.Length;
UIdent Kind = SemaTok.getUIdentForKind();
bool IsSystem = SemaTok.getIsSystem();
if (Kind.isValid())
Consumer.handleSemanticAnnotation(Offset, Length, Kind, IsSystem);
}
static UIdent SemaDiagStage("source.diagnostic.stage.swift.sema");
static UIdent ParseDiagStage("source.diagnostic.stage.swift.parse");
// If there's no value returned for diagnostics it means they are out-of-date
// (based on a different snapshot).
if (SemaDiags.hasValue()) {
Consumer.setDiagnosticStage(SemaDiagStage);
for (auto &Diag : SemaDiags.getValue())
Consumer.handleDiagnostic(Diag, SemaDiagStage);
} else {
Consumer.setDiagnosticStage(ParseDiagStage);
for (auto &Diag : Impl.ParserDiagnostics)
Consumer.handleDiagnostic(Diag, ParseDiagStage);
}
}
void SwiftEditorDocument::removeCachedAST() {
Impl.SemanticInfo->removeCachedAST();
}
void SwiftEditorDocument::applyFormatOptions(OptionsDictionary &FmtOptions) {
static UIdent KeyUseTabs("key.editor.format.usetabs");
static UIdent KeyIndentWidth("key.editor.format.indentwidth");
static UIdent KeyTabWidth("key.editor.format.tabwidth");
static UIdent KeyIndentSwitchCase("key.editor.format.indent_switch_case");
FmtOptions.valueForOption(KeyUseTabs, Impl.FormatOptions.UseTabs);
FmtOptions.valueForOption(KeyIndentWidth, Impl.FormatOptions.IndentWidth);
FmtOptions.valueForOption(KeyTabWidth, Impl.FormatOptions.TabWidth);
FmtOptions.valueForOption(KeyIndentSwitchCase, Impl.FormatOptions.IndentSwitchCase);
}
const CodeFormatOptions &SwiftEditorDocument::getFormatOptions() {
return Impl.FormatOptions;
}
const llvm::Optional<swift::SourceFileSyntax> &
SwiftEditorDocument::getSyntaxTree() const {
return Impl.SyntaxTree;
}
std::string SwiftEditorDocument::getFilePath() const { return Impl.FilePath; }
bool SwiftEditorDocument::hasUpToDateAST() const {
return Impl.SyntaxInfo->hasUpToDateAST();
}
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>
SwiftEditorDocument::getFileSystem() const {
llvm::sys::ScopedLock L(Impl.AccessMtx);
return Impl.SemanticInfo ? Impl.SemanticInfo->getFileSystem()
: llvm::vfs::getRealFileSystem();
}
void SwiftEditorDocument::formatText(unsigned Line, unsigned Length,
EditorConsumer &Consumer) {
auto SyntaxInfo = Impl.getSyntaxInfo();
SourceFile &SF = SyntaxInfo->getSourceFile();
SourceManager &SM = SyntaxInfo->getSourceManager();
LineRange inputRange = LineRange(Line, Length);
CodeFormatOptions Options = getFormatOptions();
auto indented = reformat(inputRange, Options, SM, SF);
LineRange LineRange = indented.first;
StringRef ModifiedText = indented.second;
Consumer.recordFormattedText(ModifiedText);
Consumer.recordAffectedLineRange(LineRange.startLine(), LineRange.lineCount());
}
static void
printClosureBody(const PlaceholderExpansionScanner::ClosureInfo &closure,
llvm::raw_ostream &OS, const SourceManager &SM) {
bool FirstParam = true;
for (auto &Param : closure.Params) {
if (!FirstParam)
OS << ", ";
FirstParam = false;
if (Param.NameRange.isValid()) {
// If we have a parameter name, just output the name as is and skip
// the type. For example:
// <#(arg1: Int, arg2: Int)#> turns into '{ arg1, arg2 in'.
OS << SM.extractText(Param.NameRange);
} else {
// If we only have the parameter type, output the type as a
// placeholder. For example:
// <#(Int, Int)#> turns into '{ <#Int#>, <#Int#> in'.
OS << "<#";
OS << SM.extractText(Param.TypeRange);
OS << "#>";
}
}
if (!FirstParam)
OS << " in";
OS << "\n" << getCodePlaceholder() << "\n";
}
void SwiftEditorDocument::expandPlaceholder(unsigned Offset, unsigned Length,
EditorConsumer &Consumer) {
auto SyntaxInfo = Impl.getSyntaxInfo();
SourceManager &SM = SyntaxInfo->getSourceManager();
unsigned BufID = SyntaxInfo->getBufferID();
const unsigned PlaceholderStartLen = 2;
const unsigned PlaceholderEndLen = 2;
if (Length < (PlaceholderStartLen + PlaceholderEndLen)) {
Consumer.handleRequestError("Invalid Length parameter");
return;
}
PlaceholderExpansionScanner Scanner(SM);
SourceFile &SF = SyntaxInfo->getSourceFile();
Scanner.scan(SF, BufID, Offset, Length,
[&](Expr *Args,
bool UseTrailingClosure, bool isWrappedWithBraces,
const PlaceholderExpansionScanner::ClosureInfo &closure) {
unsigned EffectiveOffset = Offset;
unsigned EffectiveLength = Length;
llvm::SmallString<128> ExpansionStr;
{
llvm::raw_svector_ostream OS(ExpansionStr);
if (UseTrailingClosure) {
assert(Args);
if (isa<ParenExpr>(Args)) {
// There appears to be no other parameters in this call, so we'll
// expand replacement for trailing closure and cover call parens.
// For example:
// foo.bar(<#closure#>) turns into foo.bar <#closure#>.
EffectiveOffset = SM.getLocOffsetInBuffer(Args->getStartLoc(), BufID);
OS << " ";
} else {
auto *TupleE = cast<TupleExpr>(Args);
auto Elems = TupleE->getElements();
assert(!Elems.empty());
if (Elems.size() == 1) {
EffectiveOffset = SM.getLocOffsetInBuffer(Args->getStartLoc(), BufID);
OS << " ";
} else {
// Expand replacement range for trailing closure.
// For example:
// foo.bar(a, <#closure#>) turns into foo.bar(a) <#closure#>.
// If the preceding token in the call is the leading parameter
// separator, we'll expand replacement to cover that.
assert(Elems.size() > 1);
SourceLoc BeforeLoc = Lexer::getLocForEndOfToken(SM,
Elems[Elems.size()-2]->getEndLoc());
EffectiveOffset = SM.getLocOffsetInBuffer(BeforeLoc, BufID);
OS << ") ";
}
}
unsigned End = SM.getLocOffsetInBuffer(Args->getEndLoc(), BufID);
EffectiveLength = (End + 1) - EffectiveOffset;
}
// Trailing closure syntax handling will replace braces anyway.
bool printBraces = !isWrappedWithBraces || UseTrailingClosure;
if (printBraces)
OS << "{ ";
printClosureBody(closure, OS, SM);
if (printBraces)
OS << "}";
}
Consumer.handleSourceText(ExpansionStr);
Consumer.recordAffectedRange(EffectiveOffset, EffectiveLength);
},[&](TupleExpr *args, unsigned firstTrailingIndex,
ArrayRef<PlaceholderExpansionScanner::ClosureInfo> trailingClosures) {
unsigned EffectiveOffset = Offset;
unsigned EffectiveLength = Length;
llvm::SmallString<128> ExpansionStr;
{
llvm::raw_svector_ostream OS(ExpansionStr);
assert(args->getNumElements() - firstTrailingIndex == trailingClosures.size());
if (firstTrailingIndex == 0) {
// foo(<....>) -> foo { <...> }
EffectiveOffset = SM.getLocOffsetInBuffer(args->getStartLoc(), BufID);
OS << " ";
} else {
// foo(blah, <....>) -> foo(blah) { <...> }
SourceLoc beforeTrailingLoc = Lexer::getLocForEndOfToken(SM,
args->getElements()[firstTrailingIndex - 1]->getEndLoc());
EffectiveOffset = SM.getLocOffsetInBuffer(beforeTrailingLoc, BufID);
OS << ") ";
}
unsigned End = SM.getLocOffsetInBuffer(args->getEndLoc(), BufID);
EffectiveLength = (End + 1) - EffectiveOffset;
unsigned argI = firstTrailingIndex;
for (unsigned i = 0; argI != args->getNumElements(); ++i, ++argI) {
const auto &closure = trailingClosures[i];
if (i == 0) {
OS << "{ ";
} else {
auto label = args->getElementName(argI);
OS << " " << (label.empty() ? "_" : label.str()) << ": { ";
}
printClosureBody(closure, OS, SM);
OS << "}";
}
OS << "\n";
}
Consumer.handleSourceText(ExpansionStr);
Consumer.recordAffectedRange(EffectiveOffset, EffectiveLength);
}, [&](EditorPlaceholderExpr *PHE) {
if (!PHE)
return false;
if (auto Ty = PHE->getTypeForExpansion()) {
std::string S;
llvm::raw_string_ostream OS(S);
Ty->print(OS);
Consumer.handleSourceText(OS.str());
Consumer.recordAffectedRange(Offset, Length);
return true;
}
return false;
});
}
ImmutableTextSnapshotRef SwiftEditorDocument::getLatestSnapshot() const {
llvm::sys::ScopedLock L(Impl.AccessMtx);
return Impl.EditableBuffer->getSnapshot();
}
void SwiftEditorDocument::reportDocumentStructure(SourceFile &SrcFile,
EditorConsumer &Consumer) {
ide::SyntaxModelContext ModelContext(SrcFile);
SwiftDocumentStructureWalker Walker(SrcFile.getASTContext().SourceMgr,
*SrcFile.getBufferID(),
Consumer);
ModelContext.walk(Walker);
}
//===----------------------------------------------------------------------===//
// EditorOpen
//===----------------------------------------------------------------------===//
void SwiftLangSupport::editorOpen(
StringRef Name, llvm::MemoryBuffer *Buf, EditorConsumer &Consumer,
ArrayRef<const char *> Args, Optional<VFSOptions> vfsOptions) {
std::string error;
// Do not provide primaryFile so that opening an existing document will
// reinitialize the filesystem instead of keeping the old one.
auto fileSystem = getFileSystem(vfsOptions, /*primaryFile=*/None, error);
if (!fileSystem)
return Consumer.handleRequestError(error.c_str());
ImmutableTextSnapshotRef Snapshot = nullptr;
auto EditorDoc = EditorDocuments->getByUnresolvedName(Name);
if (!EditorDoc) {
EditorDoc = new SwiftEditorDocument(Name, *this, fileSystem);
Snapshot = EditorDoc->initializeText(
Buf, Args, Consumer.needsSemanticInfo(), fileSystem);
EditorDoc->parse(Snapshot, *this, Consumer.syntaxTreeEnabled());
if (EditorDocuments->getOrUpdate(Name, *this, EditorDoc)) {
// Document already exists, re-initialize it. This should only happen
// if we get OPEN request while the previous document is not closed.
LOG_WARN_FUNC("Document already exists in editorOpen(..): " << Name);
Snapshot = nullptr;
}
auto numOpen = ++Stats->numOpenDocs;
Stats->maxOpenDocs.updateMax(numOpen);
}
if (!Snapshot) {
Snapshot = EditorDoc->initializeText(
Buf, Args, Consumer.needsSemanticInfo(), fileSystem);
EditorDoc->parse(Snapshot, *this, Consumer.syntaxTreeEnabled());
}
if (Consumer.needsSemanticInfo()) {
EditorDoc->updateSemaInfo();
}
EditorDoc->readSyntaxInfo(Consumer);
EditorDoc->readSemanticInfo(Snapshot, Consumer);
if (Consumer.syntaxTreeEnabled()) {
assert(EditorDoc->getSyntaxTree().hasValue());
std::unordered_set<unsigned> ReusedNodeIds;
Consumer.handleSyntaxTree(EditorDoc->getSyntaxTree().getValue(),
ReusedNodeIds);
}
}
//===----------------------------------------------------------------------===//
// EditorClose
//===----------------------------------------------------------------------===//
void SwiftLangSupport::editorClose(StringRef Name, bool RemoveCache) {
auto Removed = EditorDocuments->remove(Name);
if (Removed) {
--Stats->numOpenDocs;
} else {
IFaceGenContexts.remove(Name);
}
if (Removed && RemoveCache)
Removed->removeCachedAST();
// FIXME: Report error if Name did not apply to anything ?
}
//===----------------------------------------------------------------------===//
// EditorReplaceText
//===----------------------------------------------------------------------===//
void verifyIncrementalParse(SwiftEditorDocumentRef EditorDoc,
unsigned EditOffset, unsigned EditLength,
StringRef PreEditText, StringRef ReplaceText) {
swift::json::Output::UserInfoMap JsonUserInfo;
JsonUserInfo[swift::json::DontSerializeNodeIdsUserInfoKey] =
reinterpret_cast<void *>(true);
// Dump the incremental syntax tree
std::string IncrTreeString;
llvm::raw_string_ostream IncrTreeStream(IncrTreeString);
swift::json::Output IncrTreeOutput(IncrTreeStream, JsonUserInfo);
IncrTreeOutput << *EditorDoc->getSyntaxTree()->getRaw();
// Reparse the file from scratch
CompilerInvocation Invocation;
Invocation.getLangOptions().BuildSyntaxTree = true;
std::vector<std::string> Args;
SwiftDocumentSyntaxInfo ScratchSyntaxInfo(Invocation,
EditorDoc->getLatestSnapshot(),
Args, EditorDoc->getFilePath());
ScratchSyntaxInfo.parse();
// Dump the from-scratch syntax tree
std::string FromScratchTreeString;
llvm::raw_string_ostream ScratchTreeStream(FromScratchTreeString);
swift::json::Output ScratchTreeOutput(ScratchTreeStream, JsonUserInfo);
auto SyntaxRoot = ScratchSyntaxInfo.getSourceFile().getSyntaxRoot();
ScratchTreeOutput << *SyntaxRoot.getRaw();
// If the serialized format of the two trees doesn't match incremental parsing
// we have found an error.
if (IncrTreeStream.str().compare(ScratchTreeStream.str())) {
LOG_SECTION("Incremental Parsing", Warning) {
Log->getOS() << "Incremental parsing different to from scratch parsing\n";
Log->getOS() << "Edit was " << EditOffset << "-"
<< (EditOffset + EditLength) << "='" << ReplaceText << "'"
<< " pre-edit-text: '" << PreEditText << "'\n";
SmallString<32> DirectoryName;
if (llvm::sys::fs::createUniqueDirectory(
"SourceKit-IncrementalParsing-Inconsistency", DirectoryName)) {
Log->getOS() << "Failed to create log directory\n";
}
std::error_code ErrorCode;
// Write the incremental syntax tree
auto IncrTreeFilename = DirectoryName + "/incrementalTree.json";
llvm::raw_fd_ostream IncrementalFilestream(
IncrTreeFilename.str(), ErrorCode,
llvm::sys::fs::FA_Read | llvm::sys::fs::FA_Write);
IncrementalFilestream << IncrTreeStream.str();
if (ErrorCode) {
Log->getOS() << "Failed to write incremental syntax tree to "
<< IncrTreeFilename << "(error code " << ErrorCode.value()
<< ": " << ErrorCode.message() << ")\n";
} else {
Log->getOS() << "Incremental syntax tree written to "
<< IncrTreeFilename << '\n';
}
// Write from-scratch syntax tree
auto ScratchTreeFilename = DirectoryName + "/fromScratchTree.json";
llvm::raw_fd_ostream ScratchTreeFilestream(
ScratchTreeFilename.str(), ErrorCode,
llvm::sys::fs::FA_Read | llvm::sys::fs::FA_Write);
ScratchTreeFilestream << ScratchTreeStream.str();
if (ErrorCode) {
Log->getOS() << "Failed to write from-scratch syntax tree to "
<< ScratchTreeFilename << "(error code "
<< ErrorCode.value() << ": " << ErrorCode.message()
<< ")\n";
} else {
Log->getOS() << "From-scratch syntax tree written to "
<< ScratchTreeFilename << '\n';
}
// Write source file
auto SourceFilename = DirectoryName + "/postEditSource.swift";
llvm::raw_fd_ostream SourceFilestream(SourceFilename.str(), ErrorCode,
llvm::sys::fs::FA_Read | llvm::sys::fs::FA_Write);
auto FileBuffer = EditorDoc->getLatestSnapshot()->getBuffer();
SourceFilestream << FileBuffer->getText();
}
}
}
void SwiftLangSupport::editorReplaceText(StringRef Name,
llvm::MemoryBuffer *Buf,
unsigned Offset, unsigned Length,
EditorConsumer &Consumer) {
bool LogReuseRegions = ::getenv("SOURCEKIT_LOG_INCREMENTAL_REUSE_REGIONS");
bool ValidateSyntaxTree = ::getenv("SOURCEKIT_INCREMENTAL_PARSE_VALIDATION");
auto EditorDoc = EditorDocuments->getByUnresolvedName(Name);
if (!EditorDoc) {
Consumer.handleRequestError("No associated Editor Document");
return;
}
ImmutableTextSnapshotRef Snapshot;
if (Length != 0 || Buf->getBufferSize() != 0) {
std::string PreEditText;
if (ValidateSyntaxTree) {
auto CurBuffer = EditorDoc->getLatestSnapshot()->getBuffer();
auto BufferStart = CurBuffer->getInternalBuffer()->getBufferStart();
StringRef PreEditTextRef(BufferStart + Offset, Length);
PreEditText = PreEditTextRef.str();
}
std::string error;
Snapshot = EditorDoc->replaceText(Offset, Length, Buf,
Consumer.needsSemanticInfo(), error);
if (!Snapshot) {
assert(error.size());
Consumer.handleRequestError(error.c_str());
return;
}
llvm::Optional<SyntaxParsingCache> SyntaxCache = llvm::None;
if (EditorDoc->getSyntaxTree().hasValue()) {
SyntaxCache.emplace(EditorDoc->getSyntaxTree().getValue());
SyntaxCache->addEdit(Offset, Offset + Length, Buf->getBufferSize());
}
SyntaxParsingCache *SyntaxCachePtr = nullptr;
if (SyntaxCache.hasValue()) {
SyntaxCachePtr = SyntaxCache.getPointer();
}
EditorDoc->parse(Snapshot, *this, Consumer.syntaxTreeEnabled(),
SyntaxCachePtr);
EditorDoc->readSyntaxInfo(Consumer);
// Log reuse information
if (SyntaxCache.hasValue() && LogReuseRegions) {
auto &SyntaxTree = EditorDoc->getSyntaxTree();
auto ReuseRegions = SyntaxCache->getReusedRegions(*SyntaxTree);
LOG_SECTION("SyntaxCache", InfoHighPrio) {
Log->getOS() << "Reused ";
bool FirstIteration = true;
for (auto ReuseRegion : ReuseRegions) {
if (!FirstIteration) {
Log->getOS() << ", ";
} else {
FirstIteration = false;
}
Log->getOS() << ReuseRegion.Start << " - " << ReuseRegion.End;
}
}
}
if (Consumer.syntaxTreeEnabled()) {
std::unordered_set<unsigned> ReusedNodeIds;
if (SyntaxCache.hasValue()) {
auto &ReusedVector = SyntaxCache->getReusedNodeIds();
ReusedNodeIds = std::unordered_set<unsigned>(ReusedVector.begin(),
ReusedVector.end());
}
Consumer.handleSyntaxTree(EditorDoc->getSyntaxTree().getValue(),
ReusedNodeIds);
}
if (ValidateSyntaxTree) {
verifyIncrementalParse(EditorDoc, Offset, Length, PreEditText,
Buf->getBuffer());
}
} else {
Snapshot = EditorDoc->getLatestSnapshot();
}
EditorDoc->readSemanticInfo(Snapshot, Consumer);
}
//===----------------------------------------------------------------------===//
// EditorFormatText
//===----------------------------------------------------------------------===//
void SwiftLangSupport::editorApplyFormatOptions(StringRef Name,
OptionsDictionary &FmtOptions) {
auto EditorDoc = EditorDocuments->getByUnresolvedName(Name);
if (EditorDoc)
EditorDoc->applyFormatOptions(FmtOptions);
}
void SwiftLangSupport::editorFormatText(StringRef Name, unsigned Line,
unsigned Length,
EditorConsumer &Consumer) {
auto EditorDoc = EditorDocuments->getByUnresolvedName(Name);
if (!EditorDoc) {
Consumer.handleRequestError("No associated Editor Document");
return;
}
if (!EditorDoc->hasUpToDateAST()) {
// An up-to-date AST is needed for formatting. If it does not exist, fall
// back to a full reparse of the file
EditorDoc->parse(EditorDoc->getLatestSnapshot(), *this,
/*BuildSyntaxTree=*/true);
}
EditorDoc->formatText(Line, Length, Consumer);
}
void SwiftLangSupport::editorExtractTextFromComment(StringRef Source,
EditorConsumer &Consumer) {
Consumer.handleSourceText(extractPlainTextFromComment(Source));
}
void SwiftLangSupport::editorConvertMarkupToXML(StringRef Source,
EditorConsumer &Consumer) {
std::string Result;
llvm::raw_string_ostream OS(Result);
if (convertMarkupToXML(Source, OS)) {
Consumer.handleRequestError("Conversion failed.");
return;
}
Consumer.handleSourceText(Result);
}
//===----------------------------------------------------------------------===//
// EditorExpandPlaceholder
//===----------------------------------------------------------------------===//
void SwiftLangSupport::editorExpandPlaceholder(StringRef Name, unsigned Offset,
unsigned Length,
EditorConsumer &Consumer) {
auto EditorDoc = EditorDocuments->getByUnresolvedName(Name);
if (!EditorDoc) {
Consumer.handleRequestError("No associated Editor Document");
return;
}
EditorDoc->expandPlaceholder(Offset, Length, Consumer);
}