Skip to content

Commit 86e4467

Browse files
authored
Merge pull request #24072 from benlangmuir/kpdml-ide
Code-completion, indexing, cursor-info, etc. for KeyPath dynamic member lookup
2 parents 57a4553 + ae4827a commit 86e4467

18 files changed

+653
-38
lines changed

include/swift/AST/ASTWalker.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@ enum class SemaReferenceKind : uint8_t {
4141
struct ReferenceMetaData {
4242
SemaReferenceKind Kind;
4343
llvm::Optional<AccessKind> AccKind;
44-
ReferenceMetaData(SemaReferenceKind Kind, llvm::Optional<AccessKind> AccKind) :
45-
Kind(Kind), AccKind(AccKind) {}
44+
bool isImplicit = false;
45+
ReferenceMetaData(SemaReferenceKind Kind, llvm::Optional<AccessKind> AccKind,
46+
bool isImplicit = false)
47+
: Kind(Kind), AccKind(AccKind), isImplicit(isImplicit) {}
4648
};
4749

4850
/// An abstract class used to traverse an AST.

include/swift/IDE/SourceEntityWalker.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,11 @@ class SourceEntityWalker {
113113
///
114114
/// \param D the referenced decl.
115115
/// \param Range the source range of the source reference.
116-
/// \param AccKind whether this is a read, write or read/write access.
116+
/// \param Data whether this is a read, write or read/write access, etc.
117117
/// \param IsOpenBracket this is \c true when the method is called on an
118118
/// open bracket.
119119
virtual bool visitSubscriptReference(ValueDecl *D, CharSourceRange Range,
120-
Optional<AccessKind> AccKind,
120+
ReferenceMetaData Data,
121121
bool IsOpenBracket);
122122

123123
/// This method is called for each keyword argument in a call expression.

include/swift/IDE/Utils.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ class CursorInfoResolver : public SourceEntityWalker {
231231
bool tryResolve(ModuleEntity Mod, SourceLoc Loc);
232232
bool tryResolve(Stmt *St);
233233
bool visitSubscriptReference(ValueDecl *D, CharSourceRange Range,
234-
Optional<AccessKind> AccKind,
234+
ReferenceMetaData Data,
235235
bool IsOpenBracket) override;
236236
};
237237

include/swift/Sema/IDETypeChecking.h

+12
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,18 @@ namespace swift {
227227
/// Sometimes for diagnostics we want to work on the original argument list as
228228
/// written by the user; this performs the reverse transformation.
229229
OriginalArgumentList getOriginalArgumentList(Expr *expr);
230+
231+
/// Return true if the specified type or a super-class/super-protocol has the
232+
/// @dynamicMemberLookup attribute on it.
233+
bool hasDynamicMemberLookupAttribute(Type type);
234+
235+
/// Returns the root type of the keypath type in a keypath dynamic member
236+
/// lookup subscript, or \c None if it cannot be determined.
237+
///
238+
/// \param subscript The potential keypath dynamic member lookup subscript.
239+
/// \param DC The DeclContext from which the subscript is being referenced.
240+
Optional<Type> getRootTypeOfKeypathDynamicMember(SubscriptDecl *subscript,
241+
const DeclContext *DC);
230242
}
231243

232244
#endif

lib/IDE/SourceEntityWalker.cpp

+15-10
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@ class SemaAnnotator : public ASTWalker {
7070
bool passReference(ModuleEntity Mod, std::pair<Identifier, SourceLoc> IdLoc);
7171

7272
bool passSubscriptReference(ValueDecl *D, SourceLoc Loc,
73-
Optional<AccessKind> AccKind,
74-
bool IsOpenBracket);
73+
ReferenceMetaData Data, bool IsOpenBracket);
7574

7675
bool passCallArgNames(Expr *Fn, TupleExpr *TupleE);
7776

@@ -265,6 +264,8 @@ std::pair<bool, Expr *> SemaAnnotator::walkToExprPre(Expr *E) {
265264
!isa<MakeTemporarilyEscapableExpr>(E) &&
266265
!isa<CollectionUpcastConversionExpr>(E) &&
267266
!isa<OpaqueValueExpr>(E) &&
267+
!isa<SubscriptExpr>(E) &&
268+
!isa<KeyPathExpr>(E) &&
268269
E->isImplicit())
269270
return { true, E };
270271

@@ -326,16 +327,19 @@ std::pair<bool, Expr *> SemaAnnotator::walkToExprPre(Expr *E) {
326327
if (SE->hasDecl())
327328
SubscrD = SE->getDecl().getDecl();
328329

330+
ReferenceMetaData data(SemaReferenceKind::SubscriptRef, OpAccess,
331+
SE->isImplicit());
332+
329333
if (SubscrD) {
330-
if (!passSubscriptReference(SubscrD, E->getLoc(), OpAccess, true))
334+
if (!passSubscriptReference(SubscrD, E->getLoc(), data, true))
331335
return { false, nullptr };
332336
}
333337

334338
if (!SE->getIndex()->walk(*this))
335339
return { false, nullptr };
336340

337341
if (SubscrD) {
338-
if (!passSubscriptReference(SubscrD, E->getEndLoc(), OpAccess, false))
342+
if (!passSubscriptReference(SubscrD, E->getEndLoc(), data, false))
339343
return { false, nullptr };
340344
}
341345

@@ -577,14 +581,14 @@ bool SemaAnnotator::passModulePathElements(
577581
}
578582

579583
bool SemaAnnotator::passSubscriptReference(ValueDecl *D, SourceLoc Loc,
580-
Optional<AccessKind> AccKind,
584+
ReferenceMetaData Data,
581585
bool IsOpenBracket) {
582586
CharSourceRange Range = Loc.isValid()
583587
? CharSourceRange(Loc, 1)
584588
: CharSourceRange();
585589

586-
bool Continue = SEWalker.visitSubscriptReference(D, Range, AccKind,
587-
IsOpenBracket);
590+
bool Continue =
591+
SEWalker.visitSubscriptReference(D, Range, Data, IsOpenBracket);
588592
if (!Continue)
589593
Cancelled = true;
590594
return Continue;
@@ -727,13 +731,14 @@ bool SourceEntityWalker::visitDeclReference(ValueDecl *D, CharSourceRange Range,
727731

728732
bool SourceEntityWalker::visitSubscriptReference(ValueDecl *D,
729733
CharSourceRange Range,
730-
Optional<AccessKind> AccKind,
734+
ReferenceMetaData Data,
731735
bool IsOpenBracket) {
732736
// Most of the clients treat subscript reference the same way as a
733737
// regular reference when called on the open bracket and
734738
// ignore the closing one.
735-
return IsOpenBracket ? visitDeclReference(D, Range, nullptr, nullptr, Type(),
736-
ReferenceMetaData(SemaReferenceKind::SubscriptRef, AccKind)) : true;
739+
return IsOpenBracket
740+
? visitDeclReference(D, Range, nullptr, nullptr, Type(), Data)
741+
: true;
737742
}
738743

739744
bool SourceEntityWalker::visitCallArgName(Identifier Name,

lib/IDE/SwiftSourceDocInfo.cpp

+7-5
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,12 @@ bool CursorInfoResolver::tryResolve(Stmt *St) {
118118
return false;
119119
}
120120

121-
bool CursorInfoResolver::visitSubscriptReference(ValueDecl *D, CharSourceRange Range,
122-
Optional<AccessKind> AccKind,
121+
bool CursorInfoResolver::visitSubscriptReference(ValueDecl *D,
122+
CharSourceRange Range,
123+
ReferenceMetaData Data,
123124
bool IsOpenBracket) {
124125
// We should treat both open and close brackets equally
125-
return visitDeclReference(D, Range, nullptr, nullptr, Type(),
126-
ReferenceMetaData(SemaReferenceKind::SubscriptRef, AccKind));
126+
return visitDeclReference(D, Range, nullptr, nullptr, Type(), Data);
127127
}
128128

129129
ResolvedCursorInfo CursorInfoResolver::resolve(SourceLoc Loc) {
@@ -190,6 +190,8 @@ bool CursorInfoResolver::visitDeclReference(ValueDecl *D,
190190
ReferenceMetaData Data) {
191191
if (isDone())
192192
return false;
193+
if (Data.isImplicit)
194+
return true;
193195
return !tryResolve(D, CtorTyRef, ExtTyRef, Range.getStart(), /*IsRef=*/true, T);
194196
}
195197

@@ -1423,7 +1425,7 @@ struct RangeResolver::Implementation {
14231425
if (Data.Kind != SemaReferenceKind::DeclRef)
14241426
return;
14251427

1426-
if (!isContainedInSelection(CharSourceRange(Start, 0)))
1428+
if (Data.isImplicit || !isContainedInSelection(CharSourceRange(Start, 0)))
14271429
return;
14281430

14291431
// If the VD is declared outside of current file, exclude such decl.

lib/Index/Index.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,10 @@ class IndexSwiftASTWalker : public SourceEntityWalker {
394394
return true;
395395

396396
IndexSymbol Info;
397+
398+
if (Data.isImplicit)
399+
Info.roles |= (unsigned)SymbolRole::Implicit;
400+
397401
if (CtorTyRef)
398402
if (!reportRef(CtorTyRef, Loc, Info, Data.AccKind))
399403
return false;

lib/Migrator/APIDiffMigratorPass.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,9 @@ struct APIDiffMigratorPass : public ASTMigratorPass, public SourceEntityWalker {
484484
bool visitDeclReference(ValueDecl *D, CharSourceRange Range,
485485
TypeDecl *CtorTyRef, ExtensionDecl *ExtTyRef,
486486
Type T, ReferenceMetaData Data) override {
487+
if (Data.isImplicit)
488+
return true;
489+
487490
for (auto *Item: getRelatedDiffItems(CtorTyRef ? CtorTyRef: D)) {
488491
std::string RepText;
489492
if (isSimpleReplacement(Item, isDotMember(Range), RepText)) {
@@ -501,7 +504,7 @@ struct APIDiffMigratorPass : public ASTMigratorPass, public SourceEntityWalker {
501504
bool visitDeclReference(ValueDecl *D, CharSourceRange Range,
502505
TypeDecl *CtorTyRef, ExtensionDecl *ExtTyRef,
503506
Type T, ReferenceMetaData Data) override {
504-
if (D == Target) {
507+
if (D == Target && !Data.isImplicit) {
505508
Result = Range;
506509
return false;
507510
}

lib/Sema/CSSimplify.cpp

+11-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "swift/AST/ProtocolConformance.h"
2525
#include "swift/Basic/StringExtras.h"
2626
#include "swift/ClangImporter/ClangModule.h"
27+
#include "swift/Sema/IDETypeChecking.h"
2728
#include "llvm/ADT/SetVector.h"
2829
#include "llvm/Support/Compiler.h"
2930

@@ -3554,6 +3555,12 @@ static bool hasDynamicMemberLookupAttribute(Type type,
35543555
return result;
35553556
}
35563557

3558+
// for IDETypeChecking
3559+
bool swift::hasDynamicMemberLookupAttribute(Type type) {
3560+
llvm::DenseMap<CanType, bool> DynamicMemberLookupCache;
3561+
return ::hasDynamicMemberLookupAttribute(type, DynamicMemberLookupCache);
3562+
}
3563+
35573564
static bool isKeyPathDynamicMemberLookup(ConstraintLocator *locator) {
35583565
auto path = locator ? locator->getPath() : None;
35593566
return !path.empty() && path.back().isKeyPathDynamicMember();
@@ -3928,8 +3935,8 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
39283935
// as representing "dynamic lookup" unless it's a direct call
39293936
// to such subscript (in that case label is expected to match).
39303937
if (auto *subscript = dyn_cast<SubscriptDecl>(cand)) {
3931-
if (hasDynamicMemberLookupAttribute(instanceTy,
3932-
DynamicMemberLookupCache) &&
3938+
if (::hasDynamicMemberLookupAttribute(instanceTy,
3939+
DynamicMemberLookupCache) &&
39333940
isValidKeyPathDynamicMemberLookup(subscript, TC)) {
39343941
auto info =
39353942
getArgumentLabels(*this, ConstraintLocatorBuilder(memberLocator));
@@ -4021,7 +4028,8 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
40214028
constraintKind == ConstraintKind::ValueMember &&
40224029
memberName.isSimpleName() && !memberName.isSpecial()) {
40234030
auto name = memberName.getBaseIdentifier();
4024-
if (hasDynamicMemberLookupAttribute(instanceTy, DynamicMemberLookupCache)) {
4031+
if (::hasDynamicMemberLookupAttribute(instanceTy,
4032+
DynamicMemberLookupCache)) {
40254033
auto &ctx = getASTContext();
40264034

40274035
// Recursively look up `subscript(dynamicMember:)` methods in this type.

lib/Sema/LookupVisibleDecls.cpp

+58
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,42 @@ static void lookupVisibleMemberDeclsImpl(
623623
} while (1);
624624
}
625625

626+
static void lookupVisibleDynamicMemberLookupDecls(
627+
Type baseType, VisibleDeclConsumer &consumer, const DeclContext *dc,
628+
LookupState LS, DeclVisibilityKind reason, LazyResolver *typeResolver,
629+
GenericSignatureBuilder *GSB, VisitedSet &visited) {
630+
631+
assert(hasDynamicMemberLookupAttribute(baseType));
632+
auto &ctx = dc->getASTContext();
633+
634+
// Lookup the `subscript(dynamicMember:)` methods in this type.
635+
auto subscriptName =
636+
DeclName(ctx, DeclBaseName::createSubscript(), ctx.Id_dynamicMember);
637+
638+
SmallVector<ValueDecl *, 2> subscripts;
639+
dc->lookupQualified(baseType, subscriptName, NL_QualifiedDefault,
640+
typeResolver, subscripts);
641+
642+
for (ValueDecl *VD : subscripts) {
643+
auto *subscript = dyn_cast<SubscriptDecl>(VD);
644+
if (!subscript)
645+
continue;
646+
647+
auto rootType = getRootTypeOfKeypathDynamicMember(subscript, dc);
648+
if (!rootType)
649+
continue;
650+
651+
auto subs =
652+
baseType->getMemberSubstitutionMap(dc->getParentModule(), subscript);
653+
auto memberType = rootType->subst(subs);
654+
if (!memberType || !memberType->mayHaveMembers())
655+
continue;
656+
657+
lookupVisibleMemberDeclsImpl(memberType, consumer, dc, LS, reason,
658+
typeResolver, GSB, visited);
659+
}
660+
}
661+
626662
namespace {
627663

628664
struct FoundDeclTy {
@@ -824,6 +860,18 @@ class OverrideFilteringConsumer : public VisibleDeclConsumer {
824860
DeclsToReport.insert(FoundDeclTy(VD, Reason));
825861
}
826862
};
863+
864+
struct ShadowedKeyPathMembers : public VisibleDeclConsumer {
865+
VisibleDeclConsumer &consumer;
866+
llvm::DenseSet<DeclBaseName> &seen;
867+
ShadowedKeyPathMembers(VisibleDeclConsumer &consumer, llvm::DenseSet<DeclBaseName> &knownMembers) : consumer(consumer), seen(knownMembers) {}
868+
void foundDecl(ValueDecl *VD, DeclVisibilityKind reason) override {
869+
// Dynamic lookup members are only visible if they are not shadowed by
870+
// non-dynamic members.
871+
if (seen.count(VD->getBaseName()) == 0)
872+
consumer.foundDecl(VD, reason);
873+
}
874+
};
827875
} // end anonymous namespace
828876

829877
/// Enumerate all members in \c BaseTy (including members of extensions,
@@ -841,6 +889,16 @@ static void lookupVisibleMemberDecls(
841889
lookupVisibleMemberDeclsImpl(BaseTy, overrideConsumer, CurrDC, LS, Reason,
842890
TypeResolver, GSB, Visited);
843891

892+
if (hasDynamicMemberLookupAttribute(BaseTy)) {
893+
llvm::DenseSet<DeclBaseName> knownMembers;
894+
for (auto &kv : overrideConsumer.FoundDecls) {
895+
knownMembers.insert(kv.first);
896+
}
897+
ShadowedKeyPathMembers dynamicConsumer(overrideConsumer, knownMembers);
898+
lookupVisibleDynamicMemberLookupDecls(BaseTy, dynamicConsumer, CurrDC, LS,
899+
Reason, TypeResolver, GSB, Visited);
900+
}
901+
844902
// Report the declarations we found to the real consumer.
845903
for (const auto &DeclAndReason : overrideConsumer.DeclsToReport)
846904
Consumer.foundDecl(DeclAndReason.D, DeclAndReason.Reason);

lib/Sema/TypeCheckAttr.cpp

+22-2
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,21 @@
1414
//
1515
//===----------------------------------------------------------------------===//
1616

17-
#include "TypeChecker.h"
1817
#include "MiscDiagnostics.h"
1918
#include "TypeCheckType.h"
20-
#include "swift/AST/GenericSignatureBuilder.h"
19+
#include "TypeChecker.h"
2120
#include "swift/AST/ASTVisitor.h"
2221
#include "swift/AST/ClangModuleLoader.h"
2322
#include "swift/AST/DiagnosticsParse.h"
2423
#include "swift/AST/GenericEnvironment.h"
24+
#include "swift/AST/GenericSignatureBuilder.h"
2525
#include "swift/AST/NameLookup.h"
2626
#include "swift/AST/NameLookupRequests.h"
2727
#include "swift/AST/ParameterList.h"
2828
#include "swift/AST/TypeCheckRequests.h"
2929
#include "swift/AST/Types.h"
3030
#include "swift/Parse/Lexer.h"
31+
#include "swift/Sema/IDETypeChecking.h"
3132
#include "llvm/Support/Debug.h"
3233

3334
using namespace swift;
@@ -1032,6 +1033,25 @@ bool swift::isValidKeyPathDynamicMemberLookup(SubscriptDecl *decl,
10321033
return false;
10331034
}
10341035

1036+
Optional<Type>
1037+
swift::getRootTypeOfKeypathDynamicMember(SubscriptDecl *subscript,
1038+
const DeclContext *DC) {
1039+
auto &TC = TypeChecker::createForContext(DC->getASTContext());
1040+
1041+
if (!isValidKeyPathDynamicMemberLookup(subscript, TC))
1042+
return None;
1043+
1044+
const auto *param = subscript->getIndices()->get(0);
1045+
auto keyPathType = param->getType()->getAs<BoundGenericType>();
1046+
if (!keyPathType)
1047+
return None;
1048+
1049+
assert(!keyPathType->getGenericArgs().empty() &&
1050+
"invalid keypath dynamic member");
1051+
auto rootType = keyPathType->getGenericArgs()[0];
1052+
return rootType;
1053+
}
1054+
10351055
/// The @dynamicMemberLookup attribute is only allowed on types that have at
10361056
/// least one subscript member declared like this:
10371057
///

test/IDE/annotation.swift

+27
Original file line numberDiff line numberDiff line change
@@ -339,3 +339,30 @@ test_arg_tuple2(p1:0,0)
339339
test_arg_tuple3(0,p2:0)
340340
// CHECK: <Func@[[@LINE-7]]:6>test_arg_tuple4</Func>(<Func@[[@LINE-7]]:6#p1>p1</Func>:0,<Func@[[@LINE-7]]:6#p2>p2</Func>:0)
341341
test_arg_tuple4(p1:0,p2:0)
342+
343+
344+
@dynamicMemberLookup
345+
struct Lens<T> {
346+
var obj: T
347+
init(_ obj: T) {
348+
self.obj = obj
349+
}
350+
subscript<U>(dynamicMember member: WritableKeyPath<T, U>) -> Lens<U> {
351+
get { return Lens<U>(obj[keyPath: member]) }
352+
set { obj[keyPath: member] = newValue.obj }
353+
}
354+
}
355+
struct Point {
356+
var x: Int
357+
var y: Int
358+
}
359+
struct Rectangle {
360+
var topLeft: Point
361+
var bottomRight: Point
362+
}
363+
func testDynamicMemberLookup(r: Lens<Rectangle>) {
364+
_ = r.topLeft
365+
// CHECK: _ = <Param@[[@LINE-2]]:30>r</Param>.<Var@[[@LINE-5]]:7>topLeft</Var>
366+
_ = r.bottomRight.y
367+
// CHECK: _ = <Param@[[@LINE-4]]:30>r</Param>.<Var@[[@LINE-6]]:7>bottomRight</Var>.<Var@[[@LINE-10]]:7>y</Var>
368+
}

0 commit comments

Comments
 (0)