[IDE] Simplify isImplicitSingleExpressionReturn

Use the generalized implied result logic, and
rename to `isImpliedResult` since that's really
what we're querying here, and it needs to handle
implicit-last-exprs if enabled.
This commit is contained in:
Hamish Knight
2024-02-06 14:14:27 +00:00
parent 61a4148925
commit d7fc22aaca
16 changed files with 66 additions and 94 deletions

View File

@@ -26,7 +26,7 @@ namespace ide {
class AfterPoundExprCompletion : public TypeCheckCompletionCallback {
struct Result {
Type ExpectedTy;
bool IsImplicitSingleExpressionReturn;
bool IsImpliedResult;
/// Whether the surrounding context is async and thus calling async
/// functions is supported.

View File

@@ -39,10 +39,10 @@ public:
/// There is no known contextual type. All types are equally good.
None,
/// There is a contextual type from a single-expression closure/function
/// body. The context is a hint, and enables unresolved member completion,
/// but should not hide any results.
SingleExpressionBody,
/// There is a contextual type from e.g a single-expression closure/function
/// body, where the return is implied. The context is a hint, and enables
/// unresolved member completion, but should not hide any results.
Implied,
/// There are known contextual types, or there aren't but a nonvoid type is
/// expected.

View File

@@ -44,12 +44,12 @@ class ExpectedTypeContext {
/// Pre typechecked type of the expression at the completion position.
Type IdealType;
/// Whether the `ExpectedTypes` comes from a single-expression body, e.g.
/// Whether the `ExpectedTypes` comes from an implied result, e.g.
/// `foo({ here })`.
///
/// Since the input may be incomplete, we take into account that the types are
/// only a hint.
bool IsImplicitSingleExpressionReturn = false;
bool IsImpliedResult = false;
bool PreferNonVoid = false;
/// If not empty, \c PossibleTypes are ignored and types that have an
@@ -86,7 +86,7 @@ public:
if (!IdealType || !Other.IdealType || !IdealType->isEqual(Other.IdealType)) {
IdealType = Type();
}
IsImplicitSingleExpressionReturn |= Other.IsImplicitSingleExpressionReturn;
IsImpliedResult |= Other.IsImpliedResult;
PreferNonVoid &= Other.PreferNonVoid;
ExpectedCustomAttributeKinds |= Other.ExpectedCustomAttributeKinds;
}
@@ -96,7 +96,7 @@ public:
void setIdealType(Type IdealType) { this->IdealType = IdealType; }
bool requiresNonVoid() const {
if (IsImplicitSingleExpressionReturn)
if (IsImpliedResult)
return false;
if (PreferNonVoid)
return true;
@@ -105,13 +105,12 @@ public:
return llvm::all_of(PossibleTypes, [](Type Ty) { return !Ty->isVoid(); });
}
bool isImplicitSingleExpressionReturn() const {
return IsImplicitSingleExpressionReturn;
bool isImpliedResult() const {
return IsImpliedResult;
}
void
setIsImplicitSingleExpressionReturn(bool IsImplicitSingleExpressionReturn) {
this->IsImplicitSingleExpressionReturn = IsImplicitSingleExpressionReturn;
void setIsImpliedResult(bool IsImpliedResult) {
this->IsImpliedResult = IsImpliedResult;
}
bool getPreferNonVoid() const { return PreferNonVoid; }

View File

@@ -234,11 +234,9 @@ public:
void setIsStaticMetatype(bool value) { IsStaticMetatype = value; }
void setExpectedTypes(
ArrayRef<Type> Types, bool isImplicitSingleExpressionReturn,
bool preferNonVoid = false,
ArrayRef<Type> Types, bool isImpliedResult, bool preferNonVoid = false,
OptionSet<CustomAttributeKind> expectedCustomAttributeKinds = {}) {
expectedTypeContext.setIsImplicitSingleExpressionReturn(
isImplicitSingleExpressionReturn);
expectedTypeContext.setIsImpliedResult(isImpliedResult);
expectedTypeContext.setPreferNonVoid(preferNonVoid);
expectedTypeContext.setPossibleTypes(Types);
expectedTypeContext.setExpectedCustomAttributeKinds(
@@ -269,8 +267,8 @@ public:
if (expectedTypeContext.empty() &&
!expectedTypeContext.getPreferNonVoid()) {
return CodeCompletionContext::TypeContextKind::None;
} else if (expectedTypeContext.isImplicitSingleExpressionReturn()) {
return CodeCompletionContext::TypeContextKind::SingleExpressionBody;
} else if (expectedTypeContext.isImpliedResult()) {
return CodeCompletionContext::TypeContextKind::Implied;
} else {
return CodeCompletionContext::TypeContextKind::Required;
}

View File

@@ -23,9 +23,9 @@ namespace ide {
class ExprTypeCheckCompletionCallback : public TypeCheckCompletionCallback {
public:
struct Result {
/// If the code completion expression is an implicit return in a
/// If the code completion expression is an implied result, e.g in a
/// single-expression closure.
bool IsImplicitSingleExpressionReturn;
bool IsImpliedResult;
/// Whether the surrounding context is async and thus calling async
/// functions is supported.
@@ -75,7 +75,7 @@ private:
/// If \c AddUnresolvedMemberCompletions is false, the
/// \p UnresolvedMemberBaseType is ignored.
void addResult(
bool IsImplicitSingleExpressionReturn, bool IsInAsyncContext,
bool IsImpliedResult, bool IsInAsyncContext,
Type UnresolvedMemberBaseType,
llvm::SmallDenseMap<const VarDecl *, Type> SolutionSpecificVarTypes);

View File

@@ -50,12 +50,12 @@ class PostfixCompletionCallback : public TypeCheckCompletionCallback {
/// we know that we can't retrieve a value from it anymore.
bool ExpectsNonVoid;
/// If the code completion expression occurs as a single statement in a
/// single-expression closure. In such cases we don't want to disfavor
/// results that produce 'Void' because the user might intend to make the
/// closure a multi-statment closure, in which case this expression is no
/// longer implicitly returned.
bool IsImplicitSingleExpressionReturn;
/// If the code completion expression occurs as e.g a single statement in a
/// single-expression closure, where the return is implied. In such cases
/// we don't want to disfavor results that produce 'Void' because the user
/// might intend to make the closure a multi-statment closure, in which case
/// this expression is no longer implicitly returned.
bool IsImpliedResult;
/// Whether the surrounding context is async and thus calling async
/// functions is supported.

View File

@@ -119,8 +119,9 @@ private:
static void setInterfaceType(VarDecl *VD, Type Ty);
};
/// Whether the given completion expression is the only expression in its
/// containing closure or function body and its value is implicitly returned.
/// Whether the given completion expression is an implied result of a closure
/// or function (e.g in a single-expression closure where the return is
/// implicit).
///
/// If these conditions are met, code completion needs to avoid penalizing
/// completion results that don't match the expected return type when
@@ -128,8 +129,7 @@ private:
/// written by the user, it's possible they intend the single expression not
/// as the return value but merely the first entry in a multi-statement body
/// they just haven't finished writing yet.
bool isImplicitSingleExpressionReturn(constraints::ConstraintSystem &CS,
Expr *CompletionExpr);
bool isImpliedResult(const constraints::Solution &S, Expr *CompletionExpr);
/// Returns \c true iff the decl context \p DC allows calling async functions.
bool isContextAsync(const constraints::Solution &S, DeclContext *DC);

View File

@@ -27,7 +27,7 @@ class UnresolvedMemberTypeCheckCompletionCallback
: public TypeCheckCompletionCallback {
struct Result {
Type ExpectedTy;
bool IsImplicitSingleExpressionReturn;
bool IsImpliedResult;
/// Whether the surrounding context is async and thus calling async
/// functions is supported.

View File

@@ -22,7 +22,6 @@ using namespace swift::constraints;
using namespace swift::ide;
void AfterPoundExprCompletion::sawSolutionImpl(const constraints::Solution &S) {
auto &CS = S.getConstraintSystem();
Type ExpectedTy = getTypeForCompletion(S, CompletionExpr);
bool IsAsync = isContextAsync(S, DC);
@@ -32,8 +31,8 @@ void AfterPoundExprCompletion::sawSolutionImpl(const constraints::Solution &S) {
return R.ExpectedTy->isEqual(ExpectedTy);
};
if (!llvm::any_of(Results, IsEqual)) {
bool SingleExprBody = isImplicitSingleExpressionReturn(CS, CompletionExpr);
Results.push_back({ExpectedTy, SingleExprBody, IsAsync});
bool IsImpliedResult = isImpliedResult(S, CompletionExpr);
Results.push_back({ExpectedTy, IsImpliedResult, IsAsync});
}
}
@@ -50,8 +49,7 @@ void AfterPoundExprCompletion::collectResults(
UnifiedTypeContext.setPreferNonVoid(true);
for (auto &Result : Results) {
Lookup.setExpectedTypes({Result.ExpectedTy},
Result.IsImplicitSingleExpressionReturn,
Lookup.setExpectedTypes({Result.ExpectedTy}, Result.IsImpliedResult,
/*expectsNonVoid=*/true);
Lookup.addPoundAvailable(ParentStmtKind);
Lookup.addObjCPoundKeywordCompletions(/*needPound=*/false);

View File

@@ -346,8 +346,7 @@ void ArgumentTypeCheckCompletionCallback::collectResults(
for (auto &Result : Results) {
if (Result.IncludeSignature) {
Lookup.setHaveLParen(true);
Lookup.setExpectedTypes(ExpectedCallTypes,
/*isImplicitSingleExpressionReturn=*/false);
Lookup.setExpectedTypes(ExpectedCallTypes, /*isImpliedResult=*/false);
auto SemanticContext = SemanticContextKind::None;
NominalTypeDecl *BaseNominal = nullptr;

View File

@@ -1834,8 +1834,7 @@ void CodeCompletionCallbacksImpl::doneParsing(SourceFile *SrcFile) {
ExpectedCustomAttributeKinds |= CustomAttributeKind::DeclMacro;
}
Lookup.setExpectedTypes(/*Types=*/{},
/*isImplicitSingleExpressionReturn=*/false,
Lookup.setExpectedTypes(/*Types=*/{}, /*isImpliedResult=*/false,
/*preferNonVoid=*/false,
ExpectedCustomAttributeKinds);

View File

@@ -428,8 +428,9 @@ calculateMaxTypeRelation(Type Ty, const ExpectedTypeContext &typeContext,
auto Result = TypeRelation::Unrelated;
for (auto expectedTy : typeContext.getPossibleTypes()) {
// Do not use Void type context for a single-expression body, since the
// implicit return does not constrain the expression.
// Do not use Void type context for an implied result such as a
// single-expression closure body, since the implicit return does not
// constrain the expression.
//
// { ... -> () in x } // x can be anything
//
@@ -437,16 +438,15 @@ calculateMaxTypeRelation(Type Ty, const ExpectedTypeContext &typeContext,
//
// { ... -> Int in x } // x must be Int
// { ... -> () in return x } // x must be Void
if (typeContext.isImplicitSingleExpressionReturn() && expectedTy->isVoid())
if (typeContext.isImpliedResult() && expectedTy->isVoid())
continue;
Result = std::max(Result, calculateTypeRelation(Ty, expectedTy, DC));
}
// Map invalid -> unrelated when in a single-expression body, since the
// input may be incomplete.
if (typeContext.isImplicitSingleExpressionReturn() &&
Result == TypeRelation::Invalid)
// Map invalid -> unrelated for an implied result, since the input may be
// incomplete.
if (typeContext.isImpliedResult() && Result == TypeRelation::Invalid)
Result = TypeRelation::Unrelated;
return Result;

View File

@@ -39,8 +39,7 @@ static bool solutionSpecificVarTypesEqual(
bool ExprTypeCheckCompletionCallback::Result::operator==(
const Result &Other) const {
return IsImplicitSingleExpressionReturn ==
Other.IsImplicitSingleExpressionReturn &&
return IsImpliedResult == Other.IsImpliedResult &&
IsInAsyncContext == Other.IsInAsyncContext &&
nullableTypesEqual(UnresolvedMemberBaseType,
Other.UnresolvedMemberBaseType) &&
@@ -59,13 +58,12 @@ void ExprTypeCheckCompletionCallback::addExpectedType(Type ExpectedType) {
}
void ExprTypeCheckCompletionCallback::addResult(
bool IsImplicitSingleExpressionReturn, bool IsInAsyncContext,
Type UnresolvedMemberBaseType,
bool IsImpliedResult, bool IsInAsyncContext, Type UnresolvedMemberBaseType,
llvm::SmallDenseMap<const VarDecl *, Type> SolutionSpecificVarTypes) {
if (!AddUnresolvedMemberCompletions) {
UnresolvedMemberBaseType = Type();
}
Result NewResult = {IsImplicitSingleExpressionReturn, IsInAsyncContext,
Result NewResult = {IsImpliedResult, IsInAsyncContext,
UnresolvedMemberBaseType, SolutionSpecificVarTypes};
if (llvm::is_contained(Results, NewResult)) {
return;
@@ -75,22 +73,18 @@ void ExprTypeCheckCompletionCallback::addResult(
void ExprTypeCheckCompletionCallback::sawSolutionImpl(
const constraints::Solution &S) {
auto &CS = S.getConstraintSystem();
Type ExpectedTy = getTypeForCompletion(S, CompletionExpr);
bool ImplicitReturn = isImplicitSingleExpressionReturn(CS, CompletionExpr);
bool IsImpliedResult = isImpliedResult(S, CompletionExpr);
bool IsAsync = isContextAsync(S, DC);
llvm::SmallDenseMap<const VarDecl *, Type> SolutionSpecificVarTypes;
getSolutionSpecificVarTypes(S, SolutionSpecificVarTypes);
addResult(ImplicitReturn, IsAsync, ExpectedTy, SolutionSpecificVarTypes);
addResult(IsImpliedResult, IsAsync, ExpectedTy, SolutionSpecificVarTypes);
addExpectedType(ExpectedTy);
if (auto PatternMatchType = getPatternMatchType(S, CompletionExpr)) {
addResult(ImplicitReturn, IsAsync, PatternMatchType,
addResult(IsImpliedResult, IsAsync, PatternMatchType,
SolutionSpecificVarTypes);
addExpectedType(PatternMatchType);
}
@@ -111,8 +105,7 @@ void ExprTypeCheckCompletionCallback::collectResults(
for (auto &Result : Results) {
WithSolutionSpecificVarTypesRAII VarTypes(Result.SolutionSpecificVarTypes);
Lookup.setExpectedTypes(ExpectedTypes,
Result.IsImplicitSingleExpressionReturn);
Lookup.setExpectedTypes(ExpectedTypes, Result.IsImpliedResult);
Lookup.setCanCurrDeclContextHandleAsync(Result.IsInAsyncContext);
Lookup.setSolutionSpecificVarTypes(Result.SolutionSpecificVarTypes);

View File

@@ -64,7 +64,7 @@ void PostfixCompletionCallback::Result::merge(const Result &Other,
ExpectedTypes.push_back(OtherExpectedTy);
}
ExpectsNonVoid &= Other.ExpectsNonVoid;
IsImplicitSingleExpressionReturn |= Other.IsImplicitSingleExpressionReturn;
IsImpliedResult |= Other.IsImpliedResult;
IsInAsyncContext |= Other.IsInAsyncContext;
}
@@ -213,8 +213,7 @@ void PostfixCompletionCallback::sawSolutionImpl(
}
}
bool IsImplicitSingleExpressionReturn =
isImplicitSingleExpressionReturn(CS, CompletionExpr);
bool IsImpliedResult = isImpliedResult(S, CompletionExpr);
bool IsInAsyncContext = isContextAsync(S, DC);
llvm::DenseMap<AbstractClosureExpr *, ActorIsolation>
@@ -232,7 +231,7 @@ void PostfixCompletionCallback::sawSolutionImpl(
BaseIsStaticMetaType,
ExpectedTypes,
ExpectsNonVoid,
IsImplicitSingleExpressionReturn,
IsImpliedResult,
IsInAsyncContext,
ClosureActorIsolations
};
@@ -448,8 +447,7 @@ void PostfixCompletionCallback::collectResults(
if (!ProcessedBaseTypes.contains(Result.BaseTy)) {
Lookup.getPostfixKeywordCompletions(Result.BaseTy, BaseExpr);
}
Lookup.setExpectedTypes(Result.ExpectedTypes,
Result.IsImplicitSingleExpressionReturn,
Lookup.setExpectedTypes(Result.ExpectedTypes, Result.IsImpliedResult,
Result.ExpectsNonVoid);
if (isDynamicLookup(Result.BaseTy))
Lookup.setIsDynamicLookup();

View File

@@ -155,20 +155,12 @@ void WithSolutionSpecificVarTypesRAII::setInterfaceType(VarDecl *VD, Type Ty) {
std::move(Ty));
}
bool swift::ide::isImplicitSingleExpressionReturn(ConstraintSystem &CS,
Expr *CompletionExpr) {
Expr *ParentExpr = CS.getParentExpr(CompletionExpr);
if (!ParentExpr)
return CS.getContextualTypePurpose(CompletionExpr) == CTP_ImpliedReturnStmt;
bool swift::ide::isImpliedResult(const Solution &S, Expr *CompletionExpr) {
auto &CS = S.getConstraintSystem();
if (CS.getContextualTypePurpose(CompletionExpr) == CTP_ImpliedReturnStmt)
return true;
if (auto *ParentCE = dyn_cast<ClosureExpr>(ParentExpr)) {
if (ParentCE->hasSingleExpressionBody() &&
ParentCE->getSingleExpressionBody() == CompletionExpr) {
ASTNode Last = ParentCE->getBody()->getLastElement();
return !Last.isStmt(StmtKind::Return) || Last.isImplicit();
}
}
return false;
return S.isImpliedResult(CompletionExpr).has_value();
}
bool swift::ide::isContextAsync(const constraints::Solution &S,

View File

@@ -43,7 +43,7 @@ void UnresolvedMemberTypeCheckCompletionCallback::Result::merge(
ExpectedTy = Other.ExpectedTy;
}
IsImplicitSingleExpressionReturn |= Other.IsImplicitSingleExpressionReturn;
IsImpliedResult |= Other.IsImpliedResult;
IsInAsyncContext |= Other.IsInAsyncContext;
}
@@ -62,17 +62,15 @@ void UnresolvedMemberTypeCheckCompletionCallback::addExprResult(
void UnresolvedMemberTypeCheckCompletionCallback::sawSolutionImpl(
const constraints::Solution &S) {
auto &CS = S.getConstraintSystem();
Type ExpectedTy = getTypeForCompletion(S, CompletionExpr);
bool IsAsync = isContextAsync(S, DC);
// If the type couldn't be determined (e.g. because there isn't any context
// to derive it from), let's not attempt to do a lookup since it wouldn't
// produce any useful results anyway.
if (ExpectedTy) {
bool SingleExprBody = isImplicitSingleExpressionReturn(CS, CompletionExpr);
Result Res = {ExpectedTy, SingleExprBody, IsAsync};
bool IsImpliedResult = isImpliedResult(S, CompletionExpr);
Result Res = {ExpectedTy, IsImpliedResult, IsAsync};
addExprResult(Res);
}
@@ -81,9 +79,8 @@ void UnresolvedMemberTypeCheckCompletionCallback::sawSolutionImpl(
return R.ExpectedTy->isEqual(PatternType);
};
if (!llvm::any_of(EnumPatternTypes, IsEqual)) {
EnumPatternTypes.push_back({PatternType,
/*IsImplicitSingleExpressionReturn=*/false,
IsAsync});
EnumPatternTypes.push_back(
{PatternType, /*isImpliedResult=*/false, IsAsync});
}
}
}
@@ -106,8 +103,7 @@ void UnresolvedMemberTypeCheckCompletionCallback::collectResults(
originalTypes.insert(Result.ExpectedTy->getCanonicalType());
for (auto &Result : ExprResults) {
Lookup.setExpectedTypes({Result.ExpectedTy},
Result.IsImplicitSingleExpressionReturn,
Lookup.setExpectedTypes({Result.ExpectedTy}, Result.IsImpliedResult,
/*expectsNonVoid*/ true);
Lookup.setIdealExpectedType(Result.ExpectedTy);
Lookup.setCanCurrDeclContextHandleAsync(Result.IsInAsyncContext);
@@ -133,7 +129,7 @@ void UnresolvedMemberTypeCheckCompletionCallback::collectResults(
// EnumElementPattern.
for (auto &Result : EnumPatternTypes) {
Type Ty = Result.ExpectedTy;
Lookup.setExpectedTypes({Ty}, /*IsImplicitSingleExpressionReturn=*/false,
Lookup.setExpectedTypes({Ty}, /*isImpliedResult=*/false,
/*expectsNonVoid=*/true);
Lookup.setIdealExpectedType(Ty);
Lookup.setCanCurrDeclContextHandleAsync(Result.IsInAsyncContext);