Skip to content

Commit ba910cc

Browse files
authored
Merge pull request #37867 from fwcd/sourcekit-var-types
[SourceKit] Add `CollectVariableType` request
2 parents 735064a + b904d0f commit ba910cc

File tree

24 files changed

+717
-0
lines changed

24 files changed

+717
-0
lines changed

include/swift/Basic/SourceLoc.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@ class SourceRange {
123123
/// includes both this range and the other one.
124124
void widen(SourceRange Other);
125125

126+
/// Checks whether this range contains the given location. Note that the given
127+
/// location should correspond to the start of a token, since locations inside
128+
/// the last token may be considered outside the range by this function.
129+
bool contains(SourceLoc Loc) const;
130+
131+
/// Checks whether this range overlaps with the given range.
132+
bool overlaps(SourceRange Other) const;
133+
126134
bool operator==(const SourceRange &other) const {
127135
return Start == other.Start && End == other.End;
128136
}

include/swift/Sema/IDETypeChecking.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,33 @@ namespace swift {
206206
/// the decl context.
207207
ProtocolDecl *resolveProtocolName(DeclContext *dc, StringRef Name);
208208

209+
/// Reported type of a variable declaration.
210+
struct VariableTypeInfo {
211+
/// The start of the variable identifier.
212+
uint32_t Offset;
213+
214+
/// The length of the variable identifier.
215+
uint32_t Length;
216+
217+
/// Whether the variable has an explicit type annotation.
218+
bool HasExplicitType;
219+
220+
/// The start of the printed type in a separate string buffer.
221+
uint32_t TypeOffset;
222+
223+
VariableTypeInfo(uint32_t Offset, uint32_t Length, bool HasExplicitType,
224+
uint32_t TypeOffset);
225+
};
226+
227+
/// Collect type information for every variable declaration in \c SF
228+
/// within the given range into \c VariableTypeInfos.
229+
/// All types will be printed to \c OS and the type offsets of the
230+
/// \c VariableTypeInfos will index into the string that backs this
231+
/// stream.
232+
void collectVariableType(SourceFile &SF, SourceRange Range,
233+
std::vector<VariableTypeInfo> &VariableTypeInfos,
234+
llvm::raw_ostream &OS);
235+
209236
/// FIXME: All of the below goes away once CallExpr directly stores its
210237
/// arguments.
211238

lib/Basic/SourceLoc.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,15 @@ void SourceRange::widen(SourceRange Other) {
270270
End = Other.End;
271271
}
272272

273+
bool SourceRange::contains(SourceLoc Loc) const {
274+
return Start.Value.getPointer() <= Loc.Value.getPointer() &&
275+
Loc.Value.getPointer() <= End.Value.getPointer();
276+
}
277+
278+
bool SourceRange::overlaps(SourceRange Other) const {
279+
return contains(Other.Start) || Other.contains(Start);
280+
}
281+
273282
void SourceLoc::printLineAndColumn(raw_ostream &OS, const SourceManager &SM,
274283
unsigned BufferID) const {
275284
if (isInvalid()) {

lib/IDE/IDETypeChecking.cpp

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,117 @@ swift::collectExpressionType(SourceFile &SF,
731731
return Scratch;
732732
}
733733

734+
/// This walker will traverse the AST and report types for every variable
735+
/// declaration.
736+
class VariableTypeCollector : public SourceEntityWalker {
737+
private:
738+
const SourceManager &SM;
739+
unsigned int BufferId;
740+
741+
/// The range in which variable types are to be collected.
742+
SourceRange TotalRange;
743+
744+
/// The output vector for VariableTypeInfos emitted during traversal.
745+
std::vector<VariableTypeInfo> &Results;
746+
747+
/// We print all types into a single output stream (e.g. into a string buffer)
748+
/// and provide offsets into this string buffer to describe individual types,
749+
/// i.e. \c OS builds a string that contains all null-terminated printed type
750+
/// strings. When referring to one of these types, we can use the offsets at
751+
/// which it starts in the \c OS.
752+
llvm::raw_ostream &OS;
753+
754+
/// Map from a printed type to the offset in \c OS where the type starts.
755+
llvm::StringMap<uint32_t> TypeOffsets;
756+
757+
/// Returns the start offset of this string in \c OS. If \c PrintedType
758+
/// hasn't been printed to \c OS yet, this function will do so.
759+
uint32_t getTypeOffset(StringRef PrintedType) {
760+
auto It = TypeOffsets.find(PrintedType);
761+
if (It == TypeOffsets.end()) {
762+
TypeOffsets[PrintedType] = OS.tell();
763+
OS << PrintedType << '\0';
764+
}
765+
return TypeOffsets[PrintedType];
766+
}
767+
768+
/// Checks whether the given range overlaps the total range in which we
769+
/// collect variable types.
770+
bool overlapsTotalRange(SourceRange Range) {
771+
return TotalRange.isInvalid() || Range.overlaps(TotalRange);
772+
}
773+
774+
public:
775+
VariableTypeCollector(const SourceFile &SF, SourceRange Range,
776+
std::vector<VariableTypeInfo> &Results,
777+
llvm::raw_ostream &OS)
778+
: SM(SF.getASTContext().SourceMgr), BufferId(*SF.getBufferID()),
779+
TotalRange(Range), Results(Results), OS(OS) {}
780+
781+
bool walkToDeclPre(Decl *D, CharSourceRange DeclNameRange) override {
782+
if (DeclNameRange.isInvalid()) {
783+
return true;
784+
}
785+
// Skip this declaration and its subtree if outside the range
786+
if (!overlapsTotalRange(D->getSourceRange())) {
787+
return false;
788+
}
789+
if (auto VD = dyn_cast<VarDecl>(D)) {
790+
unsigned VarOffset =
791+
SM.getLocOffsetInBuffer(DeclNameRange.getStart(), BufferId);
792+
unsigned VarLength = DeclNameRange.getByteLength();
793+
// Print the type to a temporary buffer
794+
SmallString<64> Buffer;
795+
{
796+
llvm::raw_svector_ostream OS(Buffer);
797+
PrintOptions Options;
798+
Options.SynthesizeSugarOnTypes = true;
799+
auto Ty = VD->getType();
800+
// Skip this declaration and its children if the type is an error type.
801+
if (Ty->is<ErrorType>()) {
802+
return false;
803+
}
804+
Ty->print(OS, Options);
805+
}
806+
// Transfer the type to `OS` if needed and get the offset of this string
807+
// in `OS`.
808+
auto TyOffset = getTypeOffset(Buffer.str());
809+
bool HasExplicitType =
810+
VD->getTypeReprOrParentPatternTypeRepr() != nullptr;
811+
// Add the type information to the result list.
812+
Results.emplace_back(VarOffset, VarLength, HasExplicitType, TyOffset);
813+
}
814+
return true;
815+
}
816+
817+
bool walkToStmtPre(Stmt *S) override {
818+
// Skip this statement and its subtree if outside the range
819+
return overlapsTotalRange(S->getSourceRange());
820+
}
821+
822+
bool walkToExprPre(Expr *E) override {
823+
// Skip this expression and its subtree if outside the range
824+
return overlapsTotalRange(E->getSourceRange());
825+
}
826+
827+
bool walkToPatternPre(Pattern *P) override {
828+
// Skip this pattern and its subtree if outside the range
829+
return overlapsTotalRange(P->getSourceRange());
830+
}
831+
};
832+
833+
VariableTypeInfo::VariableTypeInfo(uint32_t Offset, uint32_t Length,
834+
bool HasExplicitType, uint32_t TypeOffset)
835+
: Offset(Offset), Length(Length), HasExplicitType(HasExplicitType),
836+
TypeOffset(TypeOffset) {}
837+
838+
void swift::collectVariableType(
839+
SourceFile &SF, SourceRange Range,
840+
std::vector<VariableTypeInfo> &VariableTypeInfos, llvm::raw_ostream &OS) {
841+
VariableTypeCollector Walker(SF, Range, VariableTypeInfos, OS);
842+
Walker.walk(SF);
843+
}
844+
734845
ArrayRef<ValueDecl*> swift::
735846
canDeclProvideDefaultImplementationFor(ValueDecl* VD) {
736847
return evaluateOrDefault(VD->getASTContext().evaluator,
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
let x: Int = 3
2+
let y = "abc"
3+
4+
var foo = ["abc" + "def"]
5+
6+
struct A {
7+
let x: String = ""
8+
let y = ""
9+
}
10+
11+
class B {
12+
var x = 4.0
13+
var y = [A]()
14+
var z: [Int: Int] = [:]
15+
var w: (Int) -> Int { { $0 * 2 } }
16+
}
17+
18+
func foo() {
19+
var local = 5
20+
}
21+
22+
// RUN: %sourcekitd-test -req=collect-var-type %s -- %s | %FileCheck %s
23+
// CHECK: (1:5, 1:6): Int (explicit type: 1)
24+
// CHECK: (2:5, 2:6): String (explicit type: 0)
25+
// CHECK: (4:5, 4:8): [String] (explicit type: 0)
26+
// CHECK: (7:7, 7:8): String (explicit type: 1)
27+
// CHECK: (8:7, 8:8): String (explicit type: 0)
28+
// CHECK: (12:7, 12:8): Double (explicit type: 0)
29+
// CHECK: (13:7, 13:8): [A] (explicit type: 0)
30+
// CHECK: (14:7, 14:8): [Int : Int] (explicit type: 1)
31+
// CHECK: (15:7, 15:8): (Int) -> Int (explicit type: 1)
32+
// CHECK: (19:7, 19:12): Int (explicit type: 0)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
let x = "an error type" + 3
2+
let y = "not an error type"
3+
4+
// RUN: %sourcekitd-test -req=collect-var-type %s -- %s | %FileCheck %s
5+
// CHECK-NOT: (1:5, 1:6)
6+
// CHECK: (2:5, 2:6): String (explicit type: 0)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
func x(_ param: inout Int) -> Int {
2+
param = 4
3+
}
4+
5+
let z = { (param: inout String) in }
6+
7+
// RUN: %sourcekitd-test -req=collect-var-type %s -- %s | %FileCheck %s
8+
// CHECK: (1:10, 1:15): Int (explicit type: 1)
9+
// CHECK: (5:5, 5:6): (inout String) -> () (explicit type: 0)
10+
// CHECK: (5:12, 5:17): String (explicit type: 1)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
func x(_ param: Int) -> Int {
2+
param
3+
}
4+
5+
let y: (String) -> Void = { param in }
6+
7+
let z = { (param: String) in
8+
param.count
9+
}
10+
11+
// RUN: %sourcekitd-test -req=collect-var-type %s -- %s | %FileCheck %s
12+
// CHECK: (1:10, 1:15): Int (explicit type: 1)
13+
// CHECK: (5:5, 5:6): (String) -> Void (explicit type: 1)
14+
// CHECK: (5:29, 5:34): String (explicit type: 0)
15+
// CHECK: (7:5, 7:6): (String) -> Int (explicit type: 0)
16+
// CHECK: (7:12, 7:17): String (explicit type: 1)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
let x = 1
2+
let y = "abc"
3+
let z: String = "def"
4+
var w = 4
5+
6+
// RUN: %sourcekitd-test -req=collect-var-type -pos=2:1 -end-pos=4:1 %s -- %s | %FileCheck %s
7+
// CHECK-NOT: (1:5, 1:6)
8+
// CHECK: (2:5, 2:6): String (explicit type: 0)
9+
// CHECK: (3:5, 3:6): String (explicit type: 1)
10+
// CHECK-NOT: (4:5, 4:6)

tools/SourceKit/docs/Protocol.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,49 @@ expr-type-info ::=
785785
$ sourcekitd-test -req=collect-type /path/to/file.swift -- /path/to/file.swift
786786
```
787787

788+
## Variable Type
789+
790+
This request collects the types of all variable declarations in a source file after type checking.
791+
To fulfill this task, the client must provide the path to the Swift source file under
792+
type checking and the necessary compiler arguments to help resolve all dependencies.
793+
794+
### Request
795+
796+
```
797+
{
798+
<key.request>: (UID) <source.request.variable.type>,
799+
<key.sourcefile>: (string) // Absolute path to the file.
800+
<key.compilerargs>: [string*] // Array of zero or more strings for the compiler arguments,
801+
// e.g ["-sdk", "/path/to/sdk"]. If key.sourcefile is provided,
802+
// these must include the path to that file.
803+
[opt] <key.offset>: (int64) // Offset of the requested range. Defaults to zero.
804+
[opt] <key.length>: (int64) // Length of the requested range. Defaults to the entire file.
805+
}
806+
```
807+
808+
### Response
809+
```
810+
{
811+
<key.variable_type_list>: (array) [var-type-info*] // A list of variable declarations and types
812+
}
813+
```
814+
815+
```
816+
var-type-info ::=
817+
{
818+
<key.variable_offset>: (int64) // Offset of a variable identifier in the source file
819+
<key.variable_length>: (int64) // Length of a variable identifier an expression in the source file
820+
<key.variable_type>: (string) // Printed type of the variable declaration
821+
<key.variable_type_explicit> (bool) // Whether the declaration has an explicit type annotation
822+
}
823+
```
824+
825+
### Testing
826+
827+
```
828+
$ sourcekitd-test -req=collect-var-type /path/to/file.swift -- /path/to/file.swift
829+
```
830+
788831
# UIDs
789832

790833
## Keys

tools/SourceKit/include/SourceKit/Core/LangSupport.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,27 @@ struct ExpressionTypesInFile {
138138
StringRef TypeBuffer;
139139
};
140140

141+
struct VariableType {
142+
/// The variable identifier's offset in the file.
143+
unsigned VarOffset;
144+
/// The variable identifier's length.
145+
unsigned VarLength;
146+
/// The offset of the type's string representation inside
147+
/// `VariableTypesInFile.TypeBuffer`.
148+
unsigned TypeOffset;
149+
/// Whether the variable declaration has an explicit type annotation.
150+
bool HasExplicitType;
151+
};
152+
153+
struct VariableTypesInFile {
154+
/// The typed variable declarations in the file.
155+
std::vector<VariableType> Results;
156+
/// A String containing the printed representation of all types in
157+
/// `Results`. Entries in `Results` refer to their types by using
158+
/// an offset into this string.
159+
StringRef TypeBuffer;
160+
};
161+
141162
class CodeCompletionConsumer {
142163
virtual void anchor();
143164

@@ -864,6 +885,15 @@ class LangSupport {
864885
std::function<void(const
865886
RequestResult<ExpressionTypesInFile> &)> Receiver) = 0;
866887

888+
/// Collects variable types for a range defined by `Offset` and `Length` in
889+
/// the source file. If `Offset` or `Length` are empty, variable types for
890+
/// the entire document are collected.
891+
virtual void collectVariableTypes(
892+
StringRef FileName, ArrayRef<const char *> Args,
893+
Optional<unsigned> Offset, Optional<unsigned> Length,
894+
std::function<void(const RequestResult<VariableTypesInFile> &)>
895+
Receiver) = 0;
896+
867897
virtual void getDocInfo(llvm::MemoryBuffer *InputBuf,
868898
StringRef ModuleName,
869899
ArrayRef<const char *> Args,

tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,12 @@ class SwiftLangSupport : public LangSupport {
603603
bool CanonicalType,
604604
std::function<void(const RequestResult<ExpressionTypesInFile> &)> Receiver) override;
605605

606+
void collectVariableTypes(
607+
StringRef FileName, ArrayRef<const char *> Args,
608+
Optional<unsigned> Offset, Optional<unsigned> Length,
609+
std::function<void(const RequestResult<VariableTypesInFile> &)> Receiver)
610+
override;
611+
606612
void semanticRefactoring(StringRef Filename, SemanticRefactoringInfo Info,
607613
ArrayRef<const char*> Args,
608614
CategorizedEditsReceiver Receiver) override;

0 commit comments

Comments
 (0)