From e0524a60a2130f6c587a2de32d8975f875fc5072 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sat, 9 Apr 2022 21:55:53 +0200 Subject: [PATCH 1/8] [AST] Make ASTNode pointer-like --- include/swift/AST/ASTNode.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/include/swift/AST/ASTNode.h b/include/swift/AST/ASTNode.h index ba6837336a8bb..a10ba88de459b 100644 --- a/include/swift/AST/ASTNode.h +++ b/include/swift/AST/ASTNode.h @@ -78,6 +78,12 @@ namespace swift { /// Whether the AST node is implicit. bool isImplicit() const; + + static inline ASTNode getFromOpaqueValue(void *VP) { + ASTNode V; + V.Val = decltype(V.Val)::getFromOpaqueValue(VP); + return V; + } }; } // namespace swift @@ -97,6 +103,19 @@ namespace llvm { return LHS.getOpaqueValue() == RHS.getOpaqueValue(); } }; + + // A ASTNode is "pointer like". + template <> + struct PointerLikeTypeTraits { + public: + static inline void *getAsVoidPointer(ASTNode N) { + return (void *)N.getOpaqueValue(); + } + static inline ASTNode getFromVoidPointer(void *P) { + return ASTNode::getFromOpaqueValue(P); + } + enum { NumLowBitsAvailable = swift::TypeAlignInBits }; + }; } #endif // LLVM_SWIFT_AST_AST_NODE_H From 91f550e0aff86bdd92453b34714b9007ddf0a47f Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 13 Apr 2022 12:05:56 +0200 Subject: [PATCH 2/8] [CodeCompletion] Don't increase the score for holes at the code completion token --- lib/Sema/CSBindings.cpp | 6 ++++++ test/IDE/complete_subscript.swift | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/Sema/CSBindings.cpp b/lib/Sema/CSBindings.cpp index fa8369c4adb86..051e3a970bf49 100644 --- a/lib/Sema/CSBindings.cpp +++ b/lib/Sema/CSBindings.cpp @@ -2017,6 +2017,12 @@ bool TypeVariableBinding::attempt(ConstraintSystem &cs) const { if (TypeVar->getImpl().getGenericParameter()) return false; + // Don't penalize solutions if we couldn't determine the type of the code + // completion token. We still want to examine the surrounding types in + // that case. + if (TypeVar->getImpl().isCodeCompletionToken()) + return false; + // Don't penalize solutions with holes due to missing arguments after the // code completion position. auto argLoc = srcLocator->findLast(); diff --git a/test/IDE/complete_subscript.swift b/test/IDE/complete_subscript.swift index a904712ba7644..3f3716d40678f 100644 --- a/test/IDE/complete_subscript.swift +++ b/test/IDE/complete_subscript.swift @@ -162,7 +162,7 @@ func testSubcscriptTuple(val: (x: Int, String)) { } struct HasSettableSub { - subscript(a: String) -> Any { + subscript(a: String) -> Int { get { return 1 } set { } } @@ -174,6 +174,6 @@ func testSettableSub(x: inout HasSettableSub) { } // SETTABLE_SUBSCRIPT: Begin completions // SETTABLE_SUBSCRIPT-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath#}[']'][#Value#]; -// SETTABLE_SUBSCRIPT-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]: ['[']{#(a): String#}[']'][#@lvalue Any#]; +// SETTABLE_SUBSCRIPT-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]: ['[']{#(a): String#}[']'][#@lvalue Int#]; // SETTABLE_SUBSCRIPT-DAG: Decl[LocalVar]/Local/TypeRelation[Convertible]: local[#String#]; name=local // SETTABLE_SUBSCRIPT: End completions From 4de23c722f6f5bfe27835d5adabe5c5065f77171 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 24 Mar 2022 09:44:25 +0100 Subject: [PATCH 3/8] [CodeCompletion] Hide underscored code completion results in argument completion --- lib/IDE/ArgumentCompletion.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/IDE/ArgumentCompletion.cpp b/lib/IDE/ArgumentCompletion.cpp index 45791b472f98e..b1b566a4ba265 100644 --- a/lib/IDE/ArgumentCompletion.cpp +++ b/lib/IDE/ArgumentCompletion.cpp @@ -111,13 +111,15 @@ void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) { // still have all the other required information (like the argument's // expected type) to provide useful code completion results. if (auto SelectedOverload = S.getOverloadChoiceIfAvailable(CalleeLocator)) { - + FuncD = SelectedOverload->choice.getDeclOrNull(); + if (FuncD && FuncD->shouldHideFromEditor()) { + return; + } CallBaseTy = SelectedOverload->choice.getBaseType(); if (CallBaseTy) { CallBaseTy = S.simplifyType(CallBaseTy)->getRValueType(); } - FuncD = SelectedOverload->choice.getDeclOrNull(); FuncTy = S.simplifyTypeForCodeCompletion(SelectedOverload->openedType); // For completion as the arg in a call to the implicit [keypath: _] From 2749c1cb8ed0984a44be2a015cb0f6b99264362a Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Fri, 25 Mar 2022 14:54:13 +0100 Subject: [PATCH 4/8] [CodeCompletion] Don't consider calls to initializers on metatypes in argument completion --- include/swift/Sema/ConstraintSystem.h | 13 ++++++ lib/IDE/ArgumentCompletion.cpp | 19 +++++++- lib/Sema/CSSimplify.cpp | 65 ++++++++++++++++----------- 3 files changed, 69 insertions(+), 28 deletions(-) diff --git a/include/swift/Sema/ConstraintSystem.h b/include/swift/Sema/ConstraintSystem.h index 546c705d5a51c..9fa17024aaa46 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -5186,6 +5186,19 @@ class ConstraintSystem { /// to a generic function? bool isArgumentGenericFunction(Type argType, Expr *argExpr); + /// Determine whether given constructor reference is valid or does it require + /// any fixes e.g. when base is a protocol metatype. + /// The function ref parameters can be used to validate an intializer ref with + /// types from a solution during code completion. + ConstraintFix *validateInitializerRef( + ConstructorDecl *init, ConstraintLocator *locator, + llvm::function_ref getType, + llvm::function_ref simplifyType, + llvm::function_ref isTypeReference, + llvm::function_ref isStaticallyDerivedMetatype); + ConstraintFix *validateInitializerRef(ConstructorDecl *init, + ConstraintLocator *locator); + // Given a type variable, attempt to find the disjunction of // bind overloads associated with it. This may return null in cases where // the disjunction has either not been created or binds the type variable diff --git a/lib/IDE/ArgumentCompletion.cpp b/lib/IDE/ArgumentCompletion.cpp index b1b566a4ba265..eba4fec02d6ab 100644 --- a/lib/IDE/ArgumentCompletion.cpp +++ b/lib/IDE/ArgumentCompletion.cpp @@ -23,7 +23,7 @@ using namespace swift::constraints; bool ArgumentTypeCheckCompletionCallback::addPossibleParams( const ArgumentTypeCheckCompletionCallback::Result &Res, SmallVectorImpl &Params, SmallVectorImpl &Types) { - if (!Res.ParamIdx) { + if (!Res.ParamIdx || !Res.FuncTy) { // We don't really know much here. Suggest global results without a specific // expected type. return true; @@ -78,6 +78,20 @@ bool ArgumentTypeCheckCompletionCallback::addPossibleParams( return ShowGlobalCompletions; } +static bool isInvalidInitRef(const Solution &S, ValueDecl *FuncD, + ConstraintLocator *Locator) { + if (auto Init = dyn_cast_or_null(FuncD)) { + return S.getConstraintSystem().validateInitializerRef( + Init, Locator, [&S](ASTNode N) { return S.getType(N); }, + [&S](Type T) { return S.simplifyType(T); }, + [&S](Expr *E) { return S.isTypeReference(E); }, + [&S](Expr *E) { return S.isStaticallyDerivedMetatype(E); }) != + nullptr; + } else { + return false; + } +} + void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) { Type ExpectedTy = getTypeForCompletion(S, CompletionExpr); @@ -112,7 +126,8 @@ void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) { // expected type) to provide useful code completion results. if (auto SelectedOverload = S.getOverloadChoiceIfAvailable(CalleeLocator)) { FuncD = SelectedOverload->choice.getDeclOrNull(); - if (FuncD && FuncD->shouldHideFromEditor()) { + if (FuncD && (FuncD->shouldHideFromEditor() || + isInvalidInitRef(S, FuncD, CallLocator))) { return; } CallBaseTy = SelectedOverload->choice.getBaseType(); diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index fcd6127adf2ac..df030d4b91650 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -8698,15 +8698,18 @@ static bool isNonFinalClass(Type type) { /// Determine whether given constructor reference is valid or does it require /// any fixes e.g. when base is a protocol metatype. -static ConstraintFix *validateInitializerRef(ConstraintSystem &cs, - ConstructorDecl *init, - ConstraintLocator *locator) { +ConstraintFix *ConstraintSystem::validateInitializerRef( + ConstructorDecl *init, ConstraintLocator *locator, + llvm::function_ref getType, + llvm::function_ref simplifyType, + llvm::function_ref isTypeReference, + llvm::function_ref isStaticallyDerivedMetatype) { auto anchor = locator->getAnchor(); if (!anchor) return nullptr; - auto getType = [&cs](Expr *expr) -> Type { - return cs.simplifyType(cs.getType(expr))->getRValueType(); + auto getSimplifiedType = [getType, simplifyType](Expr *expr) -> Type { + return simplifyType(getType(expr))->getRValueType(); }; auto locatorEndsWith = @@ -8722,21 +8725,21 @@ static ConstraintFix *validateInitializerRef(ConstraintSystem &cs, // Explicit initializer reference e.g. `T.init(...)` or `T.init`. if (auto *UDE = getAsExpr(anchor)) { baseExpr = UDE->getBase(); - baseType = getType(baseExpr); + baseType = getSimplifiedType(baseExpr); if (baseType->is()) { auto instanceType = baseType->getAs() ->getInstanceType() ->getWithoutParens(); - if (!cs.isTypeReference(baseExpr) && instanceType->isExistentialType()) { + if (!isTypeReference(baseExpr) && instanceType->isExistentialType()) { return AllowInvalidInitRef::onProtocolMetatype( - cs, baseType, init, /*isStaticallyDerived=*/true, + *this, baseType, init, /*isStaticallyDerived=*/true, baseExpr->getSourceRange(), locator); } } // Initializer call e.g. `T(...)` } else if (auto *CE = getAsExpr(anchor)) { baseExpr = CE->getFn(); - baseType = getType(baseExpr); + baseType = getSimplifiedType(baseExpr); // FIXME: Historically, UnresolvedMemberExprs have allowed implicit // construction through a metatype value, but this should probably be // illegal. @@ -8745,18 +8748,18 @@ static ConstraintFix *validateInitializerRef(ConstraintSystem &cs, // of `.init` on metatype value. if (auto *AMT = baseType->getAs()) { auto instanceType = AMT->getInstanceType()->getWithoutParens(); - if (!cs.isTypeReference(baseExpr)) { + if (!isTypeReference(baseExpr)) { if (baseType->is() && instanceType->isAnyExistentialType()) { return AllowInvalidInitRef::onProtocolMetatype( - cs, baseType, init, cs.isStaticallyDerivedMetatype(baseExpr), + *this, baseType, init, isStaticallyDerivedMetatype(baseExpr), baseExpr->getSourceRange(), locator); } if (!instanceType->isExistentialType() || instanceType->isAnyExistentialType()) { - return AllowInvalidInitRef::onNonConstMetatype(cs, baseType, init, - locator); + return AllowInvalidInitRef::onNonConstMetatype(*this, baseType, + init, locator); } } } @@ -8771,7 +8774,7 @@ static ConstraintFix *validateInitializerRef(ConstraintSystem &cs, // UnresolvedMemberExpr--instead, it will be the type of the nested type // member. // We need to find type variable which represents contextual base. - auto *baseLocator = cs.getConstraintLocator( + auto *baseLocator = getConstraintLocator( UME, locatorEndsWith(locator, ConstraintLocator::ConstructorMember) ? ConstraintLocator::UnresolvedMember : ConstraintLocator::MemberRefBase); @@ -8779,19 +8782,19 @@ static ConstraintFix *validateInitializerRef(ConstraintSystem &cs, // FIXME: Type variables responsible for contextual base could be cached // in the constraint system to speed up lookup. auto result = llvm::find_if( - cs.getTypeVariables(), [&baseLocator](const TypeVariableType *typeVar) { + getTypeVariables(), [&baseLocator](const TypeVariableType *typeVar) { return typeVar->getImpl().getLocator() == baseLocator; }); - assert(result != cs.getTypeVariables().end()); - baseType = cs.simplifyType(*result)->getRValueType(); + assert(result != getTypeVariables().end()); + baseType = simplifyType(*result)->getRValueType(); // Constraint for member base is formed as '$T.Type[.(anchor)) { // Key path can't refer to initializers e.g. `\Type.init` - return AllowInvalidRefInKeyPath::forRef(cs, init, locator); + return AllowInvalidRefInKeyPath::forRef(*this, init, locator); } if (!baseType) @@ -8803,11 +8806,10 @@ static ConstraintFix *validateInitializerRef(ConstraintSystem &cs, // constrainted Self type, 'self' has archetype type, and only // required initializers can be called. if (baseExpr && !baseExpr->isSuperExpr()) { - auto &ctx = cs.getASTContext(); if (auto *DRE = dyn_cast(baseExpr->getSemanticsProvidingExpr())) { - if (DRE->getDecl()->getName() == ctx.Id_self) { - if (getType(DRE)->is()) + if (DRE->getDecl()->getName() == getASTContext().Id_self) { + if (getSimplifiedType(DRE)->is()) applicable = true; } } @@ -8826,7 +8828,7 @@ static ConstraintFix *validateInitializerRef(ConstraintSystem &cs, instanceType->is()); // Otherwise this is something like `T.init(...)` } else { - isStaticallyDerived = cs.isStaticallyDerivedMetatype(baseExpr); + isStaticallyDerived = isStaticallyDerivedMetatype(baseExpr); } auto baseRange = baseExpr ? baseExpr->getSourceRange() : SourceRange(); @@ -8834,19 +8836,30 @@ static ConstraintFix *validateInitializerRef(ConstraintSystem &cs, if (isNonFinalClass(instanceType) && !isStaticallyDerived && !init->hasClangNode() && !(init->isRequired() || init->getDeclContext()->getSelfProtocolDecl())) { - return AllowInvalidInitRef::dynamicOnMetatype(cs, baseType, init, baseRange, - locator); + return AllowInvalidInitRef::dynamicOnMetatype(*this, baseType, init, + baseRange, locator); // Constructors cannot be called on a protocol metatype, because there is no // metatype to witness it. } else if (baseType->is() && instanceType->isExistentialType()) { return AllowInvalidInitRef::onProtocolMetatype( - cs, baseType, init, isStaticallyDerived, baseRange, locator); + *this, baseType, init, isStaticallyDerived, baseRange, locator); } return nullptr; } +ConstraintFix * +ConstraintSystem::validateInitializerRef(ConstructorDecl *init, + ConstraintLocator *locator) { + ConstraintSystem &cs = *this; + return validateInitializerRef( + init, locator, [&cs](ASTNode N) { return cs.getType(N); }, + [&cs](Type T) { return cs.simplifyType(T); }, + [&cs](Expr *E) { return cs.isTypeReference(E); }, + [&cs](Expr *E) { return cs.isStaticallyDerivedMetatype(E); }); +} + static ConstraintFix * fixMemberRef(ConstraintSystem &cs, Type baseTy, DeclNameRef memberName, const OverloadChoice &choice, @@ -8856,7 +8869,7 @@ fixMemberRef(ConstraintSystem &cs, Type baseTy, // to refer to a declaration. if (auto *decl = choice.getDeclOrNull()) { if (auto *CD = dyn_cast(decl)) { - if (auto *fix = validateInitializerRef(cs, CD, locator)) + if (auto *fix = cs.validateInitializerRef(CD, locator)) return fix; } From 1cbfb9e50dce9ec5c61b0744091a46d3dd0d667c Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 13 Apr 2022 12:06:09 +0200 Subject: [PATCH 5/8] [CodeCompletion] Compute type relations for the function calls selected by ArgumentCompletion --- include/swift/IDE/ArgumentCompletion.h | 2 + lib/IDE/ArgumentCompletion.cpp | 59 ++++++++++++++++++++++++-- lib/Sema/TypeCheckCodeCompletion.cpp | 1 + test/IDE/complete_call_arg.swift | 2 +- test/IDE/complete_subscript.swift | 2 +- 5 files changed, 60 insertions(+), 6 deletions(-) diff --git a/include/swift/IDE/ArgumentCompletion.h b/include/swift/IDE/ArgumentCompletion.h index 703259735fa67..f5c4e39cc47e2 100644 --- a/include/swift/IDE/ArgumentCompletion.h +++ b/include/swift/IDE/ArgumentCompletion.h @@ -25,6 +25,8 @@ class ArgumentTypeCheckCompletionCallback : public TypeCheckCompletionCallback { struct Result { /// The type associated with the code completion expression itself. Type ExpectedType; + /// The expected return type of the function call. + Type ExpectedCallType; /// True if this is a subscript rather than a function call. bool IsSubscript; /// The FuncDecl or SubscriptDecl associated with the call. diff --git a/lib/IDE/ArgumentCompletion.cpp b/lib/IDE/ArgumentCompletion.cpp index eba4fec02d6ab..d7619ca7e1901 100644 --- a/lib/IDE/ArgumentCompletion.cpp +++ b/lib/IDE/ArgumentCompletion.cpp @@ -78,6 +78,44 @@ bool ArgumentTypeCheckCompletionCallback::addPossibleParams( return ShowGlobalCompletions; } +/// Applies heuristic to determine whether the result type of \p E is +/// unconstrained, that is if the constraint system is satisfiable for any +/// result type of \p E. +static bool isExpressionResultTypeUnconstrained(const Solution &S, Expr *E) { + ConstraintSystem &CS = S.getConstraintSystem(); + if (auto ParentExpr = CS.getParentExpr(E)) { + if (auto Assign = dyn_cast_or_null(ParentExpr)) { + if (isa(Assign->getDest())) { + // _ = is unconstrained + return true; + } + } else if (isa(ParentExpr)) { + // super.init() is unconstrained (it always produces the correct result + // by definition) + return true; + } + } + auto targetIt = S.solutionApplicationTargets.find(E); + if (targetIt == S.solutionApplicationTargets.end()) { + return false; + } + auto target = targetIt->second; + assert(target.kind == SolutionApplicationTarget::Kind::expression); + switch (target.getExprContextualTypePurpose()) { + case CTP_Unused: + // If we aren't using the contextual type, its unconstrained by definition. + return true; + case CTP_Initialization: { + // let x = is unconstrained + auto contextualType = target.getExprContextualType(); + return !contextualType || contextualType->is(); + } + default: + // Assume that it's constrained by default. + return false; + } +} + static bool isInvalidInitRef(const Solution &S, ValueDecl *FuncD, ConstraintLocator *Locator) { if (auto Init = dyn_cast_or_null(FuncD)) { @@ -116,6 +154,11 @@ void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) { } auto ArgIdx = ArgInfo->completionIdx; + Type ExpectedCallType; + if (!isExpressionResultTypeUnconstrained(S, ParentCall)) { + ExpectedCallType = getTypeForCompletion(S, ParentCall); + } + auto *CallLocator = CS.getConstraintLocator(ParentCall); auto *CalleeLocator = S.getCalleeLocator(CallLocator); ValueDecl *FuncD = nullptr; @@ -218,10 +261,10 @@ void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) { llvm::SmallDenseMap SolutionSpecificVarTypes; getSolutionSpecificVarTypes(S, SolutionSpecificVarTypes); - Results.push_back({ExpectedTy, isa(ParentCall), FuncD, FuncTy, - ArgIdx, ParamIdx, std::move(ClaimedParams), - IsNoninitialVariadic, CallBaseTy, HasLabel, IsAsync, - SolutionSpecificVarTypes}); + Results.push_back({ExpectedTy, ExpectedCallType, + isa(ParentCall), FuncD, FuncTy, ArgIdx, + ParamIdx, std::move(ClaimedParams), IsNoninitialVariadic, + CallBaseTy, HasLabel, IsAsync, SolutionSpecificVarTypes}); } void ArgumentTypeCheckCompletionCallback::deliverResults( @@ -234,10 +277,18 @@ void ArgumentTypeCheckCompletionCallback::deliverResults( // Perform global completion as a fallback if we don't have any results. bool shouldPerformGlobalCompletion = Results.empty(); + SmallVector ExpectedCallTypes; + for (auto &Result : Results) { + ExpectedCallTypes.push_back(Result.ExpectedCallType); + } + SmallVector ExpectedTypes; if (IncludeSignature && !Results.empty()) { Lookup.setHaveLParen(true); + Lookup.setExpectedTypes(ExpectedCallTypes, + /*isImplicitSingleExpressionReturn=*/false); + for (auto &Result : Results) { auto SemanticContext = SemanticContextKind::None; NominalTypeDecl *BaseNominal = nullptr; diff --git a/lib/Sema/TypeCheckCodeCompletion.cpp b/lib/Sema/TypeCheckCodeCompletion.cpp index 244e8167d1a62..4b63570a32f5b 100644 --- a/lib/Sema/TypeCheckCodeCompletion.cpp +++ b/lib/Sema/TypeCheckCodeCompletion.cpp @@ -630,6 +630,7 @@ bool TypeChecker::typeCheckForCodeCompletion( // If solve failed to generate constraints or with some other // issue, we need to fallback to type-checking a sub-expression. + cs.setSolutionApplicationTarget(target.getAsExpr(), target); if (!cs.solveForCodeCompletion(target, solutions)) return CompletionResult::Fallback; diff --git a/test/IDE/complete_call_arg.swift b/test/IDE/complete_call_arg.swift index 294c9f8d951bf..fa0680b6a7445 100644 --- a/test/IDE/complete_call_arg.swift +++ b/test/IDE/complete_call_arg.swift @@ -1227,7 +1227,7 @@ private extension Sequence { func SubstitutableBaseTyOfSubscript(by keyPath: KeyPath) -> [Element] { return sorted { a, b in a[#^GENERICBASE_SUB^#] } // GENERICBASE_SUB: Begin completions, 1 item - // GENERICBASE_SUB: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath#}[']'][#Value#]; + // GENERICBASE_SUB: Pattern/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['[']{#keyPath: KeyPath#}[']'][#Value#]; // GENERICBASE_SUB: End completions } } diff --git a/test/IDE/complete_subscript.swift b/test/IDE/complete_subscript.swift index 3f3716d40678f..7907028b3df39 100644 --- a/test/IDE/complete_subscript.swift +++ b/test/IDE/complete_subscript.swift @@ -174,6 +174,6 @@ func testSettableSub(x: inout HasSettableSub) { } // SETTABLE_SUBSCRIPT: Begin completions // SETTABLE_SUBSCRIPT-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath#}[']'][#Value#]; -// SETTABLE_SUBSCRIPT-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]: ['[']{#(a): String#}[']'][#@lvalue Int#]; +// SETTABLE_SUBSCRIPT-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['[']{#(a): String#}[']'][#@lvalue Int#]; // SETTABLE_SUBSCRIPT-DAG: Decl[LocalVar]/Local/TypeRelation[Convertible]: local[#String#]; name=local // SETTABLE_SUBSCRIPT: End completions From 9c62b4d1a96584595fc9c673fd9a08699e220417 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 24 Mar 2022 15:52:49 +0100 Subject: [PATCH 6/8] [CodeCompletion] Don't show call pattern completions for overridden functions --- include/swift/IDE/ArgumentCompletion.h | 4 +++ include/swift/Sema/ConstraintSystem.h | 8 +++++ lib/IDE/ArgumentCompletion.cpp | 48 ++++++++++++++++++++++---- lib/Sema/CSRanking.cpp | 10 ++---- 4 files changed, 56 insertions(+), 14 deletions(-) diff --git a/include/swift/IDE/ArgumentCompletion.h b/include/swift/IDE/ArgumentCompletion.h index f5c4e39cc47e2..c232d0ebb442d 100644 --- a/include/swift/IDE/ArgumentCompletion.h +++ b/include/swift/IDE/ArgumentCompletion.h @@ -72,6 +72,10 @@ class ArgumentTypeCheckCompletionCallback : public TypeCheckCompletionCallback { void sawSolutionImpl(const constraints::Solution &solution) override; + /// Populates \p OverriddenDecls with all \c FuncD in \p Results that are less + /// specialized as some other \c FuncD in \p Results. + void computeOverriddenDecls(SmallPtrSetImpl &OverriddenDecls); + public: ArgumentTypeCheckCompletionCallback(CodeCompletionExpr *CompletionExpr, DeclContext *DC) diff --git a/include/swift/Sema/ConstraintSystem.h b/include/swift/Sema/ConstraintSystem.h index 9fa17024aaa46..9e257fd02e1b1 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -5833,6 +5833,14 @@ Type getDynamicSelfReplacementType(Type baseObjTy, const ValueDecl *member, ValueDecl *getOverloadChoiceDecl(Constraint *choice); +/// Determine whether the first declaration is as "specialized" as +/// the second declaration. +/// +/// "Specialized" is essentially a form of subtyping, defined by +/// \c CompareDeclSpecializationRequest. +bool isDeclAsSpecializedAs(DeclContext *dc, ValueDecl *decl1, ValueDecl *decl2, + bool isDynamicOverloadComparison = false); + class DisjunctionChoice { ConstraintSystem &CS; unsigned Index; diff --git a/lib/IDE/ArgumentCompletion.cpp b/lib/IDE/ArgumentCompletion.cpp index d7619ca7e1901..2cc815986dcba 100644 --- a/lib/IDE/ArgumentCompletion.cpp +++ b/lib/IDE/ArgumentCompletion.cpp @@ -267,6 +267,33 @@ void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) { CallBaseTy, HasLabel, IsAsync, SolutionSpecificVarTypes}); } +void ArgumentTypeCheckCompletionCallback::computeOverriddenDecls( + SmallPtrSetImpl &OverriddenDecls) { + for (size_t i = 0; i < Results.size(); ++i) { + auto &ResultA = Results[i]; + for (size_t j = i + 1; j < Results.size(); ++j) { + auto &ResultB = Results[j]; + if (!ResultA.FuncD || !ResultB.FuncD) { + continue; + } + if (ResultA.FuncD->getName() != ResultB.FuncD->getName()) { + continue; + } + bool aAsSpecializedAsB = + isDeclAsSpecializedAs(DC, ResultA.FuncD, ResultB.FuncD); + bool bAsSpecializedAsA = + isDeclAsSpecializedAs(DC, ResultB.FuncD, ResultA.FuncD); + if (aAsSpecializedAsB != bAsSpecializedAsA) { + if (aAsSpecializedAsB) { + OverriddenDecls.insert(ResultB.FuncD); + } else { + OverriddenDecls.insert(ResultA.FuncD); + } + } + } + } +} + void ArgumentTypeCheckCompletionCallback::deliverResults( bool IncludeSignature, SourceLoc Loc, DeclContext *DC, ide::CodeCompletionContext &CompletionCtx, @@ -275,6 +302,9 @@ void ArgumentTypeCheckCompletionCallback::deliverResults( CompletionLookup Lookup(CompletionCtx.getResultSink(), Ctx, DC, &CompletionCtx); + SmallPtrSet OverriddenDecls; + computeOverriddenDecls(OverriddenDecls); + // Perform global completion as a fallback if we don't have any results. bool shouldPerformGlobalCompletion = Results.empty(); SmallVector ExpectedCallTypes; @@ -320,13 +350,17 @@ void ArgumentTypeCheckCompletionCallback::deliverResults( if (Result.FuncTy) { if (auto FuncTy = Result.FuncTy->lookThroughAllOptionalTypes() ->getAs()) { - if (Result.IsSubscript) { - assert(SemanticContext != SemanticContextKind::None); - auto *SD = dyn_cast_or_null(Result.FuncD); - Lookup.addSubscriptCallPattern(FuncTy, SD, SemanticContext); - } else { - auto *FD = dyn_cast_or_null(Result.FuncD); - Lookup.addFunctionCallPattern(FuncTy, FD, SemanticContext); + if (OverriddenDecls.count(Result.FuncD) == 0) { + // Don't show call pattern completions if the function is + // overridden. + if (Result.IsSubscript) { + assert(SemanticContext != SemanticContextKind::None); + auto *SD = dyn_cast_or_null(Result.FuncD); + Lookup.addSubscriptCallPattern(FuncTy, SD, SemanticContext); + } else { + auto *FD = dyn_cast_or_null(Result.FuncD); + Lookup.addFunctionCallPattern(FuncTy, FD, SemanticContext); + } } } } diff --git a/lib/Sema/CSRanking.cpp b/lib/Sema/CSRanking.cpp index 6811f365feecc..107d650a4ce02 100644 --- a/lib/Sema/CSRanking.cpp +++ b/lib/Sema/CSRanking.cpp @@ -400,13 +400,9 @@ static bool paramIsIUO(const ValueDecl *decl, int paramNum) { ->isImplicitlyUnwrappedOptional(); } -/// Determine whether the first declaration is as "specialized" as -/// the second declaration. -/// -/// "Specialized" is essentially a form of subtyping, defined below. -static bool isDeclAsSpecializedAs(DeclContext *dc, ValueDecl *decl1, - ValueDecl *decl2, - bool isDynamicOverloadComparison = false) { +bool swift::constraints::isDeclAsSpecializedAs(DeclContext *dc, + ValueDecl *decl1, ValueDecl *decl2, + bool isDynamicOverloadComparison) { return evaluateOrDefault(decl1->getASTContext().evaluator, CompareDeclSpecializationRequest{ dc, decl1, decl2, isDynamicOverloadComparison}, From 11b7116f24d2758020c167af01164279259b3ce9 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 13 Apr 2022 12:06:51 +0200 Subject: [PATCH 7/8] [CodeCompletion] Migrate PostfixExprParen to solver-based --- lib/IDE/CodeCompletion.cpp | 1 + test/IDE/complete_at_top_level.swift | 2 +- test/IDE/complete_call_arg.swift | 17 +++--- test/IDE/complete_constructor.swift | 8 +-- test/IDE/complete_crashes.swift | 2 +- ..._enum_unresolved_dot_argument_labels.swift | 4 +- test/IDE/complete_in_result_builder.swift | 59 +++++++++++-------- test/IDE/complete_init_inherited.swift | 4 +- test/IDE/complete_result_builder.swift | 2 +- test/IDE/complete_vararg.swift | 8 ++- 10 files changed, 60 insertions(+), 47 deletions(-) diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp index 048f2438f3391..8ba32825cbd31 100644 --- a/lib/IDE/CodeCompletion.cpp +++ b/lib/IDE/CodeCompletion.cpp @@ -1387,6 +1387,7 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) { Lookup.deliverResults(CurDeclContext, DotLoc, CompletionContext, Consumer); return true; } + case CompletionKind::PostfixExprParen: case CompletionKind::CallArg: { assert(CodeCompleteTokenExpr); assert(CurDeclContext); diff --git a/test/IDE/complete_at_top_level.swift b/test/IDE/complete_at_top_level.swift index 02a29518be9ef..04ae83a575b75 100644 --- a/test/IDE/complete_at_top_level.swift +++ b/test/IDE/complete_at_top_level.swift @@ -327,7 +327,7 @@ func resyncParserB14() {} var stringInterp = "\(#^STRING_INTERP_3?check=STRING_INTERP^#)" _ = "" + "\(#^STRING_INTERP_4?check=STRING_INTERP^#)" + "" // STRING_INTERP: Begin completions -// STRING_INTERP-DAG: Decl[InstanceMethod]/CurrNominal/Flair[ArgLabels]/IsSystem: ['(']{#(value): T#}[')'][#Void#]; +// STRING_INTERP-DAG: Decl[InstanceMethod]/CurrNominal/Flair[ArgLabels]/IsSystem: ['(']{#(value): Any.Type#}[')'][#Void#]; // STRING_INTERP-DAG: Decl[Struct]/CurrModule/TypeRelation[Convertible]: FooStruct[#FooStruct#]; name=FooStruct // STRING_INTERP-DAG: Decl[FreeFunction]/CurrModule/TypeRelation[Invalid]: fooFunc1()[#Void#]; // STRING_INTERP-DAG: Decl[FreeFunction]/CurrModule: optStr()[#String?#]; diff --git a/test/IDE/complete_call_arg.swift b/test/IDE/complete_call_arg.swift index fa0680b6a7445..f9727eedc0e44 100644 --- a/test/IDE/complete_call_arg.swift +++ b/test/IDE/complete_call_arg.swift @@ -239,7 +239,7 @@ class C3 { // OVERLOAD6-DAG: Decl[InstanceMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#(a1): C1#}, {#b1: C2#}[')'][#Void#]; name=:b1: // OVERLOAD6-DAG: Decl[InstanceMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#a2: C2#}, {#b2: C1#}[')'][#Void#]; name=a2:b2: // OVERLOAD6-DAG: Decl[InstanceVar]/CurrNominal/TypeRelation[Convertible]: C1I[#C1#]; name=C1I -// OVERLOAD6-DAG: Decl[InstanceVar]/CurrNominal: C2I[#C2#]; name=C2I +// OVERLOAD6-DAG: Decl[InstanceVar]/CurrNominal/TypeRelation[Convertible]: C2I[#C2#]; name=C2I // OVERLOAD6: End completions extension C3 { @@ -254,7 +254,7 @@ extension C3 { } // HASERROR1: Begin completions -// HASERROR1-DAG: Decl[InstanceMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#a1: C1#}, {#b1: <>#}[')'][#Int#]; +// HASERROR1-DAG: Decl[InstanceMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#a1: C1#}, {#b1: _#}[')'][#Int#]; // HASERROR1: End completions // HASERROR2: Begin completions @@ -468,8 +468,8 @@ func curry(_ f: @escaping (T1, T2) -> R) -> (T1) -> (T2) -> R { return { t1 in { t2 in f(#^NESTED_CLOSURE^#, t2) } } // NESTED_CLOSURE: Begin completions // FIXME: Should be '/TypeRelation[Invalid]: t2[#T2#]' - // NESTED_CLOSURE: Decl[LocalVar]/Local: t2; name=t2 - // NESTED_CLOSURE: Decl[LocalVar]/Local: t1[#T1#]; name=t1 + // NESTED_CLOSURE: Decl[LocalVar]/Local: t2[#T2#]; name=t2 + // NESTED_CLOSURE: Decl[LocalVar]/Local/TypeRelation[Convertible]: t1[#T1#]; name=t1 } func trailingClosureLocal(x: Int, fn: (Int) -> Void) { @@ -608,8 +608,8 @@ func testStaticMemberCall() { let _ = TestStaticMemberCall.create2(#^STATIC_METHOD_AFTERPAREN_2^#) // STATIC_METHOD_AFTERPAREN_2: Begin completions -// STATIC_METHOD_AFTERPAREN_2-DAG: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#(arg1): Int#}[')'][#TestStaticMemberCall#]; -// STATIC_METHOD_AFTERPAREN_2-DAG: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#(arg1): Int#}, {#arg2: Int#}, {#arg3: Int#}, {#arg4: Int#}[')'][#TestStaticMemberCall#]; +// STATIC_METHOD_AFTERPAREN_2-DAG: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#(arg1): Int#}[')'][#TestStaticMemberCall#]; +// STATIC_METHOD_AFTERPAREN_2-DAG: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#(arg1): Int#}, {#arg2: Int#}, {#arg3: Int#}, {#arg4: Int#}[')'][#TestStaticMemberCall#]; // STATIC_METHOD_AFTERPAREN_2-DAG: Decl[Struct]/OtherModule[Swift]/IsSystem/TypeRelation[Convertible]: Int[#Int#]; // STATIC_METHOD_AFTERPAREN_2-DAG: Literal[Integer]/None/TypeRelation[Convertible]: 0[#Int#]; // STATIC_METHOD_AFTERPAREN_2: End completions @@ -664,9 +664,8 @@ func testImplicitMember() { // IMPLICIT_MEMBER_SKIPPED: End completions let _: TestStaticMemberCall = .createOverloaded(#^IMPLICIT_MEMBER_OVERLOADED^#) -// IMPLICIT_MEMBER_OVERLOADED: Begin completions, 2 items +// IMPLICIT_MEMBER_OVERLOADED: Begin completions, 1 item // IMPLICIT_MEMBER_OVERLOADED: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#arg1: Int#}[')'][#TestStaticMemberCall#]; name=arg1: -// IMPLICIT_MEMBER_OVERLOADED: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#arg1: String#}[')'][#String#]; name=arg1: // IMPLICIT_MEMBER_OVERLOADED: End completions } func testImplicitMemberInArrayLiteral() { @@ -730,7 +729,7 @@ struct TestHasErrorAutoclosureParam { func test() { hasErrorAutoclosureParam(#^PARAM_WITH_ERROR_AUTOCLOSURE^# // PARAM_WITH_ERROR_AUTOCLOSURE: Begin completions, 1 items -// PARAM_WITH_ERROR_AUTOCLOSURE: Decl[InstanceMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#value: <>#}[')'][#Void#]; +// PARAM_WITH_ERROR_AUTOCLOSURE: Decl[InstanceMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#value: _#}[')'][#Void#]; // PARAM_WITH_ERROR_AUTOCLOSURE: End completions } } diff --git a/test/IDE/complete_constructor.swift b/test/IDE/complete_constructor.swift index 74296051f90dd..e36fddc43c71d 100644 --- a/test/IDE/complete_constructor.swift +++ b/test/IDE/complete_constructor.swift @@ -336,8 +336,8 @@ func testDependentTypeInClosure() { let _ = DependentTypeInClosure(#^DEPENDENT_IN_CLOSURE_1^#) // DEPENDENT_IN_CLOSURE_1: Begin completions -// DEPENDENT_IN_CLOSURE_1-DAG: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#(arg): _#}, {#fn: (_.Content) -> Void##(_.Content) -> Void#}[')'][#DependentTypeInClosure<_>#]; -// DEPENDENT_IN_CLOSURE_1-DAG: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#arg: _#}, {#fn: () -> _.Content##() -> _.Content#}[')'][#DependentTypeInClosure<_>#]; +// DEPENDENT_IN_CLOSURE_1-DAG: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#(arg): DataType#}, {#fn: (_) -> Void##(_) -> Void#}[')'][#DependentTypeInClosure#]; +// DEPENDENT_IN_CLOSURE_1-DAG: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#arg: DataType#}, {#fn: () -> _##() -> _#}[')'][#DependentTypeInClosure#]; // DEPENDENT_IN_CLOSURE_1: End completions let _ = DependentTypeInClosure.#^DEPENDENT_IN_CLOSURE_2^# @@ -357,8 +357,8 @@ extension InitWithUnresolved where Self.Data: Comparable { func testInitWithUnresolved() { let _ = InitWithUnresolved(#^INIT_WITH_UNRESOLVEDTYPE_1^# // INIT_WITH_UNRESOLVEDTYPE_1: Begin completions, 2 items -// INIT_WITH_UNRESOLVEDTYPE_1-DAG: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#arg: _#}, {#fn: (_.Content) -> Void##(_.Content) -> Void#}[')'][#InitWithUnresolved<_>#]; -// INIT_WITH_UNRESOLVEDTYPE_1-DAG: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#arg2: _#}[')'][#InitWithUnresolved<_>#]; +// INIT_WITH_UNRESOLVEDTYPE_1-DAG: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#arg: DataType#}, {#fn: (_) -> Void##(_) -> Void#}[')'][#InitWithUnresolved#]; +// INIT_WITH_UNRESOLVEDTYPE_1-DAG: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#arg2: DataType#}[')'][#InitWithUnresolved#]; // INIT_WITH_UNRESOLVEDTYPE_1: End completions } diff --git a/test/IDE/complete_crashes.swift b/test/IDE/complete_crashes.swift index f76cf080f3654..775ec70cf7aec 100644 --- a/test/IDE/complete_crashes.swift +++ b/test/IDE/complete_crashes.swift @@ -165,7 +165,7 @@ func rdar22834017() { Foo(#^RDAR_22834017^#) } // RDAR_22834017: Begin completions, 1 items -// RDAR_22834017: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#a: <>#}, {#b: <>#}, {#c: <>#}[')'][#Foo#]; +// RDAR_22834017: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#a: _#}, {#b: _#}, {#c: _#}[')'][#Foo#]; // RDAR_22834017: End completions // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=RDAR_23173692 | %FileCheck %s -check-prefix=RDAR_23173692 diff --git a/test/IDE/complete_enum_unresolved_dot_argument_labels.swift b/test/IDE/complete_enum_unresolved_dot_argument_labels.swift index d489d07c5921a..5e10a859f42a9 100644 --- a/test/IDE/complete_enum_unresolved_dot_argument_labels.swift +++ b/test/IDE/complete_enum_unresolved_dot_argument_labels.swift @@ -11,7 +11,7 @@ func testInit() { var state = DragState.inactive state = .dragging(#^SIGNATURE^#) // SIGNATURE: Begin completions, 1 item - // SIGNATURE: Pattern/CurrModule/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#translationX: Int#}, {#translationY: Int#}[')'][#DragState#]; + // SIGNATURE: Pattern/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#translationX: Int#}, {#translationY: Int#}[')'][#DragState#]; // SIGNATURE: End completions state = .dragging(translationX: 2, #^ARGUMENT^#) @@ -21,7 +21,7 @@ func testInit() { state = .defaulted(#^DEFAULTED^#) // DEFAULTED: Begin completions, 1 items - // DEFAULTED: Pattern/CurrModule/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#x: Int#}, {#y: Int#}, {#z: Int#}, {#extra: Int#}[')'][#DragState#]; + // DEFAULTED: Pattern/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#x: Int#}, {#y: Int#}, {#z: Int#}, {#extra: Int#}[')'][#DragState#]; // DEFAULTED: End completions state = .defaulted(x: 1, #^DEFAULTEDARG^#) diff --git a/test/IDE/complete_in_result_builder.swift b/test/IDE/complete_in_result_builder.swift index b153b57245c59..41e0a9d5caa17 100644 --- a/test/IDE/complete_in_result_builder.swift +++ b/test/IDE/complete_in_result_builder.swift @@ -33,39 +33,42 @@ let MyConstantString = "MyConstant" let MyConstantBool = true func testGlobalLookup() { - @TupleBuilder var x1 { + @TupleBuilder var x1: String { #^GLOBAL_LOOKUP^# // GLOBAL_LOOKUP: Begin completions - // GLOBAL_LOOKUP: Decl[GlobalVar]/CurrModule: MyConstantString[#String#]; + // GLOBAL_LOOKUP: Decl[GlobalVar]/CurrModule/TypeRelation[Convertible]: MyConstantString[#String#]; // GLOBAL_LOOKUP: End completions } - @TupleBuilder var x2 { + @TupleBuilder var x2: String { if true { - #^GLOBAL_LOOKUP_IN_IF_BODY?check=GLOBAL_LOOKUP^# + #^GLOBAL_LOOKUP_IN_IF_BODY^# + // GLOBAL_LOOKUP_IN_IF_BODY: Begin completions + // GLOBAL_LOOKUP_IN_IF_BODY: Decl[GlobalVar]/CurrModule: MyConstantString[#String#]; + // GLOBAL_LOOKUP_IN_IF_BODY: End completions } } - @TupleBuilder var x3 { + @TupleBuilder var x3: String { if { - #^GLOBAL_LOOKUP_IN_IF_BODY_WITHOUT_CONDITION?check=GLOBAL_LOOKUP^# + #^GLOBAL_LOOKUP_IN_IF_BODY_WITHOUT_CONDITION?check=GLOBAL_LOOKUP_IN_IF_BODY^# } } - @TupleBuilder var x4 { + @TupleBuilder var x4: String { guard else { - #^GLOBAL_LOOKUP_IN_GUARD_BODY_WITHOUT_CONDITION?check=GLOBAL_LOOKUP^# + #^GLOBAL_LOOKUP_IN_GUARD_BODY_WITHOUT_CONDITION?check=GLOBAL_LOOKUP_IN_IF_BODY^# } } - @TupleBuilder var x5 { + @TupleBuilder var x5: String { "hello: \(#^GLOBAL_LOOKUP_IN_STRING_LITERAL^#)" // GLOBAL_LOOKUP_IN_STRING_LITERAL: Begin completions // GLOBAL_LOOKUP_IN_STRING_LITERAL: Decl[GlobalVar]/CurrModule/TypeRelation[Convertible]: MyConstantString[#String#]; // GLOBAL_LOOKUP_IN_STRING_LITERAL: End completions } - @TupleBuilder var x5 { + @TupleBuilder var x5: String { if #^GLOBAL_LOOKUP_IN_IF_CONDITION^# { // GLOBAL_LOOKUP_IN_IF_CONDITION: Begin completions // GLOBAL_LOOKUP_IN_IF_CONDITION: Decl[GlobalVar]/CurrModule/TypeRelation[Convertible]: MyConstantBool[#Bool#]; name=MyConstantBool @@ -75,21 +78,27 @@ func testGlobalLookup() { } func testStaticMemberLookup() { - @TupleBuilder var x1 { + @TupleBuilder var x1: String { StringFactory.#^COMPLETE_STATIC_MEMBER^# // COMPLETE_STATIC_MEMBER: Begin completions - // COMPLETE_STATIC_MEMBER: Decl[StaticMethod]/CurrNominal: makeString({#x: String#})[#String#]; + // COMPLETE_STATIC_MEMBER: Decl[StaticMethod]/CurrNominal/TypeRelation[Convertible]: makeString({#x: String#})[#String#]; // COMPLETE_STATIC_MEMBER: End completions } - @TupleBuilder var x2 { + @TupleBuilder var x2: String { if true { - StringFactory.#^COMPLETE_STATIC_MEMBER_IN_IF_BODY?check=COMPLETE_STATIC_MEMBER^# + StringFactory.#^COMPLETE_STATIC_MEMBER_IN_IF_BODY^# + // COMPLETE_STATIC_MEMBER_IN_IF_BODY: Begin completions + // COMPLETE_STATIC_MEMBER_IN_IF_BODY: Decl[StaticMethod]/CurrNominal: makeString({#x: String#})[#String#]; + // COMPLETE_STATIC_MEMBER_IN_IF_BODY: End completions } } - @TupleBuilder var x3 { - "hello: \(StringFactory.#^COMPLETE_STATIC_MEMBER_IN_STRING_LITERAL?check=COMPLETE_STATIC_MEMBER;xfail=rdar78015646^#)" + @TupleBuilder var x3: String { + "hello: \(StringFactory.#^COMPLETE_STATIC_MEMBER_IN_STRING_LITERAL^#)" + // COMPLETE_STATIC_MEMBER_IN_STRING_LITERAL: Begin completions + // COMPLETE_STATIC_MEMBER_IN_STRING_LITERAL: Decl[StaticMethod]/CurrNominal/TypeRelation[Convertible]: makeString({#x: String#})[#String#]; + // COMPLETE_STATIC_MEMBER_IN_STRING_LITERAL: End completions } } @@ -101,7 +110,7 @@ struct FooStruct { } func testPatternMatching() { - @TupleBuilder var x1 { + @TupleBuilder var x1: String { let x = Letters.b if case .#^COMPLETE_PATTERN_MATCHING_IN_IF?check=COMPLETE_CASE^# = x { // COMPLETE_CASE: Begin completions @@ -112,7 +121,7 @@ func testPatternMatching() { } } - @TupleBuilder var x2 { + @TupleBuilder var x2: String { let x = Letters.a switch x { case .#^COMPLETE_CASE_IN_SWITCH?check=COMPLETE_CASE^#: @@ -120,7 +129,7 @@ func testPatternMatching() { } } - @TupleBuilder var x3 { + @TupleBuilder var x3: String { let x: FooStruct? = FooStruct() guard case .#^GUARD_CASE_PATTERN_1?check=OPTIONAL_FOOSTRUCT^# = x {} // OPTIONAL_FOOSTRUCT: Begin completions, 2 items @@ -129,30 +138,30 @@ func testPatternMatching() { // OPTIONAL_FOOSTRUCT: End completions } - @TupleBuilder var x4 { + @TupleBuilder var x4: String { let x: FooStruct? = FooStruct() guard case .#^GUARD_CASE_PATTERN_2?check=OPTIONAL_FOOSTRUCT^#some() = x {} } } func testCompleteFunctionArgumentLabels() { - @TupleBuilder var x1 { + @TupleBuilder var x1: String { StringFactory.makeString(#^FUNCTION_ARGUMENT_LABEL^#) // FUNCTION_ARGUMENT_LABEL: Begin completions, 1 item - // FUNCTION_ARGUMENT_LABEL: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#x: String#}[')'][#String#]; + // FUNCTION_ARGUMENT_LABEL: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#x: String#}[')'][#String#]; // FUNCTION_ARGUMENT_LABEL: End completions } } func testCompleteFunctionArgument() { - @TupleBuilder var x1 { + @TupleBuilder var x1: String { StringFactory.makeString(x: #^ARGUMENT_LOOKUP^#) // ARGUMENT_LOOKUP: Begin completions // ARGUMENT_LOOKUP: Decl[GlobalVar]/CurrModule/TypeRelation[Convertible]: MyConstantString[#String#]; // ARGUMENT_LOOKUP: End completions } - @TupleBuilder var x2 { + @TupleBuilder var x2: String { if true { StringFactory.makeString(x: #^ARGUMENT_LOOKUP_IN_IF_BODY?check=ARGUMENT_LOOKUP^#) } @@ -164,7 +173,7 @@ func testCompleteErrorTypeInCatch() { case E1 case E2(Int32) } - @TupleBuilder var x1 { + @TupleBuilder var x1: String { do {} catch Error4.#^CATCH2^# } // CATCH2: Begin completions diff --git a/test/IDE/complete_init_inherited.swift b/test/IDE/complete_init_inherited.swift index d1088c1a42824..a2d057f8474af 100644 --- a/test/IDE/complete_init_inherited.swift +++ b/test/IDE/complete_init_inherited.swift @@ -78,7 +78,7 @@ class D : C { // TEST_D_PAREN: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#d: D#}[')'][#D#]; name=d: // TEST_D_PAREN-NEXT: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#int: Int#}[')'][#D#]; name=int: -// TEST_D_PAREN-NEXT: Decl[Constructor]/Super/Flair[ArgLabels]: ['(']{#c: C#}[')'][#C#]; name=c: +// TEST_D_PAREN-NEXT: Decl[Constructor]/Super/Flair[ArgLabels]: ['(']{#c: C#}[')'][#D#]; name=c: func testA() { A#^TEST_A^# @@ -110,6 +110,6 @@ func testR74233797() { // METATYPE_CONVERSION: Begin completions // METATYPE_CONVERSION-DAG: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#sub: Bool#}[')'][#R74233797Derived#]; // METATYPE_CONVERSION-DAG: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['('][')'][#R74233797Derived#]; -// METATYPE_CONVERSION-DAG: Decl[Constructor]/Super/Flair[ArgLabels]: ['(']{#(test): Bool#}[')'][#R74233797Base#]; +// METATYPE_CONVERSION-DAG: Decl[Constructor]/Super/Flair[ArgLabels]: ['(']{#(test): Bool#}[')'][#R74233797Derived#]; // METATYPE_CONVERSION: End completions } diff --git a/test/IDE/complete_result_builder.swift b/test/IDE/complete_result_builder.swift index 9038b18a904cb..93d12db537803 100644 --- a/test/IDE/complete_result_builder.swift +++ b/test/IDE/complete_result_builder.swift @@ -75,7 +75,7 @@ func testAcceptColorTagged(paramIntVal: Int, paramStringVal: String) { acceptColorTagged { color in paramIntVal.tag(#^IN_CLOSURE_COLOR_CONTEXT^#) // IN_CLOSURE_COLOR_CONTEXT: Begin completions -// IN_CLOSURE_COLOR_CONTEXT-DAG: Decl[LocalVar]/Local: color; name=color +// IN_CLOSURE_COLOR_CONTEXT-DAG: Decl[LocalVar]/Local: color[#Color#]; name=color // IN_CLOSURE_COLOR_CONTEXT-DAG: Decl[LocalVar]/Local: taggedValue[#Tagged#]; name=taggedValue // IN_CLOSURE_COLOR_CONTEXT-DAG: Decl[LocalVar]/Local: paramIntVal[#Int#]; name=paramIntVal // IN_CLOSURE_COLOR_CONTEXT-DAG: Decl[LocalVar]/Local: paramStringVal[#String#]; name=paramStringVal diff --git a/test/IDE/complete_vararg.swift b/test/IDE/complete_vararg.swift index 0332c743601fe..c98842da7cb1f 100644 --- a/test/IDE/complete_vararg.swift +++ b/test/IDE/complete_vararg.swift @@ -49,8 +49,10 @@ func testObjDot1() { // OBJ_DOT_1-DAG: Decl[InstanceMethod]/CurrNominal: method7({#`inout`: Int...#})[#Void#]{{; name=.+$}} // OBJ_DOT_1: End completions -func testFreeFunc() { +func testFreeFunc1() { freeFunc1(#^FREE_FUNC_1^# +} +func testFreeFunc2() { freeFunc2(#^FREE_FUNC_2^# } // FREE_FUNC_1: Begin completions, 1 items @@ -67,8 +69,10 @@ func testInit() { // INIT_1: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#x: Int...#}[')'][#C#]{{; name=.+$}} // INIT_1: End completions -func testMethod() { +func testMethod1() { obj.method1(#^METHOD_1^# +} +func testMethod2() { obj.method2(#^METHOD_2^# } // METHOD_1: Begin completions, 1 items From 0c75e3b892cb2a27a533ad7471c9e8578cd9e2fb Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 13 Apr 2022 11:50:15 +0200 Subject: [PATCH 8/8] [CodeCompletion] Support type checking attributes even if they are not part of the AST MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The code completion might occur inside an attriubte that isn’t part of the AST because it’s missing a `VarDecl` that it could be attached to. In these cases, record the `CustomAttr` and type check it standalone, pretending it was part of a `DeclContext`. --- include/swift/AST/ASTNode.h | 4 + include/swift/AST/TypeCheckRequests.h | 73 +++++++++++++- include/swift/AST/TypeCheckerTypeIDZone.def | 2 +- include/swift/Parse/CodeCompletionCallbacks.h | 5 + include/swift/Sema/IDETypeChecking.h | 14 ++- lib/AST/TypeCheckRequests.cpp | 8 ++ lib/IDE/CodeCompletion.cpp | 78 ++++++++------- lib/IDE/ConformingMethodList.cpp | 3 +- lib/IDE/ExprContextAnalysis.cpp | 58 +++++------ lib/IDE/ExprContextAnalysis.h | 4 +- lib/IDE/TypeContextInfo.cpp | 3 +- lib/Parse/ParseDecl.cpp | 20 +++- lib/Sema/TypeCheckStmt.cpp | 99 +++++++++++++------ lib/Sema/TypeChecker.cpp | 16 ++- test/IDE/complete_attributes.swift | 4 +- ...complete_property_delegate_attribute.swift | 17 ++-- 16 files changed, 284 insertions(+), 124 deletions(-) diff --git a/include/swift/AST/ASTNode.h b/include/swift/AST/ASTNode.h index a10ba88de459b..cf642f5f01326 100644 --- a/include/swift/AST/ASTNode.h +++ b/include/swift/AST/ASTNode.h @@ -84,6 +84,10 @@ namespace swift { V.Val = decltype(V.Val)::getFromOpaqueValue(VP); return V; } + + friend llvm::hash_code hash_value(ASTNode N) { + return llvm::hash_value(N.getOpaqueValue()); + } }; } // namespace swift diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index c2b9184acadb6..94bf8d4fa565b 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -18,6 +18,7 @@ #include "swift/AST/ActorIsolation.h" #include "swift/AST/AnyFunctionRef.h" +#include "swift/AST/ASTNode.h" #include "swift/AST/ASTTypeIDs.h" #include "swift/AST/Effects.h" #include "swift/AST/GenericParamList.h" @@ -1544,12 +1545,78 @@ class TypeCheckFunctionBodyRequest readDependencySource(const evaluator::DependencyRecorder &) const; }; +/// Describes the context in which the AST node to type check in a +/// \c TypeCheckASTNodeAtLocRequest should be searched. This can be either of +/// two cases: +/// 1. A \c DeclContext that contains the node representing the location to +/// type check +/// 2. If the node that should be type checked that might not be part of the +/// AST (e.g. because it is a dangling property attribute), an \c ASTNode +/// that contains the location to type check in together with a DeclContext +/// in which we should pretend that node occurs. +class TypeCheckASTNodeAtLocContext { + DeclContext *DC; + ASTNode Node; + + /// Memberwise initializer + TypeCheckASTNodeAtLocContext(DeclContext *DC, ASTNode Node) + : DC(DC), Node(Node) { + assert(DC != nullptr); + } + +public: + static TypeCheckASTNodeAtLocContext declContext(DeclContext *DC) { + return TypeCheckASTNodeAtLocContext(DC, /*Node=*/nullptr); + } + + static TypeCheckASTNodeAtLocContext node(DeclContext *DC, ASTNode Node) { + assert(!Node.isNull()); + return TypeCheckASTNodeAtLocContext(DC, Node); + } + + DeclContext *getDeclContext() const { return DC; } + + bool isForUnattachedNode() const { return !Node.isNull(); } + + ASTNode getUnattachedNode() const { + assert(isForUnattachedNode()); + return Node; + } + + ASTNode &getUnattachedNode() { + assert(isForUnattachedNode()); + return Node; + } + + friend llvm::hash_code hash_value(const TypeCheckASTNodeAtLocContext &ctx) { + return llvm::hash_combine(ctx.DC, ctx.Node); + } + + friend bool operator==(const TypeCheckASTNodeAtLocContext &lhs, + const TypeCheckASTNodeAtLocContext &rhs) { + return lhs.DC == rhs.DC && lhs.Node == rhs.Node; + } + + friend bool operator!=(const TypeCheckASTNodeAtLocContext &lhs, + const TypeCheckASTNodeAtLocContext &rhs) { + return !(lhs == rhs); + } + + friend SourceLoc + extractNearestSourceLoc(const TypeCheckASTNodeAtLocContext &ctx) { + return extractNearestSourceLoc(ctx.DC); + } +}; + +void simple_display(llvm::raw_ostream &out, + const TypeCheckASTNodeAtLocContext &ctx); + /// Request to typecheck a function body element at the given source location. /// /// Produces true if an error occurred, false otherwise. class TypeCheckASTNodeAtLocRequest : public SimpleRequest { public: using SimpleRequest::SimpleRequest; @@ -1558,7 +1625,8 @@ class TypeCheckASTNodeAtLocRequest friend SimpleRequest; // Evaluation. - bool evaluate(Evaluator &evaluator, DeclContext *DC, SourceLoc Loc) const; + bool evaluate(Evaluator &evaluator, TypeCheckASTNodeAtLocContext, + SourceLoc Loc) const; }; /// Request to obtain a list of stored properties in a nominal type. @@ -3605,6 +3673,7 @@ class GetSourceFileAsyncNode bool isCached() const { return true; } }; +void simple_display(llvm::raw_ostream &out, ASTNode node); void simple_display(llvm::raw_ostream &out, Type value); void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR); void simple_display(llvm::raw_ostream &out, ImplicitMemberAction action); diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 4ba53580348ed..ec1766664bb17 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -345,7 +345,7 @@ SWIFT_REQUEST(TypeChecker, TypeCheckFunctionBodyRequest, BraceStmt *(AbstractFunctionDecl *), SeparatelyCached, NoLocationInfo) SWIFT_REQUEST(TypeChecker, TypeCheckASTNodeAtLocRequest, - bool(DeclContext *, SourceLoc), + bool(TypeCheckASTNodeAtLocContext, SourceLoc), Uncached, NoLocationInfo) SWIFT_REQUEST(TypeChecker, UnderlyingTypeRequest, Type(TypeAliasDecl *), SeparatelyCached, NoLocationInfo) diff --git a/include/swift/Parse/CodeCompletionCallbacks.h b/include/swift/Parse/CodeCompletionCallbacks.h index ccc55319c5529..689f0075c1ecc 100644 --- a/include/swift/Parse/CodeCompletionCallbacks.h +++ b/include/swift/Parse/CodeCompletionCallbacks.h @@ -118,6 +118,11 @@ class CodeCompletionCallbacks { /// Set target decl for attribute if the CC token is in attribute of the decl. virtual void setAttrTargetDeclKind(Optional DK) {} + /// Set that the code completion token occurred in a custom attribute. This + /// allows us to type check the custom attribute even if it is not attached to + /// the AST, e.g. because there is no var declaration following it. + virtual void setCompletingInAttribute(CustomAttr *Attr){}; + /// Complete expr-dot after we have consumed the dot. virtual void completeDotExpr(CodeCompletionExpr *E, SourceLoc DotLoc) {}; diff --git a/include/swift/Sema/IDETypeChecking.h b/include/swift/Sema/IDETypeChecking.h index 750aa5ef50c3b..80485229e81f0 100644 --- a/include/swift/Sema/IDETypeChecking.h +++ b/include/swift/Sema/IDETypeChecking.h @@ -19,9 +19,11 @@ #ifndef SWIFT_SEMA_IDETYPECHECKING_H #define SWIFT_SEMA_IDETYPECHECKING_H +#include "swift/AST/ASTNode.h" #include "swift/AST/Identifier.h" -#include "swift/Basic/SourceLoc.h" +#include "swift/AST/TypeCheckRequests.h" #include "swift/Basic/OptionSet.h" +#include "swift/Basic/SourceLoc.h" #include #include @@ -144,8 +146,9 @@ namespace swift { /// Typecheck the given expression. bool typeCheckExpression(DeclContext *DC, Expr *&parsedExpr); - /// Type check a function body element which is at \p TagetLoc . - bool typeCheckASTNodeAtLoc(DeclContext *DC, SourceLoc TargetLoc); + /// Type check a function body element which is at \p TagetLoc. + bool typeCheckASTNodeAtLoc(TypeCheckASTNodeAtLocContext TypeCheckCtx, + SourceLoc TargetLoc); /// Thunk around \c TypeChecker::typeCheckForCodeCompletion to make it /// available to \c swift::ide. @@ -163,6 +166,11 @@ namespace swift { constraints::SolutionApplicationTarget &target, bool needsPrecheck, llvm::function_ref callback); + /// Thunk around \c TypeChecker::typeCheckASTNode to make it available to + /// \c swift::ide. + void typeCheckASTNode(ASTNode &node, DeclContext *DC, + bool LeaveBodyUnchecked = false); + LookupResult lookupSemanticMember(DeclContext *DC, Type ty, DeclName name); diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index ff76a46c05909..68d54b142711c 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -62,6 +62,14 @@ void swift::simple_display(llvm::raw_ostream &out, } } +void swift::simple_display(llvm::raw_ostream &out, ASTNode node) { + if (node) { + node.dump(out); + } else { + out << "null"; + } +} + void swift::simple_display(llvm::raw_ostream &out, Type type) { if (type) type.print(out); diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp index 8ba32825cbd31..51ab2297c166b 100644 --- a/lib/IDE/CodeCompletion.cpp +++ b/lib/IDE/CodeCompletion.cpp @@ -115,6 +115,12 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks { DeclContext *CurDeclContext = nullptr; DeclAttrKind AttrKind; + /// When the code completion token occurs in a custom attribute, the attribute + /// it occurs in. Used so we can complete inside the attribute even if it's + /// not attached to the AST, e.g. because there is no var decl it could be + /// attached to. + CustomAttr *AttrWithCompletion = nullptr; + /// In situations when \c SyntaxKind hints or determines /// completions, i.e. a precedence group attribute, this /// can be set and used to control the code completion scenario. @@ -227,6 +233,11 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks { AttTargetDK = DK; } + void setCompletingInAttribute(CustomAttr *Attr) override { + AttrWithCompletion = Attr; + CurDeclContext = P.CurDeclContext; + } + void completeDotExpr(CodeCompletionExpr *E, SourceLoc DotLoc) override; void completeStmtOrExpr(CodeCompletionExpr *E) override; void completePostfixExprBeginning(CodeCompletionExpr *E) override; @@ -1330,16 +1341,24 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) { ? ParsedExpr->getLoc() : CurDeclContext->getASTContext().SourceMgr.getCodeCompletionLoc(); - switch (Kind) { - case CompletionKind::DotExpr: { - assert(CodeCompleteTokenExpr); - assert(CurDeclContext); - - DotExprTypeCheckCompletionCallback Lookup(CodeCompleteTokenExpr, - CurDeclContext); + auto typeCheckWithLookup = [this, &CompletionLoc]( + TypeCheckCompletionCallback &Lookup) { llvm::SaveAndRestore CompletionCollector(Context.CompletionCallback, &Lookup); - typeCheckContextAt(CurDeclContext, CompletionLoc); + if (AttrWithCompletion) { + /// The attribute might not be attached to the AST if there is no var decl + /// it could be attached to. Type check it standalone. + ASTNode Call = CallExpr::create( + CurDeclContext->getASTContext(), AttrWithCompletion->getTypeExpr(), + AttrWithCompletion->getArgs(), /*implicit=*/true); + typeCheckContextAt( + TypeCheckASTNodeAtLocContext::node(CurDeclContext, Call), + CompletionLoc); + } else { + typeCheckContextAt( + TypeCheckASTNodeAtLocContext::declContext(CurDeclContext), + CompletionLoc); + } // This (hopefully) only happens in cases where the expression isn't // typechecked during normal compilation either (e.g. member completion in a @@ -1348,6 +1367,16 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) { // tooling in general though. if (!Lookup.gotCallback()) Lookup.fallbackTypeCheck(CurDeclContext); + }; + + switch (Kind) { + case CompletionKind::DotExpr: { + assert(CodeCompleteTokenExpr); + assert(CurDeclContext); + + DotExprTypeCheckCompletionCallback Lookup(CodeCompleteTokenExpr, + CurDeclContext); + typeCheckWithLookup(Lookup); addKeywords(CompletionContext.getResultSink(), MaybeFuncBody); @@ -1362,12 +1391,7 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) { UnresolvedMemberTypeCheckCompletionCallback Lookup(CodeCompleteTokenExpr, CurDeclContext); - llvm::SaveAndRestore - CompletionCollector(Context.CompletionCallback, &Lookup); - typeCheckContextAt(CurDeclContext, CompletionLoc); - - if (!Lookup.gotCallback()) - Lookup.fallbackTypeCheck(CurDeclContext); + typeCheckWithLookup(Lookup); addKeywords(CompletionContext.getResultSink(), MaybeFuncBody); Lookup.deliverResults(CurDeclContext, DotLoc, CompletionContext, Consumer); @@ -1382,7 +1406,7 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) { KeyPathTypeCheckCompletionCallback Lookup(KeyPath); llvm::SaveAndRestore CompletionCollector( Context.CompletionCallback, &Lookup); - typeCheckContextAt(CurDeclContext, CompletionLoc); + typeCheckWithLookup(Lookup); Lookup.deliverResults(CurDeclContext, DotLoc, CompletionContext, Consumer); return true; @@ -1395,11 +1419,7 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) { CurDeclContext); llvm::SaveAndRestore CompletionCollector( Context.CompletionCallback, &Lookup); - typeCheckContextAt(CurDeclContext, CompletionLoc); - - if (!Lookup.gotCallback()) { - Lookup.fallbackTypeCheck(CurDeclContext); - } + typeCheckWithLookup(Lookup); Lookup.deliverResults(ShouldCompleteCallPatternAfterParen, CompletionLoc, CurDeclContext, CompletionContext, Consumer); @@ -1426,13 +1446,7 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) { // need to have a TypeCheckCompletionCallback so we can call // deliverResults on it to deliver the keyword results from the completion // context's result sink to the consumer. - llvm::SaveAndRestore CompletionCollector( - Context.CompletionCallback, &Lookup); - typeCheckContextAt(CurDeclContext, CompletionLoc); - - if (!Lookup.gotCallback()) { - Lookup.fallbackTypeCheck(CurDeclContext); - } + typeCheckWithLookup(Lookup); } addKeywords(CompletionContext.getResultSink(), MaybeFuncBody); @@ -1447,13 +1461,7 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) { AfterPoundExprCompletion Lookup(CodeCompleteTokenExpr, CurDeclContext, ParentStmtKind); - llvm::SaveAndRestore CompletionCollector( - Context.CompletionCallback, &Lookup); - typeCheckContextAt(CurDeclContext, CompletionLoc); - - if (!Lookup.gotCallback()) { - Lookup.fallbackTypeCheck(CurDeclContext); - } + typeCheckWithLookup(Lookup); addKeywords(CompletionContext.getResultSink(), MaybeFuncBody); @@ -1523,7 +1531,7 @@ void CodeCompletionCallbacksImpl::doneParsing() { undoSingleExpressionReturn(CurDeclContext); typeCheckContextAt( - CurDeclContext, + TypeCheckASTNodeAtLocContext::declContext(CurDeclContext), ParsedExpr ? ParsedExpr->getLoc() : CurDeclContext->getASTContext().SourceMgr.getCodeCompletionLoc()); diff --git a/lib/IDE/ConformingMethodList.cpp b/lib/IDE/ConformingMethodList.cpp index 53ee4c645c623..6312f268d1dd5 100644 --- a/lib/IDE/ConformingMethodList.cpp +++ b/lib/IDE/ConformingMethodList.cpp @@ -68,7 +68,8 @@ void ConformingMethodListCallbacks::doneParsing() { if (!ParsedExpr) return; - typeCheckContextAt(CurDeclContext, ParsedExpr->getLoc()); + typeCheckContextAt(TypeCheckASTNodeAtLocContext::declContext(CurDeclContext), + ParsedExpr->getLoc()); Type T = ParsedExpr->getType(); diff --git a/lib/IDE/ExprContextAnalysis.cpp b/lib/IDE/ExprContextAnalysis.cpp index 58c96bb5211de..3cf1ebd62e882 100644 --- a/lib/IDE/ExprContextAnalysis.cpp +++ b/lib/IDE/ExprContextAnalysis.cpp @@ -42,39 +42,39 @@ using namespace ide; // typeCheckContextAt(DeclContext, SourceLoc) //===----------------------------------------------------------------------===// -void swift::ide::typeCheckContextAt(DeclContext *DC, SourceLoc Loc) { +void swift::ide::typeCheckContextAt(TypeCheckASTNodeAtLocContext TypeCheckCtx, + SourceLoc Loc) { // Make sure the extension has been bound. - { - // Even if the extension is invalid (e.g. nested in a function or another - // type), we want to know the "intended nominal" of the extension so that - // we can know the type of 'Self'. - SmallVector extensions; - for (auto typeCtx = DC->getInnermostTypeContext(); typeCtx != nullptr; - typeCtx = typeCtx->getParent()->getInnermostTypeContext()) { - if (auto *ext = dyn_cast(typeCtx)) - extensions.push_back(ext); - } - while (!extensions.empty()) { - extensions.back()->computeExtendedNominal(); - extensions.pop_back(); - } + auto DC = TypeCheckCtx.getDeclContext(); + // Even if the extension is invalid (e.g. nested in a function or another + // type), we want to know the "intended nominal" of the extension so that + // we can know the type of 'Self'. + SmallVector extensions; + for (auto typeCtx = DC->getInnermostTypeContext(); typeCtx != nullptr; + typeCtx = typeCtx->getParent()->getInnermostTypeContext()) { + if (auto *ext = dyn_cast(typeCtx)) + extensions.push_back(ext); + } + while (!extensions.empty()) { + extensions.back()->computeExtendedNominal(); + extensions.pop_back(); + } - // If the completion happens in the inheritance clause of the extension, - // 'DC' is the parent of the extension. We need to iterate the top level - // decls to find it. In theory, we don't need the extended nominal in the - // inheritance clause, but ASTScope lookup requires that. We don't care - // unless 'DC' is not 'SourceFile' because non-toplevel extensions are - // 'canNeverBeBound()' anyway. - if (auto *SF = dyn_cast(DC)) { - auto &SM = DC->getASTContext().SourceMgr; - for (auto *decl : SF->getTopLevelDecls()) - if (auto *ext = dyn_cast(decl)) - if (SM.rangeContainsTokenLoc(ext->getSourceRange(), Loc)) - ext->computeExtendedNominal(); - } + // If the completion happens in the inheritance clause of the extension, + // 'DC' is the parent of the extension. We need to iterate the top level + // decls to find it. In theory, we don't need the extended nominal in the + // inheritance clause, but ASTScope lookup requires that. We don't care + // unless 'DC' is not 'SourceFile' because non-toplevel extensions are + // 'canNeverBeBound()' anyway. + if (auto *SF = dyn_cast(DC)) { + auto &SM = DC->getASTContext().SourceMgr; + for (auto *decl : SF->getTopLevelDecls()) + if (auto *ext = dyn_cast(decl)) + if (SM.rangeContainsTokenLoc(ext->getSourceRange(), Loc)) + ext->computeExtendedNominal(); } - swift::typeCheckASTNodeAtLoc(DC, Loc); + swift::typeCheckASTNodeAtLoc(TypeCheckCtx, Loc); } //===----------------------------------------------------------------------===// diff --git a/lib/IDE/ExprContextAnalysis.h b/lib/IDE/ExprContextAnalysis.h index dd1ee6233dcff..215658ed50fce 100644 --- a/lib/IDE/ExprContextAnalysis.h +++ b/lib/IDE/ExprContextAnalysis.h @@ -14,6 +14,7 @@ #define SWIFT_IDE_EXPRCONTEXTANALYSIS_H #include "swift/AST/Type.h" +#include "swift/AST/TypeCheckRequests.h" #include "swift/AST/Types.h" #include "swift/Basic/LLVM.h" #include "swift/Basic/SourceLoc.h" @@ -29,7 +30,8 @@ enum class SemanticContextKind : uint8_t; /// Type check parent contexts of the given decl context, and the body of the /// given context until \c Loc if the context is a function body. -void typeCheckContextAt(DeclContext *DC, SourceLoc Loc); +void typeCheckContextAt(TypeCheckASTNodeAtLocContext TypeCheckCtx, + SourceLoc Loc); /// From \p DC, find and returns the outer most expression which source range is /// exact the same as \p TargetRange. Returns \c nullptr if not found. diff --git a/lib/IDE/TypeContextInfo.cpp b/lib/IDE/TypeContextInfo.cpp index 756dd5dafbf83..e012e41a95219 100644 --- a/lib/IDE/TypeContextInfo.cpp +++ b/lib/IDE/TypeContextInfo.cpp @@ -89,7 +89,8 @@ void ContextInfoCallbacks::doneParsing() { if (!ParsedExpr) return; - typeCheckContextAt(CurDeclContext, ParsedExpr->getLoc()); + typeCheckContextAt(TypeCheckASTNodeAtLocContext::declContext(CurDeclContext), + ParsedExpr->getLoc()); ExprContextInfo Info(CurDeclContext, ParsedExpr); diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index e8c43af987100..960419fa983f8 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -3109,15 +3109,22 @@ ParserResult Parser::parseCustomAttribute( ArgumentList *argList = nullptr; if (Tok.isFollowingLParen() && isCustomAttributeArgument()) { if (peekToken().is(tok::code_complete)) { - consumeToken(tok::l_paren); + auto lParenLoc = consumeToken(tok::l_paren); + auto typeE = new (Context) TypeExpr(type.get()); + auto CCE = new (Context) CodeCompletionExpr(Tok.getLoc()); if (CodeCompletion) { - auto typeE = new (Context) TypeExpr(type.get()); - auto CCE = new (Context) CodeCompletionExpr(Tok.getLoc()); CodeCompletion->completePostfixExprParen(typeE, CCE); } consumeToken(tok::code_complete); - skipUntil(tok::r_paren); - consumeIf(tok::r_paren); + skipUntilDeclStmtRBrace(tok::r_paren); + auto rParenLoc = PreviousLoc; + if (Tok.is(tok::r_paren)) { + rParenLoc = consumeToken(tok::r_paren); + } + + argList = ArgumentList::createParsed( + Context, lParenLoc, {Argument::unlabeled(CCE)}, rParenLoc, + /*trailingClosureIdx=*/None); status.setHasCodeCompletionAndIsError(); } else { // If we have no local context to parse the initial value into, create @@ -3147,6 +3154,9 @@ ParserResult Parser::parseCustomAttribute( auto *TE = new (Context) TypeExpr(type.get()); auto *customAttr = CustomAttr::create(Context, atLoc, TE, initContext, argList); + if (status.hasCodeCompletion() && CodeCompletion) { + CodeCompletion->setCompletingInAttribute(customAttr); + } return makeParserResult(status, customAttr); } diff --git a/lib/Sema/TypeCheckStmt.cpp b/lib/Sema/TypeCheckStmt.cpp index 90bd9e8f08630..51eb68cf8e610 100644 --- a/lib/Sema/TypeCheckStmt.cpp +++ b/lib/Sema/TypeCheckStmt.cpp @@ -1793,40 +1793,58 @@ static void checkClassConstructorBody(ClassDecl *classDecl, } } -bool TypeCheckASTNodeAtLocRequest::evaluate(Evaluator &evaluator, - DeclContext *DC, - SourceLoc Loc) const { - auto &ctx = DC->getASTContext(); +void swift::simple_display(llvm::raw_ostream &out, + const TypeCheckASTNodeAtLocContext &ctx) { + if (ctx.isForUnattachedNode()) { + llvm::errs() << "(unattached_node: "; + simple_display(out, ctx.getUnattachedNode()); + llvm::errs() << " decl_context: "; + simple_display(out, ctx.getDeclContext()); + llvm::errs() << ")"; + } else { + llvm::errs() << "(decl_context: "; + simple_display(out, ctx.getDeclContext()); + llvm::errs() << ")"; + } +} + +bool TypeCheckASTNodeAtLocRequest::evaluate( + Evaluator &evaluator, TypeCheckASTNodeAtLocContext typeCheckCtx, + SourceLoc Loc) const { + auto &ctx = typeCheckCtx.getDeclContext()->getASTContext(); assert(DiagnosticSuppression::isEnabled(ctx.Diags) && "Diagnosing and Single ASTNode type checknig don't mix"); - // Initializers aren't walked by ASTWalker and thus we don't find the context - // to type check using ASTNodeFinder. Also, initializers aren't representable - // by ASTNodes that can be type checked using typeCheckASTNode. - // Handle them specifically here. - if (auto *patternInit = dyn_cast(DC)) { - if (auto *PBD = patternInit->getBinding()) { - auto i = patternInit->getBindingIndex(); - PBD->getPattern(i)->forEachVariable( - [](VarDecl *VD) { (void)VD->getInterfaceType(); }); - if (PBD->getInit(i)) { - if (!PBD->isInitializerChecked(i)) { - typeCheckPatternBinding(PBD, i, - /*LeaveClosureBodyUnchecked=*/true); - return false; + if (!typeCheckCtx.isForUnattachedNode()) { + auto DC = typeCheckCtx.getDeclContext(); + // Initializers aren't walked by ASTWalker and thus we don't find the + // context to type check using ASTNodeFinder. Also, initializers aren't + // representable by ASTNodes that can be type checked using + // typeCheckASTNode. Handle them specifically here. + if (auto *patternInit = dyn_cast(DC)) { + if (auto *PBD = patternInit->getBinding()) { + auto i = patternInit->getBindingIndex(); + PBD->getPattern(i)->forEachVariable( + [](VarDecl *VD) { (void)VD->getInterfaceType(); }); + if (PBD->getInit(i)) { + if (!PBD->isInitializerChecked(i)) { + typeCheckPatternBinding(PBD, i, + /*LeaveClosureBodyUnchecked=*/true); + return false; + } } } - } - } else if (auto *defaultArg = dyn_cast(DC)) { - if (auto *AFD = dyn_cast(defaultArg->getParent())) { - auto *Param = AFD->getParameters()->get(defaultArg->getIndex()); - (void)Param->getTypeCheckedDefaultExpr(); - return false; - } - if (auto *SD = dyn_cast(defaultArg->getParent())) { - auto *Param = SD->getIndices()->get(defaultArg->getIndex()); - (void)Param->getTypeCheckedDefaultExpr(); - return false; + } else if (auto *defaultArg = dyn_cast(DC)) { + if (auto *AFD = dyn_cast(defaultArg->getParent())) { + auto *Param = AFD->getParameters()->get(defaultArg->getIndex()); + (void)Param->getTypeCheckedDefaultExpr(); + return false; + } + if (auto *SD = dyn_cast(defaultArg->getParent())) { + auto *Param = SD->getIndices()->get(defaultArg->getIndex()); + (void)Param->getTypeCheckedDefaultExpr(); + return false; + } } } @@ -1836,11 +1854,20 @@ bool TypeCheckASTNodeAtLocRequest::evaluate(Evaluator &evaluator, SourceManager &SM; SourceLoc Loc; ASTNode *FoundNode = nullptr; + + /// The innermost DeclContext that contains \c FoundNode. DeclContext *DC = nullptr; public: ASTNodeFinder(SourceManager &SM, SourceLoc Loc) : SM(SM), Loc(Loc) {} + /// Set an \c ASTNode and \c DeclContext to type check if we don't find a + /// more nested node. + void setInitialFind(ASTNode &FoundNode, DeclContext *DC) { + this->FoundNode = &FoundNode; + this->DC = DC; + } + bool isNull() const { return !FoundNode; } ASTNode &getRef() const { assert(FoundNode); @@ -1919,13 +1946,19 @@ bool TypeCheckASTNodeAtLocRequest::evaluate(Evaluator &evaluator, } } finder(ctx.SourceMgr, Loc); - DC->walkContext(finder); + if (typeCheckCtx.isForUnattachedNode()) { + finder.setInitialFind(typeCheckCtx.getUnattachedNode(), + typeCheckCtx.getDeclContext()); + typeCheckCtx.getUnattachedNode().walk(finder); + } else { + typeCheckCtx.getDeclContext()->walkContext(finder); + } // Nothing found at the location, or the decl context does not own the 'Loc'. if (finder.isNull()) return true; - DC = finder.getDeclContext(); + DeclContext *DC = finder.getDeclContext(); if (auto *AFD = dyn_cast(DC)) { if (AFD->isBodyTypeChecked()) @@ -1965,7 +1998,9 @@ bool TypeCheckASTNodeAtLocRequest::evaluate(Evaluator &evaluator, // signature first unless it has already been type checked. if (auto CE = dyn_cast(DC)) { if (CE->getBodyState() == ClosureExpr::BodyState::Parsed) { - swift::typeCheckASTNodeAtLoc(CE->getParent(), CE->getLoc()); + swift::typeCheckASTNodeAtLoc( + TypeCheckASTNodeAtLocContext::declContext(CE->getParent()), + CE->getLoc()); // We need the actor isolation of the closure to be set so that we can // annotate results that are on the same global actor. // Since we are evaluating TypeCheckASTNodeAtLocRequest for every closure diff --git a/lib/Sema/TypeChecker.cpp b/lib/Sema/TypeChecker.cpp index f366e90e8047d..997b159d4496c 100644 --- a/lib/Sema/TypeChecker.cpp +++ b/lib/Sema/TypeChecker.cpp @@ -480,12 +480,13 @@ void swift::typeCheckPatternBinding(PatternBindingDecl *PBD, /*patternType=*/Type(), options); } -bool swift::typeCheckASTNodeAtLoc(DeclContext *DC, SourceLoc TargetLoc) { - auto &Ctx = DC->getASTContext(); +bool swift::typeCheckASTNodeAtLoc(TypeCheckASTNodeAtLocContext TypeCheckCtx, + SourceLoc TargetLoc) { + auto &Ctx = TypeCheckCtx.getDeclContext()->getASTContext(); DiagnosticSuppression suppression(Ctx.Diags); - return !evaluateOrDefault(Ctx.evaluator, - TypeCheckASTNodeAtLocRequest{DC, TargetLoc}, - true); + return !evaluateOrDefault( + Ctx.evaluator, TypeCheckASTNodeAtLocRequest{TypeCheckCtx, TargetLoc}, + true); } bool swift::typeCheckForCodeCompletion( @@ -495,6 +496,11 @@ bool swift::typeCheckForCodeCompletion( callback); } +void swift::typeCheckASTNode(ASTNode &node, DeclContext *DC, + bool LeaveBodyUnchecked) { + return TypeChecker::typeCheckASTNode(node, DC, LeaveBodyUnchecked); +} + void TypeChecker::checkForForbiddenPrefix(ASTContext &C, DeclBaseName Name) { if (C.TypeCheckerOpts.DebugForbidTypecheckPrefix.empty()) return; diff --git a/test/IDE/complete_attributes.swift b/test/IDE/complete_attributes.swift index e7b5d203500f9..77afcff611d40 100644 --- a/test/IDE/complete_attributes.swift +++ b/test/IDE/complete_attributes.swift @@ -24,9 +24,9 @@ struct MyValue { // MEMBER_MyValue-DAG: Decl[StaticVar]/CurrNominal: val[#Int#]; // MEMBER_MyValue: End completions -class TestUknownDanglingAttr1 { +class TestUnkownDanglingAttr1 { @UknownAttr(arg: MyValue.#^ATTRARG_MEMBER^#) } -class TestUknownDanglingAttr2 { +class TestUnkownDanglingAttr2 { @UknownAttr(arg: { MyValue.#^ATTRARG_MEMBER_IN_CLOSURE^# }) } diff --git a/test/IDE/complete_property_delegate_attribute.swift b/test/IDE/complete_property_delegate_attribute.swift index 7e3adfd797574..5fa8f692b8d9b 100644 --- a/test/IDE/complete_property_delegate_attribute.swift +++ b/test/IDE/complete_property_delegate_attribute.swift @@ -1,7 +1,5 @@ -// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=AFTER_PAREN | %FileCheck %s -check-prefix=AFTER_PAREN -// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=ARG_MyEnum_NODOT | %FileCheck %s -check-prefix=ARG_MyEnum_NODOT -// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=ARG_MyEnum_DOT | %FileCheck %s -check-prefix=ARG_MyEnum_DOT -// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=ARG_MyEnum_NOBINDING | %FileCheck %s -check-prefix=ARG_MyEnum_NOBINDING +// RUN: %empty-directory(%t) +// RUN: %target-swift-ide-test -batch-code-completion -source-filename %s -filecheck %raw-FileCheck -completion-output-dir %t enum MyEnum { case east, west @@ -42,7 +40,12 @@ struct TestStruct { @MyStruct(arg1: MyEnum.#^ARG_MyEnum_NOBINDING^#) // ARG_MyEnum_NOBINDING: Begin completions -// ARG_MyEnum_NOBINDING-DAG: Decl[EnumElement]/CurrNominal: east[#MyEnum#]; -// ARG_MyEnum_NOBINDING-DAG: Decl[EnumElement]/CurrNominal: west[#MyEnum#]; -// ARG_MyEnum_NOBINDING: End completions +// ARG_MyEnum_NOBINDING-DAG: Decl[EnumElement]/CurrNominal/TypeRelation[Convertible]: east[#MyEnum#]; +// ARG_MyEnum_NOBINDING-DAG: Decl[EnumElement]/CurrNominal/TypeRelation[Convertible]: west[#MyEnum#]; +// ARG_MyEnum_NOBINDINaG: End completions + + // FIXME: No call patterns are suggested if we are completing in variable with multiple property wrappers (rdar://91480982) + func sync() {} + + @MyStruct(#^WITHOUT_VAR?check=AFTER_PAREN^# }