Skip to content
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: 5 additions & 0 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ const std::function<bool(const ExtensionDecl *)>
void PrintOptions::setBaseType(Type T) {
if (T->is<ErrorType>())
return;
if (auto DynamicSelf = T->getAs<DynamicSelfType>()) {
// TypeTransformContext requires `T` to have members. Look through dynamic
// Self.
T = DynamicSelf->getSelfType();
}
TransformContext = TypeTransformContext(T);
}

Expand Down
165 changes: 158 additions & 7 deletions lib/IDE/CursorInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "swift/AST/GenericEnvironment.h"
#include "swift/AST/NameLookup.h"
#include "swift/AST/USRGeneration.h"
#include "swift/IDE/SelectedOverloadInfo.h"
#include "swift/IDE/TypeCheckCompletionCallback.h"
#include "swift/Parse/IDEInspectionCallbacks.h"
#include "swift/Sema/ConstraintSystem.h"
Expand Down Expand Up @@ -47,21 +48,28 @@ void typeCheckDeclAndParentClosures(ValueDecl *VD) {
DC = DC->getParent();
}

typeCheckASTNodeAtLoc(
TypeCheckASTNodeAtLocContext::declContext(VD->getDeclContext()),
VD->getLoc());
if (!VD->getInterfaceType()) {
// The decl has an interface time if it came from another module. In that
// case, there's nothing to do. Otherwise, type check the decl to get its
// type.
typeCheckASTNodeAtLoc(
TypeCheckASTNodeAtLocContext::declContext(VD->getDeclContext()),
VD->getLoc());
}
if (auto VarD = dyn_cast<VarDecl>(VD)) {
// Type check any attached property wrappers so the annotated declaration
// can refer to their USRs.
(void)VarD->getPropertyWrapperBackingPropertyType();
if (VarD->hasAttachedPropertyWrapper()) {
// Type check any attached property wrappers so the annotated declaration
// can refer to their USRs.
(void)VarD->getPropertyWrapperBackingPropertyType();
}
// Visit emitted accessors so we generated accessors from property wrappers.
VarD->visitEmittedAccessors([&](AccessorDecl *accessor) {});
}
}

// MARK: - NodeFinderResults

enum class NodeFinderResultKind { Decl };
enum class NodeFinderResultKind { Decl, Expr };

class NodeFinderResult {
NodeFinderResultKind Kind;
Expand All @@ -87,6 +95,24 @@ class NodeFinderDeclResult : public NodeFinderResult {
}
};

class NodeFinderExprResult : public NodeFinderResult {
Expr *E;
/// The \c DeclContext in which \c E occurs.
DeclContext *DC;

public:
NodeFinderExprResult(Expr *E, DeclContext *DC)
: NodeFinderResult(NodeFinderResultKind::Expr), E(E), DC(DC) {}

Expr *getExpr() const { return E; }

DeclContext *getDeclContext() const { return DC; }

static bool classof(const NodeFinderResult *Res) {
return Res->getKind() == NodeFinderResultKind::Expr;
}
};

// MARK: - NodeFinder

/// Walks the AST, looking for a node at \c LocToResolve. While walking the
Expand Down Expand Up @@ -192,6 +218,23 @@ class NodeFinder : ASTWalker {
}
}

if (E->getLoc() != LocToResolve) {
return Action::Continue(E);
}

switch (E->getKind()) {
case ExprKind::DeclRef:
case ExprKind::UnresolvedDot:
case ExprKind::UnresolvedDeclRef: {
assert(Result == nullptr);
Result =
std::make_unique<NodeFinderExprResult>(E, getCurrentDeclContext());
return Action::Stop();
}
default:
break;
}

return Action::Continue(E);
}

Expand All @@ -215,6 +258,57 @@ class NodeFinder : ASTWalker {
}
};

// MARK: - Solver-based expression analysis

class CursorInfoTypeCheckSolutionCallback : public TypeCheckCompletionCallback {
public:
struct CursorInfoDeclReference {
/// If the referenced declaration is a member reference, the type of the
/// member's base, otherwise \c null.
Type BaseType;
/// Whether the reference is dynamic (see \c ide::isDynamicRef)
bool IsDynamicRef;
/// The declaration that is being referenced. Will never be \c nullptr.
ValueDecl *ReferencedDecl;
};

private:
/// The expression for which we want to provide cursor info results.
Expr *ResolveExpr;

SmallVector<CursorInfoDeclReference, 1> Results;

void sawSolutionImpl(const Solution &S) override {
auto &CS = S.getConstraintSystem();

auto Locator = CS.getConstraintLocator(ResolveExpr);
auto CalleeLocator = S.getCalleeLocator(Locator);
auto OverloadInfo = getSelectedOverloadInfo(S, CalleeLocator);
if (!OverloadInfo.Value) {
// We could not resolve the referenced declaration. Skip the solution.
return;
}

bool IsDynamicRef = false;
auto BaseLocator =
CS.getConstraintLocator(Locator, ConstraintLocator::MemberRefBase);
if (auto BaseExpr =
simplifyLocatorToAnchor(BaseLocator).dyn_cast<Expr *>()) {
IsDynamicRef =
ide::isDynamicRef(BaseExpr, OverloadInfo.Value,
[&S](Expr *E) { return S.getResolvedType(E); });
}

Results.push_back({OverloadInfo.BaseTy, IsDynamicRef, OverloadInfo.Value});
}

public:
CursorInfoTypeCheckSolutionCallback(Expr *ResolveExpr)
: ResolveExpr(ResolveExpr) {}

ArrayRef<CursorInfoDeclReference> getResults() const { return Results; }
};

// MARK: - CursorInfoDoneParsingCallback

class CursorInfoDoneParsingCallback : public IDEInspectionCallbacks {
Expand Down Expand Up @@ -242,6 +336,59 @@ class CursorInfoDoneParsingCallback : public IDEInspectionCallbacks {
return CursorInfo;
}

std::unique_ptr<ResolvedCursorInfo>
getExprResult(NodeFinderExprResult *ExprResult, SourceFile *SrcFile,
NodeFinder &Finder) const {
Expr *E = ExprResult->getExpr();
DeclContext *DC = ExprResult->getDeclContext();

// Type check the statemnt containing E and listen for solutions.
CursorInfoTypeCheckSolutionCallback Callback(E);
llvm::SaveAndRestore<TypeCheckCompletionCallback *> CompletionCollector(
DC->getASTContext().SolutionCallback, &Callback);
typeCheckASTNodeAtLoc(TypeCheckASTNodeAtLocContext::declContext(DC),
E->getLoc());

if (Callback.getResults().empty()) {
// No results.
return nullptr;
}

for (auto Info : Callback.getResults()) {
// Type check the referenced decls so that all their parent closures are
// type-checked (see comment in typeCheckDeclAndParentClosures).
typeCheckDeclAndParentClosures(Info.ReferencedDecl);
}

if (Callback.getResults().size() != 1) {
// FIXME: We need to be able to report multiple results.
return nullptr;
}

// Deliver results
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we cannot deliver them yet but that will be done in a follow-up PR

Is there more to delivering them then just adding a loop here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the results are reported using callback functions because they have pointers to stack variables – which they really shouldn’t have but that’s something else I want to fix. Thus, we cannot store the results, run the AST-based cursor info and then report them. Hence, what we do right now is serialize the results into a string and just deliver the AST-based results.

So: Delivering the results is a non-trivial change that I want to do in a follow-up PR, especially since we want to fall back to AST-based cursor info if solver-based cursor info doesn’t return any results.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I changed my mind and changed this PR to also deliver the solver-based results. The stress tester found too many (~100) differences between solver-based and AST-based cursor info and in in all of them the solver-based result was superior. So, apparently the verification has outgrown its usefulness and we should just use the solver-based results.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, we're now only delivering the one result right 😅?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, for now. Fixing the TODO in the line above is the next thing to do.

// FIXME: We need to be able to report multiple results.


auto Res = Callback.getResults()[0];
auto CursorInfo = std::make_unique<ResolvedValueRefCursorInfo>(
ResolvedCursorInfo(SrcFile), Res.ReferencedDecl, /*CtorTyRef=*/nullptr,
/*ExtTyRef=*/nullptr, /*IsRef=*/true, /*Ty=*/Type(),
/*ContainerType=*/Res.BaseType);
CursorInfo->setLoc(RequestedLoc);
CursorInfo->setIsDynamic(Res.IsDynamicRef);
if (Res.IsDynamicRef && Res.BaseType) {
if (auto ReceiverType = Res.BaseType->getAnyNominal()) {
CursorInfo->setReceiverTypes({ReceiverType});
} else if (auto MT = Res.BaseType->getAs<AnyMetatypeType>()) {
// Look through metatypes to get the nominal type decl.
if (auto ReceiverType = MT->getInstanceType()->getAnyNominal()) {
CursorInfo->setReceiverTypes({ReceiverType});
}
}
}
CursorInfo->setShorthandShadowedDecls(
Finder.getShorthandShadowedDecls(Res.ReferencedDecl));
return CursorInfo;
}

void doneParsing(SourceFile *SrcFile) override {
if (!SrcFile) {
return;
Expand All @@ -258,6 +405,10 @@ class CursorInfoDoneParsingCallback : public IDEInspectionCallbacks {
CursorInfo = getDeclResult(cast<NodeFinderDeclResult>(Result.get()),
SrcFile, Finder);
break;
case NodeFinderResultKind::Expr:
CursorInfo = getExprResult(cast<NodeFinderExprResult>(Result.get()),
SrcFile, Finder);
break;
}
if (Result) {
Consumer.handleResults(*CursorInfo);
Expand Down
3 changes: 3 additions & 0 deletions lib/IDE/SelectedOverloadInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ swift::ide::getSelectedOverloadInfo(const Solution &S,
if (Result.BaseTy) {
Result.BaseTy = S.simplifyType(Result.BaseTy)->getRValueType();
}
if (Result.BaseTy && Result.BaseTy->is<ModuleType>()) {
Result.BaseTy = nullptr;
}

Result.Value = SelectedOverload->choice.getDeclOrNull();
Result.ValueTy =
Expand Down
13 changes: 8 additions & 5 deletions lib/IDE/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,9 @@ Expr *swift::ide::getBase(ArrayRef<Expr *> ExprStack) {

Expr *CurrentE = ExprStack.back();
Expr *ParentE = getContainingExpr(ExprStack, 1);
if (ParentE && isa<FunctionConversionExpr>(ParentE)) {
ParentE = getContainingExpr(ExprStack, 2);
}
Expr *Base = nullptr;

if (auto DSE = dyn_cast_or_null<DotSyntaxCallExpr>(ParentE))
Expand Down Expand Up @@ -925,6 +928,8 @@ bool swift::ide::isDynamicRef(Expr *Base, ValueDecl *D, llvm::function_ref<Type(
if (!isDeclOverridable(D))
return false;

Base = Base->getSemanticsProvidingExpr();

// super.method()
// TODO: Should be dynamic if `D` is marked as dynamic and @objc, but in
// that case we really need to change the role the index outputs as
Expand Down Expand Up @@ -956,11 +961,9 @@ void swift::ide::getReceiverType(Expr *Base,
if (!ReceiverTy)
return;

if (auto LVT = ReceiverTy->getAs<LValueType>())
ReceiverTy = LVT->getObjectType();
else if (auto MetaT = ReceiverTy->getAs<MetatypeType>())
ReceiverTy = MetaT->getInstanceType();
else if (auto SelfT = ReceiverTy->getAs<DynamicSelfType>())
ReceiverTy = ReceiverTy->getWithoutSpecifierType();
ReceiverTy = ReceiverTy->getMetatypeInstanceType();
if (auto SelfT = ReceiverTy->getAs<DynamicSelfType>())
ReceiverTy = SelfT->getSelfType();

// TODO: Handle generics and composed protocols
Expand Down
5 changes: 1 addition & 4 deletions test/SourceKit/CompileNotifications/cursor-info.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// rdar://103369449
// REQUIRES: asserts

// RUN: %sourcekitd-test -req=track-compiles == -req=cursor %s -offset=0 -- %s | %FileCheck %s -check-prefix=COMPILE_1 --enable-yaml-compatibility
// COMPILE_1: <empty cursor info; internal diagnostic: "Unable to resolve cursor info.">
// COMPILE_1: {
Expand All @@ -12,7 +9,7 @@
// COMPILE_1: key.notification: source.notification.compile-did-finish,
// COMPILE_1: key.compileid: [[CID1]]
// COMPILE_1: }
// FIXME: Once we switch to only run solver-based cursor info, we should only receive a single compile notification
// FIXME: Once all cursor info kinds are migrated to solver-based and we remove the fallback path to AST-based cursor info, we should only receive a single compile notification
// COMPILE_1: {
// COMPILE_1: key.notification: source.notification.compile-will-start,
// COMPILE_1: key.filepath: "SOURCE_DIR{{.*}}cursor-info.swift",
Expand Down
3 changes: 1 addition & 2 deletions test/SourceKit/CursorInfo/cursor_in_pound_if.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,5 @@ func foo() {
let xxx = "hello"
#endif
}
// TODO: Once we switch to use the solver-based cursor info implementation, we also receive results for the int case
// CHECK-INT: Unable to resolve cursor info
// CHECK-INT: <Declaration>let xxx: <Type usr="s:Si">Int</Type></Declaration>
// CHECK-STR: <Declaration>let xxx: <Type usr="s:SS">String</Type></Declaration>
12 changes: 12 additions & 0 deletions test/SourceKit/CursorInfo/cursor_in_stdlib_module.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
func test() {
// RUN: %sourcekitd-test -req=cursor -pos=%(line + 1):9 %s -- %s | %FileCheck %s
Swift.min(1, 2)
}

// CHECK: source.lang.swift.ref.function.free ()
// CHECK-NEXT: min(_:_:)
// CHECK-NEXT: s:s3minyxx_xtSLRzlF
// CHECK-NEXT: source.lang.swift
// CHECK-NEXT: <T where T : Comparable> (T, T) -> T
// CHECK-NEXT: $syxx_xtcSLRzluD
// CHECK-NEXT: Swift
35 changes: 35 additions & 0 deletions test/SourceKit/CursorInfo/cursor_simplify_type.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
protocol Publisher<Output> {
associatedtype Output
}

extension Publisher {
func stage2() -> MyGenerictype<Self.Output> {
fatalError()
}

func stage3() -> Self {
fatalError()
}
}

struct MyGenerictype<Output> : Publisher {
init() {}

func stage1(with output: Self.Output) -> HasTypeAccessInTypealias<Self> {
fatalError()
}
}

struct HasTypeAccessInTypealias<Upstream> : Publisher where Upstream : Publisher {
typealias Output = Upstream.Output
}

func test() {
MyGenerictype()
.stage1(with: 0)
.stage2()
// RUN: %sourcekitd-test -req=cursor -pos=%(line + 1):6 %s -- %s
.stage3()
}

// CHECK: <Declaration>func stage3() -&gt; <Type usr="s:4test13MyGenerictypeV">MyGenerictype</Type>&lt;<Type usr="s:4test24HasTypeAccessInTypealiasV">HasTypeAccessInTypealias</Type>&lt;<Type usr="s:4test13MyGenerictypeV">MyGenerictype</Type>&lt;<Type usr="s:Si">Int</Type>&gt;&gt;.<Type usr="s:4test24HasTypeAccessInTypealiasV6Outputa">Output</Type>&gt;</Declaration>
6 changes: 3 additions & 3 deletions test/SourceKit/CursorInfo/discriminator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ func testNestedClosures() {
}

func testReuseAST(bytes: Int) {
// RUN: %sourcekitd-test -req=cursor -pos=%(line + 2):7 -req-opts=verifysolverbasedcursorinfo=1 %s -- %s == \
// RUN: -req=cursor -pos=%(line + 2):7 -req-opts=verifysolverbasedcursorinfo=1 %s -- %s | %FileCheck %s --check-prefix=REUSE_AST
// RUN: %sourcekitd-test -req=cursor -pos=%(line + 2):7 %s -- %s == \
// RUN: -req=cursor -pos=%(line + 2):7 %s -- %s | %FileCheck %s --check-prefix=REUSE_AST
let size = 3
var bytes = 6
// REUSE_AST: source.lang.swift.decl.var.local (40:7-40:11)
// REUSE_AST: s:13discriminator12testReuseAST5bytesySi_tF4sizeL_Sivp
// REUSE_AST: source.lang.swift.decl.var.local (41:7-41:12)
// REUSE_AST: s:13discriminator12testReuseAST5bytesySi_tFACL0_Sivp
}
}
7 changes: 7 additions & 0 deletions test/SourceKit/CursorInfo/dynamic_self.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class UserCollection {
static let staticMember = "ABC"
func test() {
// RUN: %sourcekitd-test -req=cursor -pos=%(line + 1):10 %s -- %s
Self.staticMember
}
}
Loading