Files
swift-mirror/lib/IDE/ArgumentCompletion.cpp
Alex Hoppen 9dd87e9c4e [CodeCompletion] Fix ASAN failure when completing in parameter packs
Whe completing in parameter packs, we were calling `getParameterAt` with `Res.FuncDeclRef`. But the substitution map in `Res.FuncDeclRef` contained type variables that were allocated in the constraint system’s arena. And that arena had been freed when we call this from `deliverResults`.

The fix is to compute the optional parameters in advance in `sawSolutionImpl`

rdar://109093909
2023-05-10 15:24:19 -07:00

396 lines
14 KiB
C++

//===--- 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/IDE/SelectedOverloadInfo.h"
#include "swift/Sema/ConstraintSystem.h"
#include "swift/Sema/IDETypeChecking.h"
using namespace swift;
using namespace swift::ide;
using namespace swift::constraints;
bool ArgumentTypeCheckCompletionCallback::addPossibleParams(
const ArgumentTypeCheckCompletionCallback::Result &Res,
SmallVectorImpl<PossibleParamInfo> &Params, SmallVectorImpl<Type> &Types) {
if (!Res.ParamIdx || !Res.FuncTy) {
// 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<AnyFunctionType::Param> ParamsToPass = Res.FuncTy->getParams();
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 *TypeParam = &ParamsToPass[Idx];
bool Required = !Res.DeclParamIsOptional[Idx];
if (TypeParam->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(TypeParam, 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(TypeParam->getPlainType());
}
if (Required) {
// The user should only be suggested the first required param. Stop.
break;
}
}
return ShowGlobalCompletions;
}
/// Applies heuristic to determine whether the result type of \p E is
/// unconstrained, that is if the constraint system is satisfiable for any
/// result type of \p E.
static bool isExpressionResultTypeUnconstrained(const Solution &S, Expr *E) {
ConstraintSystem &CS = S.getConstraintSystem();
if (auto ParentExpr = CS.getParentExpr(E)) {
if (auto Assign = dyn_cast<AssignExpr>(ParentExpr)) {
if (isa<DiscardAssignmentExpr>(Assign->getDest())) {
// _ = <expr> is unconstrained
return true;
}
} else if (isa<RebindSelfInConstructorExpr>(ParentExpr)) {
// super.init() is unconstrained (it always produces the correct result
// by definition)
return true;
}
}
auto target = S.getTargetFor(E);
if (!target)
return false;
assert(target->kind == SyntacticElementTarget::Kind::expression);
switch (target->getExprContextualTypePurpose()) {
case CTP_Unused:
// If we aren't using the contextual type, its unconstrained by definition.
return true;
case CTP_Initialization: {
// let x = <expr> is unconstrained
auto contextualType = target->getExprContextualType();
return !contextualType || contextualType->is<UnresolvedType>();
}
default:
// Assume that it's constrained by default.
return false;
}
}
void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) {
Type ExpectedTy = getTypeForCompletion(S, CompletionExpr);
auto &CS = S.getConstraintSystem();
Expr *ParentCall = CompletionExpr;
while (ParentCall && ParentCall->getArgs() == nullptr) {
ParentCall = CS.getParentExpr(ParentCall);
}
if (auto TV = S.getType(CompletionExpr)->getAs<TypeVariableType>()) {
auto Locator = TV->getImpl().getLocator();
if (Locator->isLastElement<LocatorPathElt::PatternMatch>()) {
// The code completion token is inside a pattern, which got rewritten from
// a call by ResolvePattern. Thus, we aren't actually inside a call.
// Rest 'ParentCall' to nullptr to reflect that.
ParentCall = nullptr;
}
}
if (!ParentCall || ParentCall == CompletionExpr) {
// We might not have a call that contains the code completion expression if
// we type-checked the fallback code completion expression that only
// contains the code completion token, but not the surrounding call.
return;
}
auto ArgInfo = getCompletionArgInfo(ParentCall, CS);
if (!ArgInfo) {
assert(false && "bad parent call match?");
return;
}
auto ArgIdx = ArgInfo->completionIdx;
Type ExpectedCallType;
if (!isExpressionResultTypeUnconstrained(S, ParentCall)) {
ExpectedCallType = getTypeForCompletion(S, ParentCall);
}
auto *CallLocator = CS.getConstraintLocator(ParentCall);
auto *CalleeLocator = S.getCalleeLocator(CallLocator);
auto Info = getSelectedOverloadInfo(S, CalleeLocator);
if (Info.getValue() && Info.getValue()->shouldHideFromEditor()) {
return;
}
// Disallow invalid initializer references
for (auto Fix : S.Fixes) {
if (Fix->getLocator() == CalleeLocator &&
Fix->getKind() == FixKind::AllowInvalidInitRef) {
return;
}
}
// 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<unsigned> ParamIdx;
std::set<unsigned> 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);
}
}
}
bool HasLabel = false;
if (auto PE = CS.getParentExpr(CompletionExpr)) {
if (auto Args = PE->getArgs()) {
HasLabel = !Args->getLabel(ArgIdx).empty();
}
}
bool IsAsync = isContextAsync(S, DC);
// If this is a duplicate of any other result, ignore this solution.
if (llvm::any_of(Results, [&](const Result &R) {
return R.FuncD == Info.getValue() &&
nullableTypesEqual(R.FuncTy, Info.ValueTy) &&
nullableTypesEqual(R.BaseType, Info.BaseTy) &&
R.ParamIdx == ParamIdx &&
R.IsNoninitialVariadic == IsNoninitialVariadic;
})) {
return;
}
llvm::SmallDenseMap<const VarDecl *, Type> SolutionSpecificVarTypes;
getSolutionSpecificVarTypes(S, SolutionSpecificVarTypes);
AnyFunctionType *FuncTy = nullptr;
if (Info.ValueTy) {
FuncTy = Info.ValueTy->lookThroughAllOptionalTypes()->getAs<AnyFunctionType>();
}
// Determine which parameters are optional. We need to do this in
// `sawSolutionImpl` because it accesses the substitution map in
// `Info.ValueRef`. This substitution map might contain type variables that
// are allocated in the constraint system's arena and are freed once we reach
// `deliverResults`.
llvm::BitVector DeclParamIsOptional;
if (FuncTy) {
ArrayRef<AnyFunctionType::Param> ParamsToPass = FuncTy->getParams();
for (auto Idx : range(0, ParamsToPass.size())) {
bool Optional = false;
if (Info.ValueRef) {
if (const ParamDecl *DeclParam = getParameterAt(Info.ValueRef, Idx)) {
Optional |= DeclParam->isDefaultArgument();
Optional |= DeclParam->getType()->is<PackExpansionType>();
}
}
const AnyFunctionType::Param *TypeParam = &ParamsToPass[Idx];
Optional |= TypeParam->isVariadic();
DeclParamIsOptional.push_back(Optional);
}
}
Results.push_back({ExpectedTy, ExpectedCallType,
isa<SubscriptExpr>(ParentCall), Info.getValue(), FuncTy,
ArgIdx, ParamIdx, std::move(ClaimedParams),
IsNoninitialVariadic, Info.BaseTy, HasLabel, IsAsync,
DeclParamIsOptional, SolutionSpecificVarTypes});
}
void ArgumentTypeCheckCompletionCallback::computeShadowedDecls(
SmallPtrSetImpl<ValueDecl *> &ShadowedDecls) {
for (size_t i = 0; i < Results.size(); ++i) {
auto &ResultA = Results[i];
for (size_t j = i + 1; j < Results.size(); ++j) {
auto &ResultB = Results[j];
if (!ResultA.FuncD || !ResultB.FuncD || !ResultA.FuncTy ||
!ResultB.FuncTy) {
continue;
}
if (ResultA.FuncD->getName() != ResultB.FuncD->getName()) {
continue;
}
if (!ResultA.FuncTy->isEqual(ResultB.FuncTy)) {
continue;
}
ProtocolDecl *inProtocolExtensionA =
ResultA.FuncD->getDeclContext()->getExtendedProtocolDecl();
ProtocolDecl *inProtocolExtensionB =
ResultB.FuncD->getDeclContext()->getExtendedProtocolDecl();
if (inProtocolExtensionA && !inProtocolExtensionB) {
ShadowedDecls.insert(ResultA.FuncD);
} else if (!inProtocolExtensionA && inProtocolExtensionB) {
ShadowedDecls.insert(ResultB.FuncD);
}
}
}
}
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);
SmallPtrSet<ValueDecl *, 4> ShadowedDecls;
computeShadowedDecls(ShadowedDecls);
// Perform global completion as a fallback if we don't have any results.
bool shouldPerformGlobalCompletion = Results.empty();
SmallVector<Type, 4> ExpectedCallTypes;
for (auto &Result : Results) {
ExpectedCallTypes.push_back(Result.ExpectedCallType);
}
SmallVector<Type, 8> ExpectedTypes;
if (IncludeSignature && !Results.empty()) {
Lookup.setHaveLParen(true);
Lookup.setExpectedTypes(ExpectedCallTypes,
/*isImplicitSingleExpressionReturn=*/false);
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<TupleType>() || BaseTy->is<SubstitutableType>()) {
SemanticContext = SemanticContextKind::CurrentNominal;
}
}
if (SemanticContext == SemanticContextKind::None && Result.FuncD) {
if (Result.FuncD->getDeclContext()->isTypeContext()) {
SemanticContext = SemanticContextKind::CurrentNominal;
} else if (Result.FuncD->getDeclContext()->isLocalContext()) {
SemanticContext = SemanticContextKind::Local;
} else if (Result.FuncD->getModuleContext() == DC->getParentModule()) {
SemanticContext = SemanticContextKind::CurrentModule;
}
}
if (Result.FuncTy) {
if (auto FuncTy = Result.FuncTy) {
if (ShadowedDecls.count(Result.FuncD) == 0) {
// Don't show call pattern completions if the function is
// overridden.
if (Result.IsSubscript) {
assert(SemanticContext != SemanticContextKind::None);
auto *SD = dyn_cast_or_null<SubscriptDecl>(Result.FuncD);
Lookup.addSubscriptCallPattern(FuncTy, SD, SemanticContext);
} else {
auto *FD = dyn_cast_or_null<AbstractFunctionDecl>(Result.FuncD);
Lookup.addFunctionCallPattern(FuncTy, FD, SemanticContext);
}
}
}
}
}
Lookup.setHaveLParen(false);
shouldPerformGlobalCompletion |=
!Lookup.FoundFunctionCalls || Lookup.FoundFunctionsWithoutFirstKeyword;
} else if (!Results.empty()) {
SmallVector<PossibleParamInfo, 8> Params;
for (auto &Ret : Results) {
shouldPerformGlobalCompletion |=
addPossibleParams(Ret, Params, ExpectedTypes);
}
Lookup.addCallArgumentCompletionResults(Params);
}
if (shouldPerformGlobalCompletion) {
llvm::SmallDenseMap<const VarDecl *, Type> SolutionSpecificVarTypes;
if (!Results.empty()) {
SolutionSpecificVarTypes = Results[0].SolutionSpecificVarTypes;
}
WithSolutionSpecificVarTypesRAII VarTypes(SolutionSpecificVarTypes);
for (auto &Result : Results) {
ExpectedTypes.push_back(Result.ExpectedType);
Lookup.setSolutionSpecificVarTypes(Result.SolutionSpecificVarTypes);
}
Lookup.setExpectedTypes(ExpectedTypes, false);
bool IsInAsyncContext = llvm::any_of(
Results, [](const Result &Res) { return Res.IsInAsyncContext; });
Lookup.setCanCurrDeclContextHandleAsync(IsInAsyncContext);
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);
}