Skip to content

[SourceKit] Teach range-info request to recognize single declaration and multi-statements. #5672

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 1 commit into from
Nov 8, 2016
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
5 changes: 4 additions & 1 deletion include/swift/IDE/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,11 @@ class SemaLocResolver : public SourceEntityWalker {

enum class RangeKind : int8_t{
Invalid = -1,
Expression,
SingleExpression,
SingleStatement,
SingleDecl,

MultiStatement,
};

struct ResolvedRangeInfo {
Expand Down
120 changes: 95 additions & 25 deletions lib/IDE/SwiftSourceDocInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,35 +176,102 @@ bool SemaLocResolver::visitModuleReference(ModuleEntity Mod,

struct RangeResolver::Implementation {
SourceFile &File;
private:
enum class RangeMatchKind : int8_t {
NoneMatch,
StartMatch,
EndMatch,
RangeMatch,
};

struct ContextInfo {
ASTNode Parent;
std::vector<ASTNode> StartMatches;
std::vector<ASTNode> EndMatches;
ContextInfo(ASTNode Parent) : Parent(Parent) {}
};

SourceLoc Start;
SourceLoc End;
StringRef Content;
Optional<ResolvedRangeInfo> Result;
Implementation(SourceFile &File, SourceLoc Start, SourceLoc End) :
File(File), Start(Start), End(End) {}
std::vector<ContextInfo> ContextStack;
ContextInfo &getCurrentDC() {
assert(!ContextStack.empty());
return ContextStack.back();
}

bool isRangeMatch(SourceRange Input) {
return Input.Start == Start && Input.End == End;
ResolvedRangeInfo getSingleNodeKind(ASTNode Node) {
assert(!Node.isNull());
if (Node.is<Expr*>())
return ResolvedRangeInfo(RangeKind::SingleExpression,
Node.get<Expr*>()->getType(), Content);
else if (Node.is<Stmt*>())
return ResolvedRangeInfo(RangeKind::SingleStatement, Type(), Content);
else {
assert(Node.is<Decl*>());
return ResolvedRangeInfo(RangeKind::SingleDecl, Type(), Content);
}
}

public:
Implementation(SourceFile &File, SourceLoc Start, SourceLoc End) :
File(File), Start(Start), End(End), Content(getContent()) {}
~Implementation() { assert(ContextStack.empty()); }
bool hasResult() { return Result.hasValue(); }
void enter(ASTNode Node) { ContextStack.emplace_back(Node); }
void leave() { ContextStack.pop_back(); }

void analyze(ASTNode Node) {
auto &DCInfo = getCurrentDC();
switch (getRangeMatchKind(Node.getSourceRange())) {
case RangeMatchKind::NoneMatch:
return;
case RangeMatchKind::RangeMatch:
Result = getSingleNodeKind(Node);
return;
case RangeMatchKind::StartMatch:
DCInfo.StartMatches.emplace_back(Node);
break;
case RangeMatchKind::EndMatch:
DCInfo.EndMatches.emplace_back(Node);
break;
}
if (!DCInfo.StartMatches.empty() && !DCInfo.EndMatches.empty()) {
Result = {RangeKind::MultiStatement, Type(), Content};
return;
}
}

bool shouldEnter(SourceRange Input) {
bool shouldEnter(ASTNode Node) {
SourceManager &SM = File.getASTContext().SourceMgr;
if (SM.isBeforeInBuffer(End, Input.Start))
if (SM.isBeforeInBuffer(End, Node.getSourceRange().Start))
return false;
if (SM.isBeforeInBuffer(Input.End, Start))
if (SM.isBeforeInBuffer(Node.getSourceRange().End, Start))
return false;
return true;
}

bool hasResult() {
return Result.hasValue();
}

ResolvedRangeInfo getResult() {
if (Result.hasValue())
return Result.getValue();
return ResolvedRangeInfo(RangeKind::Invalid, Type(), getContent());
}

private:
RangeMatchKind getRangeMatchKind(SourceRange Input) {
bool StartMatch = Input.Start == Start;
bool EndMatch = Input.End == End;
if (StartMatch && EndMatch)
return RangeMatchKind::RangeMatch;
else if (StartMatch)
return RangeMatchKind::StartMatch;
else if (EndMatch)
return RangeMatchKind::EndMatch;
else
return RangeMatchKind::NoneMatch;
}

StringRef getContent() {
SourceManager &SM = File.getASTContext().SourceMgr;
return CharSourceRange(SM, Start, Lexer::getLocForEndOfToken(SM, End)).str();
Expand All @@ -217,45 +284,48 @@ RangeResolver::RangeResolver(SourceFile &File, SourceLoc Start, SourceLoc End) :
RangeResolver::~RangeResolver() { delete &Impl; }

bool RangeResolver::walkToExprPre(Expr *E) {
if (!Impl.shouldEnter(E->getSourceRange()))
if (!Impl.shouldEnter(E))
return false;
if (Impl.isRangeMatch(E->getSourceRange())) {
Impl.Result = ResolvedRangeInfo(RangeKind::Expression, E->getType(),
Impl.getContent());
}
Impl.analyze(E);
Impl.enter(E);
return !Impl.hasResult();
}

bool RangeResolver::walkToStmtPre(Stmt *S) {
if (!Impl.shouldEnter(S->getSourceRange()))
if (!Impl.shouldEnter(S))
return false;
if (Impl.isRangeMatch(S->getSourceRange())) {
Impl.Result = ResolvedRangeInfo(RangeKind::SingleStatement, Type(),
Impl.getContent());
}
Impl.analyze(S);
Impl.enter(S);
return !Impl.hasResult();
};


bool RangeResolver::walkToDeclPre(Decl *D, CharSourceRange Range) {
return Impl.shouldEnter(D->getSourceRange());
if (!Impl.shouldEnter(D))
return false;
Impl.analyze(D);
Impl.enter(D);
return !Impl.hasResult();
}

bool RangeResolver::walkToExprPost(Expr *E) {
Impl.leave();
return !Impl.hasResult();
}

bool RangeResolver::walkToStmtPost(Stmt *S) {
Impl.leave();
return !Impl.hasResult();
};

bool RangeResolver::walkToDeclPost(Decl *D) {
Impl.leave();
return !Impl.hasResult();
}

ResolvedRangeInfo
RangeResolver::resolve() {
ResolvedRangeInfo RangeResolver::resolve() {
Impl.enter(ASTNode());
walk(Impl.File);
Impl.leave();
return Impl.getResult();
}

Expand Down
51 changes: 45 additions & 6 deletions test/SourceKit/RangeInfo/basic.swift
Original file line number Diff line number Diff line change
@@ -1,37 +1,76 @@
func foo() -> Int{
var aaa = 1 + 2
aaa = aaa + 3
if aaa = 3 { aaa = 4 }
if aaa == 3 { aaa = 4 }
return aaa
}

func foo1() -> Int { return 0 }
class C { func foo() {} }
struct S { func foo() {} }

// RUN: %sourcekitd-test -req=range -pos=2:13 -length 5 %s -- %s | %FileCheck %s -check-prefix=CHECK1

// RUN: %sourcekitd-test -req=range -pos=3:3 -length 13 %s -- %s | %FileCheck %s -check-prefix=CHECK2
// RUN: %sourcekitd-test -req=range -pos=3:1 -length 15 %s -- %s | %FileCheck %s -check-prefix=CHECK2

// RUN: %sourcekitd-test -req=range -pos=4:1 -length 24 %s -- %s | %FileCheck %s -check-prefix=CHECK3
// RUN: %sourcekitd-test -req=range -pos=4:1 -length 25 %s -- %s | %FileCheck %s -check-prefix=CHECK3
// RUN: %sourcekitd-test -req=range -pos=4:1 -length 26 %s -- %s | %FileCheck %s -check-prefix=CHECK3
// RUN: %sourcekitd-test -req=range -pos=4:4 -length 21 %s -- %s | %FileCheck %s -check-prefix=CHECK3
// RUN: %sourcekitd-test -req=range -pos=4:1 -length 27 %s -- %s | %FileCheck %s -check-prefix=CHECK3
// RUN: %sourcekitd-test -req=range -pos=4:4 -length 22 %s -- %s | %FileCheck %s -check-prefix=CHECK3

// RUN: %sourcekitd-test -req=range -pos=5:1 -length 12 %s -- %s | %FileCheck %s -check-prefix=CHECK4
// RUN: %sourcekitd-test -req=range -pos=5:2 -length 11 %s -- %s | %FileCheck %s -check-prefix=CHECK4
// RUN: %sourcekitd-test -req=range -pos=5:5 -length 8 %s -- %s | %FileCheck %s -check-prefix=CHECK4
// RUN: %sourcekitd-test -req=range -pos=5:5 -length 9 %s -- %s | %FileCheck %s -check-prefix=CHECK4

// CHECK1-DAG: <kind>source.lang.swift.range.expression</kind>
// RUN: %sourcekitd-test -req=range -pos=8:1 -length 31 %s -- %s | %FileCheck %s -check-prefix=CHECK5
// RUN: %sourcekitd-test -req=range -pos=9:1 -length 25 %s -- %s | %FileCheck %s -check-prefix=CHECK6
// RUN: %sourcekitd-test -req=range -pos=10:1 -length 26 %s -- %s | %FileCheck %s -check-prefix=CHECK7
// RUN: %sourcekitd-test -req=range -pos=3:1 -length 42 %s -- %s | %FileCheck %s -check-prefix=CHECK8
// RUN: %sourcekitd-test -req=range -pos=3:1 -length 55 %s -- %s | %FileCheck %s -check-prefix=CHECK9
// RUN: %sourcekitd-test -req=range -pos=4:1 -length 36 %s -- %s | %FileCheck %s -check-prefix=CHECK10

// CHECK1-DAG: <kind>source.lang.swift.range.singleexpression</kind>
// CHECK1-DAG: <content>1 + 2</content>
// CHECK1-DAG: <type>Int</type>

// CHECK2-DAG: <kind>source.lang.swift.range.expression</kind>
// CHECK2-DAG: <kind>source.lang.swift.range.singleexpression</kind>
// CHECK2-DAG: <content>aaa = aaa + 3</content>
// CHECK2-DAG: <type>()</type>

// CHECK3-DAG: <kind>source.lang.swift.range.singlestatement</kind>
// CHECK3-DAG: <content>if aaa = 3 { aaa = 4 }</content>
// CHECK3-DAG: <content>if aaa == 3 { aaa = 4 }</content>
// CHECK3-DAG: <type></type>

// CHECK4-DAG: <kind>source.lang.swift.range.singlestatement</kind>
// CHECK4-DAG: <content>return aaa</content>
// CHECK4-DAG: <type></type>

// CHECK5-DAG: <kind>source.lang.swift.range.singledeclaration</kind>
// CHECK5-DAG: <content>func foo1() -> Int { return 0 }</content>
// CHECK5-DAG: <type></type>

// CHECK6-DAG: <kind>source.lang.swift.range.singledeclaration</kind>
// CHECK6-DAG: <content>class C { func foo() {} }</content>
// CHECK6-DAG: <type></type>

// CHECK7-DAG: <kind>source.lang.swift.range.singledeclaration</kind>
// CHECK7-DAG: <content>struct S { func foo() {} }</content>
// CHECK7-DAG: <type></type>

// CHECK8-DAG: <kind>source.lang.swift.range.multistatement</kind>
// CHECK8-DAG: <content>aaa = aaa + 3
// CHECK8-DAG: if aaa == 3 { aaa = 4 }</content>
// CHECK8-DAG: <type></type>

// CHECK9-DAG: <kind>source.lang.swift.range.multistatement</kind>
// CHECK9-DAG: <content>aaa = aaa + 3
// CHECK9-DAG: if aaa == 3 { aaa = 4 }
// CHECK9-DAG: return aaa</content>
// CHECK9-DAG: <type></type>

// CHECK10-DAG: <kind>source.lang.swift.range.multistatement</kind>
// CHECK10-DAG: <content>if aaa == 3 { aaa = 4 }
// CHECK10-DAG: return aaa</content>
// CHECK10-DAG: <type></type>
10 changes: 8 additions & 2 deletions tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,11 @@ static UIdent KindStructureElemPattern("source.lang.swift.structure.elem.pattern
static UIdent KindStructureElemTypeRef("source.lang.swift.structure.elem.typeref");

static UIdent KindRangeSingleStatement("source.lang.swift.range.singlestatement");
static UIdent KindRangeExpression("source.lang.swift.range.expression");
static UIdent KindRangeSingleExpression("source.lang.swift.range.singleexpression");
static UIdent KindRangeSingleDeclaration("source.lang.swift.range.singledeclaration");

static UIdent KindRangeMultiStatement("source.lang.swift.range.multistatement");

static UIdent KindRangeInvalid("source.lang.swift.range.invalid");

std::unique_ptr<LangSupport>
Expand Down Expand Up @@ -536,8 +540,10 @@ UIdent SwiftLangSupport::getUIDForSyntaxStructureElementKind(
SourceKit::UIdent SwiftLangSupport::
getUIDForRangeKind(swift::ide::RangeKind Kind) {
switch (Kind) {
case swift::ide::RangeKind::Expression: return KindRangeExpression;
case swift::ide::RangeKind::SingleExpression: return KindRangeSingleExpression;
case swift::ide::RangeKind::SingleStatement: return KindRangeSingleStatement;
case swift::ide::RangeKind::SingleDecl: return KindRangeSingleDeclaration;
case swift::ide::RangeKind::MultiStatement: return KindRangeMultiStatement;
case swift::ide::RangeKind::Invalid: return KindRangeInvalid;
}
}
Expand Down
4 changes: 3 additions & 1 deletion tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1097,14 +1097,16 @@ static void resolveRange(SwiftLangSupport &Lang,
Result.RangeKind = Lang.getUIDForRangeKind(Info.Kind);
Result.RangeContent = Info.Content;
switch (Info.Kind) {
case RangeKind::Expression: {
case RangeKind::SingleExpression: {
SmallString<64> SS;
llvm::raw_svector_ostream OS(SS);
Info.Ty.print(OS);
Result.ExprType = OS.str();
Receiver(Result);
return;
}
case RangeKind::SingleDecl:
case RangeKind::MultiStatement:
case RangeKind::SingleStatement: {
Receiver(Result);
return;
Expand Down