From c5112acfd805a3197ee7de350885899df8c62e51 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 2 Feb 2022 11:44:04 -0800 Subject: [PATCH 01/17] Experimental support for implicitly opening existential arguments. When calling a generic function with an argument of existential type, implicitly "open" the existential type into a concrete archetype, which can then be bound to the generic type. This extends the implicit opening that is performed when accessing a member of an existential type from the "self" parameter to all parameters. For example: func unsafeFirst(_ c: C) -> C.Element { c.first! } func g(c: any Collection) { unsafeFirst(c) // currently an error // with this change, succeeds and produces an 'Any' } This avoids many common sources of errors of the form protocol 'P' as a type cannot conform to the protocol itself which come from calling generic functions with an existential, and allows another way "out" if one has an existention and needs to treat it generically. This feature is behind a frontend flag `-enable-experimental-opened-existential-types`. --- include/swift/Basic/LangOptions.h | 4 ++ include/swift/Option/FrontendOptions.td | 4 ++ include/swift/Sema/ConstraintSystem.h | 21 +++--- lib/Frontend/CompilerInvocation.cpp | 3 + lib/Sema/CSApply.cpp | 52 +++++++++++---- lib/Sema/CSSimplify.cpp | 47 ++++++++++++-- lib/Sema/ConstraintSystem.cpp | 74 +++++++++++++--------- test/Constraints/opened_existentials.swift | 62 ++++++++++++++++++ 8 files changed, 209 insertions(+), 58 deletions(-) create mode 100644 test/Constraints/opened_existentials.swift diff --git a/include/swift/Basic/LangOptions.h b/include/swift/Basic/LangOptions.h index 1437c557bf9cb..4c0827c462b89 100644 --- a/include/swift/Basic/LangOptions.h +++ b/include/swift/Basic/LangOptions.h @@ -321,6 +321,10 @@ namespace swift { /// keyword. bool EnableExplicitExistentialTypes = true; + /// Enable support for implicitly opening existential argument types + /// in calls to generic functions. + bool EnableOpenedExistentialTypes = false; + /// Enable support for protocol types parameterized by primary /// associated type. bool EnableParameterizedProtocolTypes = false; diff --git a/include/swift/Option/FrontendOptions.td b/include/swift/Option/FrontendOptions.td index 4bd70271aa1a4..5d326a26c3fb2 100644 --- a/include/swift/Option/FrontendOptions.td +++ b/include/swift/Option/FrontendOptions.td @@ -518,6 +518,10 @@ def enable_parameterized_protocol_types : Flag<["-"], "enable-parameterized-protocol-types">, HelpText<"Enable experimental support for primary associated types and parameterized protocols">; +def enable_experimental_opened_existential_types : + Flag<["-"], "enable-experimental-opened-existential-types">, + HelpText<"Enable experimental support for implicitly opened existentials">; + def enable_deserialization_recovery : Flag<["-"], "enable-deserialization-recovery">, HelpText<"Attempt to recover from missing xrefs (etc) in swiftmodules">; diff --git a/include/swift/Sema/ConstraintSystem.h b/include/swift/Sema/ConstraintSystem.h index efd33d969e7b6..7d529019d39ef 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -3334,6 +3334,11 @@ class ConstraintSystem { ConstraintLocator *getOpenOpaqueLocator( ConstraintLocatorBuilder locator, OpaqueTypeDecl *opaqueDecl); + /// Open the given existential type, recording the opened type in the + /// constraint system and returning both it and the root opened archetype. + std::pair openExistentialType( + Type type, ConstraintLocator *locator); + /// Retrive the constraint locator for the given anchor and /// path, uniqued and automatically infer the summary flags ConstraintLocator * @@ -5489,20 +5494,16 @@ matchCallArguments( MatchCallArgumentListener &listener, Optional trailingClosureMatching); -ConstraintSystem::TypeMatchResult -matchCallArguments(ConstraintSystem &cs, - FunctionType *contextualType, - ArgumentList *argumentList, - ArrayRef args, - ArrayRef params, - ConstraintKind subKind, - ConstraintLocatorBuilder locator, - Optional trailingClosureMatching); - /// Given an expression that is the target of argument labels (for a call, /// subscript, etc.), find the underlying target expression. Expr *getArgumentLabelTargetExpr(Expr *fn); +/// Given a type that includes an existential type that has been opened to +/// the given type variable, type-erase occurences of that opened type +/// variable and anything that depends on it to their non-dependent bounds. +Type typeEraseOpenedExistentialReference(Type type, Type existentialBaseType, + TypeVariableType *openedTypeVar); + /// Returns true if a reference to a member on a given base type will apply /// its curried self parameter, assuming it has one. /// diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index c8b90d568495a..7ec7aaed694d5 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -454,6 +454,9 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args, Opts.EnableParameterizedProtocolTypes |= Args.hasArg(OPT_enable_parameterized_protocol_types); + Opts.EnableOpenedExistentialTypes |= + Args.hasArg(OPT_enable_experimental_opened_existential_types); + Opts.EnableExperimentalDistributed |= Args.hasArg(OPT_enable_experimental_distributed); diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 87fefc8d0d219..86f8830999a08 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -989,9 +989,9 @@ namespace { return archetypeVal; } - /// Trying to close the active existential, if there is one. + /// Try to close the innermost active existential, if there is one. bool closeExistential(Expr *&result, ConstraintLocatorBuilder locator, - bool force=false) { + bool force) { if (OpenedExistentials.empty()) return false; @@ -1033,6 +1033,18 @@ namespace { return true; } + /// Close any active existentials. + bool closeExistentials(Expr *&result, ConstraintLocatorBuilder locator, + bool force=false) { + bool closedAny = false; + while (closeExistential(result, locator, force)) { + force = false; + closedAny = true; + } + + return closedAny; + } + /// Determines if a partially-applied member reference should be /// converted into a fully-applied member reference with a pair of /// closures. @@ -1554,7 +1566,7 @@ namespace { cs.setType(ref, refType); - closeExistential(ref, locator, /*force=*/openedExistential); + closeExistentials(ref, locator, /*force=*/openedExistential); // If this attribute was inferred based on deprecated Swift 3 rules, // complain. @@ -1606,7 +1618,7 @@ namespace { cs.setType(memberRefExpr, refTy->castTo()->getResult()); Expr *result = memberRefExpr; - closeExistential(result, locator); + closeExistentials(result, locator); // If the property is of dynamic 'Self' type, wrap an implicit // conversion around the resulting expression, with the destination @@ -1807,7 +1819,7 @@ namespace { ref, cs.getType(ref)); cs.cacheType(result); - closeExistential(result, locator, /*force=*/openedExistential); + closeExistentials(result, locator, /*force=*/openedExistential); return forceUnwrapIfExpected(result, memberLocator); } else { assert((!baseIsInstance || member->isInstanceMember()) && @@ -2111,7 +2123,7 @@ namespace { && "open existential archetype in AnyObject subscript type?"); cs.setType(subscriptExpr, resultTy); Expr *result = subscriptExpr; - closeExistential(result, locator); + closeExistentials(result, locator); return result; } @@ -2153,7 +2165,7 @@ namespace { : fullSubscriptTy->getResult()); Expr *result = subscriptExpr; - closeExistential(result, locator); + closeExistentials(result, locator); // If the element is of dynamic 'Self' type, wrap an implicit conversion // around the resulting expression, with the destination type having @@ -5881,6 +5893,22 @@ ArgumentList *ExprRewriter::coerceCallArguments( cs.cacheExprTypes(argExpr); } + auto argLoc = getArgLocator(argIdx, paramIdx, param.getParameterFlags()); + + // If the argument is an existential type that has been opened, perform + // the open operation. + if (argType->isAnyExistentialType() && paramType->hasOpenedExistential()) { + // FIXME: Look for an opened existential and use it. We need to + // know how far out we need to go to close the existentials. Huh. + auto knownOpened = solution.OpenedExistentialTypes.find( + cs.getConstraintLocator(argLoc)); + if (knownOpened != solution.OpenedExistentialTypes.end()) { + argExpr = openExistentialReference( + argExpr, knownOpened->second, callee.getDecl()); + argType = cs.getType(argExpr); + } + } + if (argRequiresAutoClosureExpr(param, argType)) { assert(!param.isInOut()); @@ -5890,8 +5918,6 @@ ArgumentList *ExprRewriter::coerceCallArguments( // - new types are propagated to constraint system auto *closureType = param.getPlainType()->castTo(); - auto argLoc = getArgLocator(argIdx, paramIdx, param.getParameterFlags()); - argExpr = coerceToType( argExpr, closureType->getResult(), argLoc.withPathElement(ConstraintLocator::AutoclosureResult)); @@ -5914,9 +5940,7 @@ ArgumentList *ExprRewriter::coerceCallArguments( convertedArg = cs.buildAutoClosureExpr(argExpr, closureType, dc); } else { - convertedArg = coerceToType( - argExpr, paramType, - getArgLocator(argIdx, paramIdx, param.getParameterFlags())); + convertedArg = coerceToType(argExpr, paramType, argLoc); } // Perform the wrapped value placeholder injection @@ -7652,8 +7676,8 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType, result, covariantResultType)); } - // Try closing the existential, if there is one. - closeExistential(result, locator); + // Try closing existentials, if there are any. + closeExistentials(result, locator); // We may also need to force the result for an IUO. We don't apply this on // SelfApplyExprs, as the force unwraps should be inserted at the result of diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index aecce5aced2d2..17102e04e4804 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -1319,13 +1319,17 @@ class OpenTypeSequenceElements { } // Match the argument of a call to the parameter. -ConstraintSystem::TypeMatchResult constraints::matchCallArguments( +static ConstraintSystem::TypeMatchResult matchCallArguments( ConstraintSystem &cs, FunctionType *contextualType, ArgumentList *argList, ArrayRef args, ArrayRef params, ConstraintKind subKind, ConstraintLocatorBuilder locator, - Optional trailingClosureMatching) { + Optional trailingClosureMatching, + SmallVectorImpl> + &openedExistentials) { + assert(subKind == ConstraintKind::OperatorArgumentConversion || + subKind == ConstraintKind::ArgumentConversion); auto *loc = cs.getConstraintLocator(locator); assert(loc->isLastElement()); @@ -1546,6 +1550,27 @@ ConstraintSystem::TypeMatchResult constraints::matchCallArguments( cs, cs.getConstraintLocator(loc))); } + // If the argument is an existential type and the parameter is generic, + // consider opening the existential type. + if (argTy->isAnyExistentialType() && + cs.getASTContext().LangOpts.EnableOpenedExistentialTypes && + paramTy->hasTypeVariable() && + !(callee && + TypeChecker::getDeclTypeCheckingSemantics(callee) == + DeclTypeCheckingSemantics::OpenExistential)) { + if (auto param = getParameterAt(callee, argIdx)) { + if (auto paramTypeVar = paramTy->getAs()) { + if (param->getInterfaceType()->is()) { + OpenedArchetypeType *opened; + std::tie(argTy, opened) = cs.openExistentialType( + argTy, cs.getConstraintLocator(loc)); + + openedExistentials.push_back({paramTypeVar, opened}); + } + } + } + } + auto argLabel = argument.getLabel(); if (paramInfo.hasExternalPropertyWrapper(argIdx) || argLabel.hasDollarPrefix()) { auto *param = getParameterAt(callee, argIdx); @@ -10667,9 +10692,11 @@ ConstraintSystem::simplifyApplicableFnConstraint( auto *argumentList = getArgumentList(argumentsLoc); // The argument type must be convertible to the input type. + SmallVector, 2> + openedExistentials; auto matchCallResult = ::matchCallArguments( *this, func2, argumentList, func1->getParams(), func2->getParams(), - subKind, argumentsLoc, trailingClosureMatching); + subKind, argumentsLoc, trailingClosureMatching, openedExistentials); switch (matchCallResult) { case SolutionKind::Error: { @@ -10724,7 +10751,8 @@ ConstraintSystem::simplifyApplicableFnConstraint( auto matchCallResult = ::matchCallArguments( *this, func2, newArgumentList, func1->getParams(), - func2->getParams(), subKind, argumentsLoc, trailingClosureMatching); + func2->getParams(), subKind, argumentsLoc, trailingClosureMatching, + openedExistentials); if (matchCallResult != SolutionKind::Solved) return SolutionKind::Error; @@ -10777,9 +10805,18 @@ ConstraintSystem::simplifyApplicableFnConstraint( break; } + // Erase all of the opened existentials. + Type result2 = func2->getResult(); + if (result2->hasTypeVariable() && !openedExistentials.empty()) { + for (const auto &opened : openedExistentials) { + result2 = typeEraseOpenedExistentialReference( + result2, opened.second->getOpenedExistentialType(), opened.first); + } + } + // The result types are equivalent. if (matchFunctionResultTypes( - func1->getResult(), func2->getResult(), subflags, + func1->getResult(), result2, subflags, locator.withPathElement(ConstraintLocator::FunctionResult)) .isFailure()) return SolutionKind::Error; diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index e487a678f96a4..ef8e632daeea7 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -550,6 +550,15 @@ ConstraintLocator *ConstraintSystem::getOpenOpaqueLocator( { LocatorPathElt::OpenedOpaqueArchetype(opaqueDecl) }, 0); } +std::pair ConstraintSystem::openExistentialType( + Type type, ConstraintLocator *locator) { + OpenedArchetypeType *opened = nullptr; + Type result = type->openAnyExistentialType(opened); + assert(OpenedExistentialTypes.count(locator) == 0); + OpenedExistentialTypes.insert({locator, opened}); + return {result, opened}; +} + /// Extend the given depth map by adding depths for all of the subexpressions /// of the given expression. static void extendDepthMap( @@ -1824,6 +1833,40 @@ static Type typeEraseCovariantExistentialSelfReferences(Type refTy, return transformFn(refTy, TypePosition::Covariant); } +Type constraints::typeEraseOpenedExistentialReference( + Type type, Type existentialBaseType, TypeVariableType *openedTypeVar) { + Type selfGP = GenericTypeParamType::get(false, 0, 0, type->getASTContext()); + + // First, temporarily reconstitute the 'Self' generic parameter. + type = type.transformRec([&](TypeBase *t) -> Optional { + // Don't recurse into children unless we have to. + if (!type->hasTypeVariable()) + return Type(t); + + if (isa(t) && t->isEqual(openedTypeVar)) + return selfGP; + + // Recurse. + return None; + }); + + // Then, type-erase occurrences of covariant 'Self'-rooted type parameters. + type = typeEraseCovariantExistentialSelfReferences(type, existentialBaseType); + + // Finally, swap the 'Self'-corresponding type variable back in. + return type.transformRec([&](TypeBase *t) -> Optional { + // Don't recurse into children unless we have to. + if (!type->hasTypeParameter()) + return Type(t); + + if (isa(t) && t->isEqual(selfGP)) + return Type(openedTypeVar); + + // Recurse. + return None; + }); +} + std::pair ConstraintSystem::getTypeOfMemberReference( Type baseTy, ValueDecl *value, DeclContext *useDC, @@ -2096,35 +2139,8 @@ ConstraintSystem::getTypeOfMemberReference( type->hasTypeVariable()) { const auto selfGP = cast( outerDC->getSelfInterfaceType()->getCanonicalType()); - - // First, temporarily reconstitute the 'Self' generic parameter. - type = type.transformRec([&](TypeBase *t) -> Optional { - // Don't recurse into children unless we have to. - if (!type->hasTypeVariable()) - return Type(t); - - if (isa(t) && t->isEqual(replacements.lookup(selfGP))) - return selfGP; - - // Recurse. - return None; - }); - - // Then, type-erase occurrences of covariant 'Self'-rooted type parameters. - type = typeEraseCovariantExistentialSelfReferences(type, baseObjTy); - - // Finally, swap the 'Self'-corresponding type variable back in. - type = type.transformRec([&](TypeBase *t) -> Optional { - // Don't recurse into children unless we have to. - if (!type->hasTypeParameter()) - return Type(t); - - if (isa(t) && t->isEqual(selfGP)) - return Type(replacements.lookup(selfGP)); - - // Recurse. - return None; - }); + auto openedTypeVar = replacements.lookup(selfGP); + type = typeEraseOpenedExistentialReference(type, baseObjTy, openedTypeVar); } // Construct an idealized parameter type of the initializer associated diff --git a/test/Constraints/opened_existentials.swift b/test/Constraints/opened_existentials.swift new file mode 100644 index 0000000000000..7a36d971e1a43 --- /dev/null +++ b/test/Constraints/opened_existentials.swift @@ -0,0 +1,62 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-opened-existential-types -enable-parameterized-protocol-types -enable-experimental-opaque-parameters + +protocol Q { } + +protocol P { + associatedtype A: Q +} + +extension Int: P { + typealias A = Double +} + +extension Array: P where Element: P { + typealias A = String +} + +extension Double: Q { } +extension String: Q { } + +func acceptGeneric(_: T) -> T.A? { nil } +func acceptCollection(_ c: C) -> C.Element { c.first! } + +// --- Simple opening of existential values +func testSimpleExistentialOpening(p: any P, pq: any P & Q, c: any Collection) { + let pa = acceptGeneric(p) + let _: Int = pa // expected-error{{cannot convert value of type 'Q?' to specified type 'Int'}} + + let pqa = acceptGeneric(pq) + let _: Int = pqa // expected-error{{cannot convert value of type 'Q?' to specified type 'Int'}} + + let element = acceptCollection(c) + let _: Int = element // expected-error{{cannot convert value of type 'Any' to specified type 'Int'}} +} + +// --- Requirements on nested types +protocol CollectionOfPs: Collection where Self.Element: P { } + +func takeCollectionOfPs(_: C) -> C.Element.A? + where C.Element: P +{ + nil +} + +func testCollectionOfPs(cp: any CollectionOfPs) { + let e = takeCollectionOfPs(cp) + let _: Int = e // expected-error{{cannot convert value of type 'Q?' to specified type 'Int'}} +} + +// --- Multiple opened existentials in the same expression +func takeTwoGenerics(_ a: T1, _ b: T2) -> (T1, T2) { (a, b) } + +extension P { + func combineThePs(_ other: T) -> (A, T.A)? { nil } +} + +func testMultipleOpened(a: any P, b: any P & Q) { + let r1 = takeTwoGenerics(a, b) + let _: Int = r1 // expected-error{{cannot convert value of type '(P, P & Q)' to specified type 'Int'}} + + let r2 = a.combineThePs(b) + let _: Int = r2 // expected-error{{cannot convert value of type '(Q, Q)?' to specified type 'Int'}} +} From 69cac7d091bd858abad33c751d7b5c6b0970914f Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 3 Feb 2022 14:04:20 -0800 Subject: [PATCH 02/17] Erase opaque types involving opened existential types to their upper bound. --- lib/AST/Type.cpp | 14 +++++++++++ lib/Sema/ConstraintSystem.cpp | 27 ++++++++++++++++++++++ test/Constraints/opened_existentials.swift | 19 ++++++++++++++- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp index 98b1aded12256..47a4284f25d58 100644 --- a/lib/AST/Type.cpp +++ b/lib/AST/Type.cpp @@ -507,6 +507,20 @@ Type TypeBase::typeEraseOpenedArchetypesWithRoot( return Type(ExistentialMetatypeType::get(erasedTy)); } + // Opaque types whose substitutions involve this type parameter are + // erased to their upper bound. + if (auto opaque = dyn_cast(ty)) { + for (auto replacementType : + opaque->getSubstitutions().getReplacementTypes()) { + if (replacementType->hasOpenedExistentialWithRoot(root)) { + Type interfaceType = opaque->getInterfaceType(); + auto genericSig = + opaque->getDecl()->getOpaqueInterfaceGenericSignature(); + return genericSig->getNonDependentUpperBounds(interfaceType); + } + } + } + auto *const archetype = dyn_cast(ty); if (!archetype) { // Recurse. diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index ef8e632daeea7..1352b5d1d1be2 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -1754,6 +1754,19 @@ static Type typeEraseCovariantExistentialSelfReferences(Type refTy, unsigned metatypeDepth = 0; + /// Check whether the given type has a reference to the generic parameter + /// that we are erasing. + auto hasErasedGenericParameter = [&](Type type) { + if (!type->hasTypeParameter()) + return false; + + return type.findIf([&](Type type) { + if (auto gp = type->getAs()) + return gp->getDepth() == 0; + return false; + }); + }; + std::function transformFn; transformFn = [&](Type type, TypePosition initialPos) -> Type { return type.transformWithPosition( @@ -1775,6 +1788,20 @@ static Type typeEraseCovariantExistentialSelfReferences(Type refTy, return Type(ExistentialMetatypeType::get(erasedTy)); } + // Opaque types whose substitutions involve this type parameter are + // erased to their upper bound. + if (auto opaque = dyn_cast(type.getPointer())) { + for (auto replacementType : + opaque->getSubstitutions().getReplacementTypes()) { + if (hasErasedGenericParameter(replacementType)) { + Type interfaceType = opaque->getInterfaceType(); + auto genericSig = + opaque->getDecl()->getOpaqueInterfaceGenericSignature(); + return genericSig->getNonDependentUpperBounds(interfaceType); + } + } + } + if (!t->isTypeParameter()) { // Recurse. return None; diff --git a/test/Constraints/opened_existentials.swift b/test/Constraints/opened_existentials.swift index 7a36d971e1a43..f4b033e373042 100644 --- a/test/Constraints/opened_existentials.swift +++ b/test/Constraints/opened_existentials.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -enable-experimental-opened-existential-types -enable-parameterized-protocol-types -enable-experimental-opaque-parameters +// RUN: %target-typecheck-verify-swift -enable-experimental-opened-existential-types -enable-parametrized-protocol-types -enable-experimental-opaque-parameters protocol Q { } @@ -60,3 +60,20 @@ func testMultipleOpened(a: any P, b: any P & Q) { let r2 = a.combineThePs(b) let _: Int = r2 // expected-error{{cannot convert value of type '(Q, Q)?' to specified type 'Int'}} } + +// --- With primary associated types and opaque parameter types +protocol CollectionOf: Collection { + @_primaryAssociatedType associatedtype Element +} + +extension Array: CollectionOf { } +extension Set: CollectionOf { } + +func reverseIt(_ c: some CollectionOf) -> some CollectionOf { + return c.reversed() +} + +func useReverseIt(_ c: any CollectionOf) { + let c = reverseIt(c) + let _: Int = c // expected-error{{cannot convert value of type 'CollectionOf' to specified type 'Int'}} +} From 8a41c4c6e964a58072015ed06a3384703f8d6ca6 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 3 Feb 2022 23:07:42 -0800 Subject: [PATCH 03/17] Opaque types are only invariant when they involve same-type constraints. An opaque type is only invariant with respect to the existential Self when the constraints on the opaque type involve Self. Such constraints are not expressible in the type-erased value, so treat them as invariant. This loosens the restriction on using members of protocol type that return an opaque type, such that (e.g.) the following is still well-formed: protocol P { } protocol Q { } extension P { func getQ() -> some Q { ... } } func test(p: any P) { let q = p.getQ() // formerly an error, now returns an "any Q" } However, this does not permit uses of members such as: extension P { func getCollection() -> some Collection { ... } // error } because the type system cannot express the corresponding existential type `any Collection`. --- lib/AST/Decl.cpp | 27 ++++++++++++++++--- lib/Sema/ConstraintSystem.cpp | 2 +- test/Constraints/opened_existentials.swift | 19 +++++++++++++ ...ntial_member_accesses_self_assoctype.swift | 4 +-- test/type/opaque.swift | 9 ++++--- 5 files changed, 50 insertions(+), 11 deletions(-) diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 5b79a3bc841fd..99f94c1233dc1 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -3914,10 +3914,29 @@ findExistentialSelfReferences(CanGenericSignature existentialSig, Type type, return info; } - // Opaque result types of protocol extension members contain an invariant - // reference to 'Self'. - if (type->is()) - return SelfReferenceInfo::forSelfRef(TypePosition::Invariant); + // If the signature of an opaque result type has a same-type constraint + // that refereces Self, it's invariant. + if (auto opaque = type->getAs()) { + auto info = SelfReferenceInfo(); + auto opaqueSig = opaque->getDecl()->getOpaqueInterfaceGenericSignature(); + for (const auto &req : opaqueSig.getRequirements()) { + switch (req.getKind()) { + case RequirementKind::Conformance: + case RequirementKind::Layout: + case RequirementKind::Superclass: + continue; + + case RequirementKind::SameType: + info |= findExistentialSelfReferences( + existentialSig, req.getFirstType(), TypePosition::Invariant); + info |= findExistentialSelfReferences( + existentialSig, req.getSecondType(), TypePosition::Invariant); + break; + } + } + + return info; + } // Protocol compositions preserve variance. if (auto *existential = type->getAs()) diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index 1352b5d1d1be2..7c7157450d32e 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -1790,7 +1790,7 @@ static Type typeEraseCovariantExistentialSelfReferences(Type refTy, // Opaque types whose substitutions involve this type parameter are // erased to their upper bound. - if (auto opaque = dyn_cast(type.getPointer())) { + if (auto opaque = dyn_cast(t)) { for (auto replacementType : opaque->getSubstitutions().getReplacementTypes()) { if (hasErasedGenericParameter(replacementType)) { diff --git a/test/Constraints/opened_existentials.swift b/test/Constraints/opened_existentials.swift index f4b033e373042..3c5aaee34726d 100644 --- a/test/Constraints/opened_existentials.swift +++ b/test/Constraints/opened_existentials.swift @@ -77,3 +77,22 @@ func useReverseIt(_ c: any CollectionOf) { let c = reverseIt(c) let _: Int = c // expected-error{{cannot convert value of type 'CollectionOf' to specified type 'Int'}} } + +/// --- Opening existentials when returning opaque types. +extension P { + func getQ() -> some Q { + let a: A? = nil + return a! + } + + func getCollectionOf() -> some CollectionOf { + return [] as [A] + } +} + +func testReturningOpaqueTypes(p: any P) { + let q = p.getQ() + let _: Int = q // expected-error{{cannot convert value of type 'Q' to specified type 'Int'}} + + p.getCollectionOf() // expected-error{{member 'getCollectionOf' cannot be used on value of protocol type 'P'; use a generic constraint instead}} +} diff --git a/test/decl/protocol/existential_member_accesses_self_assoctype.swift b/test/decl/protocol/existential_member_accesses_self_assoctype.swift index f55209d934c00..8bb8645dadc57 100644 --- a/test/decl/protocol/existential_member_accesses_self_assoctype.swift +++ b/test/decl/protocol/existential_member_accesses_self_assoctype.swift @@ -343,7 +343,7 @@ do { arg.invariantSelf1(0) // expected-error {{member 'invariantSelf1' cannot be used on value of protocol type 'P1'; use a generic constraint instead}} if #available(macOS 10.15, *) { - arg.invariantSelf1_1() // expected-error {{member 'invariantSelf1_1' cannot be used on value of protocol type 'P1'; use a generic constraint instead}} + _ = arg.invariantSelf1_1() } arg.invariantSelf2(0) // expected-error {{member 'invariantSelf2' cannot be used on value of protocol type 'P1'; use a generic constraint instead}} arg.invariantSelf3(0) // expected-error {{member 'invariantSelf3' cannot be used on value of protocol type 'P1'; use a generic constraint instead}} @@ -392,7 +392,7 @@ do { arg.invariantSelfProp1 // expected-error {{member 'invariantSelfProp1' cannot be used on value of protocol type 'P1'; use a generic constraint instead}} if #available(macOS 10.15, *) { - arg.invariantSelfProp1_1 // expected-error {{member 'invariantSelfProp1_1' cannot be used on value of protocol type 'P1'; use a generic constraint instead}} + _ = arg.invariantSelfProp1_1 } arg.invariantSelfProp2 // expected-error {{member 'invariantSelfProp2' cannot be used on value of protocol type 'P1'; use a generic constraint instead}} arg.invariantSelfProp3 // expected-error {{member 'invariantSelfProp3' cannot be used on value of protocol type 'P1'; use a generic constraint instead}} diff --git a/test/type/opaque.swift b/test/type/opaque.swift index 47f7d6ed5a7ed..e7bc16f0aba9d 100644 --- a/test/type/opaque.swift +++ b/test/type/opaque.swift @@ -503,10 +503,11 @@ extension OpaqueProtocol { } func takesOpaqueProtocol(existential: OpaqueProtocol) { - // this is not allowed: - _ = existential.asSome // expected-error{{member 'asSome' cannot be used on value of protocol type 'OpaqueProtocol'; use a generic constraint instead}} - _ = existential.getAsSome() // expected-error{{member 'getAsSome' cannot be used on value of protocol type 'OpaqueProtocol'; use a generic constraint instead}} - _ = existential[0] // expected-error{{member 'subscript' cannot be used on value of protocol type 'OpaqueProtocol'; use a generic constraint instead}} + // These are okay because we erase to the opaque type bound + let a = existential.asSome + let _: Int = a // expected-error{{cannot convert value of type 'OpaqueProtocol' to specified type 'Int'}} + _ = existential.getAsSome() + _ = existential[0] } func takesOpaqueProtocol(generic: T) { From 5a4af8d25312a29dd9f2c60eeebb00e7c02deeee Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 4 Feb 2022 07:12:19 -0800 Subject: [PATCH 04/17] Fix flag name --- test/Constraints/opened_existentials.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Constraints/opened_existentials.swift b/test/Constraints/opened_existentials.swift index 3c5aaee34726d..959848b45b47f 100644 --- a/test/Constraints/opened_existentials.swift +++ b/test/Constraints/opened_existentials.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -enable-experimental-opened-existential-types -enable-parametrized-protocol-types -enable-experimental-opaque-parameters +// RUN: %target-typecheck-verify-swift -enable-experimental-opened-existential-types -enable-parameterized-protocol-types -enable-experimental-opaque-parameters protocol Q { } From 4fed119bac4344f0cf0a19a613de58e0d91a49e4 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 4 Feb 2022 13:09:26 -0800 Subject: [PATCH 05/17] Fix test availability for opaque result types --- test/Constraints/opened_existentials.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/Constraints/opened_existentials.swift b/test/Constraints/opened_existentials.swift index 959848b45b47f..04bfa20583132 100644 --- a/test/Constraints/opened_existentials.swift +++ b/test/Constraints/opened_existentials.swift @@ -69,16 +69,19 @@ protocol CollectionOf: Collection { extension Array: CollectionOf { } extension Set: CollectionOf { } +@available(SwiftStdlib 5.1, *) func reverseIt(_ c: some CollectionOf) -> some CollectionOf { return c.reversed() } +@available(SwiftStdlib 5.1, *) func useReverseIt(_ c: any CollectionOf) { let c = reverseIt(c) let _: Int = c // expected-error{{cannot convert value of type 'CollectionOf' to specified type 'Int'}} } /// --- Opening existentials when returning opaque types. +@available(SwiftStdlib 5.1, *) extension P { func getQ() -> some Q { let a: A? = nil @@ -90,6 +93,7 @@ extension P { } } +@available(SwiftStdlib 5.1, *) func testReturningOpaqueTypes(p: any P) { let q = p.getQ() let _: Int = q // expected-error{{cannot convert value of type 'Q' to specified type 'Int'}} From 98e096f4679d011f41264e8996e35a69fb28ced3 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 7 Feb 2022 15:05:16 -0800 Subject: [PATCH 06/17] Prevent opening existentials when the corresponding parameter is variant Ensure that we only open existentials in an argument when the corresponding parameter's type is a generic parameter that is only used in covariant positions, because either invariant or contravariant uses mean that we won't be able to type-erase other uses of that parameter. This mirrors the requirement placed when opening existentials as a member of protocol type. --- include/swift/AST/Decl.h | 30 ++-- lib/AST/Decl.cpp | 180 +++++++++++++-------- lib/Sema/CSSimplify.cpp | 96 +++++++++-- test/Constraints/opened_existentials.swift | 48 +++++- 4 files changed, 255 insertions(+), 99 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 9ff8d4d04cf4b..06d308c939ef9 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -2161,9 +2161,9 @@ class PoundDiagnosticDecl : public Decl { class OpaqueTypeDecl; /// Describes the least favorable positions at which a requirement refers -/// to 'Self' in terms of variance, for use in the is-inheritable and -/// is-available-existential checks. -class SelfReferenceInfo final { +/// to a given generic parameter in terms of variance, for use in the +/// is-inheritable and is-available-existential checks. +class GenericParameterReferenceInfo final { using OptionalTypePosition = OptionalEnum; public: @@ -2173,27 +2173,27 @@ class SelfReferenceInfo final { OptionalTypePosition assocTypeRef; /// A reference to 'Self'. - static SelfReferenceInfo forSelfRef(TypePosition position) { - return SelfReferenceInfo(false, position, llvm::None); + static GenericParameterReferenceInfo forSelfRef(TypePosition position) { + return GenericParameterReferenceInfo(false, position, llvm::None); } /// A reference to 'Self' through an associated type. - static SelfReferenceInfo forAssocTypeRef(TypePosition position) { - return SelfReferenceInfo(false, llvm::None, position); + static GenericParameterReferenceInfo forAssocTypeRef(TypePosition position) { + return GenericParameterReferenceInfo(false, llvm::None, position); } - SelfReferenceInfo &operator|=(const SelfReferenceInfo &other); + GenericParameterReferenceInfo &operator|=(const GenericParameterReferenceInfo &other); explicit operator bool() const { return hasCovariantSelfResult || selfRef || assocTypeRef; } - SelfReferenceInfo() + GenericParameterReferenceInfo() : hasCovariantSelfResult(false), selfRef(llvm::None), assocTypeRef(llvm::None) {} private: - SelfReferenceInfo(bool hasCovariantSelfResult, OptionalTypePosition selfRef, + GenericParameterReferenceInfo(bool hasCovariantSelfResult, OptionalTypePosition selfRef, OptionalTypePosition assocTypeRef) : hasCovariantSelfResult(hasCovariantSelfResult), selfRef(selfRef), assocTypeRef(assocTypeRef) {} @@ -2684,7 +2684,7 @@ class ValueDecl : public Decl { /// is considered covariant only when it appears as the immediate type of a /// property, or the uncurried result type of a method/subscript, e.g. /// '() -> () -> Self'. - SelfReferenceInfo findExistentialSelfReferences( + GenericParameterReferenceInfo findExistentialSelfReferences( Type baseTy, bool treatNonResultCovariantSelfAsInvariant) const; }; @@ -7785,6 +7785,14 @@ class MissingMemberDecl : public Decl { } }; +/// Find references to the given generic paramaeter in the generic signature +/// and the type of the given value. +GenericParameterReferenceInfo findGenericParameterReferences( + const ValueDecl *value, + CanGenericSignature sig, GenericTypeParamType *genericParam, + bool treatNonResultCovarianceAsInvariant, + Optional skipParamIndex); + inline bool AbstractStorageDecl::isSettable(const DeclContext *UseDC, const DeclRefExpr *base) const { if (auto vd = dyn_cast(this)) diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 99f94c1233dc1..0b5a18b75d4e5 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -3800,8 +3800,8 @@ void ValueDecl::copyFormalAccessFrom(const ValueDecl *source, } } -SelfReferenceInfo & -SelfReferenceInfo::operator|=(const SelfReferenceInfo &other) { +GenericParameterReferenceInfo & +GenericParameterReferenceInfo::operator|=(const GenericParameterReferenceInfo &other) { hasCovariantSelfResult |= other.hasCovariantSelfResult; if (other.selfRef > selfRef) { selfRef = other.selfRef; @@ -3812,23 +3812,25 @@ SelfReferenceInfo::operator|=(const SelfReferenceInfo &other) { return *this; } -/// Report references to 'Self' within the given type using the given -/// existential generic signature. +/// Report references to the given generic parameter within the given type +/// using the given existential generic signature. /// /// \param position The current position in terms of variance. -static SelfReferenceInfo -findExistentialSelfReferences(CanGenericSignature existentialSig, Type type, - TypePosition position) { +static GenericParameterReferenceInfo +findGenericParameterReferences(CanGenericSignature existentialSig, + GenericTypeParamType *genericParam, + Type type, + TypePosition position) { // If there are no type parameters, we're done. if (!type->hasTypeParameter()) - return SelfReferenceInfo(); + return GenericParameterReferenceInfo(); // Tuples preserve variance. if (auto tuple = type->getAs()) { - auto info = SelfReferenceInfo(); + auto info = GenericParameterReferenceInfo(); for (auto &elt : tuple->getElements()) { - info |= findExistentialSelfReferences(existentialSig, elt.getType(), - position); + info |= findGenericParameterReferences(existentialSig, genericParam, + elt.getType(), position); } // A covariant Self result inside a tuple will not be bona fide. @@ -3840,23 +3842,25 @@ findExistentialSelfReferences(CanGenericSignature existentialSig, Type type, // Function types preserve variance in the result type, and flip variance in // the parameter type. if (auto funcTy = type->getAs()) { - auto inputInfo = SelfReferenceInfo(); + auto inputInfo = GenericParameterReferenceInfo(); for (auto param : funcTy->getParams()) { // inout parameters are invariant. if (param.isInOut()) { - inputInfo |= findExistentialSelfReferences( - existentialSig, param.getPlainType(), TypePosition::Invariant); + inputInfo |= findGenericParameterReferences( + existentialSig, genericParam, param.getPlainType(), + TypePosition::Invariant); continue; } - inputInfo |= findExistentialSelfReferences( - existentialSig, param.getParameterType(), position.flipped()); + inputInfo |= findGenericParameterReferences( + existentialSig, genericParam, param.getParameterType(), + position.flipped()); } // A covariant Self result inside a parameter will not be bona fide. inputInfo.hasCovariantSelfResult = false; - auto resultInfo = findExistentialSelfReferences( - existentialSig, funcTy->getResult(), position); + auto resultInfo = findGenericParameterReferences( + existentialSig, genericParam, funcTy->getResult(), position); if (resultInfo.selfRef == TypePosition::Covariant) { resultInfo.hasCovariantSelfResult = true; } @@ -3865,48 +3869,52 @@ findExistentialSelfReferences(CanGenericSignature existentialSig, Type type, // Metatypes preserve variance. if (auto metaTy = type->getAs()) { - return findExistentialSelfReferences(existentialSig, + return findGenericParameterReferences(existentialSig, genericParam, metaTy->getInstanceType(), position); } // Optionals preserve variance. if (auto optType = type->getOptionalObjectType()) { - return findExistentialSelfReferences(existentialSig, optType, position); + return findGenericParameterReferences( + existentialSig, genericParam, optType, position); } // DynamicSelfType preserves variance. // FIXME: This shouldn't ever appear in protocol requirement // signatures. if (auto selfType = type->getAs()) { - return findExistentialSelfReferences(existentialSig, + return findGenericParameterReferences(existentialSig, genericParam, selfType->getSelfType(), position); } if (auto *const nominal = type->getAs()) { - auto info = SelfReferenceInfo(); + auto info = GenericParameterReferenceInfo(); // Don't forget to look in the parent. if (const auto parent = nominal->getParent()) { - info |= findExistentialSelfReferences(existentialSig, parent, position); + info |= findGenericParameterReferences( + existentialSig, genericParam, parent, position); } // Most bound generic types are invariant. if (auto *const bgt = type->getAs()) { if (bgt->isArray()) { // Swift.Array preserves variance in its 'Value' type. - info |= findExistentialSelfReferences( - existentialSig, bgt->getGenericArgs().front(), position); + info |= findGenericParameterReferences( + existentialSig, genericParam, bgt->getGenericArgs().front(), + position); } else if (bgt->isDictionary()) { // Swift.Dictionary preserves variance in its 'Element' type. - info |= findExistentialSelfReferences(existentialSig, + info |= findGenericParameterReferences(existentialSig, genericParam, bgt->getGenericArgs().front(), TypePosition::Invariant); - info |= findExistentialSelfReferences( - existentialSig, bgt->getGenericArgs().back(), position); + info |= findGenericParameterReferences( + existentialSig, genericParam, bgt->getGenericArgs().back(), + position); } else { for (auto paramType : bgt->getGenericArgs()) { - info |= findExistentialSelfReferences(existentialSig, paramType, - TypePosition::Invariant); + info |= findGenericParameterReferences( + existentialSig, genericParam, paramType, TypePosition::Invariant); } } } @@ -3917,20 +3925,25 @@ findExistentialSelfReferences(CanGenericSignature existentialSig, Type type, // If the signature of an opaque result type has a same-type constraint // that refereces Self, it's invariant. if (auto opaque = type->getAs()) { - auto info = SelfReferenceInfo(); + auto info = GenericParameterReferenceInfo(); auto opaqueSig = opaque->getDecl()->getOpaqueInterfaceGenericSignature(); for (const auto &req : opaqueSig.getRequirements()) { switch (req.getKind()) { case RequirementKind::Conformance: case RequirementKind::Layout: - case RequirementKind::Superclass: continue; case RequirementKind::SameType: - info |= findExistentialSelfReferences( - existentialSig, req.getFirstType(), TypePosition::Invariant); - info |= findExistentialSelfReferences( - existentialSig, req.getSecondType(), TypePosition::Invariant); + info |= findGenericParameterReferences( + existentialSig, genericParam, req.getFirstType(), + TypePosition::Invariant); + + LLVM_FALLTHROUGH; + + case RequirementKind::Superclass: + info |= findGenericParameterReferences( + existentialSig, genericParam, req.getSecondType(), + TypePosition::Invariant); break; } } @@ -3945,72 +3958,81 @@ findExistentialSelfReferences(CanGenericSignature existentialSig, Type type, // 'Self' may be referenced only in an explicit superclass component. for (const auto member : comp->getMembers()) { if (member->getClassOrBoundGenericClass()) { - return findExistentialSelfReferences(existentialSig, member, position); + return findGenericParameterReferences( + existentialSig, genericParam, member, position); } } - return SelfReferenceInfo(); + return GenericParameterReferenceInfo(); } if (!type->isTypeParameter()) { - return SelfReferenceInfo(); + return GenericParameterReferenceInfo(); } - const auto selfTy = existentialSig.getGenericParams().front(); + Type selfTy(genericParam); if (!type->getRootGenericParam()->isEqual(selfTy)) { - return SelfReferenceInfo(); + return GenericParameterReferenceInfo(); } // A direct reference to 'Self'. if (selfTy->isEqual(type)) { - return SelfReferenceInfo::forSelfRef(position); + return GenericParameterReferenceInfo::forSelfRef(position); } // If the type parameter is beyond the domain of the existential generic // signature, ignore it. if (!existentialSig->isValidTypeInContext(type)) { - return SelfReferenceInfo(); + return GenericParameterReferenceInfo(); } if (const auto concreteTy = existentialSig->getConcreteType(type)) { - return findExistentialSelfReferences(existentialSig, concreteTy, position); + return findGenericParameterReferences( + existentialSig, genericParam, concreteTy, position); } // A reference to an associated type rooted on 'Self'. - return SelfReferenceInfo::forAssocTypeRef(position); + return GenericParameterReferenceInfo::forAssocTypeRef(position); } -SelfReferenceInfo ValueDecl::findExistentialSelfReferences( - Type baseTy, bool treatNonResultCovariantSelfAsInvariant) const { - assert(baseTy->isExistentialType()); +GenericParameterReferenceInfo swift::findGenericParameterReferences( + const ValueDecl *value, CanGenericSignature sig, + GenericTypeParamType *genericParam, + bool treatNonResultCovarianceAsInvariant, + Optional skipParamIndex) { // Types never refer to 'Self'. - if (isa(this)) - return SelfReferenceInfo(); + if (isa(value)) + return GenericParameterReferenceInfo(); - auto type = getInterfaceType(); + auto type = value->getInterfaceType(); // Skip invalid declarations. if (type->hasError()) - return SelfReferenceInfo(); - - const auto sig = getASTContext().getOpenedArchetypeSignature(baseTy); + return GenericParameterReferenceInfo(); - if (isa(this) || isa(this)) { + if (isa(value) || isa(value)) { // For a method, skip the 'self' parameter. - if (isa(this)) + if (value->hasCurriedSelf()) type = type->castTo()->getResult(); - auto inputInfo = SelfReferenceInfo(); - for (auto param : type->castTo()->getParams()) { + auto inputInfo = GenericParameterReferenceInfo(); + auto params = type->castTo()->getParams(); + for (auto paramIdx : indices(params)) { + // If this is the parameter we were supposed to skip, do so. + if (skipParamIndex && paramIdx == *skipParamIndex) + continue; + + const auto ¶m = params[paramIdx]; // inout parameters are invariant. if (param.isInOut()) { - inputInfo |= ::findExistentialSelfReferences(sig, param.getPlainType(), - TypePosition::Invariant); + inputInfo |= ::findGenericParameterReferences( + sig, genericParam, param.getPlainType(), TypePosition::Invariant); continue; } - inputInfo |= ::findExistentialSelfReferences( - sig, param.getParameterType(), TypePosition::Contravariant); + inputInfo |= ::findGenericParameterReferences( + sig, genericParam, param.getParameterType(), + TypePosition::Contravariant); } // A covariant Self result inside a parameter will not be bona fide. @@ -4022,13 +4044,13 @@ SelfReferenceInfo ValueDecl::findExistentialSelfReferences( // // Methods of non-final classes can only contain a covariant 'Self' // as their result type. - if (treatNonResultCovariantSelfAsInvariant && + if (treatNonResultCovarianceAsInvariant && inputInfo.selfRef == TypePosition::Covariant) { inputInfo.selfRef = TypePosition::Invariant; } - auto resultInfo = ::findExistentialSelfReferences( - sig, type->castTo()->getResult(), + auto resultInfo = ::findGenericParameterReferences( + sig, genericParam, type->castTo()->getResult(), TypePosition::Covariant); if (resultInfo.selfRef == TypePosition::Covariant) { resultInfo.hasCovariantSelfResult = true; @@ -4036,10 +4058,10 @@ SelfReferenceInfo ValueDecl::findExistentialSelfReferences( return inputInfo |= resultInfo; } else { - assert(isa(this)); + assert(isa(value)); - auto info = - ::findExistentialSelfReferences(sig, type, TypePosition::Covariant); + auto info = ::findGenericParameterReferences( + sig, genericParam, type, TypePosition::Covariant); if (info.selfRef == TypePosition::Covariant) { info.hasCovariantSelfResult = true; } @@ -4047,7 +4069,27 @@ SelfReferenceInfo ValueDecl::findExistentialSelfReferences( return info; } - return SelfReferenceInfo(); + return GenericParameterReferenceInfo(); +} + +GenericParameterReferenceInfo ValueDecl::findExistentialSelfReferences( + Type baseTy, bool treatNonResultCovariantSelfAsInvariant) const { + assert(baseTy->isExistentialType()); + + // Types never refer to 'Self'. + if (isa(this)) + return GenericParameterReferenceInfo(); + + auto type = getInterfaceType(); + + // Skip invalid declarations. + if (type->hasError()) + return GenericParameterReferenceInfo(); + + const auto sig = getASTContext().getOpenedArchetypeSignature(baseTy); + auto genericParam = sig.getGenericParams().front(); + return findGenericParameterReferences( + this, sig, genericParam, treatNonResultCovariantSelfAsInvariant, None); } Type TypeDecl::getDeclaredInterfaceType() const { diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 17102e04e4804..1761614d7ff17 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -1318,6 +1318,77 @@ class OpenTypeSequenceElements { }; } +/// Determine whether we should open up the existential argument to the +/// given parameters. +/// +/// \param callee The function or subscript being called. +/// \param paramIdx The index specifying which function parameter is being +/// initialized. +/// \param paramTy The type of the parameter as it was opened in the constraint +/// system. +/// \param argTy The type of the argument. +/// +/// \returns If the argument type is existential and opening it can bind a +/// generic parameter in the callee, returns the type variable (from the +/// opened parameter type) and the existential type that needs to be opened +/// (from the argument type). +static Optional> +shouldOpenExistentialCallArgument( + ValueDecl *callee, unsigned paramIdx, Type paramTy, Type argTy) { + if (!callee) + return None; + + // _openExistential handles its own opening. + if (TypeChecker::getDeclTypeCheckingSemantics(callee) == + DeclTypeCheckingSemantics::OpenExistential) + return None; + + ASTContext &ctx = callee->getASTContext(); + if (!ctx.LangOpts.EnableOpenedExistentialTypes) + return None; + + // The actual parameter type needs to involve a type variable, otherwise + // type inference won't be possible. + if (!paramTy->hasTypeVariable()) + return None; + + // The argument type needs to be an existential type or metatype thereof. + if (!argTy->isAnyExistentialType()) + return None; + + auto param = getParameterAt(callee, paramIdx); + if (!param) + return None; + + // If the parameter is non-generic variadic, don't open. + if (param->isVariadic() && !param->getVarargBaseTy()->hasTypeSequence()) + return None; + + // The parameter type must be a type variable. + auto paramTypeVar = paramTy->getAs(); + if (!paramTypeVar) + return None; + + auto formalParamTy = param->getInterfaceType(); + auto genericParam = formalParamTy->getAs(); + if (!genericParam) + return None; + + // Ensure that the formal parameter is only used in covariant positions, + // because it won't match anywhere else. + auto genericSig = callee->getAsGenericContext()->getGenericSignatureOfContext() + .getCanonicalSignature(); + auto referenceInfo = findGenericParameterReferences( + callee, genericSig, genericParam, + /*treatNonResultCovarianceAsInvariant=*/false, + /*skipParamIdx=*/paramIdx); + if (referenceInfo.selfRef > TypePosition::Covariant || + referenceInfo.assocTypeRef > TypePosition::Covariant) + return None; + + return std::make_pair(paramTypeVar, argTy); +} + // Match the argument of a call to the parameter. static ConstraintSystem::TypeMatchResult matchCallArguments( ConstraintSystem &cs, FunctionType *contextualType, @@ -1552,23 +1623,14 @@ static ConstraintSystem::TypeMatchResult matchCallArguments( // If the argument is an existential type and the parameter is generic, // consider opening the existential type. - if (argTy->isAnyExistentialType() && - cs.getASTContext().LangOpts.EnableOpenedExistentialTypes && - paramTy->hasTypeVariable() && - !(callee && - TypeChecker::getDeclTypeCheckingSemantics(callee) == - DeclTypeCheckingSemantics::OpenExistential)) { - if (auto param = getParameterAt(callee, argIdx)) { - if (auto paramTypeVar = paramTy->getAs()) { - if (param->getInterfaceType()->is()) { - OpenedArchetypeType *opened; - std::tie(argTy, opened) = cs.openExistentialType( - argTy, cs.getConstraintLocator(loc)); - - openedExistentials.push_back({paramTypeVar, opened}); - } - } - } + if (auto existentialArg = shouldOpenExistentialCallArgument( + callee, paramIdx, paramTy, argTy)) { + assert(existentialArg->second->isEqual(argTy)); + OpenedArchetypeType *opened; + std::tie(argTy, opened) = cs.openExistentialType( + argTy, cs.getConstraintLocator(loc)); + + openedExistentials.push_back({existentialArg->first, opened}); } auto argLabel = argument.getLabel(); diff --git a/test/Constraints/opened_existentials.swift b/test/Constraints/opened_existentials.swift index 04bfa20583132..c84a5abb686cf 100644 --- a/test/Constraints/opened_existentials.swift +++ b/test/Constraints/opened_existentials.swift @@ -69,6 +69,7 @@ protocol CollectionOf: Collection { extension Array: CollectionOf { } extension Set: CollectionOf { } +// expected-note@+2{{required by global function 'reverseIt' where 'some CollectionOf' = 'CollectionOf'}} @available(SwiftStdlib 5.1, *) func reverseIt(_ c: some CollectionOf) -> some CollectionOf { return c.reversed() @@ -76,8 +77,9 @@ func reverseIt(_ c: some CollectionOf) -> some CollectionOf { @available(SwiftStdlib 5.1, *) func useReverseIt(_ c: any CollectionOf) { - let c = reverseIt(c) - let _: Int = c // expected-error{{cannot convert value of type 'CollectionOf' to specified type 'Int'}} + // Can't type-erase the `T` from the result. + _ = reverseIt(c) // expected-error{{protocol 'CollectionOf' as a type cannot conform to the protocol itself}} + // expected-note@-1{{only concrete types such as structs, enums and classes can conform to protocols}} } /// --- Opening existentials when returning opaque types. @@ -93,10 +95,52 @@ extension P { } } +@available(SwiftStdlib 5.1, *) +func getPQ(_: T) -> some Q { + let a: T.A? = nil + return a! +} + +// expected-note@+2{{required by global function 'getCollectionOfP' where 'T' = 'P'}} +@available(SwiftStdlib 5.1, *) +func getCollectionOfP(_: T) -> some CollectionOf { + return [] as [T.A] +} + +func funnyIdentity(_ value: T) -> T? { + value +} + +func arrayOfOne(_ value: T) -> [T] { + [value] +} + +struct X { +} + +// expected-note@+1{{required by global function 'createX' where 'T' = 'P'}} +func createX(_ value: T) -> X { + X() +} + @available(SwiftStdlib 5.1, *) func testReturningOpaqueTypes(p: any P) { let q = p.getQ() let _: Int = q // expected-error{{cannot convert value of type 'Q' to specified type 'Int'}} p.getCollectionOf() // expected-error{{member 'getCollectionOf' cannot be used on value of protocol type 'P'; use a generic constraint instead}} + + let q2 = getPQ(p) + let _: Int = q2 // expected-error{{cannot convert value of type 'Q' to specified type 'Int'}} + + getCollectionOfP(p) // expected-error{{protocol 'P' as a type cannot conform to the protocol itself}} + // expected-note@-1{{only concrete types such as structs, enums and classes can conform to protocols}} + + let fi = funnyIdentity(p) + let _: Int = fi // expected-error{{cannot convert value of type 'P?' to specified type 'Int'}} + + _ = arrayOfOne(p) // okay, arrays are covariant in their argument + + _ = createX(p) // expected-error{{protocol 'P' as a type cannot conform to the protocol itself}} + // expected-note@-1{{only concrete types such as structs, enums and classes can conform to protocols}} } From 79ddd700885a8d4f66268323426c07719d979e59 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 7 Feb 2022 15:59:36 -0800 Subject: [PATCH 07/17] Support opening of arguments of existential metatype type. --- lib/Sema/CSSimplify.cpp | 9 ++++++++- test/Constraints/opened_existentials.swift | 10 ++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 1761614d7ff17..d983c752bed8d 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -1364,12 +1364,19 @@ shouldOpenExistentialCallArgument( if (param->isVariadic() && !param->getVarargBaseTy()->hasTypeSequence()) return None; + // If the argument is of an existential metatype, look through the + // metatype on the parameter. + auto formalParamTy = param->getInterfaceType(); + if (argTy->is()) { + formalParamTy = formalParamTy->getMetatypeInstanceType(); + paramTy = paramTy->getMetatypeInstanceType(); + } + // The parameter type must be a type variable. auto paramTypeVar = paramTy->getAs(); if (!paramTypeVar) return None; - auto formalParamTy = param->getInterfaceType(); auto genericParam = formalParamTy->getAs(); if (!genericParam) return None; diff --git a/test/Constraints/opened_existentials.swift b/test/Constraints/opened_existentials.swift index c84a5abb686cf..904dd69f0344c 100644 --- a/test/Constraints/opened_existentials.swift +++ b/test/Constraints/opened_existentials.swift @@ -61,6 +61,16 @@ func testMultipleOpened(a: any P, b: any P & Q) { let _: Int = r2 // expected-error{{cannot convert value of type '(Q, Q)?' to specified type 'Int'}} } +// --- Opening existential metatypes +func conjureValue(of type: T.Type) -> T? { + nil +} + +func testMagic(pt: any P.Type) { + let pOpt = conjureValue(of: pt) + let _: Int = pOpt // expected-error{{cannot convert value of type 'P?' to specified type 'Int'}} +} + // --- With primary associated types and opaque parameter types protocol CollectionOf: Collection { @_primaryAssociatedType associatedtype Element From 54687e0de4eb274fbcbcac5e68753d2680298fb8 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 7 Feb 2022 16:11:21 -0800 Subject: [PATCH 08/17] Don't open existential arguments to `type(of:)`. --- lib/Sema/CSSimplify.cpp | 13 ++++++++++--- test/Constraints/openExistential.swift | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index d983c752bed8d..43aa285168402 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -1338,11 +1338,18 @@ shouldOpenExistentialCallArgument( if (!callee) return None; - // _openExistential handles its own opening. - if (TypeChecker::getDeclTypeCheckingSemantics(callee) == - DeclTypeCheckingSemantics::OpenExistential) + // Special semantics prohibit opening existentials. + switch (TypeChecker::getDeclTypeCheckingSemantics(callee)) { + case DeclTypeCheckingSemantics::OpenExistential: + case DeclTypeCheckingSemantics::TypeOf: + // type(of:) and _openExistential handle their own opening. return None; + case DeclTypeCheckingSemantics::Normal: + case DeclTypeCheckingSemantics::WithoutActuallyEscaping: + break; + } + ASTContext &ctx = callee->getASTContext(); if (!ctx.LangOpts.EnableOpenedExistentialTypes) return None; diff --git a/test/Constraints/openExistential.swift b/test/Constraints/openExistential.swift index 48dcfb383df5b..197674470a50c 100644 --- a/test/Constraints/openExistential.swift +++ b/test/Constraints/openExistential.swift @@ -1,5 +1,6 @@ // RUN: %target-typecheck-verify-swift // RUN: %target-typecheck-verify-swift -enable-explicit-existential-types +// RUN: %target-typecheck-verify-swift -enable-experimental-opened-existential-types protocol P { } From 277282abd64caceed3ddbd91974fa7a1d2229606 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 7 Feb 2022 16:22:27 -0800 Subject: [PATCH 09/17] Ensure that we only open existential arguments to functions/subscripts. --- lib/Sema/CSSimplify.cpp | 8 ++++++-- test/Constraints/result_builder_availability.swift | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 43aa285168402..54fa0965bacf5 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -1338,6 +1338,10 @@ shouldOpenExistentialCallArgument( if (!callee) return None; + // Only applies to functions and subscripts. + if (!isa(callee) && !isa(callee)) + return None; + // Special semantics prohibit opening existentials. switch (TypeChecker::getDeclTypeCheckingSemantics(callee)) { case DeclTypeCheckingSemantics::OpenExistential: @@ -1390,8 +1394,8 @@ shouldOpenExistentialCallArgument( // Ensure that the formal parameter is only used in covariant positions, // because it won't match anywhere else. - auto genericSig = callee->getAsGenericContext()->getGenericSignatureOfContext() - .getCanonicalSignature(); + auto genericSig = callee->getInnermostDeclContext() + ->getGenericSignatureOfContext().getCanonicalSignature(); auto referenceInfo = findGenericParameterReferences( callee, genericSig, genericParam, /*treatNonResultCovarianceAsInvariant=*/false, diff --git a/test/Constraints/result_builder_availability.swift b/test/Constraints/result_builder_availability.swift index 7c765ad5aa983..62a8747dc3554 100644 --- a/test/Constraints/result_builder_availability.swift +++ b/test/Constraints/result_builder_availability.swift @@ -1,4 +1,5 @@ // RUN: %target-typecheck-verify-swift -target %target-cpu-apple-macosx10.50 +// RUN: %target-typecheck-verify-swift -target %target-cpu-apple-macosx10.50 -enable-experimental-opened-existential-types // REQUIRES: OS=macosx From 7c0364345280c177725ba708df6dd4e949c26672 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 8 Feb 2022 15:26:26 -0800 Subject: [PATCH 10/17] Don't open existentials when calling C++ function templates C++ function templates require specialization, which does not work with opened existentials. Disable opening for them. --- lib/Sema/CSSimplify.cpp | 5 +++++ test/Interop/Cxx/templates/function-template.swift | 1 + 2 files changed, 6 insertions(+) diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 54fa0965bacf5..656561aeb17cc 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -1354,6 +1354,11 @@ shouldOpenExistentialCallArgument( break; } + // C++ function templates require specialization, which is not possible with + // opened existential archetypes, so do not open. + if (isa_and_nonnull(callee->getClangDecl())) + return None; + ASTContext &ctx = callee->getASTContext(); if (!ctx.LangOpts.EnableOpenedExistentialTypes) return None; diff --git a/test/Interop/Cxx/templates/function-template.swift b/test/Interop/Cxx/templates/function-template.swift index d34e7004e27af..fafd55176331d 100644 --- a/test/Interop/Cxx/templates/function-template.swift +++ b/test/Interop/Cxx/templates/function-template.swift @@ -1,4 +1,5 @@ // RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-cxx-interop) +// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-cxx-interop -Xfrontend -enable-experimental-opened-existential-types) // // REQUIRES: executable_test From b3d7e8abb14898671908064eca1bd56ccb49a2fb Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 9 Feb 2022 17:30:12 -0800 Subject: [PATCH 11/17] SwiftOnoneSupport creates different specializations with opened existentials Implicitly disable implicit opening of existentials for SwiftOnoneSupport because it generates different specializations. --- lib/Frontend/CompilerInvocation.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 7ec7aaed694d5..b30af24940c28 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -457,6 +457,11 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args, Opts.EnableOpenedExistentialTypes |= Args.hasArg(OPT_enable_experimental_opened_existential_types); + // SwiftOnoneSupport produces different symbols when opening existentials, + // so disable it. + if (FrontendOpts.ModuleName == SWIFT_ONONE_SUPPORT) + Opts.EnableOpenedExistentialTypes = false; + Opts.EnableExperimentalDistributed |= Args.hasArg(OPT_enable_experimental_distributed); From 3476eb89bec1361b1b8238900f4728da378a6cb3 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 9 Feb 2022 22:12:10 -0800 Subject: [PATCH 12/17] Opened existential types aren't always `ExistentialType`. As with many other places in the frontend, grab the constraint type when from an existential type when we have one. --- lib/SILGen/ResultPlan.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/SILGen/ResultPlan.cpp b/lib/SILGen/ResultPlan.cpp index 16f654fc5f23a..7d68034e626f9 100644 --- a/lib/SILGen/ResultPlan.cpp +++ b/lib/SILGen/ResultPlan.cpp @@ -99,10 +99,10 @@ mapTypeOutOfOpenedExistentialContext(CanType t) { /*type sequence*/ false, /*depth*/ 0, /*index*/ i, ctx); params.push_back(param); - const auto constraintTy = openedTypes[i] - ->getOpenedExistentialType() - ->castTo() - ->getConstraintType(); + Type constraintTy = openedTypes[i]->getOpenedExistentialType(); + if (auto existentialTy = constraintTy->getAs()) + constraintTy = existentialTy->getConstraintType(); + requirements.emplace_back(RequirementKind::Conformance, param, constraintTy); } From 90358066c9a35eddb9f2c5a0d5a207833d59a16f Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 10 Feb 2022 12:29:20 -0800 Subject: [PATCH 13/17] Only open existentials for generic arguments on the called declaration --- lib/Sema/CSSimplify.cpp | 13 +++++++++++-- test/Constraints/opened_existentials.swift | 7 +++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 656561aeb17cc..7b1d11313762e 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -1397,10 +1397,19 @@ shouldOpenExistentialCallArgument( if (!genericParam) return None; - // Ensure that the formal parameter is only used in covariant positions, - // because it won't match anywhere else. + // Only allow opening the innermost generic parameters. + auto genericContext = callee->getAsGenericContext(); + if (!genericContext || !genericContext->isGeneric()) + return None; + auto genericSig = callee->getInnermostDeclContext() ->getGenericSignatureOfContext().getCanonicalSignature(); + if (genericParam->getDepth() < + genericSig.getGenericParams().back()->getDepth()) + return None; + + // Ensure that the formal parameter is only used in covariant positions, + // because it won't match anywhere else. auto referenceInfo = findGenericParameterReferences( callee, genericSig, genericParam, /*treatNonResultCovarianceAsInvariant=*/false, diff --git a/test/Constraints/opened_existentials.swift b/test/Constraints/opened_existentials.swift index 904dd69f0344c..ba6bf2e6f4679 100644 --- a/test/Constraints/opened_existentials.swift +++ b/test/Constraints/opened_existentials.swift @@ -126,6 +126,8 @@ func arrayOfOne(_ value: T) -> [T] { } struct X { + // expected-note@-1{{required by generic struct 'X' where 'T' = 'P'}} + func f(_: T) { } } // expected-note@+1{{required by global function 'createX' where 'T' = 'P'}} @@ -133,6 +135,11 @@ func createX(_ value: T) -> X { X() } +func doNotOpenOuter(p: any P) { + _ = X().f(p) // expected-error{{protocol 'P' as a type cannot conform to the protocol itself}} + // expected-note@-1{{only concrete types such as structs, enums and classes can conform to protocols}} +} + @available(SwiftStdlib 5.1, *) func testReturningOpaqueTypes(p: any P) { let q = p.getQ() From eb73e81abb6634ad267a0dc551b1920baf774c11 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 10 Feb 2022 12:34:48 -0800 Subject: [PATCH 14/17] Cannot open existentials passed to variadic parameters. --- test/Constraints/opened_existentials.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/Constraints/opened_existentials.swift b/test/Constraints/opened_existentials.swift index ba6bf2e6f4679..3536d58d2bd3f 100644 --- a/test/Constraints/opened_existentials.swift +++ b/test/Constraints/opened_existentials.swift @@ -140,6 +140,18 @@ func doNotOpenOuter(p: any P) { // expected-note@-1{{only concrete types such as structs, enums and classes can conform to protocols}} } +func takesVariadic(_ args: T...) { } +// expected-note@-1 2{{required by global function 'takesVariadic' where 'T' = 'P'}} +// expected-note@-2{{in call to function 'takesVariadic'}} + +func callVariadic(p1: any P, p2: any P) { + takesVariadic() // expected-error{{generic parameter 'T' could not be inferred}} + takesVariadic(p1) // expected-error{{protocol 'P' as a type cannot conform to the protocol itself}} + // expected-note@-1{{only concrete types such as structs, enums and classes can conform to protocols}} + takesVariadic(p1, p2) // expected-error{{protocol 'P' as a type cannot conform to the protocol itself}} + // expected-note@-1{{only concrete types such as structs, enums and classes can conform to protocols}} +} + @available(SwiftStdlib 5.1, *) func testReturningOpaqueTypes(p: any P) { let q = p.getQ() From 9b80a018f9bfaf206486e8d5f8229c926790b647 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 10 Feb 2022 14:51:22 -0800 Subject: [PATCH 15/17] Allow opening existentials for inout parameters. --- lib/Sema/CSApply.cpp | 25 +++++++++-- lib/Sema/CSSimplify.cpp | 51 ++++++++++++++++++---- test/Constraints/opened_existentials.swift | 7 +++ 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 86f8830999a08..ddbec5a6247f4 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -933,8 +933,16 @@ namespace { // Dig out the base type. Type baseTy = cs.getType(base); - // Look through lvalues. + // Look through inout. bool isLValue = false; + InOutExpr *origInOutBase = dyn_cast(base); + if (origInOutBase) { + base = origInOutBase->getSubExpr(); + baseTy = baseTy->getInOutObjectType(); + isLValue = true; + } + + // Look through lvalues. if (auto lvalueTy = baseTy->getAs()) { isLValue = true; baseTy = lvalueTy->getObjectType(); @@ -952,7 +960,7 @@ namespace { // If the base was an lvalue but it will only be treated as an // rvalue, turn the base into an rvalue now. This results in // better SILGen. - if (isLValue && + if (isLValue && !origInOutBase && (isNonMutatingMember(member) || member->getDeclContext()->getDeclaredInterfaceType() ->hasReferenceSemantics())) { @@ -986,7 +994,15 @@ namespace { // Record the opened existential. OpenedExistentials.push_back({archetype, base, archetypeVal, depth}); - return archetypeVal; + // Re-apply inout if needed. + Expr *resultExpr = archetypeVal; + if (origInOutBase) { + resultExpr = new (ctx) InOutExpr( + origInOutBase->getLoc(), resultExpr, opaqueType->getRValueType()); + cs.cacheType(resultExpr); + } + + return resultExpr; } /// Try to close the innermost active existential, if there is one. @@ -5897,7 +5913,8 @@ ArgumentList *ExprRewriter::coerceCallArguments( // If the argument is an existential type that has been opened, perform // the open operation. - if (argType->isAnyExistentialType() && paramType->hasOpenedExistential()) { + if (argType->getInOutObjectType()->isAnyExistentialType() && + paramType->hasOpenedExistential()) { // FIXME: Look for an opened existential and use it. We need to // know how far out we need to go to close the existentials. Huh. auto knownOpened = solution.OpenedExistentialTypes.find( diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 7b1d11313762e..468d4f9615e02 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -1318,6 +1318,18 @@ class OpenTypeSequenceElements { }; } +namespace { + /// Flags that should be applied to the existential argument type after + /// opening. + enum class OpenedExistentialAdjustmentFlags { + /// The argument should be made inout after opening. + InOut = 0x01, + }; + + using OpenedExistentialAdjustments = + OptionSet; +} + /// Determine whether we should open up the existential argument to the /// given parameters. /// @@ -1330,9 +1342,11 @@ class OpenTypeSequenceElements { /// /// \returns If the argument type is existential and opening it can bind a /// generic parameter in the callee, returns the type variable (from the -/// opened parameter type) and the existential type that needs to be opened -/// (from the argument type). -static Optional> +/// opened parameter type) the existential type that needs to be opened +/// (from the argument type), and the adjustements that need to be applied to +/// the existential type after it is opened. +static Optional< + std::tuple> shouldOpenExistentialCallArgument( ValueDecl *callee, unsigned paramIdx, Type paramTy, Type argTy) { if (!callee) @@ -1368,6 +1382,14 @@ shouldOpenExistentialCallArgument( if (!paramTy->hasTypeVariable()) return None; + OpenedExistentialAdjustments adjustments; + + // If the argument is inout, strip it off and we can add it back. + if (auto inOutArg = argTy->getAs()) { + argTy = inOutArg->getObjectType(); + adjustments |= OpenedExistentialAdjustmentFlags::InOut; + } + // The argument type needs to be an existential type or metatype thereof. if (!argTy->isAnyExistentialType()) return None; @@ -1380,14 +1402,19 @@ shouldOpenExistentialCallArgument( if (param->isVariadic() && !param->getVarargBaseTy()->hasTypeSequence()) return None; + // Look through an inout type on the formal type of the parameter. + auto formalParamTy = param->getInterfaceType()->getInOutObjectType(); + // If the argument is of an existential metatype, look through the // metatype on the parameter. - auto formalParamTy = param->getInterfaceType(); if (argTy->is()) { formalParamTy = formalParamTy->getMetatypeInstanceType(); paramTy = paramTy->getMetatypeInstanceType(); } + // Look through an inout type on the parameter. + paramTy = paramTy->getInOutObjectType(); + // The parameter type must be a type variable. auto paramTypeVar = paramTy->getAs(); if (!paramTypeVar) @@ -1418,7 +1445,7 @@ shouldOpenExistentialCallArgument( referenceInfo.assocTypeRef > TypePosition::Covariant) return None; - return std::make_pair(paramTypeVar, argTy); + return std::make_tuple(paramTypeVar, argTy, adjustments); } // Match the argument of a call to the parameter. @@ -1657,12 +1684,20 @@ static ConstraintSystem::TypeMatchResult matchCallArguments( // consider opening the existential type. if (auto existentialArg = shouldOpenExistentialCallArgument( callee, paramIdx, paramTy, argTy)) { - assert(existentialArg->second->isEqual(argTy)); + // My kingdom for a decent "if let" in C++. + TypeVariableType *openedTypeVar; + Type existentialType; + OpenedExistentialAdjustments adjustments; + std::tie(openedTypeVar, existentialType, adjustments) = *existentialArg; + OpenedArchetypeType *opened; std::tie(argTy, opened) = cs.openExistentialType( - argTy, cs.getConstraintLocator(loc)); + existentialType, cs.getConstraintLocator(loc)); + + if (adjustments.contains(OpenedExistentialAdjustmentFlags::InOut)) + argTy = InOutType::get(argTy); - openedExistentials.push_back({existentialArg->first, opened}); + openedExistentials.push_back({openedTypeVar, opened}); } auto argLabel = argument.getLabel(); diff --git a/test/Constraints/opened_existentials.swift b/test/Constraints/opened_existentials.swift index 3536d58d2bd3f..004facd14cc35 100644 --- a/test/Constraints/opened_existentials.swift +++ b/test/Constraints/opened_existentials.swift @@ -152,6 +152,13 @@ func callVariadic(p1: any P, p2: any P) { // expected-note@-1{{only concrete types such as structs, enums and classes can conform to protocols}} } +func takesInOut(_ value: inout T) { } + +func passesInOut(i: Int) { + var p: any P = i + takesInOut(&p) +} + @available(SwiftStdlib 5.1, *) func testReturningOpaqueTypes(p: any P) { let q = p.getQ() From 5e50d0fea4583b667cc4a284a1d6f23b8f136451 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 10 Feb 2022 17:56:07 -0800 Subject: [PATCH 16/17] Allow existential opening for parameters of optional type. --- lib/Sema/CSSimplify.cpp | 10 ++++++---- test/Constraints/opened_existentials.swift | 10 ++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 468d4f9615e02..052654eb23562 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -1402,8 +1402,10 @@ shouldOpenExistentialCallArgument( if (param->isVariadic() && !param->getVarargBaseTy()->hasTypeSequence()) return None; - // Look through an inout type on the formal type of the parameter. - auto formalParamTy = param->getInterfaceType()->getInOutObjectType(); + // Look through an inout and optional types on the formal type of the + // parameter. + auto formalParamTy = param->getInterfaceType()->getInOutObjectType() + ->lookThroughSingleOptionalType(); // If the argument is of an existential metatype, look through the // metatype on the parameter. @@ -1412,8 +1414,8 @@ shouldOpenExistentialCallArgument( paramTy = paramTy->getMetatypeInstanceType(); } - // Look through an inout type on the parameter. - paramTy = paramTy->getInOutObjectType(); + // Look through an inout and optional types on the parameter. + paramTy = paramTy->getInOutObjectType()->lookThroughSingleOptionalType(); // The parameter type must be a type variable. auto paramTypeVar = paramTy->getAs(); diff --git a/test/Constraints/opened_existentials.swift b/test/Constraints/opened_existentials.swift index 004facd14cc35..f50f043214f3e 100644 --- a/test/Constraints/opened_existentials.swift +++ b/test/Constraints/opened_existentials.swift @@ -159,6 +159,16 @@ func passesInOut(i: Int) { takesInOut(&p) } +func takesOptional(_ value: T?) { } +// expected-note@-1{{required by global function 'takesOptional' where 'T' = 'P'}} + +func passesToOptional(p: any P, pOpt: (any P)?) { + takesOptional(p) // okay + takesOptional(pOpt) // expected-error{{protocol 'P' as a type cannot conform to the protocol itself}} + // expected-note@-1{{only concrete types such as structs, enums and classes can conform to protocols}} +} + + @available(SwiftStdlib 5.1, *) func testReturningOpaqueTypes(p: any P) { let q = p.getQ() From 2641f2de7f53fd8068a81b511c3402e1bbb3a75d Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 10 Feb 2022 17:50:43 -0800 Subject: [PATCH 17/17] [DNM] Force-enable implicit opening of existentials --- lib/Frontend/CompilerInvocation.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index b30af24940c28..8cc01f4612103 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -454,6 +454,9 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args, Opts.EnableParameterizedProtocolTypes |= Args.hasArg(OPT_enable_parameterized_protocol_types); + // FIXME: Experimental. + Opts.EnableOpenedExistentialTypes = true; + Opts.EnableOpenedExistentialTypes |= Args.hasArg(OPT_enable_experimental_opened_existential_types);