diff --git a/include/swift/IDE/PostfixCompletion.h b/include/swift/IDE/PostfixCompletion.h index 36d4292f1f9c5..f097f9f4d6b36 100644 --- a/include/swift/IDE/PostfixCompletion.h +++ b/include/swift/IDE/PostfixCompletion.h @@ -35,6 +35,11 @@ class PostfixCompletionCallback : public TypeCheckCompletionCallback { /// Whether the surrounding context is async and thus calling async /// functions is supported. bool IsInAsyncContext; + + /// Actor isolations that were determined during constraint solving but that + /// haven't been saved to the AST. + llvm::DenseMap + ClosureActorIsolations; }; CodeCompletionExpr *CompletionExpr; diff --git a/lib/IDE/ArgumentCompletion.cpp b/lib/IDE/ArgumentCompletion.cpp index 65a4e0f5e020d..270ca59114c72 100644 --- a/lib/IDE/ArgumentCompletion.cpp +++ b/lib/IDE/ArgumentCompletion.cpp @@ -88,6 +88,15 @@ void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) { while (ParentCall && ParentCall->getArgs() == nullptr) { ParentCall = CS.getParentExpr(ParentCall); } + if (auto TV = S.getType(CompletionExpr)->getAs()) { + auto Locator = TV->getImpl().getLocator(); + if (Locator->isLastElement()) { + // The code completion token is inside a pattern, which got rewritten from + // a call by ResolvePattern. Thus, we aren't actually inside a call. + // Rest 'ParentCall' to nullptr to reflect that. + ParentCall = nullptr; + } + } if (!ParentCall || ParentCall == CompletionExpr) { // We might not have a call that contains the code completion expression if diff --git a/lib/IDE/PostfixCompletion.cpp b/lib/IDE/PostfixCompletion.cpp index 25988edf5b4fc..67c9602826734 100644 --- a/lib/IDE/PostfixCompletion.cpp +++ b/lib/IDE/PostfixCompletion.cpp @@ -44,6 +44,32 @@ void PostfixCompletionCallback::fallbackTypeCheck(DeclContext *DC) { [&](const Solution &S) { sawSolution(S); }); } +static ClosureActorIsolation +getClosureActorIsolation(const Solution &S, AbstractClosureExpr *ACE) { + auto getType = [&S](Expr *E) -> Type { + // Prefer the contextual type of the closure because it might be 'weaker' + // than the type determined for the closure by the constraints system. E.g. + // the contextual type might have a global actor attribute but because no + // methods from that global actor are called in the closure, the closure has + // a non-actor type. + auto target = S.solutionApplicationTargets.find(dyn_cast(E)); + if (target != S.solutionApplicationTargets.end()) { + if (auto Ty = target->second.getClosureContextualType()) { + return Ty; + } + } + if (!S.hasType(E)) { + return Type(); + } + return getTypeForCompletion(S, E); + }; + auto getClosureActorIsolationThunk = [&S](AbstractClosureExpr *ACE) { + return getClosureActorIsolation(S, ACE); + }; + return determineClosureActorIsolation(ACE, getType, + getClosureActorIsolationThunk); +} + void PostfixCompletionCallback::sawSolutionImpl( const constraints::Solution &S) { auto &CS = S.getConstraintSystem(); @@ -60,7 +86,7 @@ void PostfixCompletionCallback::sawSolutionImpl( auto *Locator = CS.getConstraintLocator(SemanticExpr); Type ExpectedTy = getTypeForCompletion(S, CompletionExpr); Expr *ParentExpr = CS.getParentExpr(CompletionExpr); - if (!ParentExpr) + if (!ParentExpr && !ExpectedTy) ExpectedTy = CS.getContextualType(CompletionExpr, /*forConstraint=*/false); auto *CalleeLocator = S.getCalleeLocator(Locator); @@ -68,21 +94,37 @@ void PostfixCompletionCallback::sawSolutionImpl( if (auto SelectedOverload = S.getOverloadChoiceIfAvailable(CalleeLocator)) ReferencedDecl = SelectedOverload->choice.getDeclOrNull(); + llvm::DenseMap + ClosureActorIsolations; bool IsAsync = isContextAsync(S, DC); + for (auto SAT : S.solutionApplicationTargets) { + if (auto ACE = getAsExpr(SAT.second.getAsASTNode())) { + ClosureActorIsolations[ACE] = getClosureActorIsolation(S, ACE); + } + } auto Key = std::make_pair(BaseTy, ReferencedDecl); auto Ret = BaseToSolutionIdx.insert({Key, Results.size()}); if (Ret.second) { bool ISDMT = S.isStaticallyDerivedMetatype(ParsedExpr); bool ImplicitReturn = isImplicitSingleExpressionReturn(CS, CompletionExpr); - bool DisallowVoid = ExpectedTy - ? !ExpectedTy->isVoid() - : !ParentExpr && CS.getContextualTypePurpose( - CompletionExpr) != CTP_Unused; + bool DisallowVoid = false; + DisallowVoid |= ExpectedTy && !ExpectedTy->isVoid(); + DisallowVoid |= !ParentExpr && + CS.getContextualTypePurpose(CompletionExpr) != CTP_Unused; + for (auto SAT : S.solutionApplicationTargets) { + if (DisallowVoid) { + // DisallowVoid is already set. No need to iterate further. + break; + } + if (SAT.second.getAsExpr() == CompletionExpr) { + DisallowVoid |= SAT.second.getExprContextualTypePurpose() != CTP_Unused; + } + } Results.push_back({BaseTy, ReferencedDecl, /*ExpectedTypes=*/{}, DisallowVoid, ISDMT, - ImplicitReturn, IsAsync}); + ImplicitReturn, IsAsync, ClosureActorIsolations}); if (ExpectedTy) { Results.back().ExpectedTypes.push_back(ExpectedTy); } @@ -122,6 +164,7 @@ void PostfixCompletionCallback::deliverResults( Lookup.shouldCheckForDuplicates(Results.size() > 1); for (auto &Result : Results) { Lookup.setCanCurrDeclContextHandleAsync(Result.IsInAsyncContext); + Lookup.setClosureActorIsolations(Result.ClosureActorIsolations); Lookup.setIsStaticMetatype(Result.BaseIsStaticMetaType); Lookup.getPostfixKeywordCompletions(Result.BaseTy, BaseExpr); Lookup.setExpectedTypes(Result.ExpectedTypes, diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 82d5bd869bab6..9202fbc795d80 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -1175,6 +1175,7 @@ Parser::parseExprPostfixSuffix(ParserResult Result, bool isExprBasic, // [.foo(), .bar()] // '.bar()' is probably not a part of the inserting element. Moreover, // having suffixes doesn't help type inference in any way. + return Result; } diff --git a/lib/Parse/ParsePattern.cpp b/lib/Parse/ParsePattern.cpp index a824e521675f9..cde02ff832575 100644 --- a/lib/Parse/ParsePattern.cpp +++ b/lib/Parse/ParsePattern.cpp @@ -1303,6 +1303,19 @@ ParserResult Parser::parseMatchingPattern(bool isExprBasic) { if (subExpr.isNull()) return status; + if (isa(subExpr.get()) && Tok.isFollowingLParen()) { + // We are in the case like the following of parsing a pattern with the code + // completion token as base and associated value matches: + // #^COMPLETE^#(let a) + // We will have not consumed the `(let a)` in `parseExprPostfixSuffix` + // because usually suffixes don't influence the code completion's type and + // the suffix might be unrelated. But the trailing `(let a)` that is left + // prevents us from forming a valid pattern. + // Consume and discard the `(let a)`, which just leaves us with the base + // of the pattern. + (void)parseExprCallSuffix(subExpr, isExprBasic); + } + // The most common case here is to parse something that was a lexically // obvious pattern, which will come back wrapped in an immediate // UnresolvedPatternExpr. Transform this now to simplify later code. diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index e666ec45240cf..74b24d25d40f3 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -2393,6 +2393,12 @@ namespace { // function, to set the type of the pattern. auto setType = [&](Type type) { CS.setType(pattern, type); + if (auto PE = dyn_cast(pattern)) { + // Set the type of the pattern's sub-expression as well, so code + // completion can retrieve the expression's type in case it is a code + // completion token. + CS.setType(PE->getSubExpr(), type); + } return type; }; diff --git a/lib/Sema/CSSyntacticElement.cpp b/lib/Sema/CSSyntacticElement.cpp index beff805b7dc84..d6a43d84b0d84 100644 --- a/lib/Sema/CSSyntacticElement.cpp +++ b/lib/Sema/CSSyntacticElement.cpp @@ -1030,6 +1030,23 @@ class SyntacticElementConstraintGenerator SmallVector elements; for (auto element : braceStmt->getElements()) { + if (cs.isForCodeCompletion() && + !cs.containsIDEInspectionTarget(element)) { + // Statements and expressions can't influence the expresion that + // contains the code completion token. To improve performance, skip + // type checking them entirely. + if (element.is() && !element.isExpr(ExprKind::TypeJoin)) { + // Type join expressions are not really pure expressions, they kind of + // declare new type variables and are important to a result builder's + // structure. Don't skip them. + continue; + } else if (element.is() && !element.isStmt(StmtKind::Guard)) { + // Guard statements might define variables that are used in the code + // completion expression. Don't skip them. + continue; + } + } + if (auto *decl = element.dyn_cast()) { if (auto *PDB = dyn_cast(decl)) { visitPatternBinding(PDB, elements); diff --git a/lib/Sema/TypeCheckCodeCompletion.cpp b/lib/Sema/TypeCheckCodeCompletion.cpp index 46542656b880e..5caac6c3bf1f7 100644 --- a/lib/Sema/TypeCheckCodeCompletion.cpp +++ b/lib/Sema/TypeCheckCodeCompletion.cpp @@ -307,7 +307,9 @@ getTypeOfExpressionWithoutApplying(Expr *&expr, DeclContext *dc, ConstraintSystemOptions options; options |= ConstraintSystemFlags::SuppressDiagnostics; - options |= ConstraintSystemFlags::LeaveClosureBodyUnchecked; + if (!Context.CompletionCallback) { + options |= ConstraintSystemFlags::LeaveClosureBodyUnchecked; + } // Construct a constraint system from this expression. ConstraintSystem cs(dc, options); @@ -400,7 +402,6 @@ getTypeOfCompletionOperatorImpl(DeclContext *DC, Expr *expr, ConstraintSystemOptions options; options |= ConstraintSystemFlags::SuppressDiagnostics; - options |= ConstraintSystemFlags::LeaveClosureBodyUnchecked; // Construct a constraint system from this expression. ConstraintSystem CS(DC, options); @@ -612,8 +613,9 @@ bool TypeChecker::typeCheckForCodeCompletion( options |= ConstraintSystemFlags::AllowFixes; options |= ConstraintSystemFlags::SuppressDiagnostics; options |= ConstraintSystemFlags::ForCodeCompletion; - options |= ConstraintSystemFlags::LeaveClosureBodyUnchecked; - + if (!Context.CompletionCallback) { + options |= ConstraintSystemFlags::LeaveClosureBodyUnchecked; + } ConstraintSystem cs(DC, options); diff --git a/lib/Sema/TypeCheckStmt.cpp b/lib/Sema/TypeCheckStmt.cpp index 7fc38d7ed5c21..755d8e06d8ff1 100644 --- a/lib/Sema/TypeCheckStmt.cpp +++ b/lib/Sema/TypeCheckStmt.cpp @@ -2393,6 +2393,8 @@ bool TypeCheckASTNodeAtLocRequest::evaluate( } } + bool LeaveBodyUnchecked = !ctx.CompletionCallback; + // The enclosing closure might be a single expression closure or a function // builder closure. In such cases, the body elements are type checked with // the closure itself. So we need to try type checking the enclosing closure @@ -2416,13 +2418,17 @@ bool TypeCheckASTNodeAtLocRequest::evaluate( auto ActorIsolation = determineClosureActorIsolation( CE, __Expr_getType, __AbstractClosureExpr_getActorIsolation); CE->setActorIsolation(ActorIsolation); + if (!LeaveBodyUnchecked) { + // Type checking the parent closure also type checked this node. + // Nothing to do anymore. + return false; + } if (CE->getBodyState() != ClosureExpr::BodyState::ReadyForTypeChecking) return false; } } - TypeChecker::typeCheckASTNode(finder.getRef(), DC, - /*LeaveBodyUnchecked=*/true); + TypeChecker::typeCheckASTNode(finder.getRef(), DC, LeaveBodyUnchecked); return false; } diff --git a/test/IDE/complete_in_closures.swift b/test/IDE/complete_in_closures.swift index bc26890a922aa..fe78458beaa41 100644 --- a/test/IDE/complete_in_closures.swift +++ b/test/IDE/complete_in_closures.swift @@ -104,7 +104,7 @@ struct NestedStructWithClosureMember1 { struct StructWithClosureMemberAndLocal { var c = { var x = 0 - #^DELAYED_10?check=WITH_GLOBAL_DECLS_AND_LOCAL1;xfail=https://github.com/apple/swift/issues/58273^# + #^DELAYED_10?check=WITH_GLOBAL_DECLS_AND_LOCAL1^# } } @@ -416,3 +416,83 @@ func testClosureInPatternBindingInit() { // CLOSURE_IN_PATTERN_BINDING: End completion } + +func testSwitchInClosure() { + func executeClosure(closure: () -> Void) {} + + struct Boredom { + static func doNothing() {} + } + + enum MyEnum { + case first + case second + } + + let item: MyEnum? = nil + executeClosure(closure: { + switch item { + case .#^SWITCH_IN_CLOSURE^#first: + break + case .second: + Boredom.doNothing() + } + }) + +// SWITCH_IN_CLOSURE: Begin completions +// SWITCH_IN_CLOSURE-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: first[#MyEnum#]; +// SWITCH_IN_CLOSURE-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: second[#MyEnum#]; +// SWITCH_IN_CLOSURE: End completions +} + +func testSwitchWithAssociatedValueInClosure() { + func executeClosure(closure: () -> Void) {} + + struct Boredom { + static func doNothing() {} + } + + enum MyEnum { + case first(String) + } + + let item: MyEnum? = nil + executeClosure(closure: { + switch item { + case .#^SWITCH_WITH_ASSOC_IN_CLOSURE^#first(_): + break + } + }) + +// SWITCH_WITH_ASSOC_IN_CLOSURE: Begin completions +// SWITCH_WITH_ASSOC_IN_CLOSURE-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: first({#String#})[#MyEnum#]; +// SWITCH_WITH_ASSOC_IN_CLOSURE: End completions +} + +func testCompleteInMatchOfAssociatedValueInSwitchCase() { + func testSwitchWithAssociatedValueInClosure() { + func executeClosure(closure: () -> Void) {} + + struct Boredom { + static func doNothing() {} + } + + enum MyEnum { + case first(Bool, String) + } + + let item: MyEnum? = nil + let str = "hi" + executeClosure(closure: { + switch item { + case .first(true, #^IN_ASSOC_OF_CASE_IN_CLOSURE^#str): + break + } + }) + +// IN_ASSOC_OF_CASE_IN_CLOSURE: Begin completions +// IN_ASSOC_OF_CASE_IN_CLOSURE-DAG: Decl[LocalVar]/Local: str[#String#]; name=str +// IN_ASSOC_OF_CASE_IN_CLOSURE: End completions +} + +} diff --git a/test/IDE/complete_in_result_builder.swift b/test/IDE/complete_in_result_builder.swift index 2312d098a7f8b..aaf92a0fe353c 100644 --- a/test/IDE/complete_in_result_builder.swift +++ b/test/IDE/complete_in_result_builder.swift @@ -274,3 +274,94 @@ func testAmbiguousInResultBuilder() { // AMBIGUOUS_IN_RESULT_BUILDER: End completions } } + +func testCompleteGlobalInResultBuilderIf() { + func buildView(@ViewBuilder2 content: () -> MyView) {} + + @resultBuilder public struct ViewBuilder2 { + static func buildBlock() -> MyView { fatalError() } + static func buildBlock(_ content: MyView) -> MyView { fatalError() } + static func buildIf(_ content: MyView?) -> MyView? { fatalError() } + static func buildEither(first: MyView) -> MyView { fatalError() } + static func buildEither(second: MyView) -> MyView { fatalError() } + } + + struct MyView {} + + func test() { + buildView { + if true { + MyView() + } else { + #^GLOBAL_IN_RESULT_BUILDER_IF^# + } + } + } + + // GLOBAL_IN_RESULT_BUILDER_IF: Begin completions + // GLOBAL_IN_RESULT_BUILDER_IF-DAG: Decl[Struct]/Local/TypeRelation[Convertible]: MyView[#MyView#]; name=MyView + // GLOBAL_IN_RESULT_BUILDER_IF: End completions +} + +func testInStringLiteralInResultBuilder() { + func buildResult(@MyResultBuilder content: () -> Content) {} + + @resultBuilder + struct MyResultBuilder { + static func buildBlock(_ components: String) -> String { + components + } + } + + struct Foo { + var bar: Int + } + + func withClosure(_ x: () -> Bool) -> String { return "" } + + func test(foo: Foo) { + buildResult { + "\(withClosure { foo.#^IN_STRING_LITERAL_IN_RESULT_BUILDER^# })" + } + } +// IN_STRING_LITERAL_IN_RESULT_BUILDER: Begin completions, 2 items +// IN_STRING_LITERAL_IN_RESULT_BUILDER-DAG: Keyword[self]/CurrNominal: self[#Foo#]; name=self +// IN_STRING_LITERAL_IN_RESULT_BUILDER-DAG: Decl[InstanceVar]/CurrNominal: bar[#Int#]; name=bar +// IN_STRING_LITERAL_IN_RESULT_BUILDER: End completions +} + +func testSwitchInResultBuilder() { + @resultBuilder + enum ReducerBuilder2 { + static func buildBlock(_ r: Reduce2) -> Reduce2 { r } + static func buildBlock(_ r0: Reduce2, _ r1: Reduce2) -> Reduce2 { r0 } + static func buildExpression(_ r: Reduce2) -> Reduce2 { r } + } + + enum Action { + case alertDismissed + } + + struct Reduce2 { + init() {} + + init(_ reduce: (Action) -> Int) {} + } + + struct Login2 { + @ReducerBuilder2 + var body: Reduce2 { + Reduce2() + Reduce2 { action in + switch action { + case .#^SWITCH_IN_RESULT_BUILDER^# alertDismissed: + return 0 + } + } + } + } +// SWITCH_IN_RESULT_BUILDER: Begin completions, 2 items +// SWITCH_IN_RESULT_BUILDER-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: alertDismissed[#Action#]; +// SWITCH_IN_RESULT_BUILDER-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): Action#})[#(into: inout Hasher) -> Void#]; +// SWITCH_IN_RESULT_BUILDER: End completions +} diff --git a/test/IDE/complete_skipbody.swift b/test/IDE/complete_skipbody.swift index 996e2db284fb6..44c6f3ef3dcd7 100644 --- a/test/IDE/complete_skipbody.swift +++ b/test/IDE/complete_skipbody.swift @@ -45,8 +45,6 @@ func test(valueOptOpt: MyStruct??) { case let x where x < 2: let unrelated3 = FORBIDDEN_Struct() _ = { xx in - let unrelated4 = FORBIDDEN_Struct() - if xx == localFunc(value.#^FUNCTIONBODY^#) { let unrelated5 = FORBIDDEN_Struct() return 1 diff --git a/test/IDE/complete_unresolved_members.swift b/test/IDE/complete_unresolved_members.swift index 7ca0dc99d3871..10468d04b5dd0 100644 --- a/test/IDE/complete_unresolved_members.swift +++ b/test/IDE/complete_unresolved_members.swift @@ -732,3 +732,14 @@ func testSameType() { // SUGAR_TYPE: End completions } +struct DispatchTime { + static func now() -> DispatchTime { .init() } +} +func +(_ x: DispatchTime, _ y: Double) -> DispatchTime { return x } + +let _: DispatchTime = .#^UNRESOLVED_FUNCTION_CALL^#now() + 0.2 + +// UNRESOLVED_FUNCTION_CALL: Begin completions, 2 items +// UNRESOLVED_FUNCTION_CALL-DAG: Decl[StaticMethod]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: now()[#DispatchTime#]; +// UNRESOLVED_FUNCTION_CALL-DAG: Decl[Constructor]/CurrNominal/TypeRelation[Convertible]: init()[#DispatchTime#]; +// UNRESOLVED_FUNCTION_CALL: End completions diff --git a/test/IDE/complete_with_trailing_closure.swift b/test/IDE/complete_with_trailing_closure.swift index 0ab752b7d7ba3..b8675fddda235 100644 --- a/test/IDE/complete_with_trailing_closure.swift +++ b/test/IDE/complete_with_trailing_closure.swift @@ -16,10 +16,20 @@ func sink(receiveValue: (MyArray) -> Void) { func foo() { sink { items in let a = items.#^COMPLETE_WITHOUT_SPACE?check=CHECK^#map{ $0.content } + } + sink { items in let b = items.#^COMPLETE_WITH_SPACE?check=CHECK^# map{ $0.content } + } + sink { items in let c = items.#^COMPLETE_WITH_SPACE_AND_PARENS?check=CHECK^# map({ $0.content }) + } + sink { items in let d = items.#^COMPLETE_WITHOUT_SPACE_BUT_PARENS?check=CHECK^#map({ $0.content }) + } + sink { items in let e = items.#^COMPLETE_WITHOUT_MAP?check=CHECK^# { $0.content } + } + sink { items in let f = items.#^COMPLETE_WITHOUT_MAP_BUT_PARENS?check=CHECK^# ({ $0.content }) } }