| //===--- Utils.h - Misc utilities -------------------------------*- C++ -*-===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef SWIFT_IDE_UTILS_H |
| #define SWIFT_IDE_UTILS_H |
| |
| #include "llvm/ADT/PointerIntPair.h" |
| #include "swift/Basic/LLVM.h" |
| #include "swift/AST/ASTNode.h" |
| #include "swift/AST/DeclNameLoc.h" |
| #include "swift/AST/Module.h" |
| #include "swift/AST/ASTPrinter.h" |
| #include "swift/IDE/SourceEntityWalker.h" |
| #include "swift/Parse/Token.h" |
| #include "llvm/ADT/StringRef.h" |
| #include <memory> |
| #include <string> |
| #include <functional> |
| #include <vector> |
| |
| namespace llvm { |
| template<typename Fn> class function_ref; |
| class MemoryBuffer; |
| } |
| |
| namespace clang { |
| class Module; |
| class NamedDecl; |
| } |
| |
| namespace swift { |
| class ModuleDecl; |
| class ValueDecl; |
| class ASTContext; |
| class CompilerInvocation; |
| class SourceFile; |
| class TypeDecl; |
| class SourceLoc; |
| class Type; |
| class Decl; |
| class DeclContext; |
| class CallExpr; |
| class ClangNode; |
| class ClangImporter; |
| class Token; |
| |
| namespace ide { |
| struct SourceCompleteResult { |
| // Set to true if the input source is fully formed, false otherwise. |
| bool IsComplete; |
| // The text to use as the indent string when auto indenting the next line. |
| // This will contain the exactly what the client typed (any whitespaces and |
| // tabs) and can be used to indent subsequent lines. It does not include |
| // the current indent level, IDE clients should insert the correct indentation |
| // with spaces or tabs to account for the current indent level. The indent |
| // prefix will contain the leading space characters of the line that |
| // contained the '{', '(' or '[' character that was unbalanced. |
| std::string IndentPrefix; |
| // Returns the indent level as an indentation count (number of indentations |
| // to apply). Clients can translate this into the standard indentation that |
| // is being used by the IDE (3 spaces? 1 tab?) and should use the indent |
| // prefix string followed by the correct indentation. |
| uint32_t IndentLevel; |
| |
| SourceCompleteResult() : |
| IsComplete(false), |
| IndentPrefix(), |
| IndentLevel(0) {} |
| }; |
| |
| SourceCompleteResult |
| isSourceInputComplete(std::unique_ptr<llvm::MemoryBuffer> MemBuf, SourceFileKind SFKind); |
| SourceCompleteResult isSourceInputComplete(StringRef Text, SourceFileKind SFKind); |
| |
| bool initCompilerInvocation( |
| CompilerInvocation &Invocation, ArrayRef<const char *> OrigArgs, |
| DiagnosticEngine &Diags, StringRef UnresolvedPrimaryFile, |
| llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem, |
| const std::string &runtimeResourcePath, |
| const std::string &diagnosticDocumentationPath, |
| bool shouldOptimizeForIDE, time_t sessionTimestamp, std::string &Error); |
| |
| bool initInvocationByClangArguments(ArrayRef<const char *> ArgList, |
| CompilerInvocation &Invok, |
| std::string &Error); |
| |
| /// Visits all overridden declarations exhaustively from VD, including protocol |
| /// conformances and clang declarations. |
| void walkOverriddenDecls(const ValueDecl *VD, |
| llvm::function_ref<void(llvm::PointerUnion< |
| const ValueDecl*, const clang::NamedDecl*>)> Fn); |
| |
| void collectModuleNames(StringRef SDKPath, std::vector<std::string> &Modules); |
| |
| struct PlaceholderOccurrence { |
| /// The complete placeholder string. |
| StringRef FullPlaceholder; |
| /// The inner string of the placeholder. |
| StringRef PlaceholderContent; |
| /// The dollar identifier that was used to replace the placeholder. |
| StringRef IdentifierReplacement; |
| }; |
| |
| /// Replaces Xcode editor placeholders (<#such as this#>) with dollar |
| /// identifiers and returns a new memory buffer. |
| /// |
| /// The replacement identifier will be the same size as the placeholder so that |
| /// the new buffer will have the same size as the input buffer. |
| std::unique_ptr<llvm::MemoryBuffer> |
| replacePlaceholders(std::unique_ptr<llvm::MemoryBuffer> InputBuf, |
| llvm::function_ref<void(const PlaceholderOccurrence &)> Callback); |
| |
| std::unique_ptr<llvm::MemoryBuffer> |
| replacePlaceholders(std::unique_ptr<llvm::MemoryBuffer> InputBuf, |
| bool *HadPlaceholder = nullptr); |
| |
| void getLocationInfo( |
| const ValueDecl *VD, |
| llvm::Optional<std::pair<unsigned, unsigned>> &DeclarationLoc, |
| StringRef &Filename); |
| |
| void getLocationInfoForClangNode(ClangNode ClangNode, |
| ClangImporter *Importer, |
| llvm::Optional<std::pair<unsigned, unsigned>> &DeclarationLoc, |
| StringRef &Filename); |
| |
| Optional<std::pair<unsigned, unsigned>> parseLineCol(StringRef LineCol); |
| |
| class XMLEscapingPrinter : public StreamPrinter { |
| public: |
| XMLEscapingPrinter(raw_ostream &OS) : StreamPrinter(OS){}; |
| void printText(StringRef Text) override; |
| void printXML(StringRef Text); |
| }; |
| |
| enum class CursorInfoKind { |
| Invalid, |
| ValueRef, |
| ModuleRef, |
| ExprStart, |
| StmtStart, |
| }; |
| |
| struct ResolvedCursorInfo { |
| CursorInfoKind Kind = CursorInfoKind::Invalid; |
| SourceFile *SF; |
| SourceLoc Loc; |
| ValueDecl *ValueD = nullptr; |
| TypeDecl *CtorTyRef = nullptr; |
| ExtensionDecl *ExtTyRef = nullptr; |
| ModuleEntity Mod; |
| bool IsRef = true; |
| bool IsKeywordArgument = false; |
| Type Ty; |
| DeclContext *DC = nullptr; |
| Type ContainerType; |
| Stmt *TrailingStmt = nullptr; |
| Expr *TrailingExpr = nullptr; |
| |
| ResolvedCursorInfo() = default; |
| ResolvedCursorInfo(SourceFile *SF) : SF(SF) {} |
| |
| friend bool operator==(const ResolvedCursorInfo &lhs, |
| const ResolvedCursorInfo &rhs) { |
| return lhs.SF == rhs.SF && |
| lhs.Loc.getOpaquePointerValue() == rhs.Loc.getOpaquePointerValue(); |
| } |
| |
| void setValueRef(ValueDecl *ValueD, |
| TypeDecl *CtorTyRef, |
| ExtensionDecl *ExtTyRef, |
| bool IsRef, |
| Type Ty, |
| Type ContainerType) { |
| Kind = CursorInfoKind::ValueRef; |
| this->ValueD = ValueD; |
| this->CtorTyRef = CtorTyRef; |
| this->ExtTyRef = ExtTyRef; |
| this->IsRef = IsRef; |
| this->Ty = Ty; |
| this->DC = ValueD->getDeclContext(); |
| this->ContainerType = ContainerType; |
| } |
| void setModuleRef(ModuleEntity Mod) { |
| Kind = CursorInfoKind::ModuleRef; |
| this->Mod = Mod; |
| } |
| void setTrailingStmt(Stmt *TrailingStmt) { |
| Kind = CursorInfoKind::StmtStart; |
| this->TrailingStmt = TrailingStmt; |
| } |
| void setTrailingExpr(Expr* TrailingExpr) { |
| Kind = CursorInfoKind::ExprStart; |
| this->TrailingExpr = TrailingExpr; |
| } |
| |
| bool isValid() const { return !isInvalid(); } |
| bool isInvalid() const { return Kind == CursorInfoKind::Invalid; } |
| }; |
| |
| void simple_display(llvm::raw_ostream &out, const ResolvedCursorInfo &info); |
| |
| struct UnresolvedLoc { |
| SourceLoc Loc; |
| bool ResolveArgLocs; |
| }; |
| |
| enum class LabelRangeType { |
| None, |
| CallArg, // foo([a: ]2) or .foo([a: ]String) |
| Param, // func([a b]: Int) |
| NoncollapsibleParam, // subscript([a a]: Int) |
| Selector, // #selector(foo.func([a]:)) |
| }; |
| |
| struct ResolvedLoc { |
| ASTWalker::ParentTy Node; |
| CharSourceRange Range; |
| std::vector<CharSourceRange> LabelRanges; |
| Optional<unsigned> FirstTrailingLabel; |
| LabelRangeType LabelType; |
| bool IsActive; |
| bool IsInSelector; |
| }; |
| |
| /// Used by NameMatcher to track parent CallExprs when walking a checked AST. |
| struct CallingParent { |
| Expr *ApplicableTo; |
| CallExpr *Call; |
| }; |
| |
| |
| /// Finds the parse-only AST nodes and corresponding name and param/argument |
| /// label ranges for a given list of input name start locations |
| /// |
| /// Resolved locations also indicate the nature of the matched occurrence (e.g. |
| /// whether it is within active/inactive code, or a selector or string literal). |
| class NameMatcher: public ASTWalker { |
| SourceFile &SrcFile; |
| std::vector<UnresolvedLoc> LocsToResolve; |
| std::vector<ResolvedLoc> ResolvedLocs; |
| ArrayRef<Token> TokensToCheck; |
| |
| /// The \c Expr argument of a parent \c CustomAttr (if one exists) and |
| /// the \c SourceLoc of the type name it applies to. |
| llvm::Optional<Located<Expr *>> CustomAttrArg; |
| unsigned InactiveConfigRegionNestings = 0; |
| unsigned SelectorNestings = 0; |
| |
| /// The stack of parent CallExprs and the innermost expression they apply to. |
| std::vector<CallingParent> ParentCalls; |
| |
| SourceManager &getSourceMgr() const; |
| |
| SourceLoc nextLoc() const; |
| bool isDone() const { return LocsToResolve.empty(); }; |
| bool isActive() const { return !InactiveConfigRegionNestings; }; |
| bool isInSelector() const { return SelectorNestings; }; |
| bool checkComments(); |
| void skipLocsBefore(SourceLoc Start); |
| bool shouldSkip(Expr *E); |
| bool shouldSkip(SourceRange Range); |
| bool shouldSkip(CharSourceRange Range); |
| bool tryResolve(ASTWalker::ParentTy Node, SourceLoc NameLoc); |
| bool tryResolve(ASTWalker::ParentTy Node, DeclNameLoc NameLoc, Expr *Arg); |
| bool tryResolve(ASTWalker::ParentTy Node, SourceLoc NameLoc, LabelRangeType RangeType, |
| ArrayRef<CharSourceRange> LabelLocs, |
| Optional<unsigned> FirstTrailingLabel); |
| bool handleCustomAttrs(Decl *D); |
| Expr *getApplicableArgFor(Expr* E); |
| |
| std::pair<bool, Expr*> walkToExprPre(Expr *E) override; |
| Expr* walkToExprPost(Expr *E) override; |
| bool walkToDeclPre(Decl *D) override; |
| bool walkToDeclPost(Decl *D) override; |
| std::pair<bool, Stmt*> walkToStmtPre(Stmt *S) override; |
| Stmt* walkToStmtPost(Stmt *S) override; |
| bool walkToTypeReprPre(TypeRepr *T) override; |
| bool walkToTypeReprPost(TypeRepr *T) override; |
| std::pair<bool, Pattern*> walkToPatternPre(Pattern *P) override; |
| bool shouldWalkIntoGenericParams() override { return true; } |
| |
| // FIXME: Remove this |
| bool shouldWalkAccessorsTheOldWay() override { return true; } |
| |
| public: |
| explicit NameMatcher(SourceFile &SrcFile) : SrcFile(SrcFile) { } |
| std::vector<ResolvedLoc> resolve(ArrayRef<UnresolvedLoc> Locs, ArrayRef<Token> Tokens); |
| ResolvedLoc resolve(UnresolvedLoc Loc); |
| }; |
| |
| enum class RangeKind : int8_t { |
| Invalid = -1, |
| SingleExpression, |
| SingleStatement, |
| SingleDecl, |
| |
| MultiStatement, |
| PartOfExpression, |
| |
| MultiTypeMemberDecl, |
| }; |
| |
| struct DeclaredDecl { |
| ValueDecl *VD; |
| bool ReferredAfterRange; |
| DeclaredDecl(ValueDecl* VD) : VD(VD), ReferredAfterRange(false) {} |
| DeclaredDecl(): DeclaredDecl(nullptr) {} |
| bool operator==(const DeclaredDecl& other); |
| }; |
| |
| struct ReferencedDecl { |
| ValueDecl *VD; |
| Type Ty; |
| ReferencedDecl(ValueDecl* VD, Type Ty) : VD(VD), Ty(Ty) {} |
| ReferencedDecl() : ReferencedDecl(nullptr, Type()) {} |
| }; |
| |
| enum class OrphanKind : int8_t { |
| None, |
| Break, |
| Continue, |
| }; |
| |
| enum class ExitState: int8_t { |
| Positive, |
| Negative, |
| Unsure, |
| }; |
| |
| struct ReturnInfo { |
| TypeBase* ReturnType; |
| ExitState Exit; |
| ReturnInfo(): ReturnInfo(nullptr, ExitState::Unsure) {} |
| ReturnInfo(TypeBase* ReturnType, ExitState Exit): |
| ReturnType(ReturnType), Exit(Exit) {} |
| ReturnInfo(ASTContext &Ctx, ArrayRef<ReturnInfo> Branches); |
| }; |
| |
| struct ResolvedRangeInfo { |
| RangeKind Kind; |
| ReturnInfo ExitInfo; |
| ArrayRef<Token> TokensInRange; |
| CharSourceRange ContentRange; |
| bool HasSingleEntry; |
| bool ThrowingUnhandledError; |
| OrphanKind Orphan; |
| |
| // The topmost ast nodes contained in the given range. |
| ArrayRef<ASTNode> ContainedNodes; |
| ArrayRef<DeclaredDecl> DeclaredDecls; |
| ArrayRef<ReferencedDecl> ReferencedDecls; |
| DeclContext* RangeContext; |
| Expr* CommonExprParent; |
| |
| ResolvedRangeInfo(RangeKind Kind, ReturnInfo ExitInfo, |
| ArrayRef<Token> TokensInRange, |
| DeclContext* RangeContext, |
| Expr *CommonExprParent, bool HasSingleEntry, |
| bool ThrowingUnhandledError, |
| OrphanKind Orphan, ArrayRef<ASTNode> ContainedNodes, |
| ArrayRef<DeclaredDecl> DeclaredDecls, |
| ArrayRef<ReferencedDecl> ReferencedDecls): Kind(Kind), |
| ExitInfo(ExitInfo), |
| TokensInRange(TokensInRange), |
| ContentRange(calculateContentRange(TokensInRange)), |
| HasSingleEntry(HasSingleEntry), |
| ThrowingUnhandledError(ThrowingUnhandledError), |
| Orphan(Orphan), ContainedNodes(ContainedNodes), |
| DeclaredDecls(DeclaredDecls), |
| ReferencedDecls(ReferencedDecls), |
| RangeContext(RangeContext), |
| CommonExprParent(CommonExprParent) {} |
| ResolvedRangeInfo(ArrayRef<Token> TokensInRange) : |
| ResolvedRangeInfo(RangeKind::Invalid, {nullptr, ExitState::Unsure}, |
| TokensInRange, nullptr, /*Commom Expr Parent*/nullptr, |
| /*Single entry*/true, /*unhandled error*/false, |
| OrphanKind::None, {}, {}, {}) {} |
| ResolvedRangeInfo(): ResolvedRangeInfo(ArrayRef<Token>()) {} |
| void print(llvm::raw_ostream &OS) const; |
| ExitState exit() const { return ExitInfo.Exit; } |
| Type getType() const { return ExitInfo.ReturnType; } |
| |
| friend bool operator==(const ResolvedRangeInfo &lhs, |
| const ResolvedRangeInfo &rhs) { |
| if (lhs.TokensInRange.size() != rhs.TokensInRange.size()) |
| return false; |
| if (lhs.TokensInRange.empty()) |
| return true; |
| return lhs.TokensInRange.front().getLoc() == |
| rhs.TokensInRange.front().getLoc(); |
| } |
| |
| private: |
| static CharSourceRange calculateContentRange(ArrayRef<Token> Tokens); |
| }; |
| |
| void simple_display(llvm::raw_ostream &out, const ResolvedRangeInfo &info); |
| |
| /// This provides a utility to view a printed name by parsing the components |
| /// of that name. The components include a base name and an array of argument |
| /// labels. |
| class DeclNameViewer { |
| StringRef BaseName; |
| SmallVector<StringRef, 4> Labels; |
| bool IsValid; |
| bool HasParen; |
| public: |
| DeclNameViewer(StringRef Text); |
| DeclNameViewer() : DeclNameViewer(StringRef()) {} |
| operator bool() const { return !BaseName.empty(); } |
| StringRef base() const { return BaseName; } |
| llvm::ArrayRef<StringRef> args() const { return llvm::makeArrayRef(Labels); } |
| unsigned argSize() const { return Labels.size(); } |
| unsigned partsCount() const { return 1 + Labels.size(); } |
| unsigned commonPartsCount(DeclNameViewer &Other) const; |
| bool isValid() const { return IsValid; } |
| bool isFunction() const { return HasParen; } |
| }; |
| |
| /// This provide a utility for writing to an underlying string buffer multiple |
| /// string pieces and retrieve them later when the underlying buffer is stable. |
| class DelayedStringRetriever : public raw_ostream { |
| SmallVectorImpl<char> &OS; |
| llvm::raw_svector_ostream Underlying; |
| SmallVector<std::pair<unsigned, unsigned>, 4> StartEnds; |
| unsigned CurrentStart; |
| |
| public: |
| explicit DelayedStringRetriever(SmallVectorImpl<char> &OS) : OS(OS), |
| Underlying(OS) {} |
| void startPiece() { |
| CurrentStart = OS.size(); |
| } |
| void endPiece() { |
| StartEnds.emplace_back(CurrentStart, OS.size()); |
| } |
| void write_impl(const char *ptr, size_t size) override { |
| Underlying.write(ptr, size); |
| } |
| uint64_t current_pos() const override { |
| return Underlying.tell(); |
| } |
| size_t preferred_buffer_size() const override { |
| return 0; |
| } |
| void retrieve(llvm::function_ref<void(StringRef)> F) const { |
| for (auto P : StartEnds) { |
| F(StringRef(OS.begin() + P.first, P.second - P.first)); |
| } |
| } |
| StringRef operator[](unsigned I) const { |
| auto P = StartEnds[I]; |
| return StringRef(OS.begin() + P.first, P.second - P.first); |
| } |
| }; |
| |
| enum class RegionType { |
| Unmatched, |
| Mismatch, |
| ActiveCode, |
| InactiveCode, |
| String, |
| Selector, |
| Comment, |
| }; |
| |
| enum class RefactoringRangeKind { |
| BaseName, // func [foo](a b: Int) |
| KeywordBaseName, // [init](a: Int) |
| ParameterName, // func foo(a[ b]: Int) |
| NoncollapsibleParameterName, // subscript(a[ a]: Int) |
| DeclArgumentLabel, // func foo([a] b: Int) |
| CallArgumentLabel, // foo([a]: 1) |
| CallArgumentColon, // foo(a[: ]1) |
| CallArgumentCombined, // foo([]1) could expand to foo([a: ]1) |
| SelectorArgumentLabel, // foo([a]:) |
| }; |
| |
| struct NoteRegion { |
| RefactoringRangeKind Kind; |
| |
| // The below are relative to the containing Replacement's Text |
| unsigned StartLine; |
| unsigned StartColumn; |
| unsigned EndLine; |
| unsigned EndColumn; |
| Optional<unsigned> ArgIndex; |
| }; |
| |
| struct Replacement { |
| CharSourceRange Range; |
| StringRef Text; |
| ArrayRef<NoteRegion> RegionsWorthNote; |
| }; |
| |
| class SourceEditConsumer { |
| public: |
| virtual void accept(SourceManager &SM, RegionType RegionType, ArrayRef<Replacement> Replacements) = 0; |
| virtual ~SourceEditConsumer() = default; |
| void accept(SourceManager &SM, CharSourceRange Range, StringRef Text, ArrayRef<NoteRegion> SubRegions = {}); |
| void accept(SourceManager &SM, SourceLoc Loc, StringRef Text, ArrayRef<NoteRegion> SubRegions = {}); |
| void insertAfter(SourceManager &SM, SourceLoc Loc, StringRef Text, ArrayRef<NoteRegion> SubRegions = {}); |
| void accept(SourceManager &SM, Replacement Replacement) { accept(SM, RegionType::ActiveCode, {Replacement}); } |
| void remove(SourceManager &SM, CharSourceRange Range); |
| }; |
| |
| /// This helper stream inserts text into a SourceLoc by calling functions in |
| /// SourceEditorConsumer when it is destroyed. |
| class EditorConsumerInsertStream: public raw_ostream { |
| SourceEditConsumer &Consumer; |
| SourceManager &SM; |
| CharSourceRange Range; |
| llvm::SmallString<64> Buffer; |
| llvm::raw_svector_ostream OS; |
| |
| public: |
| explicit EditorConsumerInsertStream(SourceEditConsumer &Consumer, |
| SourceManager &SM, |
| CharSourceRange Range): |
| Consumer(Consumer), SM(SM), Range(Range), Buffer(), OS(Buffer) {} |
| |
| explicit EditorConsumerInsertStream(SourceEditConsumer &Consumer, |
| SourceManager &SM, |
| SourceLoc Loc): |
| EditorConsumerInsertStream(Consumer, SM, CharSourceRange(Loc, 0)) {} |
| |
| ~EditorConsumerInsertStream() { |
| Consumer.accept(SM, Range, OS.str()); |
| } |
| |
| void write_impl(const char *ptr, size_t size) override { |
| OS.write(ptr, size); |
| } |
| uint64_t current_pos() const override { |
| return OS.tell(); |
| } |
| size_t preferred_buffer_size() const override { |
| return 0; |
| } |
| }; |
| |
| class SourceEditJsonConsumer : public SourceEditConsumer { |
| struct Implementation; |
| Implementation &Impl; |
| public: |
| SourceEditJsonConsumer(llvm::raw_ostream &OS); |
| ~SourceEditJsonConsumer(); |
| void accept(SourceManager &SM, RegionType RegionType, ArrayRef<Replacement> Replacements) override; |
| }; |
| |
| class SourceEditOutputConsumer : public SourceEditConsumer { |
| struct Implementation; |
| Implementation &Impl; |
| |
| public: |
| SourceEditOutputConsumer(SourceManager &SM, unsigned BufferId, llvm::raw_ostream &OS); |
| ~SourceEditOutputConsumer(); |
| void accept(SourceManager &SM, RegionType RegionType, ArrayRef<Replacement> Replacements) override; |
| }; |
| |
| enum class LabelRangeEndAt: int8_t { |
| BeforeElemStart, |
| LabelNameOnly, |
| }; |
| |
| struct CallArgInfo { |
| Expr *ArgExp; |
| CharSourceRange LabelRange; |
| bool IsTrailingClosure; |
| CharSourceRange getEntireCharRange(const SourceManager &SM) const; |
| }; |
| |
| std::vector<CallArgInfo> |
| getCallArgInfo(SourceManager &SM, Expr *Arg, LabelRangeEndAt EndKind); |
| |
| // Get the ranges of argument labels from an Arg, either tuple or paren, and |
| // the index of the first trailing closure argument, if any. This includes empty |
| // ranges for any unlabelled arguments, including the first trailing closure. |
| std::pair<std::vector<CharSourceRange>, Optional<unsigned>> |
| getCallArgLabelRanges(SourceManager &SM, Expr *Arg, LabelRangeEndAt EndKind); |
| |
| /// Whether a decl is defined from clang source. |
| bool isFromClang(const Decl *D); |
| |
| /// Retrieve the effective Clang node for the given declaration, which |
| /// copes with the odd case of imported Error enums. |
| ClangNode getEffectiveClangNode(const Decl *decl); |
| |
| /// Retrieve the Clang node for the given extension, if it has one. |
| ClangNode extensionGetClangNode(const ExtensionDecl *ext); |
| |
| /// Utility for finding the referenced declaration from a call, which might |
| /// include a second level of function application for a 'self.' expression, |
| /// or a curry thunk, etc. |
| std::pair<Type, ConcreteDeclRef> getReferencedDecl(Expr *expr); |
| |
| } // namespace ide |
| } // namespace swift |
| |
| #endif // SWIFT_IDE_UTILS_H |
| |