diff --git a/lib/Sema/CSOptimizer.cpp b/lib/Sema/CSOptimizer.cpp index 2f1fd1cb56598..6e31d135b0dd4 100644 --- a/lib/Sema/CSOptimizer.cpp +++ b/lib/Sema/CSOptimizer.cpp @@ -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" @@ -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(GenericSignature, ValueDecl *, Type, - Type, MatchOptions)> + std::function(GenericSignature, ValueDecl *, + std::optional, Type, Type, + MatchOptions)> scoreCandidateMatch = [&](GenericSignature genericSig, ValueDecl *choice, - Type candidateType, Type paramType, - MatchOptions options) -> std::optional { + std::optional paramIdx, Type candidateType, + Type paramType, MatchOptions options) -> std::optional { auto areEqual = [&](Type a, Type b) { return a->getDesugaredType()->isEqual(b->getDesugaredType()); }; @@ -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; @@ -1347,8 +1350,8 @@ static void determineBestChoicesInContext( if ((paramOptionals.empty() && paramType->is()) || 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 @@ -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()) { + 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()) { @@ -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; @@ -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; diff --git a/lib/Sema/OpenedExistentials.cpp b/lib/Sema/OpenedExistentials.cpp index 284d3e81d66e2..567eceea9e3a9 100644 --- a/lib/Sema/OpenedExistentials.cpp +++ b/lib/Sema/OpenedExistentials.cpp @@ -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, @@ -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 @@ -715,7 +727,7 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx, } if (!containsNonSelfConformance) - return std::nullopt; + return false; } auto existentialSig = ctx.getOpenedExistentialSignature(existentialTy); @@ -726,10 +738,7 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx, callee, existentialSig.OpenedSig, genericParam, existentialSig.SelfType->castTo(), /*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 diff --git a/lib/Sema/OpenedExistentials.h b/lib/Sema/OpenedExistentials.h index 8475261b11dfe..8d748f57e9497 100644 --- a/lib/Sema/OpenedExistentials.h +++ b/lib/Sema/OpenedExistentials.h @@ -150,6 +150,13 @@ std::optional> 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. diff --git a/test/Constraints/opened_existentials.swift b/test/Constraints/opened_existentials.swift index ebbcae1227f10..ae28b515b6218 100644 --- a/test/Constraints/opened_existentials.swift +++ b/test/Constraints/opened_existentials.swift @@ -591,3 +591,18 @@ protocol PP3 { associatedtype A } +protocol PP4 { +} + +do { + func test(env: T) where T: PP4 {} + + func test(env: PP4? = nil) { + guard let env else { + return + } + + // CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range= + test(env: env) + } +} diff --git a/validation-test/compiler_crashers_2/cad1ff6235b5b3d.swift b/validation-test/compiler_crashers_2_fixed/cad1ff6235b5b3d.swift similarity index 82% rename from validation-test/compiler_crashers_2/cad1ff6235b5b3d.swift rename to validation-test/compiler_crashers_2_fixed/cad1ff6235b5b3d.swift index 05625423a687e..66d43cda3d2e4 100644 --- a/validation-test/compiler_crashers_2/cad1ff6235b5b3d.swift +++ b/validation-test/compiler_crashers_2_fixed/cad1ff6235b5b3d.swift @@ -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