From ca9056a275567ac5c5ccf33f9053d01802d550fc Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 3 Mar 2022 09:45:05 +0100 Subject: [PATCH 1/2] [TypeChecker] Add method to check whether a type variable represents a code completion token --- include/swift/Sema/ConstraintSystem.h | 4 ++++ lib/Sema/TypeCheckConstraints.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/include/swift/Sema/ConstraintSystem.h b/include/swift/Sema/ConstraintSystem.h index e37baa80e0ad9..ad9f62b72c835 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -386,6 +386,10 @@ class TypeVariableType::Implementation { bool isTypeSequence() const; + /// Determine whether this type variable represents a code completion + /// expression. + bool isCodeCompletionToken() const; + /// Retrieve the representative of the equivalence class to which this /// type variable belongs. /// diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp index fd25525dae56a..ca87285584fdf 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -130,6 +130,10 @@ bool TypeVariableType::Implementation::isTypeSequence() const { && locator->getGenericParameter()->isTypeSequence(); } +bool TypeVariableType::Implementation::isCodeCompletionToken() const { + return locator && locator->directlyAt(); +} + void *operator new(size_t bytes, ConstraintSystem& cs, size_t alignment) { return cs.getAllocator().Allocate(bytes, alignment); From f538d33e5f56a32c3688070917ff6731e717729f Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 17 Mar 2022 09:06:47 +0100 Subject: [PATCH 2/2] [CodeCompletion][Sema] Migrate CallArgurment position completion to the solver-based implementation This hooks up call argument position completion to the typeCheckForCodeCompletion API to generate completions from all the solutions the constraint solver produces (even those requiring fixes), rather than relying on a single solution being applied to the AST (if any). Co-authored-by: Nathan Hawes --- include/swift/IDE/ArgumentCompletion.h | 79 ++++ include/swift/IDE/CodeCompletion.h | 4 + include/swift/IDE/PossibleParamInfo.h | 15 + include/swift/Sema/ConstraintSystem.h | 59 ++- lib/IDE/ArgumentCompletion.cpp | 285 +++++++++++++ lib/IDE/CMakeLists.txt | 1 + lib/IDE/CodeCompletion.cpp | 76 +--- lib/Sema/BuilderTransform.cpp | 7 + lib/Sema/CSBindings.cpp | 7 + lib/Sema/CSClosure.cpp | 4 +- lib/Sema/CSGen.cpp | 89 +++- lib/Sema/CSRanking.cpp | 15 +- lib/Sema/CSSimplify.cpp | 270 ++++++++---- lib/Sema/ConstraintSystem.cpp | 62 ++- lib/Sema/TypeCheckCodeCompletion.cpp | 11 +- test/IDE/complete_ambiguous.swift | 104 +++++ test/IDE/complete_call_arg.swift | 386 +++++++++++++++++- ..._enum_unresolved_dot_argument_labels.swift | 54 ++- test/IDE/complete_subscript.swift | 36 +- test/IDE/complete_swift_key_path.swift | 4 +- test/IDE/complete_unresolved_members.swift | 13 +- ...completion-type-not-part-of-solution.swift | 17 + .../0027-autoclosure-curry-thunk.swift | 26 ++ .../0028-member-reference-error-type.swift | 9 + .../0029-closure-implicit-return.swift | 32 ++ .../0030-arg-completion-no-locator.swift | 15 + ...2-constructor-call-in-result-builder.swift | 15 + ...ptimize-constraints-for-ignored-args.swift | 7 + ...-trailing-closure-arg-label-matching.swift | 11 + 29 files changed, 1532 insertions(+), 181 deletions(-) create mode 100644 include/swift/IDE/ArgumentCompletion.h create mode 100644 lib/IDE/ArgumentCompletion.cpp create mode 100644 validation-test/IDE/crashers_2_fixed/0026-completion-type-not-part-of-solution.swift create mode 100644 validation-test/IDE/crashers_2_fixed/0027-autoclosure-curry-thunk.swift create mode 100644 validation-test/IDE/crashers_2_fixed/0028-member-reference-error-type.swift create mode 100644 validation-test/IDE/crashers_2_fixed/0029-closure-implicit-return.swift create mode 100644 validation-test/IDE/crashers_2_fixed/0030-arg-completion-no-locator.swift create mode 100644 validation-test/IDE/crashers_2_fixed/0032-constructor-call-in-result-builder.swift create mode 100644 validation-test/IDE/crashers_2_fixed/0033-dont-optimize-constraints-for-ignored-args.swift create mode 100644 validation-test/IDE/crashers_2_fixed/0033-trailing-closure-arg-label-matching.swift diff --git a/include/swift/IDE/ArgumentCompletion.h b/include/swift/IDE/ArgumentCompletion.h new file mode 100644 index 0000000000000..c6b9d404c8b8e --- /dev/null +++ b/include/swift/IDE/ArgumentCompletion.h @@ -0,0 +1,79 @@ +//===--- ArgumentCompletion.h -----------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_IDE_ARGUMENTCOMPLETION_H +#define SWIFT_IDE_ARGUMENTCOMPLETION_H + +#include "swift/IDE/CodeCompletionConsumer.h" +#include "swift/IDE/CodeCompletionContext.h" +#include "swift/IDE/PossibleParamInfo.h" +#include "swift/Sema/CodeCompletionTypeChecking.h" + +namespace swift { +namespace ide { + +class ArgumentTypeCheckCompletionCallback : public TypeCheckCompletionCallback { + struct Result { + /// The type associated with the code completion expression itself. + Type ExpectedType; + /// True if this is a subscript rather than a function call. + bool IsSubscript; + /// The FuncDecl or SubscriptDecl associated with the call. + ValueDecl *FuncD; + /// The type of the function being called. + Type FuncTy; + /// The index of the argument containing the completion location + unsigned ArgIdx; + /// The index of the parameter corresponding to the completion argument. + Optional ParamIdx; + /// The indices of all params that were bound to non-synthesized + /// arguments. Used so we don't suggest them even when the args are out of + /// order. + std::set ClaimedParamIndices; + /// True if the completion is a noninitial term in a variadic argument. + bool IsNoninitialVariadic; + /// The base type of the call/subscript (null for free functions). + Type BaseType; + /// True if an argument label precedes the completion location. + bool HasLabel; + }; + + CodeCompletionExpr *CompletionExpr; + SmallVector Results; + + /// Populates a vector of parameters to suggest along with a vector of types + /// to match the lookup results against. + /// + /// \Returns true if global lookup should be performed. + bool addPossibleParams(const ArgumentTypeCheckCompletionCallback::Result &Res, + SmallVectorImpl &Params, + SmallVectorImpl &Types); + +public: + ArgumentTypeCheckCompletionCallback(CodeCompletionExpr *CompletionExpr) + : CompletionExpr(CompletionExpr) {} + + void sawSolution(const constraints::Solution &solution) override; + + /// \param IncludeSignature Whether to include a suggestion for the entire + /// function signature instead of suggesting individual labels. Used when + /// completing after the opening '(' of a function call \param Loc The + /// location of the code completion token + void deliverResults(bool IncludeSignature, SourceLoc Loc, DeclContext *DC, + CodeCompletionContext &CompletionCtx, + CodeCompletionConsumer &Consumer); +}; + +} // end namespace ide +} // end namespace swift + +#endif // SWIFT_IDE_ARGUMENTCOMPLETION_H diff --git a/include/swift/IDE/CodeCompletion.h b/include/swift/IDE/CodeCompletion.h index 4d5522a865be8..8a8a4df0a97a1 100644 --- a/include/swift/IDE/CodeCompletion.h +++ b/include/swift/IDE/CodeCompletion.h @@ -84,6 +84,10 @@ void lookupCodeCompletionResultsFromModule(CodeCompletionResultSink &targetSink, bool needLeadingDot, const SourceFile *SF); +void addExprKeywords(CodeCompletionResultSink &Sink, DeclContext *DC); + +void addSuperKeyword(CodeCompletionResultSink &Sink, DeclContext *DC); + } // end namespace ide } // end namespace swift diff --git a/include/swift/IDE/PossibleParamInfo.h b/include/swift/IDE/PossibleParamInfo.h index 429986e1075ff..dcc82ebda6921 100644 --- a/include/swift/IDE/PossibleParamInfo.h +++ b/include/swift/IDE/PossibleParamInfo.h @@ -33,6 +33,21 @@ struct PossibleParamInfo { assert((Param || !IsRequired) && "nullptr with required flag is not allowed"); }; + + friend bool operator==(const PossibleParamInfo &lhs, + const PossibleParamInfo &rhs) { + bool ParamsMatch; + if (lhs.Param == nullptr && rhs.Param == nullptr) { + ParamsMatch = true; + } else if (lhs.Param == nullptr || rhs.Param == nullptr) { + // One is nullptr but the other is not. + ParamsMatch = false; + } else { + // Both are not nullptr. + ParamsMatch = (*lhs.Param == *rhs.Param); + } + return ParamsMatch && (lhs.IsRequired == rhs.IsRequired); + } }; } // end namespace ide diff --git a/include/swift/Sema/ConstraintSystem.h b/include/swift/Sema/ConstraintSystem.h index ad9f62b72c835..81a9bb32d59a4 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -2406,6 +2406,10 @@ class ConstraintSystem { /// diagnostics when result builder has multiple overloads. llvm::SmallDenseSet InvalidResultBuilderBodies; + /// Arguments after the code completion token that were thus ignored (i.e. + /// assigned fresh type variables) for type checking. + llvm::SetVector IgnoredArguments; + /// Maps node types used within all portions of the constraint /// system, instead of directly using the types on the /// nodes themselves. This allows us to typecheck and @@ -3171,9 +3175,24 @@ class ConstraintSystem { return TypeVariables.count(typeVar) > 0; } - /// Whether the given expression's source range contains the code + /// Whether the given ASTNode's source range contains the code /// completion location. - bool containsCodeCompletionLoc(Expr *expr) const; + bool containsCodeCompletionLoc(ASTNode node) const; + bool containsCodeCompletionLoc(const ArgumentList *args) const; + + /// Marks the argument with the \p ArgLoc locator as being ignored because it + /// occurs after the code completion token. This assumes that the argument is + /// not type checked (by assigning it a fresh type variable) and prevents + /// fixes from being generated for this argument. + void markArgumentIgnoredForCodeCompletion(ConstraintLocator *ArgLoc) { + IgnoredArguments.insert(ArgLoc); + } + + /// Whether the argument with the \p ArgLoc locator occurs after the code + /// completion tokena and thus should be ignored and not generate any fixes. + bool isArgumentIgnoredForCodeCompletion(ConstraintLocator *ArgLoc) { + return IgnoredArguments.count(ArgLoc) > 0; + } void setClosureType(const ClosureExpr *closure, FunctionType *type) { assert(closure); @@ -5534,8 +5553,44 @@ class MatchCallArgumentListener { /// \returns true to indicate that this should cause a failure, false /// otherwise. virtual bool relabelArguments(ArrayRef newNames); + + /// \returns true if matchCallArguments should try to claim the argument at + /// \p argIndex while recovering from a failure. This is used to prevent + /// claiming of arguments after the code completion token. + virtual bool shouldClaimArgDuringRecovery(unsigned argIdx); + + /// \returns true if \p arg can be claimed even though its argument label + /// doesn't match. This is the case for arguments representing the code + /// completion token if they don't contain a label. In these cases completion + /// will suggest the label. + virtual bool + canClaimArgIgnoringNameMismatch(const AnyFunctionType::Param &arg); +}; + +/// For a callsite containing a code completion expression, stores the index of +/// the arg containing it along with the index of the first trailing closure and +/// how many arguments were passed in total. +struct CompletionArgInfo { + unsigned completionIdx; + Optional firstTrailingIdx; + unsigned argCount; + + /// \returns true if the given argument index is possibly about to be written + /// by the user (given the completion index) so shouldn't be penalised as + /// missing when ranking solutions. + bool allowsMissingArgAt(unsigned argInsertIdx, AnyFunctionType::Param param); + + /// \returns true if the argument containing the completion location is before + /// the argument with the given index. + bool isBefore(unsigned argIdx) { return completionIdx < argIdx; } }; +/// Extracts the index of the argument containing the code completion location +/// from the provided anchor if it's a \c CallExpr, \c SubscriptExpr, or +/// \c ObjectLiteralExpr. +Optional getCompletionArgInfo(ASTNode anchor, + ConstraintSystem &cs); + /// Match the call arguments (as described by the given argument type) to /// the parameters (as described by the given parameter type). /// diff --git a/lib/IDE/ArgumentCompletion.cpp b/lib/IDE/ArgumentCompletion.cpp new file mode 100644 index 0000000000000..d019522ddfefe --- /dev/null +++ b/lib/IDE/ArgumentCompletion.cpp @@ -0,0 +1,285 @@ +//===--- ArgumentCompletion.cpp ---------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#include "swift/IDE/ArgumentCompletion.h" +#include "swift/IDE/CodeCompletion.h" +#include "swift/IDE/CompletionLookup.h" +#include "swift/Sema/ConstraintSystem.h" +#include "swift/Sema/IDETypeChecking.h" + +using namespace swift; +using namespace swift::ide; +using namespace swift::constraints; + +/// Returns true if both types are null or if they are equal. +static bool nullableTypesEqual(Type LHS, Type RHS) { + if (LHS.isNull() && RHS.isNull()) { + return true; + } else if (LHS.isNull() || RHS.isNull()) { + // One type is null but the other is not. + return false; + } else { + return LHS->isEqual(RHS); + } +} + +bool ArgumentTypeCheckCompletionCallback::addPossibleParams( + const ArgumentTypeCheckCompletionCallback::Result &Res, + SmallVectorImpl &Params, SmallVectorImpl &Types) { + if (!Res.ParamIdx) { + // We don't really know much here. Suggest global results without a specific + // expected type. + return true; + } + + if (Res.HasLabel) { + // We already have a parameter label, suggest types + Types.push_back(Res.ExpectedType); + return true; + } + + ArrayRef ParamsToPass = + Res.FuncTy->getAs()->getParams(); + + ParameterList *PL = nullptr; + if (Res.FuncD) { + PL = swift::getParameterList(Res.FuncD); + } + assert(!PL || PL->size() == ParamsToPass.size()); + + bool ShowGlobalCompletions = false; + for (auto Idx : range(*Res.ParamIdx, ParamsToPass.size())) { + bool IsCompletion = (Idx == Res.ParamIdx); + + // Stop at the first param claimed by other arguments. + if (!IsCompletion && Res.ClaimedParamIndices.count(Idx) > 0) { + break; + } + + const AnyFunctionType::Param *P = &ParamsToPass[Idx]; + bool Required = + !(PL && PL->get(Idx)->isDefaultArgument()) && !P->isVariadic(); + + if (P->hasLabel() && !(IsCompletion && Res.IsNoninitialVariadic)) { + // Suggest parameter label if parameter has label, we are completing in it + // and it is not a variadic parameter that already has arguments + PossibleParamInfo PP(P, Required); + if (!llvm::is_contained(Params, PP)) { + Params.push_back(std::move(PP)); + } + } else { + // We have a parameter that doesn't require a label. Suggest global + // results for that type. + ShowGlobalCompletions = true; + Types.push_back(P->getPlainType()); + } + if (Required) { + // The user should only be suggested the first required param. Stop. + break; + } + } + return ShowGlobalCompletions; +} + +void ArgumentTypeCheckCompletionCallback::sawSolution(const Solution &S) { + TypeCheckCompletionCallback::sawSolution(S); + + Type ExpectedTy = getTypeForCompletion(S, CompletionExpr); + if (!ExpectedTy) { + return; + } + + auto &CS = S.getConstraintSystem(); + + Expr *ParentCall = CompletionExpr; + while (ParentCall && ParentCall->getArgs() == nullptr) { + ParentCall = CS.getParentExpr(ParentCall); + } + + if (!ParentCall || ParentCall == CompletionExpr) { + assert(false && "no containing call?"); + return; + } + + auto ArgInfo = getCompletionArgInfo(ParentCall, CS); + if (!ArgInfo) { + assert(false && "bad parent call match?"); + return; + } + auto ArgIdx = ArgInfo->completionIdx; + + auto *CallLocator = CS.getConstraintLocator(ParentCall); + auto *CalleeLocator = S.getCalleeLocator(CallLocator); + auto SelectedOverload = S.getOverloadChoiceIfAvailable(CalleeLocator); + if (!SelectedOverload) { + return; + } + + Type CallBaseTy = SelectedOverload->choice.getBaseType(); + if (CallBaseTy) { + CallBaseTy = S.simplifyType(CallBaseTy)->getRValueType(); + } + + ValueDecl *FuncD = SelectedOverload->choice.getDeclOrNull(); + Type FuncTy = S.simplifyType(SelectedOverload->openedType)->getRValueType(); + + // For completion as the arg in a call to the implicit [keypath: _] subscript + // the solver can't know what kind of keypath is expected without an actual + // argument (e.g. a KeyPath vs WritableKeyPath) so it ends up as a hole. + // Just assume KeyPath so we show the expected keypath's root type to users + // rather than '_'. + if (SelectedOverload->choice.getKind() == + OverloadChoiceKind::KeyPathApplication) { + auto Params = FuncTy->getAs()->getParams(); + if (Params.size() == 1 && Params[0].getPlainType()->is()) { + auto *KPDecl = CS.getASTContext().getKeyPathDecl(); + Type KPTy = + KPDecl->mapTypeIntoContext(KPDecl->getDeclaredInterfaceType()); + Type KPValueTy = KPTy->castTo()->getGenericArgs()[1]; + KPTy = BoundGenericType::get(KPDecl, Type(), {CallBaseTy, KPValueTy}); + FuncTy = FunctionType::get({Params[0].withType(KPTy)}, KPValueTy); + } + } + + // Find the parameter the completion was bound to (if any), as well as which + // parameters are already bound (so we don't suggest them even when the args + // are out of order). + Optional ParamIdx; + std::set ClaimedParams; + bool IsNoninitialVariadic = false; + + ConstraintLocator *ArgumentLocator; + ArgumentLocator = + CS.getConstraintLocator(CallLocator, ConstraintLocator::ApplyArgument); + auto ArgMatchChoices = S.argumentMatchingChoices.find(ArgumentLocator); + if (ArgMatchChoices != S.argumentMatchingChoices.end()) { + // We might not have argument matching choices when applying a subscript + // found via @dynamicMemberLookup. + auto Bindings = ArgMatchChoices->second.parameterBindings; + + for (auto i : indices(Bindings)) { + bool Claimed = false; + for (auto j : Bindings[i]) { + if (j == ArgIdx) { + assert(!ParamIdx); + ParamIdx = i; + IsNoninitialVariadic = llvm::any_of( + Bindings[i], [j](unsigned other) { return other < j; }); + } + // Synthesized args don't count. + if (j < ArgInfo->argCount) { + Claimed = true; + } + } + if (Claimed) { + ClaimedParams.insert(i); + } + } + } else { + // FIXME: We currently don't look through @dynamicMemberLookup applications + // for subscripts (rdar://90363138) + } + + bool HasLabel = false; + if (auto PE = CS.getParentExpr(CompletionExpr)) { + if (auto Args = PE->getArgs()) { + HasLabel = !Args->getLabel(ArgIdx).empty(); + } + } + + // If this is a duplicate of any other result, ignore this solution. + if (llvm::any_of(Results, [&](const Result &R) { + return R.FuncD == FuncD && nullableTypesEqual(R.FuncTy, FuncTy) && + nullableTypesEqual(R.BaseType, CallBaseTy) && + R.ParamIdx == ParamIdx && + R.IsNoninitialVariadic == IsNoninitialVariadic; + })) { + return; + } + + Results.push_back({ExpectedTy, isa(ParentCall), FuncD, FuncTy, + ArgIdx, ParamIdx, std::move(ClaimedParams), + IsNoninitialVariadic, CallBaseTy, HasLabel}); +} + +void ArgumentTypeCheckCompletionCallback::deliverResults( + bool IncludeSignature, SourceLoc Loc, DeclContext *DC, + ide::CodeCompletionContext &CompletionCtx, + CodeCompletionConsumer &Consumer) { + ASTContext &Ctx = DC->getASTContext(); + CompletionLookup Lookup(CompletionCtx.getResultSink(), Ctx, DC, + &CompletionCtx); + + // Perform global completion as a fallback if we don't have any results. + bool shouldPerformGlobalCompletion = Results.empty(); + SmallVector ExpectedTypes; + + if (IncludeSignature && !Results.empty()) { + Lookup.setHaveLParen(true); + for (auto &Result : Results) { + auto SemanticContext = SemanticContextKind::None; + NominalTypeDecl *BaseNominal = nullptr; + if (Result.BaseType) { + Type BaseTy = Result.BaseType; + if (auto InstanceTy = BaseTy->getMetatypeInstanceType()) { + BaseTy = InstanceTy; + } + if ((BaseNominal = BaseTy->getAnyNominal())) { + SemanticContext = SemanticContextKind::CurrentNominal; + if (Result.FuncD && + Result.FuncD->getDeclContext()->getSelfNominalTypeDecl() != + BaseNominal) { + SemanticContext = SemanticContextKind::Super; + } + } else if (BaseTy->is() || BaseTy->is()) { + SemanticContext = SemanticContextKind::CurrentNominal; + } + } + if (Result.IsSubscript) { + assert(SemanticContext != SemanticContextKind::None); + auto *SD = dyn_cast_or_null(Result.FuncD); + Lookup.addSubscriptCallPattern(Result.FuncTy->getAs(), + SD, SemanticContext); + } else { + auto *FD = dyn_cast_or_null(Result.FuncD); + Lookup.addFunctionCallPattern(Result.FuncTy->getAs(), + FD, SemanticContext); + } + } + Lookup.setHaveLParen(false); + + shouldPerformGlobalCompletion |= + !Lookup.FoundFunctionCalls || Lookup.FoundFunctionsWithoutFirstKeyword; + } else if (!Results.empty()) { + SmallVector Params; + for (auto &Ret : Results) { + shouldPerformGlobalCompletion |= + addPossibleParams(Ret, Params, ExpectedTypes); + } + Lookup.addCallArgumentCompletionResults(Params); + } + + if (shouldPerformGlobalCompletion) { + for (auto &Result : Results) { + ExpectedTypes.push_back(Result.ExpectedType); + } + Lookup.setExpectedTypes(ExpectedTypes, false); + Lookup.getValueCompletionsInDeclContext(Loc); + Lookup.getSelfTypeCompletionInDeclContext(Loc, /*isForDeclResult=*/false); + + // Add any keywords that can be used in an argument expr position. + addSuperKeyword(CompletionCtx.getResultSink(), DC); + addExprKeywords(CompletionCtx.getResultSink(), DC); + } + + deliverCompletionResults(CompletionCtx, Lookup, DC, Consumer); +} diff --git a/lib/IDE/CMakeLists.txt b/lib/IDE/CMakeLists.txt index 94c727d02480f..83d79f787d236 100644 --- a/lib/IDE/CMakeLists.txt +++ b/lib/IDE/CMakeLists.txt @@ -1,5 +1,6 @@ add_swift_host_library(swiftIDE STATIC + ArgumentCompletion.cpp CodeCompletion.cpp CodeCompletionCache.cpp CodeCompletionContext.cpp diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp index 17c291472e03d..52e99ebc8db40 100644 --- a/lib/IDE/CodeCompletion.cpp +++ b/lib/IDE/CodeCompletion.cpp @@ -32,6 +32,7 @@ #include "swift/ClangImporter/ClangImporter.h" #include "swift/ClangImporter/ClangModule.h" #include "swift/Frontend/FrontendOptions.h" +#include "swift/IDE/ArgumentCompletion.h" #include "swift/IDE/CodeCompletionCache.h" #include "swift/IDE/CodeCompletionConsumer.h" #include "swift/IDE/CodeCompletionResultPrinter.h" @@ -830,7 +831,8 @@ static void addObserverKeywords(CodeCompletionResultSink &Sink) { addKeyword(Sink, "didSet", CodeCompletionKeywordKind::None); } -static void addExprKeywords(CodeCompletionResultSink &Sink, DeclContext *DC) { +void swift::ide::addExprKeywords(CodeCompletionResultSink &Sink, + DeclContext *DC) { // Expression is invalid at top-level of non-script files. CodeCompletionFlair flair; if (isCodeCompletionAtTopLevelOfLibraryFile(DC)) { @@ -844,7 +846,8 @@ static void addExprKeywords(CodeCompletionResultSink &Sink, DeclContext *DC) { addKeyword(Sink, "await", CodeCompletionKeywordKind::None, "", flair); } -static void addSuperKeyword(CodeCompletionResultSink &Sink, DeclContext *DC) { +void swift::ide::addSuperKeyword(CodeCompletionResultSink &Sink, + DeclContext *DC) { if (!DC) return; auto *TC = DC->getInnermostTypeContext(); @@ -1373,6 +1376,22 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) { Lookup.deliverResults(CurDeclContext, DotLoc, CompletionContext, Consumer); return true; } + case CompletionKind::CallArg: { + assert(CodeCompleteTokenExpr); + assert(CurDeclContext); + ArgumentTypeCheckCompletionCallback Lookup(CodeCompleteTokenExpr); + llvm::SaveAndRestore CompletionCollector( + Context.CompletionCallback, &Lookup); + typeCheckContextAt(CurDeclContext, CompletionLoc); + + if (!Lookup.gotCallback()) { + Lookup.fallbackTypeCheck(CurDeclContext); + } + + Lookup.deliverResults(ShouldCompleteCallPatternAfterParen, CompletionLoc, + CurDeclContext, CompletionContext, Consumer); + return true; + } default: return false; } @@ -1494,6 +1513,7 @@ void CodeCompletionCallbacksImpl::doneParsing() { case CompletionKind::DotExpr: case CompletionKind::UnresolvedMember: case CompletionKind::KeyPathExprSwift: + case CompletionKind::CallArg: llvm_unreachable("should be already handled"); return; @@ -1657,58 +1677,6 @@ void CodeCompletionCallbacksImpl::doneParsing() { Lookup.addImportModuleNames(); break; } - case CompletionKind::CallArg: { - ExprContextInfo ContextInfo(CurDeclContext, CodeCompleteTokenExpr); - - bool shouldPerformGlobalCompletion = true; - - if (ShouldCompleteCallPatternAfterParen && - !ContextInfo.getPossibleCallees().empty()) { - Lookup.setHaveLParen(true); - for (auto &typeAndDecl : ContextInfo.getPossibleCallees()) { - auto apply = ContextInfo.getAnalyzedExpr(); - if (isa_and_nonnull(apply)) { - Lookup.addSubscriptCallPattern( - typeAndDecl.Type, - dyn_cast_or_null(typeAndDecl.Decl), - typeAndDecl.SemanticContext); - } else { - Lookup.addFunctionCallPattern( - typeAndDecl.Type, - dyn_cast_or_null(typeAndDecl.Decl), - typeAndDecl.SemanticContext); - } - } - Lookup.setHaveLParen(false); - - shouldPerformGlobalCompletion = - !Lookup.FoundFunctionCalls || - (Lookup.FoundFunctionCalls && - Lookup.FoundFunctionsWithoutFirstKeyword); - } else if (!ContextInfo.getPossibleParams().empty()) { - auto params = ContextInfo.getPossibleParams(); - Lookup.addCallArgumentCompletionResults(params); - - shouldPerformGlobalCompletion = !ContextInfo.getPossibleTypes().empty(); - // Fallback to global completion if the position is out of number. It's - // better than suggest nothing. - shouldPerformGlobalCompletion |= llvm::all_of( - params, [](const PossibleParamInfo &P) { return !P.Param; }); - } - - if (shouldPerformGlobalCompletion) { - Lookup.setExpectedTypes(ContextInfo.getPossibleTypes(), - ContextInfo.isImplicitSingleExpressionReturn()); - - // Add any keywords that can be used in an argument expr position. - addSuperKeyword(CompletionContext.getResultSink(), CurDeclContext); - addExprKeywords(CompletionContext.getResultSink(), CurDeclContext); - - DoPostfixExprBeginning(); - } - break; - } - case CompletionKind::LabeledTrailingClosure: { ExprContextInfo ContextInfo(CurDeclContext, CodeCompleteTokenExpr); diff --git a/lib/Sema/BuilderTransform.cpp b/lib/Sema/BuilderTransform.cpp index 301361498684d..9016862e26ca8 100644 --- a/lib/Sema/BuilderTransform.cpp +++ b/lib/Sema/BuilderTransform.cpp @@ -1876,6 +1876,13 @@ ConstraintSystem::matchResultBuilder(AnyFunctionRef fn, Type builderType, return getTypeMatchFailure(locator); } + // If we're solving for code completion and the body contains the code + // completion location, skipping it won't get us to a useful solution so + // just bail. + if (isForCodeCompletion() && containsCodeCompletionLoc(fn.getBody())) { + return getTypeMatchFailure(locator); + } + // Record the first unhandled construct as a fix. if (recordFix( SkipUnhandledConstructInResultBuilder::create( diff --git a/lib/Sema/CSBindings.cpp b/lib/Sema/CSBindings.cpp index ca318a2eb4f91..35bc46b4dc381 100644 --- a/lib/Sema/CSBindings.cpp +++ b/lib/Sema/CSBindings.cpp @@ -2016,11 +2016,18 @@ bool TypeVariableBinding::attempt(ConstraintSystem &cs) const { // Don't penalize solutions with unresolved generics. if (TypeVar->getImpl().getGenericParameter()) return false; + // Don't penalize solutions with holes due to missing arguments after the // code completion position. auto argLoc = srcLocator->findLast(); if (argLoc && argLoc->isAfterCodeCompletionLoc()) return false; + + // Don't penailze solutions that have holes for ignored arguments. + if (cs.isArgumentIgnoredForCodeCompletion( + TypeVar->getImpl().getLocator())) { + return false; + } } // Reflect in the score that this type variable couldn't be // resolved and had to be bound to a placeholder "hole" type. diff --git a/lib/Sema/CSClosure.cpp b/lib/Sema/CSClosure.cpp index 13bee8a11518a..b64b9fb8a6b20 100644 --- a/lib/Sema/CSClosure.cpp +++ b/lib/Sema/CSClosure.cpp @@ -895,7 +895,9 @@ class ClosureConstraintGenerator // Single-expression closures are effectively a `return` statement, // so let's give them a special locator as to indicate that. - if (closure->hasSingleExpressionBody()) { + // Return statements might not have a result if we have a closure whose + // implicit returned value is coerced to Void. + if (closure->hasSingleExpressionBody() && returnStmt->hasResult()) { auto *expr = returnStmt->getResult(); assert(expr && "single expression closure without expression?"); diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index b0464b870fcf5..fb95be286feb3 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -747,7 +747,38 @@ namespace { favorCallOverloads(expr, CS, isFavoredDecl); } - + + /// If \p expr is a call and that call contains the code completion token, + /// add the expressions of all arguments after the code completion token to + /// \p ignoredArguemnts. + /// Otherwise, returns an empty vector. + /// Asssumes that we are solving for code completion. + void getArgumentsAfterCodeCompletionToken( + Expr *expr, ConstraintSystem &CS, + SmallVectorImpl &ignoredArguments) { + assert(CS.isForCodeCompletion()); + + /// Don't ignore the rhs argument if the code completion token is the lhs of + /// an operator call. Main use case is the implicit ` ~= $match` + /// call created for pattern matching, in which we need to type-check + /// `$match` to get a contextual type for `` + if (isa(expr)) { + return; + } + + auto args = expr->getArgs(); + auto argInfo = getCompletionArgInfo(expr, CS); + if (!args || !argInfo) { + return; + } + + for (auto argIndex : indices(*args)) { + if (argInfo->isBefore(argIndex)) { + ignoredArguments.push_back(args->get(argIndex).getExpr()); + } + } + } + class ConstraintOptimizer : public ASTWalker { ConstraintSystem &CS; @@ -757,6 +788,10 @@ namespace { CS(cs) {} std::pair walkToExprPre(Expr *expr) override { + if (CS.isArgumentIgnoredForCodeCompletion( + CS.getConstraintLocator(expr))) { + return {false, expr}; + } if (CS.shouldReusePrecheckedType() && !CS.getType(expr)->hasTypeVariable()) { @@ -2053,7 +2088,14 @@ namespace { /*outerAlternatives=*/{}); } - FunctionType *inferClosureType(ClosureExpr *closure) { + /// If \p allowResultBindToHole is \c true, we always allow the closure's + /// result type to bind to a hole, otherwise the result type may only bind + /// to a hole if the closure does not participate in type inference. Setting + /// \p allowResultBindToHole to \c true is useful when ignoring a closure + /// argument in a function call after the code completion token and thus + /// wanting to ignore the closure's type. + FunctionType *inferClosureType(ClosureExpr *closure, + bool allowResultBindToHole = false) { SmallVector closureParams; if (auto *paramList = closure->getParameters()) { @@ -2141,9 +2183,10 @@ namespace { // If this is a multi-statement closure, let's mark result // as potential hole right away. return Type(CS.createTypeVariable( - resultLocator, CS.participatesInInference(closure) - ? 0 - : TVO_CanBindToHole)); + resultLocator, + (!CS.participatesInInference(closure) || allowResultBindToHole) + ? TVO_CanBindToHole + : 0)); }(); // For a non-async function type, add the global actor if present. @@ -3574,6 +3617,26 @@ namespace { } llvm_unreachable("unhandled operation"); } + + /// Assuming that we are solving for code completion, assign \p expr a fresh + /// and unconstrained type variable as its type. + void setTypeForArgumentIgnoredForCompletion(Expr *expr) { + assert(CS.isForCodeCompletion()); + ConstraintSystem &CS = getConstraintSystem(); + + if (auto closure = dyn_cast(expr)) { + FunctionType *closureTy = + inferClosureType(closure, /*allowResultBindToHole=*/true); + CS.setClosureType(closure, closureTy); + CS.setType(closure, closureTy); + } else { + TypeVariableType *exprType = CS.createTypeVariable( + CS.getConstraintLocator(expr), + TVO_CanBindToLValue | TVO_CanBindToInOut | TVO_CanBindToNoEscape | + TVO_CanBindToHole); + CS.setType(expr, exprType); + } + } }; class ConstraintWalker : public ASTWalker { @@ -3583,6 +3646,13 @@ namespace { ConstraintWalker(ConstraintGenerator &CG) : CG(CG) { } std::pair walkToExprPre(Expr *expr) override { + auto &CS = CG.getConstraintSystem(); + + if (CS.isArgumentIgnoredForCodeCompletion( + CS.getConstraintLocator(expr))) { + CG.setTypeForArgumentIgnoredForCompletion(expr); + return {false, expr}; + } if (CG.getConstraintSystem().shouldReusePrecheckedType()) { if (expr->getType()) { @@ -3650,6 +3720,15 @@ namespace { return { false, expr }; } + if (CS.isForCodeCompletion()) { + SmallVector ignoredArgs; + getArgumentsAfterCodeCompletionToken(expr, CS, ignoredArgs); + for (auto ignoredArg : ignoredArgs) { + CS.markArgumentIgnoredForCodeCompletion( + CS.getConstraintLocator(ignoredArg)); + } + } + return { true, expr }; } diff --git a/lib/Sema/CSRanking.cpp b/lib/Sema/CSRanking.cpp index 536ee9fcf3e7b..fcc0886938046 100644 --- a/lib/Sema/CSRanking.cpp +++ b/lib/Sema/CSRanking.cpp @@ -1322,13 +1322,16 @@ SolutionCompareResult ConstraintSystem::compareSolutions( // The systems are not considered equivalent. identical = false; - // A concrete type is better than an archetype. + // Archetypes are worse than concrete types (i.e. non-placeholder and + // non-archetype) // FIXME: Total hack. - if (type1->is() != type2->is()) { - if (type1->is()) - ++score2; - else - ++score1; + if (type1->is() && !type2->is() && + !type2->is()) { + ++score2; + continue; + } else if (type2->is() && !type1->is() && + !type1->is()) { + ++score2; continue; } diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 89194a0c58967..c7b1018c6c4df 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -66,6 +66,15 @@ bool MatchCallArgumentListener::relabelArguments(ArrayRef newNames){ return true; } +bool MatchCallArgumentListener::shouldClaimArgDuringRecovery(unsigned argIdx) { + return true; +} + +bool MatchCallArgumentListener::canClaimArgIgnoringNameMismatch( + const AnyFunctionType::Param &arg) { + return false; +} + /// Produce a score (smaller is better) comparing a parameter name and /// potentially-typo'd argument name. /// @@ -254,6 +263,15 @@ static bool anyParameterRequiresArgument( return false; } +static bool isCodeCompletionTypeVar(Type type) { + if (auto *TVT = type->getAs()) { + if (TVT->getImpl().isCodeCompletionToken()) { + return true; + } + } + return false; +} + static bool matchCallArgumentsImpl( SmallVectorImpl &args, ArrayRef params, @@ -340,7 +358,10 @@ static bool matchCallArgumentsImpl( // Go hunting for an unclaimed argument whose name does match. Optional claimedWithSameName; + unsigned firstArgIdx = nextArgIdx; for (unsigned i = nextArgIdx; i != numArgs; ++i) { + auto argLabel = args[i].getLabel(); + bool claimIgnoringNameMismatch = false; if (!args[i].matchParameterLabel(paramLabel)) { // If this is an attempt to claim additional unlabeled arguments @@ -348,9 +369,15 @@ static bool matchCallArgumentsImpl( if (forVariadic) return None; - // Otherwise we can continue trying to find argument which - // matches parameter with or without label. - continue; + if ((i == firstArgIdx || ignoreNameMismatch) && + listener.canClaimArgIgnoringNameMismatch(args[i])) { + // Avoid triggering relabelling fixes about the completion arg. + claimIgnoringNameMismatch = true; + } else { + // Otherwise we can continue trying to find argument which + // matches parameter with or without label. + continue; + } } // Skip claimed arguments. @@ -374,14 +401,14 @@ static bool matchCallArgumentsImpl( // func foo(_ a: Int, _ b: Int = 0, c: Int = 0, _ d: Int) {} // foo(1, c: 2, 3) // -> `3` will be claimed as '_ b:'. // ``` - if (args[i].getLabel().empty()) + if (argLabel.empty() && !claimIgnoringNameMismatch) continue; potentiallyOutOfOrder = true; } // Claim it. - return claim(paramLabel, i); + return claim(paramLabel, i, claimIgnoringNameMismatch); } // If we're not supposed to attempt any fixes, we're done. @@ -585,6 +612,10 @@ static bool matchCallArgumentsImpl( llvm::SmallVector unclaimedNamedArgs; for (auto argIdx : indices(args)) { if (claimedArgs[argIdx]) continue; + + if (!listener.shouldClaimArgDuringRecovery(argIdx)) + continue; + if (!args[argIdx].getLabel().empty()) unclaimedNamedArgs.push_back(argIdx); } @@ -661,6 +692,9 @@ static bool matchCallArgumentsImpl( continue; bindNextParameter(paramIdx, nextArgIdx, true); + + if (!listener.shouldClaimArgDuringRecovery(nextArgIdx)) + continue; } } @@ -674,12 +708,15 @@ static bool matchCallArgumentsImpl( continue; // If parameter has a default value, we don't really - // now if label doesn't match because it's incorrect + // know if label doesn't match because it's incorrect // or argument belongs to some other parameter, so // we just leave this parameter unfulfilled. if (paramInfo.hasDefaultArgument(i)) continue; + if (!listener.shouldClaimArgDuringRecovery(i)) + continue; + // Looks like there was no parameter claimed at the same // position, it could only mean that label is completely // different, because typo correction has been attempted already. @@ -783,8 +820,8 @@ static bool matchCallArgumentsImpl( for (const auto &binding : parameterBindings) { paramToArgMap.push_back(argIdx); // Ignore argument bindings that were synthesized due to missing args. - argIdx += llvm::count_if( - binding, [numArgs](unsigned argIdx) { return argIdx < numArgs; }); + argIdx += llvm::count_if( + binding, [numArgs](unsigned argIdx) { return argIdx < numArgs; }); } } @@ -994,35 +1031,32 @@ constraints::matchCallArguments( }; } -struct CompletionArgInfo { - unsigned completionIdx; - Optional firstTrailingIdx; +bool CompletionArgInfo::allowsMissingArgAt(unsigned argInsertIdx, + AnyFunctionType::Param param) { + // If the argument is before or at the index of the argument containing the + // completion, the user would likely have already written it if they + // intended this overload. + if (completionIdx >= argInsertIdx) { + return false; + } - bool isAllowableMissingArg(unsigned argInsertIdx, - AnyFunctionType::Param param) { - // If the argument is before or at the index of the argument containing the - // completion, the user would likely have already written it if they - // intended this overload. - if (completionIdx >= argInsertIdx) + // If the argument is after the first trailing closure, the user can only + // continue on to write more trailing arguments, so only allow this overload + // if the missing argument is of function type. + if (firstTrailingIdx && argInsertIdx > *firstTrailingIdx) { + if (param.isInOut()) { return false; - - // If the argument is after the first trailing closure, the user can only - // continue on to write more trailing arguments, so only allow this overload - // if the missing argument is of function type. - if (firstTrailingIdx && argInsertIdx > *firstTrailingIdx) { - if (param.isInOut()) - return false; - - Type expectedTy = param.getPlainType()->lookThroughAllOptionalTypes(); - return expectedTy->is() || expectedTy->isAny() || - expectedTy->isTypeVariableOrMember(); } - return true; + + Type expectedTy = param.getPlainType()->lookThroughAllOptionalTypes(); + return expectedTy->is() || expectedTy->isAny() || + expectedTy->isTypeVariableOrMember(); } -}; + return true; +} -static Optional -getCompletionArgInfo(ASTNode anchor, ConstraintSystem &CS) { +Optional +constraints::getCompletionArgInfo(ASTNode anchor, ConstraintSystem &CS) { auto *exprAnchor = getAsExpr(anchor); if (!exprAnchor) return None; @@ -1033,20 +1067,46 @@ getCompletionArgInfo(ASTNode anchor, ConstraintSystem &CS) { for (unsigned i : indices(*args)) { if (CS.containsCodeCompletionLoc(args->getExpr(i))) - return CompletionArgInfo{i, args->getFirstTrailingClosureIndex()}; + return CompletionArgInfo{i, args->getFirstTrailingClosureIndex(), + args->size()}; } return None; } class ArgumentFailureTracker : public MatchCallArgumentListener { +protected: ConstraintSystem &CS; SmallVectorImpl &Arguments; ArrayRef Parameters; ConstraintLocatorBuilder Locator; +private: SmallVector MissingArguments; SmallVector, 4> ExtraArguments; - Optional CompletionArgInfo; + +protected: + /// Synthesizes an argument that is intended to match against a missing + /// argument for the parameter at \p paramIdx. + /// \returns The index of the new argument in \c Arguments. + unsigned synthesizeArgument(unsigned paramIdx, + bool isAfterCodeCompletionLoc) { + const auto ¶m = Parameters[paramIdx]; + + unsigned newArgIdx = Arguments.size(); + auto *argLoc = CS.getConstraintLocator( + Locator, {LocatorPathElt::ApplyArgToParam(newArgIdx, paramIdx, + param.getParameterFlags()), + LocatorPathElt::SynthesizedArgument( + newArgIdx, isAfterCodeCompletionLoc)}); + + auto *argType = CS.createTypeVariable( + argLoc, TVO_CanBindToInOut | TVO_CanBindToLValue | + TVO_CanBindToNoEscape | TVO_CanBindToHole); + + auto synthesizedArg = param.withType(argType); + Arguments.push_back(synthesizedArg); + return newArgIdx; + } public: ArgumentFailureTracker(ConstraintSystem &cs, @@ -1070,36 +1130,9 @@ class ArgumentFailureTracker : public MatchCallArgumentListener { if (!CS.shouldAttemptFixes()) return None; - const auto ¶m = Parameters[paramIdx]; - - unsigned newArgIdx = Arguments.size(); - - bool isAfterCodeCompletionLoc = false; - if (CS.isForCodeCompletion()) { - if (!CompletionArgInfo) - CompletionArgInfo = getCompletionArgInfo(Locator.getAnchor(), CS); - isAfterCodeCompletionLoc = CompletionArgInfo && - CompletionArgInfo->isAllowableMissingArg(argInsertIdx, param); - } - - auto *argLoc = CS.getConstraintLocator( - Locator, {LocatorPathElt::ApplyArgToParam(newArgIdx, paramIdx, - param.getParameterFlags()), - LocatorPathElt::SynthesizedArgument(newArgIdx, isAfterCodeCompletionLoc)}); - - auto *argType = - CS.createTypeVariable(argLoc, TVO_CanBindToInOut | TVO_CanBindToLValue | - TVO_CanBindToNoEscape | TVO_CanBindToHole); - - auto synthesizedArg = param.withType(argType); - Arguments.push_back(synthesizedArg); - - // When solving for code completion, if any argument contains the - // completion location, later arguments shouldn't be considered missing - // (causing the solution to have a worse score) as the user just hasn't - // written them yet. Early exit to avoid recording them in this case. - if (isAfterCodeCompletionLoc) - return newArgIdx; + unsigned newArgIdx = + synthesizeArgument(paramIdx, /*isAfterCodeCompletionLoc=*/false); + auto synthesizedArg = Arguments[newArgIdx]; MissingArguments.push_back(SynthesizedArg{paramIdx, synthesizedArg}); @@ -1213,6 +1246,64 @@ class ArgumentFailureTracker : public MatchCallArgumentListener { } }; +/// Ignores any failures after the code comletion token. +class CompletionArgumentTracker : public ArgumentFailureTracker { + struct CompletionArgInfo ArgInfo; + +public: + CompletionArgumentTracker(ConstraintSystem &cs, + SmallVectorImpl &args, + ArrayRef params, + ConstraintLocatorBuilder locator, + struct CompletionArgInfo ArgInfo) + : ArgumentFailureTracker(cs, args, params, locator), ArgInfo(ArgInfo) {} + + Optional missingArgument(unsigned paramIdx, + unsigned argInsertIdx) override { + // When solving for code completion, if any argument contains the + // completion location, later arguments shouldn't be considered missing + // (causing the solution to have a worse score) as the user just hasn't + // written them yet. Early exit to avoid recording them in this case. + if (ArgInfo.allowsMissingArgAt(argInsertIdx, Parameters[paramIdx])) { + return synthesizeArgument(paramIdx, /*isAfterCodeCompletionLoc=*/true); + } + + return ArgumentFailureTracker::missingArgument(paramIdx, argInsertIdx); + } + + bool extraArgument(unsigned argIdx) override { + if (ArgInfo.isBefore(argIdx)) { + return false; + } + return ArgumentFailureTracker::extraArgument(argIdx); + } + + bool outOfOrderArgument(unsigned argIdx, unsigned prevArgIdx, + ArrayRef bindings) override { + if (ArgInfo.isBefore(argIdx)) { + return false; + } + + return ArgumentFailureTracker::outOfOrderArgument(argIdx, prevArgIdx, + bindings); + } + + bool shouldClaimArgDuringRecovery(unsigned argIdx) override { + return !ArgInfo.isBefore(argIdx); + } + + bool + canClaimArgIgnoringNameMismatch(const AnyFunctionType::Param &arg) override { + if (!isCodeCompletionTypeVar(arg.getPlainType())) { + return false; + } + if (!arg.getLabel().empty()) { + return false; + } + return true; + } +}; + class AllowLabelMismatches : public MatchCallArgumentListener { SmallVector NewLabels; bool HadLabelingIssues = false; @@ -1541,11 +1632,23 @@ static ConstraintSystem::TypeMatchResult matchCallArguments( TrailingClosureMatching::Forward; { - ArgumentFailureTracker listener(cs, argsWithLabels, params, locator); + std::unique_ptr listener; + if (cs.isForCodeCompletion()) { + if (auto completionInfo = getCompletionArgInfo(locator.getAnchor(), cs)) { + listener = std::make_unique( + cs, argsWithLabels, params, locator, *completionInfo); + } + } + if (!listener) { + // We didn't create an argument tracker for code completion. Create a + // normal one. + listener = std::make_unique(cs, argsWithLabels, + params, locator); + } auto callArgumentMatch = constraints::matchCallArguments( argsWithLabels, params, paramInfo, argList->getFirstTrailingClosureIndex(), cs.shouldAttemptFixes(), - listener, trailingClosureMatching); + *listener, trailingClosureMatching); if (!callArgumentMatch) return cs.getTypeMatchFailure(locator); @@ -1572,7 +1675,7 @@ static ConstraintSystem::TypeMatchResult matchCallArguments( // Take the parameter bindings we selected. parameterBindings = std::move(callArgumentMatch->parameterBindings); - auto extraArguments = listener.getExtraneousArguments(); + auto extraArguments = listener->getExtraneousArguments(); if (!extraArguments.empty()) { if (RemoveExtraneousArguments::isMinMaxNameShadowing(cs, locator)) return cs.getTypeMatchFailure(locator); @@ -7719,14 +7822,14 @@ static bool isForKeyPathSubscript(ConstraintSystem &cs, return false; } -static bool isForKeyPathSubscriptWithoutLabel(ConstraintSystem &cs, - ConstraintLocator *locator) { +static bool mayBeForKeyPathSubscriptWithoutLabel(ConstraintSystem &cs, + ConstraintLocator *locator) { if (!locator || !locator->getAnchor()) return false; if (auto *SE = getAsExpr(locator->getAnchor())) { if (auto *unary = SE->getArgs()->getUnlabeledUnaryExpr()) - return isa(unary); + return isa(unary) || isa(unary); } return false; } @@ -7862,8 +7965,8 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName, // subscript without a `keyPath:` label. Add it to the result so that it // can be caught by the missing argument label checking later. if (isForKeyPathSubscript(*this, memberLocator) || - (isForKeyPathSubscriptWithoutLabel(*this, memberLocator) - && includeInaccessibleMembers)) { + (mayBeForKeyPathSubscriptWithoutLabel(*this, memberLocator) && + includeInaccessibleMembers)) { if (baseTy->isAnyObject()) { result.addUnviable( OverloadChoice(baseTy, OverloadChoiceKind::KeyPathApplication), @@ -8011,8 +8114,10 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName, if (decl->isRecursiveValidation()) return; - // If the result is invalid, skip it. - if (decl->isInvalid()) { + // If the result is invalid, skip it unless solving for code completion + // For code completion include the result because we can partially match + // against function types that only have one parameter with error type. + if (decl->isInvalid() && !isForCodeCompletion()) { result.markErrorAlreadyDiagnosed(); return; } @@ -10555,7 +10660,17 @@ ConstraintSystem::simplifyKeyPathApplicationConstraint( return matchTypes(resultTy, valueTy, ConstraintKind::Bind, subflags, locator); } - + + if (keyPathTy->isPlaceholder()) { + if (rootTy->hasTypeVariable()) { + recordAnyTypeVarAsPotentialHole(rootTy); + } + if (valueTy->hasTypeVariable()) { + recordAnyTypeVarAsPotentialHole(valueTy); + } + return SolutionKind::Solved; + } + if (auto bgt = keyPathTy->getAs()) { // We have the key path type. Match it to the other ends of the constraint. auto kpRootTy = bgt->getGenericArgs()[0]; @@ -12355,6 +12470,11 @@ bool ConstraintSystem::recordFix(ConstraintFix *fix, unsigned impact) { log << ")\n"; } + if (isArgumentIgnoredForCodeCompletion(fix->getLocator())) { + // The argument was ignored. Don't record any fixes for it. + return false; + } + // Record the fix. // If this is just a warning, it shouldn't affect the solver. Otherwise, diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index 30e6315fb3747..20f218405255e 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -341,8 +341,16 @@ ConstraintSystem::getAlternativeLiteralTypes(KnownProtocolKind kind, return scratch; } -bool ConstraintSystem::containsCodeCompletionLoc(Expr *expr) const { - SourceRange range = expr->getSourceRange(); +bool ConstraintSystem::containsCodeCompletionLoc(ASTNode node) const { + SourceRange range = node.getSourceRange(); + if (range.isInvalid()) + return false; + return Context.SourceMgr.rangeContainsCodeCompletionLoc(range); +} + +bool ConstraintSystem::containsCodeCompletionLoc( + const ArgumentList *args) const { + SourceRange range = args->getSourceRange(); if (range.isInvalid()) return false; return Context.SourceMgr.rangeContainsCodeCompletionLoc(range); @@ -1938,6 +1946,38 @@ Type constraints::typeEraseOpenedExistentialReference( }); } +/// For every parameter in \p type that has an error type, replace that +/// parameter's type by a placeholder type, where \p value is the declaration +/// that declared \p type. This is useful for code completion so we can match +/// the types we do know instead of bailing out completely because \p type +/// contains an error type. +static Type replaceParamErrorTypeByPlaceholder(Type type, ValueDecl *value) { + if (!type->is() || !isa(value)) { + return type; + } + auto funcType = type->castTo(); + auto funcDecl = cast(value); + + auto declParams = funcDecl->getParameters(); + auto typeParams = funcType->getParams(); + assert(declParams->size() == typeParams.size()); + SmallVector newParams; + newParams.reserve(declParams->size()); + for (auto i : indices(typeParams)) { + AnyFunctionType::Param param = typeParams[i]; + if (param.getPlainType()->is()) { + auto paramDecl = declParams->get(i); + auto placeholder = + PlaceholderType::get(paramDecl->getASTContext(), paramDecl); + newParams.push_back(param.withType(placeholder)); + } else { + newParams.push_back(param); + } + } + assert(newParams.size() == declParams->size()); + return FunctionType::get(newParams, funcType->getResult()); +} + std::pair ConstraintSystem::getTypeOfMemberReference( Type baseTy, ValueDecl *value, DeclContext *useDC, @@ -2019,6 +2059,10 @@ ConstraintSystem::getTypeOfMemberReference( if (isa(value) || isa(value)) { + if (auto ErrorTy = value->getInterfaceType()->getAs()) { + return {ErrorType::get(ErrorTy->getASTContext()), + ErrorType::get(ErrorTy->getASTContext())}; + } // This is the easy case. funcType = value->getInterfaceType()->castTo(); @@ -2248,6 +2292,12 @@ ConstraintSystem::getTypeOfMemberReference( if (isReferenceOptional && !isa(value)) type = OptionalType::get(type->getRValueType()); + if (isForCodeCompletion() && type->hasError()) { + // In code completion, replace error types by placeholder types so we can + // match the types we know instead of bailing out completely. + type = replaceParamErrorTypeByPlaceholder(type, value); + } + // If we opened up any type variables, record the replacements. recordOpenedTypes(locator, replacements); @@ -3200,7 +3250,13 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator, refType = subscriptTy; // Increase the score so that actual subscripts get preference. - increaseScore(SK_KeyPathSubscript); + // ...except if we're solving for code completion and the index expression + // contains the completion location + auto SE = getAsExpr(locator->getAnchor()); + if (!isForCodeCompletion() || + (SE && !containsCodeCompletionLoc(SE->getArgs()))) { + increaseScore(SK_KeyPathSubscript); + } break; } } diff --git a/lib/Sema/TypeCheckCodeCompletion.cpp b/lib/Sema/TypeCheckCodeCompletion.cpp index 8c44b905c3c25..3b11b12885968 100644 --- a/lib/Sema/TypeCheckCodeCompletion.cpp +++ b/lib/Sema/TypeCheckCodeCompletion.cpp @@ -189,13 +189,19 @@ class SanitizeExpr : public ASTWalker { continue; } - // Restore '@autoclosure'd value. if (auto ACE = dyn_cast(expr)) { + // Restore '@autoclosure'd value. // This is only valid if the closure doesn't have parameters. if (ACE->getParameters()->size() == 0) { expr = ACE->getSingleExpressionBody(); continue; } + // Restore autoclosure'd function reference. + if (auto *unwrapped = ACE->getUnwrappedCurryThunkExpr()) { + expr = unwrapped; + continue; + } + llvm_unreachable("other AutoClosureExpr must be handled specially"); } @@ -548,8 +554,7 @@ void TypeChecker::filterSolutionsForCodeCompletion( })->getFixedScore(); llvm::erase_if(solutions, [&](const Solution &S) { - return S.getFixedScore().Data[SK_Fix] != 0 && - S.getFixedScore() > minScore; + return S.getFixedScore().Data[SK_Fix] > minScore.Data[SK_Fix]; }); } diff --git a/test/IDE/complete_ambiguous.swift b/test/IDE/complete_ambiguous.swift index 628e333dcf1c4..d4e840bbf8983 100644 --- a/test/IDE/complete_ambiguous.swift +++ b/test/IDE/complete_ambiguous.swift @@ -383,6 +383,10 @@ _ = testing([Point(4, 89)]) { arg in struct Thing { init(_ block: (Point) -> Void) {} + + enum ThingEnum { case first, second } + func doStuff(_ x: ThingEnum, _ y: Int) -> Thing { return self } + func takesRef(_ ref: () -> ()) -> Thing { return self } } @resultBuilder struct ThingBuilder { @@ -429,6 +433,20 @@ CreateThings { Thing. // ErrorExpr } +struct TestFuncBodyBuilder { + func someFunc() {} + + @ThingBuilder func foo() -> [Thing] { + Thing() + .doStuff(.#^FUNCBUILDER_FUNCBODY^#, 3) + .takesRef(someFunc) + } +} +// FUNCBUILDER_FUNCBODY: Begin completions, 3 items +// FUNCBUILDER_FUNCBODY-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: first[#Thing.ThingEnum#]; +// FUNCBUILDER_FUNCBODY-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: second[#Thing.ThingEnum#]; +// FUNCBUILDER_FUNCBODY-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): Thing.ThingEnum#})[#(into: inout Hasher) -> Void#]; +// FUNCBUILDER_FUNCBODY: End completions func takesClosureOfPoint(_: (Point)->()) {} func overloadedWithDefaulted(_: ()->()) {} @@ -473,3 +491,89 @@ func testBestSolutionGeneric() { // BEST_SOLUTION_FILTER_GEN-DAG: Keyword[self]/CurrNominal: self[#Test1#]; name=self // BEST_SOLUTION_FILTER_GEN: End completions +func testAmbiguousArgs() { + struct A { + func someFunc(a: Int, b: Int) -> A { return self } + func variadic(y: Int, x: Int...) + } + + struct B { + func someFunc(a: Int, c: String = "", d: String = "") {} + } + + func overloaded() -> A { return A() } + func overloaded() -> B { return B() } + + let localInt = 10 + let localString = "Hello" + + overloaded().someFunc(a: 2, #^ARG_NO_LABEL^#) + + // ARG_NO_LABEL: Begin completions, 3 items + // ARG_NO_LABEL-DAG: Pattern/Local/Flair[ArgLabels]: {#b: Int#}[#Int#]; name=b: + // ARG_NO_LABEL-DAG: Pattern/Local/Flair[ArgLabels]: {#c: String#}[#String#]; name=c: + // ARG_NO_LABEL-DAG: Pattern/Local/Flair[ArgLabels]: {#d: String#}[#String#]; name=d: + // ARG_NO_LABEL: End completions + + overloaded().someFunc(a: 2, b: #^ARG_LABEL^#) + + // ARG_LABEL: Begin completions + // ARG_LABEL-DAG: Decl[LocalVar]/Local: localString[#String#]; name=localString + // ARG_LABEL-DAG: Decl[LocalVar]/Local/TypeRelation[Identical]: localInt[#Int#]; name=localInt + // ARG_LABEL: End completions + + overloaded().someFunc(a: 2, c: "Foo", #^ARG_NO_LABEL2^#) + + // ARG_NO_LABEL2: Begin completions, 1 item + // ARG_NO_LABEL2: Pattern/Local/Flair[ArgLabels]: {#d: String#}[#String#]; name=d: + // ARG_NO_LABEL2: End completions + + overloaded().someFunc(a: 2, wrongLabel: "Foo", #^ARG_NO_LABEL_PREV_WRONG^#) + + // ARG_NO_LABEL_PREV_WRONG: Begin completions, 3 items + // ARG_NO_LABEL_PREV_WRONG-DAG: Pattern/Local/Flair[ArgLabels]: {#b: Int#}[#Int#]; name=b: + // ARG_NO_LABEL_PREV_WRONG-DAG: Pattern/Local/Flair[ArgLabels]: {#c: String#}[#String#]; name=c: + // ARG_NO_LABEL_PREV_WRONG-DAG: Pattern/Local/Flair[ArgLabels]: {#d: String#}[#String#]; name=d: + // ARG_NO_LABEL_PREV_WRONG: End completions + + overloaded().someFunc(a: 2, d: "Foo", #^ARG_NO_LABEL_OUT_OF_ORDER^#) + // ARG_NO_LABEL_OUT_OF_ORDER: Begin completions + // ARG_NO_LABEL_OUT_OF_ORDER-NOT: name=d: String + // ARG_NO_LABEL_OUT_OF_ORDER: Pattern/Local/Flair[ArgLabels]: {#c: String#}[#String#]; name=c: + // ARG_NO_LABEL_OUT_OF_ORDER-NOT: name=d: String + // ARG_NO_LABEL_OUT_OF_ORDER: End completions + + func noArgs() {} + noArgs(12, #^ARG_EXTRANEOUS^#) + // ARG_EXTRANEOUS: Begin completions + // ARG_EXTRANEOUS-DAG: localInt + // ARG_EXTRANEOUS-DAG: localString + // ARG_EXTRANEOUS: End completions + + overloaded().someFunc(a: 2, #^LATER_ARGS^#, d: "foo") + // LATER_ARGS: Begin completions, 2 items + + // LATER_ARGS-DAG: Pattern/Local/Flair[ArgLabels]: {#b: Int#}[#Int#]; name=b: + // LATER_ARGS-DAG: Pattern/Local/Flair[ArgLabels]: {#c: String#}[#String#]; name=c: + // LATER_ARGS: End completions + + overloaded().someFunc(a: 2, #^LATER_ARGS_WRONG^#, k: 4.5) + // LATER_ARGS_WRONG: Begin completions, 3 items + // LATER_ARGS_WRONG-DAG: Pattern/Local/Flair[ArgLabels]: {#b: Int#}[#Int#]; name=b: + // LATER_ARGS_WRONG-DAG: Pattern/Local/Flair[ArgLabels]: {#c: String#}[#String#]; name=c: + // LATER_ARGS_WRONG-DAG: Pattern/Local/Flair[ArgLabels]: {#d: String#}[#String#]; name=d: + // LATER_ARGS_WRONG-DAG: End completions + + + overloaded().variadic(y: 2, #^INITIAL_VARARG^#, 4) + // INITIAL_VARARG: Begin completions, 1 item + // INITIAL_VARARG: Pattern/Local/Flair[ArgLabels]: {#x: Int...#}[#Int#]; name=x: + // INITIAL VARARG: End completions + + overloaded().variadic(y: 2, x: 2, #^NONINITIAL_VARARG^#) + // NONINITIAL_VARARG: Begin completions + // NONINITIAL_VARARG-NOT: name=x: + // NONINITIAL_VARARG: Decl[LocalVar]/Local/TypeRelation[Identical]: localInt[#Int#]; name=localInt + // NONINITIAL_VARARG-NOT: name=x: + // NONINITIAL_VARARG: End completions +} diff --git a/test/IDE/complete_call_arg.swift b/test/IDE/complete_call_arg.swift index e89d07500ae36..5bbceb99a29a2 100644 --- a/test/IDE/complete_call_arg.swift +++ b/test/IDE/complete_call_arg.swift @@ -180,10 +180,10 @@ class C3 { var C1I = C1() var C2I = C2() func f1() { - foo2(C1I, #^OVERLOAD1?xfail=FIXME^#) + foo2(C1I, #^OVERLOAD1^#) } func f2() { - foo2(C2I, #^OVERLOAD2?xfail=FIXME^#) + foo2(C2I, #^OVERLOAD2^#) } func f3() { foo2(C1I, b1: #^OVERLOAD3^#) @@ -207,11 +207,11 @@ class C3 { } // OVERLOAD1: Begin completions, 1 items -// OVERLOAD1-NEXT: Keyword/ExprSpecific: b1: [#Argument name#]; name=b1: +// OVERLOAD1-NEXT: Pattern/Local/Flair[ArgLabels]: {#b1: C2#}[#C2#]; name=b1: // OVERLOAD1-NEXT: End completions // OVERLOAD2: Begin completions, 1 items -// OVERLOAD2-NEXT: Keyword/ExprSpecific: b2: [#Argument name#]; name=b2: +// OVERLOAD2-NEXT: Pattern/Local/Flair[ArgLabels]: {#b2: C1#}[#C1#]; name=b2: // OVERLOAD2-NEXT: End completions // OVERLOAD3: Begin completions @@ -221,9 +221,6 @@ class C3 { // OVERLOAD3-DAG: Decl[Class]/CurrModule/TypeRelation[Identical]: C2[#C2#] // OVERLOAD3: End completions -// FIXME: This should be a negative test case -// NEGATIVE_OVERLOAD_3-NOT: Decl[Class]{{.*}} C1 - // OVERLOAD4: Begin completions // OVERLOAD4-DAG: Decl[InstanceVar]/CurrNominal/TypeRelation[Identical]: C1I[#C1#]; name=C1I // OVERLOAD4-DAG: Decl[InstanceVar]/CurrNominal: C2I[#C2#]; name=C2I @@ -231,9 +228,6 @@ class C3 { // OVERLOAD4-DAG: Decl[Class]/CurrModule/TypeRelation[Identical]: C1[#C1#] // OVERLOAD4: End completions -// FIXME: This should be a negative test case -// NEGATIVE_OVERLOAD4-NOT: Decl[Class]{{.*}} C2 - // OVERLOAD5: Begin completions // OVERLOAD5-DAG: Decl[FreeFunction]/CurrModule/Flair[ArgLabels]: ['(']{#(a): C1#}, {#b1: C2#}[')'][#Void#]; name=:b1: // OVERLOAD5-DAG: Decl[FreeFunction]/CurrModule/Flair[ArgLabels]: ['(']{#(a): C2#}, {#b2: C1#}[')'][#Void#]; name=:b2: @@ -269,7 +263,7 @@ extension C3 { // HASERROR2: End completions // HASERROR3: Begin completions -// HASERROR3-DAG: Pattern/Local/Flair[ArgLabels]: {#b1: <>#}[#<>#]; +// HASERROR3-DAG: Pattern/Local/Flair[ArgLabels]: {#b1: _#}[#_#]; // HASERROR3: End completions // HASERROR4: Begin completions @@ -992,8 +986,10 @@ struct Rdar77867723 { } func test2 { self.fn(eee: .up, #^OVERLOAD_LABEL2^#) -// OVERLOAD_LABEL2: Begin completions, 2 items +// OVERLOAD_LABEL2: Begin completions, 4 items +// OVERLOAD_LABEL2-DAG: Pattern/Local/Flair[ArgLabels]: {#aaa: Horizontal#}[#Horizontal#]; // OVERLOAD_LABEL2-DAG: Pattern/Local/Flair[ArgLabels]: {#bbb: Vertical#}[#Vertical#]; +// OVERLOAD_LABEL2-DAG: Pattern/Local/Flair[ArgLabels]: {#ccc: Vertical#}[#Vertical#]; // OVERLOAD_LABEL2-DAG: Pattern/Local/Flair[ArgLabels]: {#ddd: Horizontal#}[#Horizontal#]; // OVERLOAD_LABEL2: End completions } @@ -1008,10 +1004,370 @@ extension SR14737 where T == Int { func test_SR14737() { invalidCallee { SR14737(arg1: true, #^GENERIC_INIT_IN_INVALID^#) -// FIXME: 'onlyInt' shouldn't be offered because 'arg1' is Bool. -// GENERIC_INIT_IN_INVALID: Begin completions, 2 items +// GENERIC_INIT_IN_INVALID: Begin completions, 1 item // GENERIC_INIT_IN_INVALID-DAG: Pattern/Local/Flair[ArgLabels]: {#arg2: Bool#}[#Bool#]; -// GENERIC_INIT_IN_INVALID-DAG: Pattern/Local/Flair[ArgLabels]: {#onlyInt: Bool#}[#Bool#]; // GENERIC_INIT_IN_INVALID: End completions } } + +struct CondConfType { + init(_: U) +} +extension CondConfType: Equatable where U == Int {} + +func testArgsAfterCompletion() { + enum A { case a } + enum B { case b } + + let localA = A.a + let localB = B.b + + func overloaded(x: Int, _ first: A, _ second: B) {} + func overloaded(x: Int, _ first: B, _ second: A) {} + struct SubOverloaded { + subscript(x x: Int, first: A, second: B) -> Int { return 1 } + subscript(x x: Int, first: B, second: A) -> Int { return 1} + } + + overloaded(x: 1, .#^VALID_UNRESOLVED?check=VALID_UNRESOLVED_NOCOMMA^#, localB) + SubOverloaded()[x: 1, .#^VALID_UNRESOLVED_SUB?check=VALID_UNRESOLVED_NOCOMMA^#, localB] + + // VALID_UNRESOLVED: Begin completions, 2 items + // VALID_UNRESOLVED-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: a[#A#]; name=a + // VALID_UNRESOLVED-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): A#})[#(into: inout Hasher) -> Void#]; name=hash(:) + // VALID_UNRESOLVED: End completions + + overloaded(x: 1, .#^VALID_UNRESOLVED_NOCOMMA^# localB) + SubOverloaded()[x: 1, .#^VALID_UNRESOLVED_NOCOMA_SUB?check=VALID_UNRESOLVED_NOCOMMA^# localB] + + // VALID_UNRESOLVED_NOCOMMA: Begin completions, 4 items + // VALID_UNRESOLVED_NOCOMMA-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: a[#A#]; name=a + // VALID_UNRESOLVED_NOCOMMA-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: b[#B#]; name=b + // VALID_UNRESOLVED_NOCOMMA-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): A#})[#(into: inout Hasher) -> Void#]; name=hash(:) + // VALID_UNRESOLVED_NOCOMMA-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): B#})[#(into: inout Hasher) -> Void#]; name=hash(:) + // VALID_UNRESOLVED_NOCOMMA: End completions + + overloaded(x: 1, .#^INVALID_UNRESOLVED?check=VALID_UNRESOLVED_NOCOMMA^#, "wrongType") + SubOverloaded()[x: 1, .#^INVALID_UNRESOLVED_SUB?check=VALID_UNRESOLVED_NOCOMMA^#, "wrongType"] + + overloaded(x: 1, #^VALID_GLOBAL^#, localB) + SubOverloaded()[x: 1, #^VALID_GLOBAL_SUB?check=VALID_GLOBAL^#, localB] + // VALID_GLOBAL: Begin completions + // VALID_GLOBAL-DAG: Decl[LocalVar]/Local/TypeRelation[Identical]: localA[#A#]; name=localA + // VALID_GLOBAL-DAG: Decl[LocalVar]/Local/TypeRelation[Identical]: localB[#B#]; name=localB + // VALID_GLOBAL: End completions + + func takesIntGivesB(_ x: Int) -> B { return B.b } + overloaded(x: 1, .#^VALID_NESTED?check=VALID_UNRESOLVED_NOCOMMA^#, takesIntGivesB(1)) + overloaded(x: 1, .#^INVALID_NESTED?check=VALID_UNRESOLVED_NOCOMMA^#, takesIntGivesB("string")) + + func overloadedLabel(x: Int, firstA: A, second: B) {} + func overloadedLabel(x: Int, firstB: B, second: A) {} + struct SubOverloadedLabel { + subscript(x x: Int, firstA firstA: A, second second: B) -> Int { return 1 } + subscript(x x: Int, firstB firstB: B, second second: A) -> Int { return 1 } + } + + overloadedLabel(x: 1, #^VALID_LABEL^#, second: localB) + SubOverloadedLabel()[x: 1, #^VALID_LABEL_SUB?check=VALID_LABEL^#, second: localB] + // VALID_LABEL: Begin completions, 2 items + // VALID_LABEL: Pattern/Local/Flair[ArgLabels]: {#firstA: A#}[#A#]; name=firstA: + // VALID_LABEL: Pattern/Local/Flair[ArgLabels]: {#firstB: B#}[#B#]; name=firstB: + // VALID_LABEL: End completions + + overloadedLabel(x: 1, #^VALID_LABEL_NOCOMMA^# second: localB) + SubOverloadedLabel()[x: 1, #^VALID_LABEL_NOCOMMA_SUB?check=VALID_LABEL_NOCOMMA^# second: localB] + + // VALID_LABEL_NOCOMMA: Begin completions, 2 items + // VALID_LABEL_NOCOMMA-DAG: Pattern/Local/Flair[ArgLabels]: {#firstA: A#}[#A#]; name=firstA: + // VALID_LABEL_NOCOMMA-DAG: Pattern/Local/Flair[ArgLabels]: {#firstB: B#}[#B#]; name=firstB: + // VALID_LABEL_NOCOMMA: End completions + + // The parser eats the existing localB arg, so we still suggest both labels here. + overloadedLabel(x: 1, #^VALID_LABEL_NOCOMMA_NOLABEL?check=VALID_LABEL_NOCOMMA^# localB) + SubOverloadedLabel()[x: 1, #^VALID_LABEL_NOCOMMA_NOLABEL_SUB?check=VALID_LABEL_NOCOMMA^# localB] + + overloadedLabel(x: 1, #^INVALID_LABEL^#, wrongLabelRightType: localB) + SubOverloadedLabel()[x: 1, #^INVALID_LABEL_SUB?check=INVALID_LABEL^#, wrongLabelRightType: localB] + // INVALID_LABEL: Begin completions, 2 items + // INVALID_LABEL-DAG: Pattern/Local/Flair[ArgLabels]: {#firstA: A#}[#A#]; name=firstA: + // INVALID_LABEL-DAG: Pattern/Local/Flair[ArgLabels]: {#firstB: B#}[#B#]; name=firstB: + // INVALID_LABEL: End completions + + overloadedLabel(x: 1, #^INVALID_TYPE?check=INVALID_LABEL^#, second: 34) + SubOverloadedLabel()[x: 1, #^INVALID_TYPE_SUB?check=INVALID_LABEL^#, second: 34] + + overloadedLabel(x: 1, #^INVALID_LABEL_TYPE?check=INVALID_LABEL^#, wrongLabelWrongType: 2) + SubOverloadedLabel()[x: 1, #^INVALID_LABEL_TYPE_SUB?check=INVALID_LABEL^#, wrongLabelWrongType: 2] + + func overloadedArity(x: Int, firstA: A, second: B, third: Double) {} + func overloadedArity(x: Int, firstB: B, second: A) {} + struct SubOverloadedArity { + subscript(x x: Int, firstA firstA: A, second second: B, third third: Double) -> Int { return 1} + subscript(x x: Int, firstB firstB: B, second second: A) -> Int { return 1} + } + + overloadedArity(x: 1, #^VALID_ARITY^#, second: localB, third: 4.5) + SubOverloadedArity()[x: 1, #^VALID_ARITY_SUB?check=VALID_ARITY^#, second: localB, third: 4.5] + // VALID_ARITY: Begin completions, 2 items + // VALID_ARITY: Pattern/Local/Flair[ArgLabels]: {#firstA: A#}[#A#]; name=firstA: + // VALID_ARITY: Pattern/Local/Flair[ArgLabels]: {#firstB: B#}[#B#]; name=firstB: + // VALID_ARITY: End completions + + overloadedArity(x: 1, #^VALID_ARITY_NOCOMMA?check=VALID_ARITY^# second: localB, third: 4.5) + SubOverloadedArity()[x: 1, #^VALID_ARITY_NOCOMMA_SUB?check=VALID_ARITY^# second: localB, third: 4.5] + + overloadedArity(x: 1, #^INVALID_ARITY^#, wrong: localB) + SubOverloadedArity()[x: 1, #^INVALID_ARITY_SUB?check=INVALID_ARITY^#, wrong: localB] + // INVALID_ARITY: Begin completions, 2 items + // INVALID_ARITY-DAG: Pattern/Local/Flair[ArgLabels]: {#firstA: A#}[#A#]; name=firstA: + // INVALID_ARITY-DAG: Pattern/Local/Flair[ArgLabels]: {#firstB: B#}[#B#]; name=firstB: + // INVALID_ARITY: End completions + + // type mismatch in 'second' vs extra arg 'third'. + overloadedArity(x: 1, #^INVALID_ARITY_TYPE?check=INVALID_ARITY^#, second: localA, third: 2.5) + SubOverloadedArity()[x: 1, #^INVALID_ARITY_TYPE_SUB?check=INVALID_ARITY^#, second: localA, third: 2.5] + overloadedArity(x: 1, #^INVALID_ARITY_TYPE_2?check=INVALID_ARITY^#, second: localA, third: "wrong") + SubOverloadedArity()[x: 1, #^INVALID_ARITY_TYPE_2_SUB?check=INVALID_ARITY^#, second: localA, third: "wrong"] + + + func overloadedDefaulted(x: Int, p: A) {} + func overloadedDefaulted(x: Int, y: A = A.a, z: A = A.a) {} + struct SubOverloadedDefaulted { + subscript(x x: Int, p p: A) -> Int { return 1 } + subscript(x x: Int, y y: A = A.a, z z: A = A.a) -> Int { return 1 } + } + + overloadedDefaulted(x: 1, #^VALID_DEFAULTED^#) + SubOverloadedDefaulted()[x: 1, #^VALID_DEFAULTED_SUB?check=VALID_DEFAULTED^#] + // VALID_DEFAULTED: Begin completions, 3 items + // VALID_DEFAULTED-DAG: Pattern/Local/Flair[ArgLabels]: {#p: A#}[#A#]; name=p: + // VALID_DEFAULTED-DAG: Pattern/Local/Flair[ArgLabels]: {#y: A#}[#A#]; name=y: + // VALID_DEFAULTED-DAG: Pattern/Local/Flair[ArgLabels]: {#z: A#}[#A#]; name=z: + // VALID_DEFAULTED: End completions + + overloadedDefaulted(x: 1, #^VALID_DEFAULTED_AFTER^#, z: localA) + SubOverloadedDefaulted()[x: 1, #^VALID_DEFAULTED_AFTER_SUB?check=VALID_DEFAULTED_AFTER^#, z: localA] + // VALID_DEFAULTED_AFTER: Begin completions, 2 items + // VALID_DEFAULTED_AFTER-DAG: Pattern/Local/Flair[ArgLabels]: {#p: A#}[#A#]; name=p: + // VALID_DEFAULTED_AFTER-DAG: Pattern/Local/Flair[ArgLabels]: {#y: A#}[#A#]; name=y: + // VALID_DEFAULTED_AFTER: End completions + + overloadedDefaulted(x: 1, #^VALID_DEFAULTED_AFTER_NOCOMMA?check=VALID_DEFAULTED^# z: localA) + overloadedDefaulted(x: 1, #^INVALID_DEFAULTED?check=VALID_DEFAULTED^#, w: "hello") + overloadedDefaulted(x: 1, #^INVALID_DEFAULTED_TYPO?check=VALID_DEFAULTED^#, zz: localA) + overloadedDefaulted(x: 1, #^INVALID_DEFAULTED_TYPO_TYPE?check=VALID_DEFAULTED^#, zz: "hello") + SubOverloadedDefaulted()[x: 1, #^VALID_DEFAULTED_AFTER_NOCOMMA_SUB?check=VALID_DEFAULTED^# z: localA] + SubOverloadedDefaulted()[x: 1, #^INVALID_DEFAULTED_SUB?check=VALID_DEFAULTED^#, w: "hello"] + SubOverloadedDefaulted()[x: 1, #^INVALID_DEFAULTED_TYPO_SUB?check=VALID_DEFAULTED^#, zz: localA] + SubOverloadedDefaulted()[x: 1, #^INVALID_DEFAULTED_TYPO_TYPE_SUB?check=VALID_DEFAULTED^#, zz: "hello"] + + overloadedDefaulted(x: 1, #^INVALID_DEFAULTED_TYPE^#, z: localB) + SubOverloadedDefaulted()[x: 1, #^INVALID_DEFAULTED_TYPE_SUB?check=INVALID_DEFAULTED_TYPE^#, z: localB] + // INVALID_DEFAULTED_TYPE: Begin completions, 2 items + // INVALID_DEFAULTED_TYPE-DAG: Pattern/Local/Flair[ArgLabels]: {#p: A#}[#A#]; name=p: + // INVALID_DEFAULTED_TYPE-DAG: Pattern/Local/Flair[ArgLabels]: {#y: A#}[#A#]; name=y: + // INVALID_DEFAULTED_TYPE: End completions + + func overloadedGeneric(x: Int, y: String, z: T, zz: T) {} + func overloadedGeneric(x: Int, p: String, q: Int) {} + struct SubOverloadedGeneric { + subscript(x x: Int, y y: String, z z: T, zz zz: T) -> Int { return 1} + subscript(x x: Int, p p: String, q q: Int) -> Int { return 1 } + } + + struct MissingConformance {} + overloadedGeneric(x: 1, #^INVALID_MISSINGCONFORMANCE^#, z: MissingConformance(), zz: MissingConformance()) + SubOverloadedGeneric()[x: 1, #^INVALID_MISSINGCONFORMANCE_SUB?check=INVALID_MISSINGCONFORMANCE^#, z: MissingConformance(), zz: MissingConformance()] + // INVALID_MISSINGCONFORMANCE: Begin completions, 2 items + // INVALID_MISSINGCONFORMANCE-DAG: Pattern/Local/Flair[ArgLabels]: {#p: String#}[#String#]; name=p: + // INVALID_MISSINGCONFORMANCE-DAG: Pattern/Local/Flair[ArgLabels]: {#y: String#}[#String#]; name=y: + // INVALID_MISSINGCONFORMANCE: End completions + + overloadedGeneric(x: 1, #^INVALID_MISSINGCONFORMANCE_NOCOMMA?check=INVALID_MISSINGCONFORMANCE^# z: MisingConformance(), zz: MissingConformance()) + overloadedGeneric(x: 1, #^INVALID_MISSINGCONFORMANCE_INDIRECT?check=INVALID_MISSINGCONFORMANCE^#, z: [MissingConformance()], zz: [MissingConformance()]) + overloadedGeneric(x: 1, #^INVALID_MISSINGCONFORMANCE_CONSTRAINT?check=INVALID_MISSINGCONFORMANCE_CONSTAINT^#, z: [CondConfType("foo")], zz: [CondConfType("bar")]) + SubOverloadedGeneric()[x: 1, #^INVALID_MISSINGCONFORMANCE_NOCOMMA_SUB?check=INVALID_MISSINGCONFORMANCE^# z: MisingConformance(), zz: MissingConformance()] + SubOverloadedGeneric()[x: 1, #^INVALID_MISSINGCONFORMANCE_INDIRECT_SUB?check=INVALID_MISSINGCONFORMANCE^#, z: [MissingConformance()], zz: [MissingConformance()]] + SubOverloadedGeneric()[x: 1, #^INVALID_MISSINGCONFORMANCE_CONSTRAINT_SUB?check=INVALID_MISSINGCONFORMANCE_CONSTAINT^#, z: [CondConfType("foo")], zz: [CondConfType("bar")]] + + // INVALID_MISSINGCONFORMANCE_CONSTAINT: Begin completions, 2 items + // INVALID_MISSINGCONFORMANCE_CONSTAINT-DAG: Pattern/Local/Flair[ArgLabels]: {#y: String#}[#String#]; name=y: + // INVALID_MISSINGCONFORMANCE_CONSTAINT-DAG: Pattern/Local/Flair[ArgLabels]: {#p: String#}[#String#]; name=p: + // INVALID_MISSINGCONFORMANCE_CONSTAINT: End completions +} + +func testFuncTyVars(param: (Int, String, Double) -> ()) { + var local = { (a: Int, b: String, c: Double) in } + + let someInt = 2 + let someString = "hello" + let someDouble = 3.5 + + param(2, #^PARAM_ARG2?check=FUNCTY_STRING^#) + local(2, #^LOCAL_ARG2?check=FUNCTY_STRING^#) + param(2, "hello", #^PARAM_ARG3?check=FUNCTY_DOUBLE^#) + local(2, "hello", #^LOCAL_ARG3?check=FUNCTY_DOUBLE^#) + + // FUNCTY_STRING: Begin completions + // FUNCTY_STRING-DAG: Decl[LocalVar]/Local/TypeRelation[Identical]: someString[#String#]; + // FUNCTY_STRING-DAG: Decl[LocalVar]/Local: someDouble[#Double#]; + // FUNCTY_STRING-DAG: Decl[LocalVar]/Local: someInt[#Int#]; + // FUNCTY_STRING: End completions + + // FUNCTY_DOUBLE: Begin completions + // FUNCTY_DOUBLE-DAG: Decl[LocalVar]/Local/TypeRelation[Identical]: someDouble[#Double#]; + // FUNCTY_DOUBLE-DAG: Decl[LocalVar]/Local: someString[#String#]; + // FUNCTY_DOUBLE-DAG: Decl[LocalVar]/Local: someInt[#Int#]; + // FUNCTY_DOUBLE: End completions +} + + +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: End completions + } +} + + +func testLValueBaseTyOfSubscript() { + var cache: [String: Codable] = [:] + if let cached = cache[#^LVALUEBASETY^# + + // LVALUEBASETY: Begin completions + // LVALUEBASETY-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]/IsSystem: ['[']{#(position): Dictionary.Index#}[']'][#(key: String, value: Codable)#]; + // LVALUEBASETY-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]/IsSystem: ['[']{#(key): String#}[']'][#@lvalue Codable?#]; + // LVALUEBASETY: End completions +} + +func testSkippedCallArgInInvalidResultBuilderBody() { + protocol MyView { + associatedtype Body + var body: Body { get } + } + struct MyEmptyView: MyView { + var body: Never + } + + @resultBuilder public struct MyViewBuilder { + public static func buildBlock() -> MyEmptyView { fatalError() } + public static func buildBlock(_ content: Content) -> Content where Content : MyView { fatalError() } + } + + struct MyImage : MyView { + var body: Never + public init(systemName: String, otherArg: Int) {} + } + + struct Other { + public init(action: Int, @MyViewBuilder label: () -> Label) {} + public init(_ titleKey: Int, action: Int) {} + } + + func foo() -> Bool { + Other(action: 2) { + MyImage(systemName: "", #^INVALID_RESULTBUILDER_ARG^# + struct Invalid + } + + // INVALID_RESULTBUILDER_ARG: Begin completions, 1 item + // INVALID_RESULTBUILDER_ARG: Pattern/Local/Flair[ArgLabels]: {#otherArg: Int#}[#Int#]; + // INVALID_RESULTBUILDER_ARG: End completions +} + +func testFunctionConversionAfterCodecompletionPos() { + func searchSection(_ category: Int, _ items: [String]) -> String {} + + struct ForEach where Data : RandomAccessCollection { + init(_ data: Data, content: (Data.Element) -> String) {} + init(_ data: Data, id: Int, content: (Data.Element) -> String) {} + } + + var searchCategories: [(Int, [String])] + ForEach(searchCategories, #^FUNC_CONVERSION_AFTER_COMPLETION_POS^#id: 0, content: searchSection) +// FUNC_CONVERSION_AFTER_COMPLETION_POS: Begin completions, 2 items +// FUNC_CONVERSION_AFTER_COMPLETION_POS-DAG: Pattern/Local/Flair[ArgLabels]: {#content: ((Int, [String])) -> String##((Int, [String])) -> String#}[#((Int, [String])) -> String#]; +// FUNC_CONVERSION_AFTER_COMPLETION_POS-DAG: Pattern/Local/Flair[ArgLabels]: {#id: Int#}[#Int#]; +// FUNC_CONVERSION_AFTER_COMPLETION_POS: End completions +} + +func testPlaceholderNoBetterThanArchetype() { + // The following test case used to pose a problem because for the `init(header:footer:content)` overload, received receive an opaque type (i.e. an archetype) for `Content` whereas for the `init(header:content:)` overload we are unable to infer it and thus assign a placeholder type to `Content`. Placeholder types were mistakingly ranked higher than archetypes, which made us prefer the `init(header:content:)` overload. + protocol View {} + + struct Section { + init(header: TectionHeaderView, footer: String, content: () -> Content) {} + init(header: TectionHeaderView, content: () -> Content) {} + } + + struct TectionHeaderView { + init(text: String) {} + } + + struct Text: View { + init(_ content: String) {} + func sag(_ tag: String) -> some View { return self } + } + + Section(header: TectionHeaderView(text: "abc"), #^PLACEHOLDER_NO_BETTER_THAN_ARCHETYPE_1^#) { + Text("abc").sag("abc") + } +// PLACEHOLDER_NO_BETTER_THAN_ARCHETYPE_1: Begin completions, 2 items +// PLACEHOLDER_NO_BETTER_THAN_ARCHETYPE_1-DAG: Pattern/Local/Flair[ArgLabels]: {#footer: String#}[#String#]; +// PLACEHOLDER_NO_BETTER_THAN_ARCHETYPE_1-DAG: Pattern/Local/Flair[ArgLabels]: {#content: () -> _##() -> _#}[#() -> _#]; +// PLACEHOLDER_NO_BETTER_THAN_ARCHETYPE_1: End completions + + Section(header: TectionHeaderView(text: "abc"), #^PLACEHOLDER_NO_BETTER_THAN_ARCHETYPE_2?check=PLACEHOLDER_NO_BETTER_THAN_ARCHETYPE_1^#) + +} + +func testConversionBetterThanIgnoreArgs() { + enum Letters { + case a, b, c + } + + enum Numbers { + case one, two, three + } + + func consumeLetterOrNumber(_ letter: Letters) {} + func consumeLetterOrNumber(_ number: Numbers, _ someOtherArg: Int?) {} + + consumeLetterOrNumber(.#^CONVERSION_NO_BETTER_THAN_IGNORE_ARGS^#one, 32) +} + +// CONVERSION_NO_BETTER_THAN_IGNORE_ARGS: Begin completions +// CONVERSION_NO_BETTER_THAN_IGNORE_ARGS-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: one[#Numbers#]; +// CONVERSION_NO_BETTER_THAN_IGNORE_ARGS-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: two[#Numbers#]; +// CONVERSION_NO_BETTER_THAN_IGNORE_ARGS-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: three[#Numbers#]; +// CONVERSION_NO_BETTER_THAN_IGNORE_ARGS: End completions + +func testDynamicMemberSubscriptLookup() { + struct MyStruct { + subscript(_ index: Int) -> Int { + return index + } + } + + @dynamicMemberLookup public struct Binding { + subscript(dynamicMember keyPath: WritableKeyPath) -> Binding { + fatalError() + } + } + + struct Foo { + var bar: Binding + + func test(index: Int) { + _ = bar[#^DYNAMIC_MEMBER_SUBSCRIPT_LOOKUP?xfail=rdar90363138^#index] + } + } +} + +// DYNAMIC_MEMBER_SUBSCRIPT_LOOKUP: Begin completions +// DYNAMIC_MEMBER_SUBSCRIPT_LOOKUP-DAG: Decl[LocalVar]/Local/TypeRelation[Identical]: index[#Int#]; name=index +// DYNAMIC_MEMBER_SUBSCRIPT_LOOKUP-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath, Value>#}[']'][#Value#]; name=keyPath: +// DYNAMIC_MEMBER_SUBSCRIPT_LOOKUP: End completions diff --git a/test/IDE/complete_enum_unresolved_dot_argument_labels.swift b/test/IDE/complete_enum_unresolved_dot_argument_labels.swift index cad2f79bd602e..bbdd6b39cc1a1 100644 --- a/test/IDE/complete_enum_unresolved_dot_argument_labels.swift +++ b/test/IDE/complete_enum_unresolved_dot_argument_labels.swift @@ -1,15 +1,55 @@ -// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=COMPLETE | %FileCheck %s +// RUN: %empty-directory(%t) +// RUN: %target-swift-ide-test -batch-code-completion -source-filename %s -filecheck %raw-FileCheck -completion-output-dir %t enum DragState { case inactive - case dragging(translation: Int) + case dragging(translationX: Int, translationY: Int) + case defaulted(x: Int = 2, y: Int = 3, z: Int, extra: Int) } -func foo() { +func testInit() { var state = DragState.inactive - state = .dragging(#^COMPLETE^# + state = .dragging(#^SIGNATURE^#) + // SIGNATURE: Begin completions, 1 item + // SIGNATURE: Pattern/CurrModule/Flair[ArgLabels]/TypeRelation[Identical]: ['(']{#translationX: Int#}, {#translationY: Int#}[')'][#DragState#]; + // SIGNATURE: End completions + + state = .dragging(translationX: 2, #^ARGUMENT^#) + // ARGUMENT: Begin completions, 1 item + // ARGUMENT: Pattern/Local/Flair[ArgLabels]: {#translationY: Int#}[#Int#]; + // ARGUMENT: End completions + + state = .defaulted(#^DEFAULTED^#) + // DEFAULTED: Begin completions, 1 items + // DEFAULTED: Pattern/CurrModule/Flair[ArgLabels]/TypeRelation[Identical]: ['(']{#x: Int#}, {#y: Int#}, {#z: Int#}, {#extra: Int#}[')'][#DragState#]; + // DEFAULTED: End completions + + state = .defaulted(x: 1, #^DEFAULTEDARG^#) + state = .defaulted(x: "Wrong type", #^ERRORTYPE?check=DEFAULTEDARG^#) + // DEFAULTEDARG: Begin completions, 2 items + // DEFAULTEDARG: Pattern/Local/Flair[ArgLabels]: {#y: Int#}[#Int#]; + // DEFAULTEDARG: Pattern/Local/Flair[ArgLabels]: {#z: Int#}[#Int#]; + // DEFAULTEDARG: End completions + + state = .defaulted(wrongLabel: 2, #^ERRORARG^#) + // ERRORARG: Begin completions, 3 items + // ERRORARG: Pattern/Local/Flair[ArgLabels]: {#x: Int#}[#Int#]; + // ERRORARG: Pattern/Local/Flair[ArgLabels]: {#y: Int#}[#Int#]; + // ERRORARG: Pattern/Local/Flair[ArgLabels]: {#z: Int#}[#Int#]; } -// CHECK: Begin completions, 1 item -// CHECK: Pattern/CurrModule/Flair[ArgLabels]/TypeRelation[Identical]: ['(']{#translation: Int#}[')'][#DragState#]; -// CHECK: End completions +func testMatch() { + var state = DragState.inactive + let localInt = 42 + switch state { + case .dragging(translationX: 2, #^MATCH_ARGY^#): + // MATCH_ARGY: Begin completions + // FIXME: This should have an identical type relation + // MATCH_ARGY: Decl[LocalVar]/Local/TypeRelation[Identical]: localInt[#Int#]; name=localInt + // FIXME: We should offer 'translationY:' (without any flair since it's optional), `let translationY`, and `_` + // MATCH_ARGY: End completions + break + default: + break + } +} diff --git a/test/IDE/complete_subscript.swift b/test/IDE/complete_subscript.swift index 0e4c527fad4c5..d5ea97656488c 100644 --- a/test/IDE/complete_subscript.swift +++ b/test/IDE/complete_subscript.swift @@ -17,6 +17,7 @@ // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=LABELED_SUBSCRIPT | %FileCheck %s -check-prefix=LABELED_SUBSCRIPT // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=TUPLE | %FileCheck %s -check-prefix=TUPLE +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=SETTABLE_SUBSCRIPT | %FileCheck %s -check-prefix=SETTABLE_SUBSCRIPT struct MyStruct { static subscript(x: Int, static defValue: T) -> MyStruct { @@ -63,7 +64,7 @@ func test1() { let _ = MyStruct()[#^INSTANCE_INT_BRACKET^# // INSTANCE_INT_BRACKET: Begin completions // INSTANCE_INT_BRACKET-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]: ['[']{#(x): Int#}, {#instance: Int#}[']'][#Int#]; -// INSTANCE_INT_BRACKET-DAG: Pattern/CurrModule/Flair[ArgLabels]: ['[']{#keyPath: KeyPath, Value>#}[']'][#Value#]; +// INSTANCE_INT_BRACKET-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath, Value>#}[']'][#Value#]; // INSTANCE_INT_BRACKET: End completions } func test2(value: MyStruct) { @@ -89,7 +90,7 @@ func test2(value: MyStruct) { let _ = value[#^INSTANCE_ARCHETYPE_BRACKET^# // INSTANCE_ARCHETYPE_BRACKET: Begin completions // INSTANCE_ARCHETYPE_BRACKET-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]: ['[']{#(x): Int#}, {#instance: U#}[']'][#Int#]; -// INSTANCE_ARCHETYPE_BRACKET-DAG: Pattern/CurrModule/Flair[ArgLabels]: ['[']{#keyPath: KeyPath, Value>#}[']'][#Value#]; +// INSTANCE_ARCHETYPE_BRACKET-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath, Value>#}[']'][#Value#]; // INSTANCE_ARCHETYPE_BRACKET: End completions let _ = MyStruct[42, #^METATYPE_LABEL^# @@ -116,26 +117,28 @@ class Derived: Base { // SELF_IN_INSTANCEMETHOD: Begin completions, 3 items // SELF_IN_INSTANCEMETHOD-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]: ['[']{#derivedInstance: Int#}[']'][#Int#]; // SELF_IN_INSTANCEMETHOD-DAG: Decl[Subscript]/Super/Flair[ArgLabels]: ['[']{#instance: Int#}[']'][#Int#]; -// SELF_IN_INSTANCEMETHOD-DAG: Pattern/CurrModule/Flair[ArgLabels]: ['[']{#keyPath: KeyPath#}[']'][#Value#]; +// SELF_IN_INSTANCEMETHOD-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath#}[']'][#Value#]; // SELF_IN_INSTANCEMETHOD: End completions let _ = super[#^SUPER_IN_INSTANCEMETHOD^#] // SUPER_IN_INSTANCEMETHOD: Begin completions, 2 items // SUPER_IN_INSTANCEMETHOD-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]: ['[']{#instance: Int#}[']'][#Int#]; -// SUPER_IN_INSTANCEMETHOD-DAG: Pattern/CurrModule/Flair[ArgLabels]: ['[']{#keyPath: KeyPath#}[']'][#Value#]; +// SUPER_IN_INSTANCEMETHOD-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath#}[']'][#Value#]; // SUPER_IN_INSTANCEMETHOD: End completions } static func testStatic() { let _ = self[#^SELF_IN_STATICMETHOD^#] -// SELF_IN_STATICMETHOD: Begin completions, 2 items +// SELF_IN_STATICMETHOD: Begin completions, 3 items // SELF_IN_STATICMETHOD-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]: ['[']{#derivedStatic: Int#}[']'][#Int#]; // SELF_IN_STATICMETHOD-DAG: Decl[Subscript]/Super/Flair[ArgLabels]: ['[']{#static: Int#}[']'][#Int#]; +// SELF_IN_STATICMETHOD-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath#}[']'][#Value#]; // SELF_IN_STATICMETHOD: End completions let _ = super[#^SUPER_IN_STATICMETHOD^#] -// SUPER_IN_STATICMETHOD: Begin completions, 1 items +// SUPER_IN_STATICMETHOD: Begin completions, 2 items // SUPER_IN_STATICMETHOD-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]: ['[']{#static: Int#}[']'][#Int#]; +// SUPER_IN_STATICMETHOD-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath#}[']'][#Value#]; // SUPER_IN_STATICMETHOD: End completions } } @@ -147,13 +150,30 @@ func testSubscriptCallSig(val: MyStruct1) { val[#^LABELED_SUBSCRIPT^# // LABELED_SUBSCRIPT: Begin completions, 2 items // LABELED_SUBSCRIPT-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]: ['[']{#idx1: Int#}, {#idx2: Comparable#}[']'][#Int!#]; -// LABELED_SUBSCRIPT-DAG: Pattern/CurrModule/Flair[ArgLabels]: ['[']{#keyPath: KeyPath, Value>#}[']'][#Value#]; +// LABELED_SUBSCRIPT-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath, Value>#}[']'][#Value#]; // LABELED_SUBSCRIPT: End completions } func testSubcscriptTuple(val: (x: Int, String)) { val[#^TUPLE^#] // TUPLE: Begin completions, 1 items -// TUPLE-DAG: Pattern/CurrModule/Flair[ArgLabels]: ['[']{#keyPath: KeyPath<(x: Int, String), Value>#}[']'][#Value#]; +// TUPLE-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath<(x: Int, String), Value>#}[']'][#Value#]; // TUPLE: End completions } + +struct HasSettableSub { + subscript(a: String) -> Any { + get { return 1 } + set { } + } +} + +func testSettableSub(x: inout HasSettableSub) { + let local = "some string" + x[#^SETTABLE_SUBSCRIPT^#] = 32 +} +// 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[LocalVar]/Local/TypeRelation[Identical]: local[#String#]; name=local +// SETTABLE_SUBSCRIPT: End completions diff --git a/test/IDE/complete_swift_key_path.swift b/test/IDE/complete_swift_key_path.swift index 8340122ee2186..5e19f81300163 100644 --- a/test/IDE/complete_swift_key_path.swift +++ b/test/IDE/complete_swift_key_path.swift @@ -32,7 +32,9 @@ // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CONTEXT_FUNC_INOUT | %FileCheck %s -check-prefix=PERSONTYPE-DOT // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CONTEXT_FUNC_VARIADIC | %FileCheck %s -check-prefix=ARRAYTYPE-DOT -// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=GENERIC_KEY_PATH_BASE | %FileCheck %s -check-prefix=PERSONTYPE-DOT +// FIXME: We need the argument after the code completion token to determine the base of the key path in this test case. But since we ignore the types of arguments after the code completion token, we can't determine the base type. +// DISABLED: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=GENERIC_KEY_PATH_BASE | %FileCheck %s -check-prefix=PERSONTYPE-DOT + // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=GENERIC_KEY_PATH_RESULT | %FileCheck %s -check-prefix=PERSONTYPE-DOT // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=COMPLETE_AFTER_SELF | %FileCheck %s -check-prefix=OBJ-NODOT diff --git a/test/IDE/complete_unresolved_members.swift b/test/IDE/complete_unresolved_members.swift index 0cc2bb602ed85..8fbf88d5045ff 100644 --- a/test/IDE/complete_unresolved_members.swift +++ b/test/IDE/complete_unresolved_members.swift @@ -216,7 +216,18 @@ OptionSetTaker5(.Option1, .Option4, .#^UNRESOLVED_13?check=UNRESOLVED_3^#, .West OptionSetTaker5(.#^UNRESOLVED_14?check=UNRESOLVED_1^#, .Option4, .South, .West) OptionSetTaker5([.#^UNRESOLVED_15?check=UNRESOLVED_1^#], .Option4, .South, .West) -OptionSetTaker6(.#^UNRESOLVED_16?check=UNRESOLVED_4^#, .Option4) +OptionSetTaker6(.#^UNRESOLVED_16^#, .Option4) +// UNRESOLVED_16: Begin completions +// UNRESOLVED_16-DAG: Decl[StaticVar]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: Option1[#SomeOptions1#]; +// UNRESOLVED_16-DAG: Decl[StaticVar]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: Option2[#SomeOptions1#]; +// UNRESOLVED_16-DAG: Decl[StaticVar]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: Option3[#SomeOptions1#]; +// UNRESOLVED_16-DAG: Decl[StaticVar]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: Option4[#SomeOptions2#]; +// UNRESOLVED_16-DAG: Decl[StaticVar]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: Option5[#SomeOptions2#]; +// UNRESOLVED_16-DAG: Decl[StaticVar]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: Option6[#SomeOptions2#]; +// UNRESOLVED_16-DAG: Decl[StaticVar]/CurrNominal: NotOption[#Int#]; name=NotOption +// UNRESOLVED_16: End completion + + OptionSetTaker6(.Option4, .#^UNRESOLVED_17?check=UNRESOLVED_4^#,) var a = {() in diff --git a/validation-test/IDE/crashers_2_fixed/0026-completion-type-not-part-of-solution.swift b/validation-test/IDE/crashers_2_fixed/0026-completion-type-not-part-of-solution.swift new file mode 100644 index 0000000000000..28d907854b95d --- /dev/null +++ b/validation-test/IDE/crashers_2_fixed/0026-completion-type-not-part-of-solution.swift @@ -0,0 +1,17 @@ +// RUN: %swift-ide-test -code-completion -code-completion-token=COMPLETE -source-filename=%s + +struct Image {} + +@resultBuilder struct ViewBuilder2 { + static func buildBlock(_ content: Content) -> Content { fatalError() } +} + +struct NavigationLink { + init(@ViewBuilder2 destination: () -> Destination) + init(_ title: String) +} + +struct CollectionRowView { + func foo() { + NavigationLink() { + Image().#^COMPLETE^# diff --git a/validation-test/IDE/crashers_2_fixed/0027-autoclosure-curry-thunk.swift b/validation-test/IDE/crashers_2_fixed/0027-autoclosure-curry-thunk.swift new file mode 100644 index 0000000000000..07748f564169d --- /dev/null +++ b/validation-test/IDE/crashers_2_fixed/0027-autoclosure-curry-thunk.swift @@ -0,0 +1,26 @@ +// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token COMPLETE + +struct MyView { + func pnAppear(perform action: (() -> Void)) -> MyView { + return self + } + func qadding(_ edges: Int, _ length: Int) -> MyView { + return self + } +} + +@resultBuilder struct ViewBuilder { + static func buildBlock(_ content: MyView) -> MyView { + return content + } +} + +struct AdaptsToSoftwareKeyboard { + @ViewBuilder func body(content: MyView) -> MyView { + content + .qadding(1234567, #^COMPLETE^#0) + .pnAppear(perform: subscribeToKeyboardEvents) + } + + func subscribeToKeyboardEvents() {} +} diff --git a/validation-test/IDE/crashers_2_fixed/0028-member-reference-error-type.swift b/validation-test/IDE/crashers_2_fixed/0028-member-reference-error-type.swift new file mode 100644 index 0000000000000..cc4108a7e70eb --- /dev/null +++ b/validation-test/IDE/crashers_2_fixed/0028-member-reference-error-type.swift @@ -0,0 +1,9 @@ +// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token COMPLETE + +struct PopularityBadge { + init( + + +#if DEBUG +var previews { + PopularityBadge(score: #^COMPLETE^# diff --git a/validation-test/IDE/crashers_2_fixed/0029-closure-implicit-return.swift b/validation-test/IDE/crashers_2_fixed/0029-closure-implicit-return.swift new file mode 100644 index 0000000000000..39d5d8d5bd654 --- /dev/null +++ b/validation-test/IDE/crashers_2_fixed/0029-closure-implicit-return.swift @@ -0,0 +1,32 @@ +// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token COMPLETE + +public enum MyEnum { + case one + case two +} + +struct MyStruct { + func takesClosure(_ action: (() -> Void)) -> MyStruct { + return self + } + + func generic(_ keyPath: V, _ value: MyEnum) -> MyStruct { + return self + } +} + +@resultBuilder struct MyBuilder { + static func buildBlock(_ content: MyStruct) -> MyStruct { + return content + } +} + +struct ItemDetailView { + @MyBuilder var body: MyStruct { + MyStruct() + .generic("xorizontalSizeClass", .#^COMPLETE^#regular) + .takesClosure { + "abc" + } + } +} diff --git a/validation-test/IDE/crashers_2_fixed/0030-arg-completion-no-locator.swift b/validation-test/IDE/crashers_2_fixed/0030-arg-completion-no-locator.swift new file mode 100644 index 0000000000000..13409d6d32add --- /dev/null +++ b/validation-test/IDE/crashers_2_fixed/0030-arg-completion-no-locator.swift @@ -0,0 +1,15 @@ +// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token COMPLETE + +@dynamicMemberLookup public struct Binding { + subscript(dynamicMember keyPath: WritableKeyPath) -> Binding { + fatalError() + } +} + +struct Foo { + var bar: Binding<[String]> + + func test(index: Int) { + _ = bar[#^COMPLETE^#index] + } +} diff --git a/validation-test/IDE/crashers_2_fixed/0032-constructor-call-in-result-builder.swift b/validation-test/IDE/crashers_2_fixed/0032-constructor-call-in-result-builder.swift new file mode 100644 index 0000000000000..e487ed2bbbcd6 --- /dev/null +++ b/validation-test/IDE/crashers_2_fixed/0032-constructor-call-in-result-builder.swift @@ -0,0 +1,15 @@ +// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token COMPLETE + +@resultBuilder public struct ViewBuilder { + static func buildBlock(_ content: TextField) -> TextField { fatalError() } +} + +struct TextField { + init(_ title: String, text: String) {} +} + +struct EncodedView { + @ViewBuilder var body: TextField { + TextField("Encoded", #^COMPLETE^#) + } +} diff --git a/validation-test/IDE/crashers_2_fixed/0033-dont-optimize-constraints-for-ignored-args.swift b/validation-test/IDE/crashers_2_fixed/0033-dont-optimize-constraints-for-ignored-args.swift new file mode 100644 index 0000000000000..c5db6d2696700 --- /dev/null +++ b/validation-test/IDE/crashers_2_fixed/0033-dont-optimize-constraints-for-ignored-args.swift @@ -0,0 +1,7 @@ +// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token COMPLETE + +func foo() { + bar { + .dragging(arg1: #^COMPLETE^#, arg2: drag ?? .zero) + } +} diff --git a/validation-test/IDE/crashers_2_fixed/0033-trailing-closure-arg-label-matching.swift b/validation-test/IDE/crashers_2_fixed/0033-trailing-closure-arg-label-matching.swift new file mode 100644 index 0000000000000..a54abec7a41fd --- /dev/null +++ b/validation-test/IDE/crashers_2_fixed/0033-trailing-closure-arg-label-matching.swift @@ -0,0 +1,11 @@ +// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token COMPLETE | %FileCheck %s + +func foo() { + _ = sink { items in }#^COMPLETE^# +} + +func sink(receiveCompletion: (Int) -> Void, receiveValue: (Int) -> Void) { fatalError() } + +// CHECK: Begin completions, 1 items +// CHECK-DAG: Pattern/Local/Flair[ArgLabels]: {#receiveValue: (Int) -> Void {<#Int#> in|}#}[#(Int) -> Void#]; +// CHECK: End completions