Skip to content

[5.1] cherry pick recent sourcekitd/IDE fixes #22952

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/swift/AST/DiagnosticsCommon.def
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ ERROR(not_implemented,none,
ERROR(error_opening_output,none,
"error opening '%0' for output: %1", (StringRef, StringRef))

ERROR(cannot_find_group_info_file,none,
"cannot find group info file at path: '%0'", (StringRef))

ERROR(cannot_parse_group_info_file,none,
"cannot parse group info file at path: '%0'", (StringRef))

ERROR(error_no_group_info,none,
"no group info found for file: '%0'", (StringRef))

Expand Down
22 changes: 22 additions & 0 deletions include/swift/Sema/IDETypeChecking.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,28 @@ namespace swift {
bool shouldPrintRequirement(ExtensionDecl *ED, StringRef Req);
bool hasMergeGroup(MergeGroupKind Kind);
};

/// Reported type for an expression. This expression is represented by offset
/// length in the source buffer;
struct ExpressionTypeInfo {

/// The start of the expression;
uint32_t offset;

/// The length of the expression;
uint32_t length;

/// The start of the printed type in a separately given string buffer.
uint32_t typeOffset;

/// The length of the printed type
uint32_t typeLength;
};

/// Collect type information for every expression in \c SF; all types will
/// be printed to \c OS.
ArrayRef<ExpressionTypeInfo> collectExpressionType(SourceFile &SF,
std::vector<ExpressionTypeInfo> &scratch, llvm::raw_ostream &OS);
}

#endif
83 changes: 83 additions & 0 deletions lib/IDE/IDETypeChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include "swift/AST/NameLookup.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/Sema/IDETypeChecking.h"
#include "swift/IDE/SourceEntityWalker.h"
#include "swift/Parse/Lexer.h"

using namespace swift;

Expand Down Expand Up @@ -577,3 +579,84 @@ collectDefaultImplementationForProtocolMembers(ProtocolDecl *PD,
for (auto *IP : PD->getInheritedProtocols())
HandleMembers(IP->getMembers());
}

/// This walker will traverse the AST and report types for every expression.
class ExpressionTypeCollector: public SourceEntityWalker {
SourceManager &SM;
unsigned int BufferId;
std::vector<ExpressionTypeInfo> &Results;

// This is to where we print all types.
llvm::raw_ostream &OS;

// Map from a printed type to the offset in OS where the type starts.
llvm::StringMap<uint32_t> TypeOffsets;

// This keeps track of whether we have a type reported for a given
// [offset, length].
llvm::DenseMap<unsigned, llvm::DenseSet<unsigned>> AllPrintedTypes;

bool shouldReport(unsigned Offset, unsigned Length, Expr *E) {
// We shouldn't report null types.
if (E->getType().isNull())
return false;

// If we have already reported types for this source range, we shouldn't
// report again. This makes sure we always report the outtermost type of
// several overlapping expressions.
auto &Bucket = AllPrintedTypes[Offset];
return Bucket.find(Length) == Bucket.end();
}

// Find an existing offset in the type buffer otherwise print the type to
// the buffer.
uint32_t getTypeOffsets(StringRef PrintedType) {
auto It = TypeOffsets.find(PrintedType);
if (It == TypeOffsets.end()) {
TypeOffsets[PrintedType] = OS.tell();
OS << PrintedType;
}
return TypeOffsets[PrintedType];
}

public:
ExpressionTypeCollector(SourceFile &SF, std::vector<ExpressionTypeInfo> &Results,
llvm::raw_ostream &OS): SM(SF.getASTContext().SourceMgr),
BufferId(*SF.getBufferID()),
Results(Results), OS(OS) {}
bool walkToExprPre(Expr *E) override {
if (E->getSourceRange().isInvalid())
return true;
CharSourceRange Range =
Lexer::getCharSourceRangeFromSourceRange(SM, E->getSourceRange());
unsigned Offset = SM.getLocOffsetInBuffer(Range.getStart(), BufferId);
unsigned Length = Range.getByteLength();
if (!shouldReport(Offset, Length, E))
return true;
// Print the type to a temporary buffer.
SmallString<64> Buffer;
{
llvm::raw_svector_ostream OS(Buffer);
E->getType()->getRValueType()->reconstituteSugar(true)->print(OS);
// Ensure the end user can directly use the char*
OS << '\0';
}

// Add the type information to the result list.
Results.push_back({Offset, Length, getTypeOffsets(Buffer.str()),
static_cast<uint32_t>(Buffer.size()) - 1});

// Keep track of that we have a type reported for this range.
AllPrintedTypes[Offset].insert(Length);
return true;
}
};

ArrayRef<ExpressionTypeInfo>
swift::collectExpressionType(SourceFile &SF,
std::vector<ExpressionTypeInfo> &Scratch,
llvm::raw_ostream &OS) {
ExpressionTypeCollector Walker(SF, Scratch, OS);
Walker.walk(SF);
return Scratch;
}
3 changes: 1 addition & 2 deletions lib/IDE/Refactoring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1909,8 +1909,7 @@ class ExpandableAssignTernaryExprInfo: public ExpandableTernaryExprInfo {
IfExpr *getIf() {
if (!Assign)
return nullptr;

return dyn_cast<IfExpr>(Assign->getSrc());
return dyn_cast_or_null<IfExpr>(Assign->getSrc());
}

SourceRange getNameRange() {
Expand Down
24 changes: 16 additions & 8 deletions lib/Serialization/SerializeDoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ using pFileNameToGroupNameMap = std::unique_ptr<FileNameToGroupNameMap>;

namespace {
class YamlGroupInputParser {
ASTContext &Ctx;
StringRef RecordPath;
static constexpr const char * const Separator = "/";

Expand Down Expand Up @@ -86,12 +87,20 @@ class YamlGroupInputParser {
}

public:
YamlGroupInputParser(StringRef RecordPath): RecordPath(RecordPath) {}
YamlGroupInputParser(ASTContext &Ctx, StringRef RecordPath):
Ctx(Ctx), RecordPath(RecordPath) {}

FileNameToGroupNameMap* getParsedMap() {
return AllMaps[RecordPath].get();
}

bool diagnoseGroupInfoFile(bool FileMissing = false) {
Ctx.Diags.diagnose(SourceLoc(),
FileMissing ? diag::cannot_find_group_info_file:
diag::cannot_parse_group_info_file, RecordPath);
return true;
}

// Parse the Yaml file that contains the group information.
// True on failure; false on success.
bool parse() {
Expand All @@ -102,32 +111,31 @@ class YamlGroupInputParser {

auto Buffer = llvm::MemoryBuffer::getFile(RecordPath);
if (!Buffer) {
// The group info file does not exist.
return true;
return diagnoseGroupInfoFile(/*Missing File*/true);
}
llvm::SourceMgr SM;
llvm::yaml::Stream YAMLStream(Buffer.get()->getMemBufferRef(), SM);
llvm::yaml::document_iterator I = YAMLStream.begin();
if (I == YAMLStream.end()) {
// Cannot parse correctly.
return true;
return diagnoseGroupInfoFile();
}
llvm::yaml::Node *Root = I->getRoot();
if (!Root) {
// Cannot parse correctly.
return true;
return diagnoseGroupInfoFile();
}

// The format is a map of ("group0" : ["file1", "file2"]), meaning all
// symbols from file1 and file2 belong to "group0".
auto *Map = dyn_cast<llvm::yaml::MappingNode>(Root);
if (!Map) {
return true;
return diagnoseGroupInfoFile();
}
pFileNameToGroupNameMap pMap(new FileNameToGroupNameMap());
std::string Empty;
if (parseRoot(*pMap, Root, Empty))
return true;
return diagnoseGroupInfoFile();

// Save the parsed map to the owner.
AllMaps[RecordPath] = std::move(pMap);
Expand Down Expand Up @@ -168,7 +176,7 @@ class DeclGroupNameContext {
StringRef FullPath =
Ctx.SourceMgr.getIdentifierForBuffer(PathOp.getValue());
if (!pMap) {
YamlGroupInputParser Parser(RecordPath);
YamlGroupInputParser Parser(Ctx, RecordPath);
if (!Parser.parse()) {

// Get the file-name to group map if parsing correctly.
Expand Down
22 changes: 22 additions & 0 deletions test/IDE/Inputs/ExprType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

func foo() -> Int { return 1 }

func bar(f: Float) -> Float { return bar(f: 1) }

protocol P {}

func fooP(_ p: P) { fooP(p) }

class C {}

func ArrayC(_ a: [C]) {
_ = a.count
_ = a.description.count.advanced(by: 1).description
}

struct S {
let val = 4
}
func DictS(_ a: [Int: S]) {
_ = a[2]?.val.advanced(by: 1).byteSwapped
}
8 changes: 8 additions & 0 deletions test/IDE/expr_type.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// RUN: %target-swift-ide-test -print-expr-type -source-filename %S/Inputs/ExprType.swift -swift-version 5 | %FileCheck %s

// CHECK: func foo() -> Int { return <expr type:"Int">1</expr> }
// CHECK: func bar(f: Float) -> Float { return <expr type:"Float"><expr type:"(Float) -> Float">bar</expr><expr type:"(f: Float)">(f: <expr type:"Float">1</expr>)</expr></expr> }
// CHECK: func fooP(_ p: P) { <expr type:"()"><expr type:"(P) -> ()">fooP</expr><expr type:"(P)">(<expr type:"P">p</expr>)</expr></expr> }
// CHECK: <expr type:"()"><expr type:"Int">_</expr> = <expr type:"Int"><expr type:"[C]">a</expr>.count</expr></expr>
// CHECK: <expr type:"()"><expr type:"String">_</expr> = <expr type:"String"><expr type:"Int"><expr type:"(Int) -> Int"><expr type:"Int"><expr type:"String"><expr type:"[C]">a</expr>.description</expr>.count</expr>.<expr type:"(Int) -> (Int) -> Int">advanced</expr></expr><expr type:"(by: Int)">(by: <expr type:"Int">1</expr>)</expr></expr>.description</expr></expr>
// CHECK: <expr type:"()"><expr type:"Int?">_</expr> = <expr type:"Int?"><expr type:"Int"><expr type:"(Int) -> Int"><expr type:"Int"><expr type:"S"><expr type:"S?"><expr type:"[Int : S]">a</expr><expr type:"(Int)">[<expr type:"Int">2</expr>]</expr></expr>?</expr>.val</expr>.<expr type:"(Int) -> (Int) -> Int">advanced</expr></expr><expr type:"(by: Int)">(by: <expr type:"Int">1</expr>)</expr></expr>.byteSwapped</expr></expr>
3 changes: 3 additions & 0 deletions test/Serialization/Inputs/corrupted_group_info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
corrupteddata
}
10 changes: 10 additions & 0 deletions test/Serialization/group_info_diags.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// RUN: %empty-directory(%t)
// RUN: not %target-swift-frontend -emit-module %s -module-name HasArray -o %t -module-cache-path %t/mcp -group-info-path %t/nonexist.json -emit-module-doc &> %t/result.txt
// RUN: %FileCheck %s -check-prefix=MISSING < %t/result.txt
// RUN: not %target-swift-frontend -emit-module %s -module-name HasArray -o %t -module-cache-path %t/mcp -group-info-path %S/Inputs/corrupted_group_info.json -emit-module-doc &> %t/result.txt
// RUN: %FileCheck %s -check-prefix=CORRUPTED < %t/result.txt

public protocol P {}

// MISSING: error: cannot find group info file at path
// CORRUPTED: error: cannot parse group info file at path
34 changes: 34 additions & 0 deletions test/SourceKit/ExpressionType/basic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
func foo() -> Int { return 1 }

func bar(f: Float) -> Float { return bar(f: 1) }

protocol P {}

func fooP(_ p: P) { fooP(p) }

class C {}

func ArrayC(_ a: [C]) {
_ = a.count
_ = a.description.count.advanced(by: 1).description
}

struct S {
let val = 4
}
func DictS(_ a: [Int: S]) {
_ = a[2]?.val.advanced(by: 1).byteSwapped
}

// RUN: %sourcekitd-test -req=collect-type %s -- %s | %FileCheck %s
// CHECK: (183, 202): Int
// CHECK: (183, 196): String
// CHECK: (183, 184): [C]
// CHECK: (203, 211): (Int) -> (Int) -> Int
// CHECK: (211, 218): (by: Int)
// CHECK: (216, 217): Int
// CHECK: (257, 258): Int
// CHECK: (291, 332): ()
// CHECK: (291, 292): Int?
// CHECK: (295, 332): Int?
// CHECK: (295, 320): Int
39 changes: 39 additions & 0 deletions tools/SourceKit/docs/Protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,46 @@ Welcome to SourceKit. Type ':help' for assistance.
}
```

## Expression Type
This request collects the types of all expressions in a source file after type checking.
To fulfill this task, the client must provide the path to the Swift source file under
type checking and the necessary compiler arguments to help resolve all dependencies.

### Request

```
{
<key.request>: (UID) <source.request.expression.type>,
<key.sourcefile>: (string) // Absolute path to the file.
<key.compilerargs>: [string*] // Array of zero or more strings for the compiler arguments,
// e.g ["-sdk", "/path/to/sdk"]. If key.sourcefile is provided,
// these must include the path to that file.
}
```

### Response
```
{
<key.printedtypebuffer>: (string) // A text buffer where all expression types are printed to.
<key.expression_type_list>: (array) [expr-type-info*] // A list of expression and type
}
```

```
expr-type-info ::=
{
<key.expression_offset>: (int64) // Offset of an expression in the source file
<key.expression_length>: (int64) // Length of an expression in the source file
<key.type_offset>: (int64) // Offset of the printed type of the expression in the printed type buffer
<key.type_length>: (int64) // Length of the printed type of the expression in the printed type buffer
}
```

### Testing

```
$ sourcekitd-test -req=collect-type /path/to/file.swift -- /path/to/file.swift
```

# UIDs

Expand Down
16 changes: 15 additions & 1 deletion tools/SourceKit/include/SourceKit/Core/LangSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,17 @@ struct CodeCompletionInfo {
Optional<ArrayRef<ParameterStructure>> parametersStructure;
};

struct ExpressionType {
unsigned ExprOffset;
unsigned ExprLength;
unsigned TypeOffset;
};

struct ExpressionTypesInFile {
std::vector<ExpressionType> Results;
StringRef TypeBuffer;
};

class CodeCompletionConsumer {
virtual void anchor();

Expand Down Expand Up @@ -708,6 +719,10 @@ class LangSupport {
ArrayRef<const char*> Args,
CategorizedEditsReceiver Receiver) = 0;

virtual void collectExpressionTypes(StringRef FileName,
ArrayRef<const char *> Args,
std::function<void(const ExpressionTypesInFile&)> Receiver) = 0;

virtual void getDocInfo(llvm::MemoryBuffer *InputBuf,
StringRef ModuleName,
ArrayRef<const char *> Args,
Expand All @@ -726,7 +741,6 @@ class LangSupport {

virtual void getStatistics(StatisticsReceiver) = 0;
};

} // namespace SourceKit

#endif
Loading