|
| 1 | +//===--- UnresolvedMemberCodeCompletion.cpp -------------------------------===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift.org open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors |
| 6 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 7 | +// |
| 8 | +// See https://swift.org/LICENSE.txt for license information |
| 9 | +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| 10 | +// |
| 11 | +//===----------------------------------------------------------------------===// |
| 12 | + |
| 13 | +#include "swift/IDE/UnresolvedMemberCompletion.h" |
| 14 | +#include "swift/IDE/CodeCompletion.h" |
| 15 | +#include "swift/IDE/CompletionLookup.h" |
| 16 | +#include "swift/Sema/CompletionContextFinder.h" |
| 17 | +#include "swift/Sema/ConstraintSystem.h" |
| 18 | +#include "swift/Sema/IDETypeChecking.h" |
| 19 | + |
| 20 | +using namespace swift; |
| 21 | +using namespace swift::constraints; |
| 22 | +using namespace swift::ide; |
| 23 | + |
| 24 | +/// If the code completion variable occurs in a pattern matching position, we |
| 25 | +/// have an AST that looks like this. |
| 26 | +/// \code |
| 27 | +/// (binary_expr implicit type='$T3' |
| 28 | +/// (overloaded_decl_ref_expr function_ref=compound decls=[ |
| 29 | +/// Swift.(file).~=, |
| 30 | +/// Swift.(file).Optional extension.~=]) |
| 31 | +/// (argument_list implicit |
| 32 | +/// (argument |
| 33 | +/// (code_completion_expr implicit type='$T1')) |
| 34 | +/// (argument |
| 35 | +/// (declref_expr implicit decl=swift_ide_test.(file).foo(x:).$match)))) |
| 36 | +/// \endcode |
| 37 | +/// If the code completion expression occurs in such an AST, return the |
| 38 | +/// declaration of the \c $match variable, otherwise return \c nullptr. |
| 39 | +static VarDecl *getMatchVarIfInPatternMatch(CodeCompletionExpr *CompletionExpr, |
| 40 | + ConstraintSystem &CS) { |
| 41 | + auto &Context = CS.getASTContext(); |
| 42 | + |
| 43 | + auto *Binary = dyn_cast_or_null<BinaryExpr>(CS.getParentExpr(CompletionExpr)); |
| 44 | + if (!Binary || !Binary->isImplicit() || Binary->getLHS() != CompletionExpr) { |
| 45 | + return nullptr; |
| 46 | + } |
| 47 | + |
| 48 | + auto CalledOperator = Binary->getFn(); |
| 49 | + if (!CalledOperator || !CalledOperator->isImplicit()) { |
| 50 | + return nullptr; |
| 51 | + } |
| 52 | + // The reference to the ~= operator might be an OverloadedDeclRefExpr or a |
| 53 | + // DeclRefExpr, depending on how many ~= operators are viable. |
| 54 | + if (auto Overloaded = |
| 55 | + dyn_cast_or_null<OverloadedDeclRefExpr>(CalledOperator)) { |
| 56 | + if (!llvm::all_of(Overloaded->getDecls(), [&Context](ValueDecl *D) { |
| 57 | + return D->getBaseName() == Context.Id_MatchOperator; |
| 58 | + })) { |
| 59 | + return nullptr; |
| 60 | + } |
| 61 | + } else if (auto Ref = dyn_cast_or_null<DeclRefExpr>(CalledOperator)) { |
| 62 | + if (Ref->getDecl()->getBaseName() != Context.Id_MatchOperator) { |
| 63 | + return nullptr; |
| 64 | + } |
| 65 | + } else { |
| 66 | + return nullptr; |
| 67 | + } |
| 68 | + |
| 69 | + auto MatchArg = dyn_cast_or_null<DeclRefExpr>(Binary->getRHS()); |
| 70 | + if (!MatchArg || !MatchArg->isImplicit()) { |
| 71 | + return nullptr; |
| 72 | + } |
| 73 | + |
| 74 | + auto MatchVar = MatchArg->getDecl(); |
| 75 | + if (MatchVar && MatchVar->isImplicit() && |
| 76 | + MatchVar->getBaseName() == Context.Id_PatternMatchVar) { |
| 77 | + return dyn_cast<VarDecl>(MatchVar); |
| 78 | + } else { |
| 79 | + return nullptr; |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +void UnresolvedMemberTypeCheckCompletionCallback::sawSolution( |
| 84 | + const constraints::Solution &S) { |
| 85 | + GotCallback = true; |
| 86 | + |
| 87 | + auto &CS = S.getConstraintSystem(); |
| 88 | + Type ExpectedTy = getTypeForCompletion(S, CompletionExpr); |
| 89 | + // If the type couldn't be determined (e.g. because there isn't any context |
| 90 | + // to derive it from), let's not attempt to do a lookup since it wouldn't |
| 91 | + // produce any useful results anyway. |
| 92 | + if (ExpectedTy && !ExpectedTy->is<UnresolvedType>()) { |
| 93 | + // If ExpectedTy is a duplicate of any other result, ignore this solution. |
| 94 | + if (!llvm::any_of(ExprResults, [&](const ExprResult &R) { |
| 95 | + return R.ExpectedTy->isEqual(ExpectedTy); |
| 96 | + })) { |
| 97 | + bool SingleExprBody = |
| 98 | + isImplicitSingleExpressionReturn(CS, CompletionExpr); |
| 99 | + ExprResults.push_back({ExpectedTy, SingleExprBody}); |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + if (auto MatchVar = getMatchVarIfInPatternMatch(CompletionExpr, CS)) { |
| 104 | + Type MatchVarType; |
| 105 | + // If the MatchVar has an explicit type, it's not part of the solution. But |
| 106 | + // we can look it up in the constraint system directly. |
| 107 | + if (auto T = S.getConstraintSystem().getVarType(MatchVar)) { |
| 108 | + MatchVarType = T; |
| 109 | + } else { |
| 110 | + MatchVarType = S.getResolvedType(MatchVar); |
| 111 | + } |
| 112 | + if (MatchVarType && !MatchVarType->is<UnresolvedType>()) { |
| 113 | + if (!llvm::any_of(EnumPatternTypes, [&](const Type &R) { |
| 114 | + return R->isEqual(MatchVarType); |
| 115 | + })) { |
| 116 | + EnumPatternTypes.push_back(MatchVarType); |
| 117 | + } |
| 118 | + } |
| 119 | + } |
| 120 | +} |
| 121 | + |
| 122 | +void UnresolvedMemberTypeCheckCompletionCallback::fallbackTypeCheck( |
| 123 | + DeclContext *DC) { |
| 124 | + assert(!gotCallback()); |
| 125 | + |
| 126 | + CompletionContextFinder finder(DC); |
| 127 | + if (!finder.hasCompletionExpr()) |
| 128 | + return; |
| 129 | + |
| 130 | + auto fallback = finder.getFallbackCompletionExpr(); |
| 131 | + if (!fallback) |
| 132 | + return; |
| 133 | + |
| 134 | + SolutionApplicationTarget completionTarget(fallback->E, fallback->DC, |
| 135 | + CTP_Unused, Type(), |
| 136 | + /*isDiscared=*/true); |
| 137 | + typeCheckForCodeCompletion(completionTarget, /*needsPrecheck*/ true, |
| 138 | + [&](const Solution &S) { sawSolution(S); }); |
| 139 | +} |
| 140 | + |
| 141 | +void swift::ide::deliverUnresolvedMemberResults( |
| 142 | + ArrayRef<UnresolvedMemberTypeCheckCompletionCallback::ExprResult> Results, |
| 143 | + ArrayRef<Type> EnumPatternTypes, DeclContext *DC, SourceLoc DotLoc, |
| 144 | + ide::CodeCompletionContext &CompletionCtx, |
| 145 | + CodeCompletionConsumer &Consumer) { |
| 146 | + ASTContext &Ctx = DC->getASTContext(); |
| 147 | + CompletionLookup Lookup(CompletionCtx.getResultSink(), Ctx, DC, |
| 148 | + &CompletionCtx); |
| 149 | + |
| 150 | + assert(DotLoc.isValid()); |
| 151 | + Lookup.setHaveDot(DotLoc); |
| 152 | + Lookup.shouldCheckForDuplicates(Results.size() + EnumPatternTypes.size() > 1); |
| 153 | + |
| 154 | + // Get the canonical versions of the top-level types |
| 155 | + SmallPtrSet<CanType, 4> originalTypes; |
| 156 | + for (auto &Result : Results) |
| 157 | + originalTypes.insert(Result.ExpectedTy->getCanonicalType()); |
| 158 | + |
| 159 | + for (auto &Result : Results) { |
| 160 | + Lookup.setExpectedTypes({Result.ExpectedTy}, |
| 161 | + Result.IsImplicitSingleExpressionReturn, |
| 162 | + /*expectsNonVoid*/ true); |
| 163 | + Lookup.setIdealExpectedType(Result.ExpectedTy); |
| 164 | + |
| 165 | + // For optional types, also get members of the unwrapped type if it's not |
| 166 | + // already equivalent to one of the top-level types. Handling it via the top |
| 167 | + // level type and not here ensures we give the correct type relation |
| 168 | + // (identical, rather than convertible). |
| 169 | + if (Result.ExpectedTy->getOptionalObjectType()) { |
| 170 | + Type Unwrapped = Result.ExpectedTy->lookThroughAllOptionalTypes(); |
| 171 | + if (originalTypes.insert(Unwrapped->getCanonicalType()).second) |
| 172 | + Lookup.getUnresolvedMemberCompletions(Unwrapped); |
| 173 | + } |
| 174 | + Lookup.getUnresolvedMemberCompletions(Result.ExpectedTy); |
| 175 | + } |
| 176 | + |
| 177 | + // Offer completions when interpreting the pattern match as an |
| 178 | + // EnumElementPattern. |
| 179 | + for (auto &Ty : EnumPatternTypes) { |
| 180 | + Lookup.setExpectedTypes({Ty}, /*IsImplicitSingleExpressionReturn=*/false, |
| 181 | + /*expectsNonVoid=*/true); |
| 182 | + Lookup.setIdealExpectedType(Ty); |
| 183 | + |
| 184 | + // We can pattern match MyEnum against Optional<MyEnum> |
| 185 | + if (Ty->getOptionalObjectType()) { |
| 186 | + Type Unwrapped = Ty->lookThroughAllOptionalTypes(); |
| 187 | + Lookup.getEnumElementPatternCompletions(Unwrapped); |
| 188 | + } |
| 189 | + |
| 190 | + Lookup.getEnumElementPatternCompletions(Ty); |
| 191 | + } |
| 192 | + |
| 193 | + deliverCompletionResults(CompletionCtx, Lookup, DC, Consumer); |
| 194 | +} |
0 commit comments