Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 30 additions & 10 deletions lib/Sema/CSOptimizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
//===----------------------------------------------------------------------===//

#include "TypeChecker.h"
#include "OpenedExistentials.h"
#include "swift/AST/ConformanceLookup.h"
#include "swift/AST/ExistentialLayout.h"
#include "swift/AST/GenericSignature.h"
Expand Down Expand Up @@ -1197,18 +1198,20 @@ static void determineBestChoicesInContext(
// - Superclass conversion
// - Array-to-pointer conversion
// - Value to existential conversion
// - Existential opening
// - Exact match on top-level types
//
// In situations when it's not possible to determine whether a candidate
// type matches a parameter type (i.e. when partially resolved generic
// types are matched) this function is going to produce \c std::nullopt
// instead of `0` that indicates "not a match".
std::function<std::optional<double>(GenericSignature, ValueDecl *, Type,
Type, MatchOptions)>
std::function<std::optional<double>(GenericSignature, ValueDecl *,
std::optional<unsigned>, Type, Type,
MatchOptions)>
scoreCandidateMatch =
[&](GenericSignature genericSig, ValueDecl *choice,
Type candidateType, Type paramType,
MatchOptions options) -> std::optional<double> {
std::optional<unsigned> paramIdx, Type candidateType,
Type paramType, MatchOptions options) -> std::optional<double> {
auto areEqual = [&](Type a, Type b) {
return a->getDesugaredType()->isEqual(b->getDesugaredType());
};
Expand Down Expand Up @@ -1260,7 +1263,7 @@ static void determineBestChoicesInContext(
// This helps to determine whether there are any generic
// overloads that are a possible match.
auto score =
scoreCandidateMatch(genericSig, choice, candidateType,
scoreCandidateMatch(genericSig, choice, paramIdx, candidateType,
paramType, options - MatchFlag::Literal);
if (score == 0)
return 0;
Expand Down Expand Up @@ -1347,8 +1350,8 @@ static void determineBestChoicesInContext(
if ((paramOptionals.empty() &&
paramType->is<GenericTypeParamType>()) ||
paramOptionals.size() >= candidateOptionals.size()) {
auto score = scoreCandidateMatch(genericSig, choice, candidateType,
paramType, options);
auto score = scoreCandidateMatch(genericSig, choice, paramIdx,
candidateType, paramType, options);

if (score > 0) {
// Injection lowers the score slightly to comply with
Expand Down Expand Up @@ -1394,6 +1397,21 @@ static void determineBestChoicesInContext(
if (paramType->isAny())
return 1;

// Check if a candidate could be matched to a parameter by
// an existential opening.
if (options.contains(MatchFlag::OnParam) &&
candidateType->getMetatypeInstanceType()->isExistentialType()) {
if (auto *genericParam = paramType->getMetatypeInstanceType()
->getAs<GenericTypeParamType>()) {
if (canOpenExistentialAt(choice, *paramIdx, genericParam,
candidateType->getMetatypeInstanceType())) {
// Lower the score slightly for operators to make sure that
// concrete overloads are always preferred over generic ones.
return choice->isOperator() ? 0.9 : 1;
}
}
}

// Check protocol requirement(s) if this parameter is a
// generic parameter type.
if (genericSig && paramType->isTypeParameter()) {
Expand Down Expand Up @@ -1661,9 +1679,10 @@ static void determineBestChoicesInContext(
options |= MatchFlag::StringInterpolation;

// The specifier for a candidate only matters for `inout` check.
auto candidateScore = scoreCandidateMatch(
genericSig, decl, candidate.type->getWithoutSpecifierType(),
paramType, options);
auto candidateScore =
scoreCandidateMatch(genericSig, decl, paramIdx,
candidate.type->getWithoutSpecifierType(),
paramType, options);

if (!candidateScore)
continue;
Expand Down Expand Up @@ -1706,6 +1725,7 @@ static void determineBestChoicesInContext(
(score > 0 || !hasArgumentCandidates)) {
if (llvm::any_of(resultTypes, [&](const Type candidateResultTy) {
return scoreCandidateMatch(genericSig, decl,
/*paramIdx=*/std::nullopt,
overloadType->getResult(),
candidateResultTy,
/*options=*/{}) > 0;
Expand Down
39 changes: 24 additions & 15 deletions lib/Sema/OpenedExistentials.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -677,16 +677,6 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx,
if (!typeVar || !genericParam)
return std::nullopt;

// Only allow opening the innermost generic parameters.
auto genericContext = callee->getAsGenericContext();
if (!genericContext || !genericContext->isGeneric())
return std::nullopt;

auto genericSig = callee->getInnermostDeclContext()
->getGenericSignatureOfContext().getCanonicalSignature();
if (genericParam->getDepth() < genericSig->getMaxDepth())
return std::nullopt;

// The binding could be an existential metatype. Get the instance type for
// conformance checks and to build an opened existential signature. If the
// instance type is not an existential type, i.e., the metatype is nested,
Expand All @@ -695,6 +685,28 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx,
if (!existentialTy->isExistentialType())
return std::nullopt;

if (!canOpenExistentialAt(callee, paramIdx, genericParam, existentialTy))
return std::nullopt;

return std::pair(typeVar, bindingTy);
}

bool swift::canOpenExistentialAt(ValueDecl *callee, unsigned paramIdx,
GenericTypeParamType *genericParam,
Type existentialTy) {
ASSERT(existentialTy->isExistentialType());

// Only allow opening the innermost generic parameters.
auto genericContext = callee->getAsGenericContext();
if (!genericContext || !genericContext->isGeneric())
return false;

auto genericSig = callee->getInnermostDeclContext()
->getGenericSignatureOfContext()
.getCanonicalSignature();
if (genericParam->getDepth() < genericSig->getMaxDepth())
return false;

auto &ctx = callee->getASTContext();

// If the existential argument conforms to all of protocol requirements on
Expand All @@ -715,7 +727,7 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx,
}

if (!containsNonSelfConformance)
return std::nullopt;
return false;
}

auto existentialSig = ctx.getOpenedExistentialSignature(existentialTy);
Expand All @@ -726,10 +738,7 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx,
callee, existentialSig.OpenedSig, genericParam,
existentialSig.SelfType->castTo<GenericTypeParamType>(),
/*skipParamIdx=*/paramIdx);
if (referenceInfo.hasNonCovariantRef())
return std::nullopt;

return std::pair(typeVar, bindingTy);
return !referenceInfo.hasNonCovariantRef();
}

/// For each occurrence of a type **type** in `refTy` that satisfies
Expand Down
7 changes: 7 additions & 0 deletions lib/Sema/OpenedExistentials.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ std::optional<std::pair<TypeVariableType *, Type>>
canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx,
Type paramTy, Type argTy);

/// A limited form of the check performed by \c canOpenExistentialCallArgument
/// that assumes that a declaration where parameter came from, the parameter
/// itself, and the types involved have been validated already.
bool canOpenExistentialAt(ValueDecl *callee, unsigned paramIdx,
GenericTypeParamType *genericParam,
Type existentialTy);

/// Given a type that includes an existential type that has been opened to
/// the given type variable, replace the opened type variable and its member
/// types with their upper bounds.
Expand Down
15 changes: 15 additions & 0 deletions test/Constraints/opened_existentials.swift
Original file line number Diff line number Diff line change
Expand Up @@ -591,3 +591,18 @@ protocol PP3 {
associatedtype A
}

protocol PP4 {
}

do {
func test<T>(env: T) where T: PP4 {}

func test(env: PP4? = nil) {
guard let env else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the guard let essential to the test case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it needs to be unwrapped before passing it to test to make sure that the argument is non-optional and known in advance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should note that today existential opening doesn't work for stuff like env ?? <<default>> because it currently happens in simplifyApplicableFnConstraint and at that point the argument won't be resolved. This is a bug we need to fix.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, the issue is that test() is overloaded, I see!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep!

return
}

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
test(env: env)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// {"kind":"typecheck","signature":"swift::constraints::ConstraintSystem::isArgumentGenericFunction(swift::Type, swift::Expr*)","signatureAssert":"Assertion failed: (!getFixedType(tyvar)), function getUnboundBindOverloadDisjunction"}
// RUN: not --crash %target-swift-frontend -typecheck %s
// RUN: not %target-swift-frontend -typecheck %s
{
print($0) $00 + 0. / 1