From fc1ad79dddd9356fea4ecddd6f32b2a91a8d80c2 Mon Sep 17 00:00:00 2001 From: Anthony Latsis Date: Wed, 18 Dec 2024 18:06:11 +0000 Subject: [PATCH 1/2] [NFC] ConstraintSystem: Rename `openExistentialType` to `openAnyExistentialType` This method can open an existential metatype too, and we already use `isAnyExistentialType` to mean "existential type or existential metatype" --- include/swift/Sema/ConstraintSystem.h | 9 +++++---- lib/Sema/CSSimplify.cpp | 8 ++++---- lib/Sema/ConstraintSystem.cpp | 5 +++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/include/swift/Sema/ConstraintSystem.h b/include/swift/Sema/ConstraintSystem.h index 7ea7b7ae8f24d..fe17f4bcd7e94 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -3277,10 +3277,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); + /// Open the given existential type or existential metatype, recording the + /// opened archetype in the constraint system and returning both the opened + /// type and opened archetype. + std::pair + openAnyExistentialType(Type type, ConstraintLocator *locator); /// Update OpenedExistentials and record a change in the trail. void recordOpenedExistentialType(ConstraintLocator *locator, diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index a253f1e58057c..f6833094e2b67 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -1825,7 +1825,7 @@ static ConstraintSystem::TypeMatchResult matchCallArguments( std::tie(openedTypeVar, existentialType, adjustments) = *existentialArg; OpenedArchetypeType *opened; - std::tie(argTy, opened) = cs.openExistentialType( + std::tie(argTy, opened) = cs.openAnyExistentialType( existentialType, cs.getConstraintLocator(loc)); if (adjustments.contains(OpenedExistentialAdjustmentFlags::LValue)) @@ -8647,7 +8647,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyConformsToConstraint( if (type->isExistentialType()) { if (auto elt = loc->getLastElementAs()) { if (elt->getPurpose() == CTP_ForEachSequence) { - type = openExistentialType(type, loc).first; + type = openAnyExistentialType(type, loc).first; } } } @@ -12430,8 +12430,8 @@ ConstraintSystem::simplifyOpenedExistentialOfConstraint( if (type2->isAnyExistentialType()) { // We have the existential side. Produce an opened archetype and bind // type1 to it. - Type openedTy = openExistentialType(type2, getConstraintLocator(locator)) - .first; + Type openedTy = + openAnyExistentialType(type2, getConstraintLocator(locator)).first; return matchTypes(type1, openedTy, ConstraintKind::Bind, subflags, locator); } if (!type2->isTypeVariableOrMember()) diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index 14354a0bfd498..52fa14326226a 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -853,8 +853,9 @@ ConstraintLocator *ConstraintSystem::getOpenOpaqueLocator( { LocatorPathElt::OpenedOpaqueArchetype(opaqueDecl) }, 0); } -std::pair ConstraintSystem::openExistentialType( - Type type, ConstraintLocator *locator) { +std::pair +ConstraintSystem::openAnyExistentialType(Type type, + ConstraintLocator *locator) { Type result = OpenedArchetypeType::getAny(type); Type t = result; while (t->is()) From 56d943a77eaa4cd90b6bb474079bd00e7eaf8af3 Mon Sep 17 00:00:00 2001 From: Anthony Latsis Date: Wed, 25 Dec 2024 02:25:28 +0000 Subject: [PATCH 2/2] [NFC] Sema: Tidy up `canOpenExistentialCallArgument` --- lib/Sema/CSSimplify.cpp | 33 +++++---- lib/Sema/OpenedExistentials.cpp | 86 ++++++++++------------ lib/Sema/OpenedExistentials.h | 27 ++----- test/Constraints/opened_existentials.swift | 40 ++++++++++ 4 files changed, 104 insertions(+), 82 deletions(-) diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index f6833094e2b67..fb26793c6ff58 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -1441,8 +1441,7 @@ class AllowLabelMismatches : public MatchCallArgumentListener { } }; -static std::optional< - std::tuple> +static std::optional> shouldOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx, Type paramTy, Type argTy, Expr *argExpr, ConstraintSystem &cs) { @@ -1816,25 +1815,29 @@ static ConstraintSystem::TypeMatchResult matchCallArguments( // If the argument is an existential type and the parameter is generic, // consider opening the existential type. - if (auto existentialArg = shouldOpenExistentialCallArgument( + if (auto typeVarAndBindingTy = shouldOpenExistentialCallArgument( callee, paramIdx, paramTy, argTy, argExpr, cs)) { // My kingdom for a decent "if let" in C++. - TypeVariableType *openedTypeVar; - Type existentialType; - OpenedExistentialAdjustments adjustments; - std::tie(openedTypeVar, existentialType, adjustments) = *existentialArg; + TypeVariableType *typeVar; + Type bindingTy; + std::tie(typeVar, bindingTy) = *typeVarAndBindingTy; - OpenedArchetypeType *opened; - std::tie(argTy, opened) = cs.openAnyExistentialType( - existentialType, cs.getConstraintLocator(loc)); + OpenedArchetypeType *openedArchetype; - if (adjustments.contains(OpenedExistentialAdjustmentFlags::LValue)) - argTy = LValueType::get(argTy); + // Open the argument type. + argTy = argTy.transformRec([&](TypeBase *t) -> std::optional { + if (t->isAnyExistentialType()) { + Type openedTy; + std::tie(openedTy, openedArchetype) = + cs.openAnyExistentialType(t, cs.getConstraintLocator(loc)); - if (adjustments.contains(OpenedExistentialAdjustmentFlags::InOut)) - argTy = InOutType::get(argTy); + return openedTy; + } + + return std::nullopt; + }); - openedExistentials.push_back({openedTypeVar, opened}); + openedExistentials.push_back({typeVar, openedArchetype}); } // If we have a compound function reference (e.g `fn($x:)`), respect diff --git a/lib/Sema/OpenedExistentials.cpp b/lib/Sema/OpenedExistentials.cpp index 0f2143b2ca9df..0ff0adf9bc562 100644 --- a/lib/Sema/OpenedExistentials.cpp +++ b/lib/Sema/OpenedExistentials.cpp @@ -590,8 +590,7 @@ swift::isMemberAvailableOnExistential(Type baseTy, const ValueDecl *member) { return result; } -std::optional< - std::tuple> +std::optional> swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx, Type paramTy, Type argTy) { if (!callee) @@ -623,24 +622,6 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx, if (!paramTy->hasTypeVariable()) return std::nullopt; - OpenedExistentialAdjustments adjustments; - - // The argument may be a "var" instead of a "let". - if (auto lv = argTy->getAs()) { - argTy = lv->getObjectType(); - adjustments |= OpenedExistentialAdjustmentFlags::LValue; - } - - // 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 std::nullopt; - auto param = getParameterAt(callee, paramIdx); if (!param) return std::nullopt; @@ -649,26 +630,40 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx, if (param->isVariadic()) return std::nullopt; - // Look through an inout and an optional type on the parameter types. - auto formalParamTy = param->getInterfaceType()->getInOutObjectType() - ->lookThroughSingleOptionalType(); - // Look through an inout and optional types on the parameter. - paramTy = paramTy->getInOutObjectType()->lookThroughSingleOptionalType(); - - // If the argument is of an existential metatype, look through the - // metatype on the parameter. - if (argTy->is()) { - formalParamTy = formalParamTy->getMetatypeInstanceType(); - paramTy = paramTy->getMetatypeInstanceType(); - } - - // The parameter type must be a type variable. - auto paramTypeVar = paramTy->getAs(); - if (!paramTypeVar) + // The rvalue argument type needs to be an existential type or metatype + // thereof. + const auto rValueArgTy = argTy->getWithoutSpecifierType(); + if (!rValueArgTy->isAnyExistentialType()) return std::nullopt; - auto genericParam = formalParamTy->getAs(); - if (!genericParam) + GenericTypeParamType *genericParam; + TypeVariableType *typeVar; + Type bindingTy; + + std::tie(genericParam, typeVar, bindingTy) = [=] { + // Look through an inout and optional type. + Type genericParam = param->getInterfaceType() + ->getInOutObjectType() + ->lookThroughSingleOptionalType(); + Type typeVar = + paramTy->getInOutObjectType()->lookThroughSingleOptionalType(); + + Type bindingTy = rValueArgTy; + + // Look through a metatype. + if (genericParam->is()) { + genericParam = genericParam->getMetatypeInstanceType(); + typeVar = typeVar->getMetatypeInstanceType(); + bindingTy = bindingTy->getMetatypeInstanceType(); + } + + return std::tuple(genericParam->getAs(), + typeVar->getAs(), bindingTy); + }(); + + // The should have reached a type variable and corresponding generic + // parameter. + if (!typeVar || !genericParam) return std::nullopt; // Only allow opening the innermost generic parameters. @@ -681,14 +676,11 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx, if (genericParam->getDepth() < genericSig->getMaxDepth()) return std::nullopt; - Type existentialTy; - if (auto existentialMetaTy = argTy->getAs()) - existentialTy = existentialMetaTy->getInstanceType(); - else - existentialTy = argTy; - - ASSERT(existentialTy->isAnyExistentialType()); - + // 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, + // bail out. + const Type existentialTy = bindingTy->getMetatypeInstanceType(); if (!existentialTy->isExistentialType()) return std::nullopt; @@ -726,7 +718,7 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx, if (referenceInfo.hasNonCovariantRef()) return std::nullopt; - return std::make_tuple(paramTypeVar, argTy, adjustments); + return std::pair(typeVar, bindingTy); } /// For each occurrence of a type **type** in `refTy` that satisfies diff --git a/lib/Sema/OpenedExistentials.h b/lib/Sema/OpenedExistentials.h index 12d30eafaef8d..8475261b11dfe 100644 --- a/lib/Sema/OpenedExistentials.h +++ b/lib/Sema/OpenedExistentials.h @@ -132,19 +132,10 @@ enum class ExistentialMemberAccessLimitation : uint8_t { ExistentialMemberAccessLimitation isMemberAvailableOnExistential(Type baseTy, const ValueDecl *member); -/// 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, - LValue = 0x02, -}; - -using OpenedExistentialAdjustments = - OptionSet; - -/// Determine whether we should open up the existential argument to the -/// given parameters. +/// Determine whether opening an existential argument for a function parameter +/// is supported. +/// A necessary condition for this is that the parameter interface type contains +/// a generic parameter type to which the opened argument can bind. /// /// \param callee The function or subscript being called. /// \param paramIdx The index specifying which function parameter is being @@ -153,13 +144,9 @@ using OpenedExistentialAdjustments = /// 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) the existential type that needs to be opened (from the -/// argument type), and the adjustments that need to be applied to the -/// existential type after it is opened. -std::optional< - std::tuple> +/// \returns If opening is supported, returns the type variable representing the +/// generic parameter type, and the unopened type it binds to. +std::optional> canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx, Type paramTy, Type argTy); diff --git a/test/Constraints/opened_existentials.swift b/test/Constraints/opened_existentials.swift index c13a64a52c216..397433a8fa529 100644 --- a/test/Constraints/opened_existentials.swift +++ b/test/Constraints/opened_existentials.swift @@ -498,3 +498,43 @@ do { // CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range= foo(&lValueP) } + +do { + do { + func foo(_: T) -> T {} + + let exist: any Any.Type + // CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range= + let result = foo(exist) + do { + // FIXME: The result type should be 'any Any.Type' + // var types = SwiftTypePair(typeOf: result, type2: SwiftType.self) + var types = SwiftTypePair(typeOf: result, type2: SwiftType.self) + types.assertTypesAreEqual() + } + } + + do { + func foo(_: T) -> T {} + + let exist: any Any.Type.Type + // CHECK-NOT: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range= + let result = foo(exist) + do { + var types = SwiftTypePair(typeOf: result, type2: SwiftType.self) + types.assertTypesAreEqual() + } + } + + do { + func foo(_: T) -> T {} + + let exist: any Any.Type + // CHECK-NOT: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range= + let result = foo(exist) + do { + var types = SwiftTypePair(typeOf: result, type2: SwiftType.self) + types.assertTypesAreEqual() + } + } +}