From 851a8290633f3d55c80bf5f186c44cd2b35f3408 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Wed, 4 Sep 2024 10:50:55 -0400 Subject: [PATCH 1/3] Sema: Consolidate logic for opening existentials in OpenedExistentials.cpp --- include/swift/AST/Decl.h | 62 -- include/swift/Sema/ConstraintSystem.h | 21 - lib/AST/Decl.cpp | 351 -------- lib/Sema/CMakeLists.txt | 1 + lib/Sema/CSApply.cpp | 9 +- lib/Sema/CSSimplify.cpp | 163 +--- lib/Sema/ConstraintSystem.cpp | 349 +------- lib/Sema/OpenedExistentials.cpp | 901 +++++++++++++++++++++ lib/Sema/OpenedExistentials.h | 141 ++++ lib/Sema/TypeCheckDecl.cpp | 29 - lib/Sema/TypeCheckEffects.cpp | 3 +- lib/Sema/TypeCheckProtocol.cpp | 5 +- test/Constraints/opened_existentials.swift | 7 + 13 files changed, 1065 insertions(+), 977 deletions(-) create mode 100644 lib/Sema/OpenedExistentials.cpp create mode 100644 lib/Sema/OpenedExistentials.h diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 85922459ebace..ae7c2e04028b8 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -2753,54 +2753,6 @@ class PoundDiagnosticDecl : public Decl { class OpaqueTypeDecl; -/// Describes the least favorable positions at which a requirement refers -/// 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: - /// Whether the uncurried interface type of the declaration, stripped of any - /// optionality, is a direct reference to the generic parameter at hand. For - /// example, "func foo(x: Int) -> () -> Self?" has a covariant 'Self' result. - bool hasCovariantSelfResult; - - OptionalTypePosition selfRef; - OptionalTypePosition assocTypeRef; - - /// A reference to 'Self'. - static GenericParameterReferenceInfo forSelfRef(TypePosition position) { - return GenericParameterReferenceInfo(false, position, std::nullopt); - } - - /// A reference to the generic parameter in covariant result position. - static GenericParameterReferenceInfo forCovariantResult() { - return GenericParameterReferenceInfo(true, TypePosition::Covariant, - std::nullopt); - } - - /// A reference to 'Self' through an associated type. - static GenericParameterReferenceInfo forAssocTypeRef(TypePosition position) { - return GenericParameterReferenceInfo(false, std::nullopt, position); - } - - GenericParameterReferenceInfo &operator|=(const GenericParameterReferenceInfo &other); - - explicit operator bool() const { - return hasCovariantSelfResult || selfRef || assocTypeRef; - } - - GenericParameterReferenceInfo() - : hasCovariantSelfResult(false), selfRef(std::nullopt), - assocTypeRef(std::nullopt) {} - -private: - GenericParameterReferenceInfo(bool hasCovariantSelfResult, OptionalTypePosition selfRef, - OptionalTypePosition assocTypeRef) - : hasCovariantSelfResult(hasCovariantSelfResult), selfRef(selfRef), - assocTypeRef(assocTypeRef) {} -}; - /// ValueDecl - All named decls that are values in the language. These can /// have a type, etc. class ValueDecl : public Decl { @@ -3334,9 +3286,6 @@ class ValueDecl : public Decl { /// @_dynamicReplacement(for: ...), compute the original declaration /// that this declaration dynamically replaces. ValueDecl *getDynamicallyReplacedDecl() const; - - /// Find references to 'Self' in the type signature of this declaration. - GenericParameterReferenceInfo findExistentialSelfReferences() const; }; /// This is a common base class for declarations which declare a type. @@ -9517,17 +9466,6 @@ class MacroExpansionDecl : public Decl, public FreestandingMacroExpansion { } }; -/// Find references to the given generic parameter in the type signature of the -/// given declaration using the given generic signature. -/// -/// \param skipParamIndex If the value is a function or subscript declaration, -/// specifies the index of the parameter that shall be skipped. -GenericParameterReferenceInfo -findGenericParameterReferences(const ValueDecl *value, CanGenericSignature sig, - GenericTypeParamType *origParam, - GenericTypeParamType *openedParam, - std::optional skipParamIndex); - inline void AbstractStorageDecl::overwriteSetterAccess(AccessLevel accessLevel) { Accessors.setInt(accessLevel); diff --git a/include/swift/Sema/ConstraintSystem.h b/include/swift/Sema/ConstraintSystem.h index b06193b20d080..571f090da593a 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -5697,13 +5697,6 @@ class ConstraintSystem { /// part of the constraint system. void forEachExpr(Expr *expr, llvm::function_ref callback); - /// Determine whether referencing the given member on the - /// given existential base type is supported. This is the case only if the - /// type of the member, spelled in the context of \p baseTy, does not contain - /// 'Self' or 'Self'-rooted dependent member types in non-covariant position. - bool isMemberAvailableOnExistential(Type baseTy, - const ValueDecl *member) const; - /// Attempts to infer a capability of a key path (i.e. whether it /// is read-only, writable, etc.) based on the referenced members. /// @@ -5961,20 +5954,6 @@ std::optional matchCallArguments( /// 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, replace the opened type variable and its member -/// types with their upper bounds. -Type typeEraseOpenedExistentialReference(Type type, Type existentialBaseType, - TypeVariableType *openedTypeVar, - TypePosition outermostPosition); - - -/// Given a type that includes opened existential archetypes derived from -/// the given generic environment, replace the archetypes with their upper -/// bounds. -Type typeEraseOpenedArchetypesFromEnvironment(Type type, - GenericEnvironment *env); - /// 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/AST/Decl.cpp b/lib/AST/Decl.cpp index e7ef69823acce..255e74b6dabe5 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -4811,357 +4811,6 @@ void ValueDecl::copyFormalAccessFrom(const ValueDecl *source, } } -GenericParameterReferenceInfo & -GenericParameterReferenceInfo::operator|=(const GenericParameterReferenceInfo &other) { - hasCovariantSelfResult |= other.hasCovariantSelfResult; - if (other.selfRef > selfRef) { - selfRef = other.selfRef; - } - if (other.assocTypeRef > assocTypeRef) { - assocTypeRef = other.assocTypeRef; - } - return *this; -} - -/// Forward declaration. -static GenericParameterReferenceInfo -findGenericParameterReferencesRec(CanGenericSignature, - GenericTypeParamType *, - GenericTypeParamType *, - Type, TypePosition, bool); - -/// Determine whether a function type with the given result type may have -/// a covariant generic parameter type result. This is true if the result type -/// is either a function type, or a generic parameter, possibly wrapped in some -/// level of optionality. -static bool canResultTypeHaveCovariantGenericParameterResult(Type resultTy) { - if (resultTy->is()) - return true; - - resultTy = resultTy->lookThroughAllOptionalTypes(); - return resultTy->is(); -} - -/// Report references to the given generic parameter within the given function -/// type using the given generic signature. -/// -/// \param position The current position in terms of variance. -/// \param skipParamIndex The index of the parameter that shall be skipped. -static GenericParameterReferenceInfo findGenericParameterReferencesInFunction( - CanGenericSignature genericSig, - GenericTypeParamType *origParam, - GenericTypeParamType *openedParam, - const AnyFunctionType *fnType, TypePosition position, - bool canBeCovariantResult, std::optional skipParamIndex) { - // If there are no type parameters, we're done. - if (!isa(fnType) && !fnType->hasTypeParameter()) - return GenericParameterReferenceInfo(); - - auto inputInfo = GenericParameterReferenceInfo(); - const auto params = fnType->getParams(); - for (const 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 types are invariant. - if (param.isInOut()) { - inputInfo |= ::findGenericParameterReferencesRec( - genericSig, origParam, openedParam, param.getPlainType(), - TypePosition::Invariant, /*canBeCovariantResult=*/false); - continue; - } - - // Parameters are contravariant, but if we're prior to the skipped - // parameter treat them as invariant because we're not allowed to - // reference the parameter at all. - TypePosition paramPos = position.flipped(); - if (skipParamIndex && paramIdx < *skipParamIndex) - paramPos = TypePosition::Invariant; - - inputInfo |= ::findGenericParameterReferencesRec( - genericSig, origParam, openedParam, param.getParameterType(), paramPos, - /*canBeCovariantResult=*/false); - } - - canBeCovariantResult = - // &= does not short-circuit. - canBeCovariantResult && - canResultTypeHaveCovariantGenericParameterResult(fnType->getResult()); - - const auto resultInfo = ::findGenericParameterReferencesRec( - genericSig, origParam, openedParam, fnType->getResult(), - position, canBeCovariantResult); - - return inputInfo |= resultInfo; -} - -/// Report references to the given generic parameter within the given type -/// using the given generic signature. -/// -/// \param position The current position in terms of variance. -static GenericParameterReferenceInfo -findGenericParameterReferencesRec(CanGenericSignature genericSig, - GenericTypeParamType *origParam, - GenericTypeParamType *openedParam, - Type type, - TypePosition position, - bool canBeCovariantResult) { - // If there are no type parameters, we're done. - if (!type->getCanonicalType()->hasTypeParameter()) - return GenericParameterReferenceInfo(); - - // Tuples preserve variance. - if (auto tuple = type->getAs()) { - auto info = GenericParameterReferenceInfo(); - for (auto &elt : tuple->getElements()) { - info |= findGenericParameterReferencesRec( - genericSig, origParam, openedParam, elt.getType(), position, - /*canBeCovariantResult=*/false); - } - - return info; - } - - // Function types preserve variance in the result type, and flip variance in - // the parameter type. - if (auto funcTy = type->getAs()) { - return findGenericParameterReferencesInFunction( - genericSig, origParam, openedParam, funcTy, - position, canBeCovariantResult, - /*skipParamIndex=*/std::nullopt); - } - - // Metatypes preserve variance. - if (auto metaTy = type->getAs()) { - return findGenericParameterReferencesRec(genericSig, origParam, openedParam, - metaTy->getInstanceType(), - position, canBeCovariantResult); - } - - // Optionals preserve variance. - if (auto optType = type->getOptionalObjectType()) { - return findGenericParameterReferencesRec( - genericSig, origParam, openedParam, optType, - position, canBeCovariantResult); - } - - // DynamicSelfType preserves variance. - if (auto selfType = type->getAs()) { - return findGenericParameterReferencesRec(genericSig, origParam, openedParam, - selfType->getSelfType(), position, - /*canBeCovariantResult=*/false); - } - - if (auto *const nominal = type->getAs()) { - auto info = GenericParameterReferenceInfo(); - - // Don't forget to look in the parent. - if (const auto parent = nominal->getParent()) { - info |= findGenericParameterReferencesRec( - genericSig, origParam, openedParam, parent, TypePosition::Invariant, - /*canBeCovariantResult=*/false); - } - - // Most bound generic types are invariant. - if (auto *const bgt = type->getAs()) { - if (bgt->isArray()) { - // Swift.Array preserves variance in its 'Value' type. - info |= findGenericParameterReferencesRec( - genericSig, origParam, openedParam, bgt->getGenericArgs().front(), - position, /*canBeCovariantResult=*/false); - } else if (bgt->isDictionary()) { - // Swift.Dictionary preserves variance in its 'Element' type. - info |= findGenericParameterReferencesRec( - genericSig, origParam, openedParam, bgt->getGenericArgs().front(), - TypePosition::Invariant, /*canBeCovariantResult=*/false); - info |= findGenericParameterReferencesRec( - genericSig, origParam, openedParam, bgt->getGenericArgs().back(), - position, /*canBeCovariantResult=*/false); - } else { - for (const auto ¶mType : bgt->getGenericArgs()) { - info |= findGenericParameterReferencesRec( - genericSig, origParam, openedParam, paramType, - TypePosition::Invariant, /*canBeCovariantResult=*/false); - } - } - } - - return info; - } - - // If the signature of an opaque result type has a same-type constraint - // that references Self, it's invariant. - if (auto opaque = type->getAs()) { - auto info = GenericParameterReferenceInfo(); - auto opaqueSig = opaque->getDecl()->getOpaqueInterfaceGenericSignature(); - for (const auto &req : opaqueSig.getRequirements()) { - switch (req.getKind()) { - case RequirementKind::SameShape: - llvm_unreachable("Same-shape requirement not supported here"); - - case RequirementKind::Conformance: - case RequirementKind::Layout: - continue; - - case RequirementKind::SameType: - info |= findGenericParameterReferencesRec( - genericSig, origParam, openedParam, req.getFirstType(), - TypePosition::Invariant, /*canBeCovariantResult=*/false); - - LLVM_FALLTHROUGH; - - case RequirementKind::Superclass: - info |= findGenericParameterReferencesRec( - genericSig, origParam, openedParam, req.getSecondType(), - TypePosition::Invariant, /*canBeCovariantResult=*/false); - break; - } - } - - return info; - } - - if (auto *existential = type->getAs()) - type = existential->getConstraintType(); - - // Protocol compositions are invariant. - if (auto *comp = type->getAs()) { - auto info = GenericParameterReferenceInfo(); - - for (auto member : comp->getMembers()) { - info |= findGenericParameterReferencesRec( - genericSig, origParam, openedParam, member, - TypePosition::Invariant, /*canBeCovariantResult=*/false); - } - - return info; - } - - // Packs are invariant. - if (auto *pack = type->getAs()) { - auto info = GenericParameterReferenceInfo(); - - for (auto arg : pack->getElementTypes()) { - info |= findGenericParameterReferencesRec( - genericSig, origParam, openedParam, arg, - TypePosition::Invariant, /*canBeCovariantResult=*/false); - } - - return info; - } - - // Pack expansions are invariant. - if (auto *expansion = type->getAs()) { - return findGenericParameterReferencesRec( - genericSig, origParam, openedParam, expansion->getPatternType(), - TypePosition::Invariant, /*canBeCovariantResult=*/false); - } - - // Specifically ignore parameterized protocols and existential - // metatypes because we can erase them to the upper bound. - if (type->is() || - type->is()) { - return GenericParameterReferenceInfo(); - } - - // Everything else should be a type parameter. - if (!type->isTypeParameter()) { - llvm::errs() << "Unhandled type:\n"; - type->dump(llvm::errs()); - abort(); - } - - if (!type->getRootGenericParam()->isEqual(origParam)) { - return GenericParameterReferenceInfo(); - } - - // A direct reference to 'Self'. - if (type->is()) { - if (position == TypePosition::Covariant && canBeCovariantResult) - return GenericParameterReferenceInfo::forCovariantResult(); - - return GenericParameterReferenceInfo::forSelfRef(position); - } - - if (origParam != openedParam) { - // Replace the original parameter with the parameter in the opened - // signature. - type = type.subst( - [&](SubstitutableType *type) { - ASSERT(type->isEqual(origParam)); - return openedParam; - }, - MakeAbstractConformanceForGenericType()); - } - - if (genericSig) { - // If the type parameter is beyond the domain of the opened - // signature, ignore it. - if (!genericSig->isValidTypeParameter(type)) { - return GenericParameterReferenceInfo(); - } - - if (auto reducedTy = genericSig.getReducedType(type)) { - if (!reducedTy->isEqual(type)) { - // Note: origParam becomes openedParam for the recursive call, - // because concreteTy is written in terms of genericSig and not - // the signature of the old origParam. - return findGenericParameterReferencesRec( - CanGenericSignature(), openedParam, openedParam, reducedTy, - position, canBeCovariantResult); - } - } - } - - // A reference to an associated type rooted on 'Self'. - return GenericParameterReferenceInfo::forAssocTypeRef(position); -} - -GenericParameterReferenceInfo -swift::findGenericParameterReferences(const ValueDecl *value, - CanGenericSignature sig, - GenericTypeParamType *origParam, - GenericTypeParamType *openedParam, - std::optional skipParamIndex) { - if (isa(value)) - return GenericParameterReferenceInfo(); - - auto type = value->getInterfaceType(); - - // Skip invalid declarations. - if (type->hasError()) - return GenericParameterReferenceInfo(); - - // For functions and subscripts, take skipParamIndex into account. - if (isa(value) || isa(value)) { - // And for a method, skip the 'self' parameter. - if (value->hasCurriedSelf()) - type = type->castTo()->getResult(); - - return ::findGenericParameterReferencesInFunction( - sig, origParam, openedParam, type->castTo(), - TypePosition::Covariant, /*canBeCovariantResult=*/true, - skipParamIndex); - } - - return ::findGenericParameterReferencesRec(sig, origParam, openedParam, type, - TypePosition::Covariant, - /*canBeCovariantResult=*/true); -} - -GenericParameterReferenceInfo ValueDecl::findExistentialSelfReferences() const { - auto *dc = getDeclContext(); - ASSERT(dc->getSelfProtocolDecl()); - - auto sig = dc->getGenericSignatureOfContext().getCanonicalSignature(); - auto genericParam = dc->getSelfInterfaceType()->castTo(); - - return findGenericParameterReferences(this, sig, genericParam, genericParam, - std::nullopt); -} - TypeDecl::CanBeInvertible::Result NominalTypeDecl::canConformTo(InvertibleProtocolKind ip) const { auto *proto = getASTContext().getProtocol(getKnownProtocolKind(ip)); diff --git a/lib/Sema/CMakeLists.txt b/lib/Sema/CMakeLists.txt index 567eda88df213..6c429921401c0 100644 --- a/lib/Sema/CMakeLists.txt +++ b/lib/Sema/CMakeLists.txt @@ -38,6 +38,7 @@ add_swift_host_library(swiftSema STATIC InstrumenterSupport.cpp LookupVisibleDecls.cpp MiscDiagnostics.cpp + OpenedExistentials.cpp PCMacro.cpp PlaygroundTransform.cpp PreCheckTarget.cpp diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 23afdb2712560..b21a73b425511 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -19,6 +19,7 @@ #include "CSDiagnostics.h" #include "CodeSynthesis.h" #include "MiscDiagnostics.h" +#include "OpenedExistentials.h" #include "TypeCheckConcurrency.h" #include "TypeCheckMacros.h" #include "TypeCheckProtocol.h" @@ -965,7 +966,7 @@ namespace { auto *env = record.Archetype->getGenericEnvironment(); if (resultTy->hasLocalArchetypeFromEnvironment(env)) { - Type erasedTy = constraints::typeEraseOpenedArchetypesFromEnvironment( + Type erasedTy = typeEraseOpenedArchetypesFromEnvironment( resultTy, env); auto range = result->getSourceRange(); result = coerceToType(result, erasedTy, locator); @@ -1676,7 +1677,7 @@ namespace { } else { // Erase opened existentials from the type of the thunk; we're // going to open the existential inside the thunk's body. - containerTy = constraints::typeEraseOpenedArchetypesFromEnvironment( + containerTy = typeEraseOpenedArchetypesFromEnvironment( containerTy, knownOpened->second->getGenericEnvironment()); selfTy = containerTy; } @@ -1740,7 +1741,7 @@ namespace { // If the base was an opened existential, erase the opened // existential. if (openedExistential) { - refType = constraints::typeEraseOpenedArchetypesFromEnvironment( + refType = typeEraseOpenedArchetypesFromEnvironment( refType, baseTy->castTo()->getGenericEnvironment()); } @@ -1966,7 +1967,7 @@ namespace { getConstraintSystem().getConstraintLocator(memberLocator)); if (knownOpened != solution.OpenedExistentialTypes.end()) { curryThunkTy = - constraints::typeEraseOpenedArchetypesFromEnvironment( + typeEraseOpenedArchetypesFromEnvironment( curryThunkTy, knownOpened->second->getGenericEnvironment()) ->castTo(); } diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index b08d7afbb1692..2f3dc4ad118c6 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -16,6 +16,7 @@ //===----------------------------------------------------------------------===// #include "CSDiagnostics.h" +#include "OpenedExistentials.h" #include "TypeCheckConcurrency.h" #include "TypeCheckEffects.h" #include "swift/AST/ASTPrinter.h" @@ -1440,66 +1441,13 @@ class AllowLabelMismatches : public MatchCallArgumentListener { } }; -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, - LValue = 0x02, - }; - - using OpenedExistentialAdjustments = - OptionSet; -} - -/// 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 generic parameter, 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. static std::optional> shouldOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx, Type paramTy, Type argTy, Expr *argExpr, ConstraintSystem &cs) { - if (!callee) - return std::nullopt; - - // Only applies to functions and subscripts. - if (!isa(callee) && !isa(callee)) - return std::nullopt; - - // Special semantics prohibit opening existentials. - switch (TypeChecker::getDeclTypeCheckingSemantics(callee)) { - case DeclTypeCheckingSemantics::OpenExistential: - case DeclTypeCheckingSemantics::TypeOf: - // type(of:) and _openExistential handle their own opening. - return std::nullopt; - - case DeclTypeCheckingSemantics::Normal: - case DeclTypeCheckingSemantics::WithoutActuallyEscaping: - 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 std::nullopt; - - // The actual parameter type needs to involve a type variable, otherwise - // type inference won't be possible. - if (!paramTy->hasTypeVariable()) + auto result = canOpenExistentialCallArgument(callee, paramIdx, paramTy, argTy); + if (!result) return std::nullopt; // An argument expression that explicitly coerces to an existential @@ -1517,110 +1465,7 @@ shouldOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx, } } - 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; - - // If the parameter is non-generic variadic, don't open. - if (param->isVariadic()) - return std::nullopt; - - // 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. - if (argTy->is()) { - formalParamTy = formalParamTy->getMetatypeInstanceType(); - paramTy = paramTy->getMetatypeInstanceType(); - } - - // 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(); - if (!paramTypeVar) - return std::nullopt; - - auto genericParam = formalParamTy->getAs(); - if (!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; - - Type existentialTy; - if (auto existentialMetaTy = argTy->getAs()) - existentialTy = existentialMetaTy->getInstanceType(); - else - existentialTy = argTy; - - ASSERT(existentialTy->isExistentialType()); - - auto &ctx = cs.getASTContext(); - - // If the existential argument conforms to all of protocol requirements on - // the formal parameter's type, don't open unless ImplicitOpenExistentials is - // enabled. - - // If all of the conformance requirements on the formal parameter's type - // are self-conforming, don't open. - if (!ctx.LangOpts.hasFeature(Feature::ImplicitOpenExistentials)) { - bool containsNonSelfConformance = false; - for (auto proto : genericSig->getRequiredProtocols(genericParam)) { - auto conformance = lookupExistentialConformance( - existentialTy, proto); - if (conformance.isInvalid()) { - containsNonSelfConformance = true; - break; - } - } - - if (!containsNonSelfConformance) - return std::nullopt; - } - - auto existentialSig = ctx.getOpenedExistentialSignature(existentialTy); - - // Ensure that the formal parameter is only used in covariant positions, - // because it won't match anywhere else. - auto referenceInfo = findGenericParameterReferences( - callee, existentialSig.OpenedSig, genericParam, - existentialSig.SelfType->castTo(), - /*skipParamIdx=*/paramIdx); - if (referenceInfo.selfRef > TypePosition::Covariant || - referenceInfo.assocTypeRef > TypePosition::Covariant) - return std::nullopt; - - return std::make_tuple(genericParam, paramTypeVar, argTy, adjustments); + return result; } // Match the argument of a call to the parameter. diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index 0593bd1c75028..2ae76835a7dfe 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -17,6 +17,7 @@ //===----------------------------------------------------------------------===// #include "swift/Sema/ConstraintSystem.h" #include "CSDiagnostics.h" +#include "OpenedExistentials.h" #include "TypeCheckAvailability.h" #include "TypeCheckConcurrency.h" #include "TypeCheckMacros.h" @@ -2283,213 +2284,6 @@ static bool isMainDispatchQueueMember(ConstraintLocator *locator) { return true; } -/// For each occurrence of a type **type** in `refTy` that satisfies -/// `predicateFn` in covariant position, **type** is erased to an -/// existential using `eraseFn`. -static Type typeEraseExistentialSelfReferences( - Type refTy, TypePosition outermostPosition, - llvm::function_ref containsFn, - llvm::function_ref predicateFn, - llvm::function_ref eraseFn) { - if (!containsFn(refTy)) - return refTy; - - return refTy.transformWithPosition( - outermostPosition, - [&](TypeBase *t, TypePosition currPos) -> std::optional { - if (!containsFn(t)) { - return Type(t); - } - - if (t->is()) { - const auto instanceTy = t->getMetatypeInstanceType(); - auto erasedTy = typeEraseExistentialSelfReferences( - instanceTy, currPos, - containsFn, predicateFn, eraseFn); - if (instanceTy.getPointer() == erasedTy.getPointer()) { - return Type(t); - } - - // - If the output instance type is an existential, but the input is - // not, wrap the output in an existential metatype. - // - // X.Type → X → any Y → any Y.Type - // - // - Otherwise, both are existential or the output instance type is - // not existential; wrap the output in a singleton metatype. - if (erasedTy->isAnyExistentialType() && - !erasedTy->isConstraintType() && - !(instanceTy->isAnyExistentialType() && - !instanceTy->isConstraintType())) { - return Type(ExistentialMetatypeType::get(erasedTy)); - } - - return Type(MetatypeType::get(erasedTy)); - } - - // Opaque types whose substitutions involve this type parameter are - // erased to their upper bound. - if (auto opaque = dyn_cast(t)) { - for (auto replacementType : - opaque->getSubstitutions().getReplacementTypes()) { - auto erasedReplacementType = typeEraseExistentialSelfReferences( - replacementType, TypePosition::Covariant, - containsFn, predicateFn, eraseFn); - if (erasedReplacementType.getPointer() != - replacementType.getPointer()) - return opaque->getExistentialType(); - } - } - - // Parameterized protocol types whose arguments involve this type - // parameter are erased to the base type. - if (auto parameterized = dyn_cast(t)) { - for (auto argType : parameterized->getArgs()) { - auto erasedArgType = typeEraseExistentialSelfReferences( - argType, TypePosition::Covariant, - containsFn, predicateFn, eraseFn); - if (erasedArgType.getPointer() != argType.getPointer()) - return parameterized->getBaseType(); - } - } - /* - if (auto lvalue = dyn_cast(t)) { - auto objTy = lvalue->getObjectType(); - auto erasedTy = - typeEraseExistentialSelfReferences( - objTy, currPos, - containsFn, predicateFn, eraseFn); - - if (erasedTy.getPointer() == objTy.getPointer()) - return Type(lvalue); - - return erasedTy; - } - */ - - if (!predicateFn(t)) { - // Recurse. - return std::nullopt; - } - - auto erasedTy = eraseFn(t, currPos); - if (!erasedTy) - return Type(t); - - return erasedTy; - }); -} - -Type constraints::typeEraseOpenedExistentialReference( - Type type, Type existentialBaseType, TypeVariableType *openedTypeVar, - TypePosition outermostPosition) { - auto existentialSig = - type->getASTContext().getOpenedExistentialSignature( - existentialBaseType); - - auto applyOuterSubstitutions = [&](Type t) -> Type { - if (t->hasTypeParameter()) { - auto outerSubs = existentialSig.Generalization; - unsigned depth = existentialSig.OpenedSig->getMaxDepth(); - OuterSubstitutions replacer{outerSubs, depth}; - return t.subst(replacer, replacer); - } - - return t; - }; - - auto erase = [&](Type paramTy, TypePosition currPos) -> Type { - switch (currPos) { - case TypePosition::Covariant: - break; - - case TypePosition::Contravariant: - case TypePosition::Invariant: - case TypePosition::Shape: - return Type(); - } - - // The upper bounds of 'Self' is the existential base type. - if (paramTy->is()) - return existentialBaseType; - - return applyOuterSubstitutions( - existentialSig.OpenedSig->getExistentialType(paramTy)); - }; - - return typeEraseExistentialSelfReferences( - type, - outermostPosition, - /*containsFn=*/[](Type t) { - return t->hasTypeVariable(); - }, - /*predicateFn=*/[](Type t) { - return t->isTypeVariableOrMember(); - }, - /*eraseFn=*/[&](Type t, TypePosition currPos) -> Type { - bool found = false; - auto paramTy = t.transformRec([&](Type t) -> std::optional { - if (t.getPointer() == openedTypeVar) { - found = true; - return existentialSig.SelfType; - } - return std::nullopt; - }); - - if (!found) - return Type(); - - assert(paramTy->isTypeParameter()); - - // This can happen with invalid code. - if (!existentialSig.OpenedSig->isValidTypeParameter(paramTy)) { - return Type(t); - } - - // Check if this existential fixes this `Self`-rooted type to something - // in the existential's outer generic signature. - Type reducedTy = existentialSig.OpenedSig.getReducedType(paramTy); - if (!reducedTy->isEqual(paramTy)) { - reducedTy = applyOuterSubstitutions(reducedTy); - - auto erasedTy = typeEraseExistentialSelfReferences( - reducedTy, currPos, - [&](Type t) { return t->hasTypeParameter(); }, - [&](Type t) { return t->isTypeParameter(); }, - [&](Type t, TypePosition currPos) { return erase(t, currPos); }); - if (erasedTy.getPointer() == reducedTy.getPointer()) { - return Type(t); - } - - return erasedTy; - } - - return erase(paramTy, currPos); - }); -} - -Type constraints::typeEraseOpenedArchetypesFromEnvironment( - Type type, GenericEnvironment *env) { - assert(env->getKind() == GenericEnvironment::Kind::OpenedExistential); - - return typeEraseExistentialSelfReferences( - type, - TypePosition::Covariant, - /*containsFn=*/[](Type t) { - return t->hasOpenedExistential(); - }, - /*predicateFn=*/[](Type t) { - return t->is(); - }, - /*eraseFn=*/[&](Type t, TypePosition currPos) { - auto *openedTy = t->castTo(); - if (openedTy->getGenericEnvironment() == env) - return openedTy->getExistentialType(); - - return Type(); - }); -} - static bool isExistentialMemberAccessWithExplicitBaseExpression( Type baseInstanceTy, ValueDecl *member, ConstraintLocator *locator, bool isDynamicLookup) { @@ -7421,147 +7215,6 @@ void ConstraintSystem::maybeProduceFallbackDiagnostic( ctx.Diags.diagnose(target.getLoc(), diag::failed_to_produce_diagnostic); } -/// A protocol member accessed with an existential value might have generic -/// constraints that require the ability to spell an opened archetype in order -/// to be satisfied. Such are -/// - superclass requirements, when the object is a non-'Self'-rooted type -/// parameter, and the subject is dependent on 'Self', e.g. U : G -/// - same-type requirements, when one side is dependent on 'Self', and the -/// other is a non-'Self'-rooted type parameter, e.g. U.Element == Self. -/// -/// Because opened archetypes are not part of the surface language, these -/// constraints render the member inaccessible. -static bool doesMemberHaveUnfulfillableConstraintsWithExistentialBase( - OpenedExistentialSignature existentialSig, const ValueDecl *member) { - const auto sig = - member->getInnermostDeclContext()->getGenericSignatureOfContext(); - - // Fast path: the member is generic only over 'Self'. - if (sig.getGenericParams().size() == 1) { - return false; - } - - class IsDependentOnOpenedExistentialSelf : public TypeWalker { - OpenedExistentialSignature existentialSig; - - public: - explicit IsDependentOnOpenedExistentialSelf(OpenedExistentialSignature existentialSig) - : existentialSig(existentialSig) {} - - Action walkToTypePre(Type ty) override { - // We're looking at the interface type of a protocol member, so it's written - // in terms of `Self` (tau_0_0) and possibly type parameters at higher depth: - // - // - if (!ty->isTypeParameter()) { - return Action::Continue; - } - - if (ty->getRootGenericParam()->getDepth() > 0) { - return Action::SkipNode; - } - - // Ok, we found a type parameter rooted in `Self`. Replace `Self` with the - // opened Self type in the existential signature, which looks like this: - // - // <..., Self where ..., Self: P> - ty = ty.subst( - [&](SubstitutableType *type) -> Type { - return existentialSig.SelfType; - }, - MakeAbstractConformanceForGenericType()); - - // Make sure this is valid first. - if (!existentialSig.OpenedSig->isValidTypeParameter(ty)) { - return Action::SkipNode; - } - - // If the existential type constrains Self.U to a type from the outer - // context, then the reduced type of Self.U in the existential signature - // will no longer contain Self. - ty = existentialSig.OpenedSig.getReducedType(ty); - - if (!ty.findIf([&](Type t) -> bool { - if (auto *paramTy = t->getAs()) - return paramTy->isEqual(existentialSig.SelfType); - return false; - })) { - return Action::SkipNode; - } - - // Ok, we found a type that depends on the opened existential Self. - return Action::Stop; - } - } isDependentOnSelf(existentialSig); - - for (const auto &req : sig.getRequirements()) { - switch (req.getKind()) { - case RequirementKind::Superclass: { - if (req.getFirstType()->getRootGenericParam()->getDepth() > 0 && - req.getSecondType().walk(isDependentOnSelf)) { - return true; - } - - break; - } - case RequirementKind::SameType: - case RequirementKind::SameShape: { - const auto isNonSelfRootedTypeParam = [](Type ty) { - return ty->isTypeParameter() && - ty->getRootGenericParam()->getDepth() > 0; - }; - - if ((isNonSelfRootedTypeParam(req.getFirstType()) && - req.getSecondType().walk(isDependentOnSelf)) || - (isNonSelfRootedTypeParam(req.getSecondType()) && - req.getFirstType().walk(isDependentOnSelf))) { - return true; - } - - break; - } - case RequirementKind::Conformance: - case RequirementKind::Layout: - break; - } - } - - return false; -} - -bool ConstraintSystem::isMemberAvailableOnExistential( - Type baseTy, const ValueDecl *member) const { - assert(member->getDeclContext()->getSelfProtocolDecl()); - - auto existentialSig = getASTContext().getOpenedExistentialSignature(baseTy); - - auto *dc = member->getDeclContext(); - auto origParam = dc->getSelfInterfaceType()->castTo(); - auto openedParam = existentialSig.SelfType->castTo(); - - auto info = findGenericParameterReferences( - member, existentialSig.OpenedSig, origParam, openedParam, - std::nullopt); - - if (info.selfRef > TypePosition::Covariant || - info.assocTypeRef > TypePosition::Covariant) { - return false; - } - - // FIXME: Appropriately diagnose assignments instead. - if (auto *const storageDecl = dyn_cast(member)) { - if (info.hasCovariantSelfResult && storageDecl->supportsMutation()) - return false; - } - - if (doesMemberHaveUnfulfillableConstraintsWithExistentialBase(existentialSig, - member)) { - return false; - } - - return true; -} - SourceLoc constraints::getLoc(ASTNode anchor) { if (auto *E = anchor.dyn_cast()) { return E->getLoc(); diff --git a/lib/Sema/OpenedExistentials.cpp b/lib/Sema/OpenedExistentials.cpp new file mode 100644 index 0000000000000..29592f4d4f8cb --- /dev/null +++ b/lib/Sema/OpenedExistentials.cpp @@ -0,0 +1,901 @@ +//===--- OpenedExistentials.cpp - Utilities for existential types ---------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file defines common utilities for existential opening and some related +// things, such as the checks around covariant `Self` in class conformances. +// +//===----------------------------------------------------------------------===// + +#include "OpenedExistentials.h" +#include "TypeChecker.h" +#include "swift/AST/ASTContext.h" +#include "swift/AST/ConformanceLookup.h" +#include "swift/AST/Decl.h" +#include "swift/AST/GenericEnvironment.h" +#include "swift/AST/Types.h" +#include "swift/AST/TypeCheckRequests.h" +#include "swift/Basic/Assertions.h" + +using namespace swift; + +GenericParameterReferenceInfo & +GenericParameterReferenceInfo::operator|=(const GenericParameterReferenceInfo &other) { + hasCovariantSelfResult |= other.hasCovariantSelfResult; + if (other.selfRef > selfRef) { + selfRef = other.selfRef; + } + if (other.assocTypeRef > assocTypeRef) { + assocTypeRef = other.assocTypeRef; + } + return *this; +} + +/// Forward declaration. +static GenericParameterReferenceInfo +findGenericParameterReferencesRec(CanGenericSignature, + GenericTypeParamType *, + GenericTypeParamType *, + Type, TypePosition, bool); + +/// Determine whether a function type with the given result type may have +/// a covariant generic parameter type result. This is true if the result type +/// is either a function type, or a generic parameter, possibly wrapped in some +/// level of optionality. +static bool canResultTypeHaveCovariantGenericParameterResult(Type resultTy) { + if (resultTy->is()) + return true; + + resultTy = resultTy->lookThroughAllOptionalTypes(); + return resultTy->is(); +} + +/// Report references to the given generic parameter within the given function +/// type using the given generic signature. +/// +/// \param position The current position in terms of variance. +/// \param skipParamIndex The index of the parameter that shall be skipped. +static GenericParameterReferenceInfo findGenericParameterReferencesInFunction( + CanGenericSignature genericSig, + GenericTypeParamType *origParam, + GenericTypeParamType *openedParam, + const AnyFunctionType *fnType, TypePosition position, + bool canBeCovariantResult, std::optional skipParamIndex) { + // If there are no type parameters, we're done. + if (!isa(fnType) && !fnType->hasTypeParameter()) + return GenericParameterReferenceInfo(); + + auto inputInfo = GenericParameterReferenceInfo(); + const auto params = fnType->getParams(); + for (const 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 types are invariant. + if (param.isInOut()) { + inputInfo |= ::findGenericParameterReferencesRec( + genericSig, origParam, openedParam, param.getPlainType(), + TypePosition::Invariant, /*canBeCovariantResult=*/false); + continue; + } + + // Parameters are contravariant, but if we're prior to the skipped + // parameter treat them as invariant because we're not allowed to + // reference the parameter at all. + TypePosition paramPos = position.flipped(); + if (skipParamIndex && paramIdx < *skipParamIndex) + paramPos = TypePosition::Invariant; + + inputInfo |= ::findGenericParameterReferencesRec( + genericSig, origParam, openedParam, param.getParameterType(), paramPos, + /*canBeCovariantResult=*/false); + } + + canBeCovariantResult = + // &= does not short-circuit. + canBeCovariantResult && + canResultTypeHaveCovariantGenericParameterResult(fnType->getResult()); + + const auto resultInfo = ::findGenericParameterReferencesRec( + genericSig, origParam, openedParam, fnType->getResult(), + position, canBeCovariantResult); + + return inputInfo |= resultInfo; +} + +/// Report references to the given generic parameter within the given type +/// using the given generic signature. +/// +/// \param position The current position in terms of variance. +static GenericParameterReferenceInfo +findGenericParameterReferencesRec(CanGenericSignature genericSig, + GenericTypeParamType *origParam, + GenericTypeParamType *openedParam, + Type type, + TypePosition position, + bool canBeCovariantResult) { + // If there are no type parameters, we're done. + if (!type->getCanonicalType()->hasTypeParameter()) + return GenericParameterReferenceInfo(); + + // Tuples preserve variance. + if (auto tuple = type->getAs()) { + auto info = GenericParameterReferenceInfo(); + for (auto &elt : tuple->getElements()) { + info |= findGenericParameterReferencesRec( + genericSig, origParam, openedParam, elt.getType(), position, + /*canBeCovariantResult=*/false); + } + + return info; + } + + // Function types preserve variance in the result type, and flip variance in + // the parameter type. + if (auto funcTy = type->getAs()) { + return findGenericParameterReferencesInFunction( + genericSig, origParam, openedParam, funcTy, + position, canBeCovariantResult, + /*skipParamIndex=*/std::nullopt); + } + + // Metatypes preserve variance. + if (auto metaTy = type->getAs()) { + return findGenericParameterReferencesRec(genericSig, origParam, openedParam, + metaTy->getInstanceType(), + position, canBeCovariantResult); + } + + // Optionals preserve variance. + if (auto optType = type->getOptionalObjectType()) { + return findGenericParameterReferencesRec( + genericSig, origParam, openedParam, optType, + position, canBeCovariantResult); + } + + // DynamicSelfType preserves variance. + if (auto selfType = type->getAs()) { + return findGenericParameterReferencesRec(genericSig, origParam, openedParam, + selfType->getSelfType(), position, + /*canBeCovariantResult=*/false); + } + + if (auto *const nominal = type->getAs()) { + auto info = GenericParameterReferenceInfo(); + + // Don't forget to look in the parent. + if (const auto parent = nominal->getParent()) { + info |= findGenericParameterReferencesRec( + genericSig, origParam, openedParam, parent, TypePosition::Invariant, + /*canBeCovariantResult=*/false); + } + + // Most bound generic types are invariant. + if (auto *const bgt = type->getAs()) { + if (bgt->isArray()) { + // Swift.Array preserves variance in its 'Value' type. + info |= findGenericParameterReferencesRec( + genericSig, origParam, openedParam, bgt->getGenericArgs().front(), + position, /*canBeCovariantResult=*/false); + } else if (bgt->isDictionary()) { + // Swift.Dictionary preserves variance in its 'Element' type. + info |= findGenericParameterReferencesRec( + genericSig, origParam, openedParam, bgt->getGenericArgs().front(), + TypePosition::Invariant, /*canBeCovariantResult=*/false); + info |= findGenericParameterReferencesRec( + genericSig, origParam, openedParam, bgt->getGenericArgs().back(), + position, /*canBeCovariantResult=*/false); + } else { + for (const auto ¶mType : bgt->getGenericArgs()) { + info |= findGenericParameterReferencesRec( + genericSig, origParam, openedParam, paramType, + TypePosition::Invariant, /*canBeCovariantResult=*/false); + } + } + } + + return info; + } + + // If the signature of an opaque result type has a same-type constraint + // that references Self, it's invariant. + if (auto opaque = type->getAs()) { + auto info = GenericParameterReferenceInfo(); + auto opaqueSig = opaque->getDecl()->getOpaqueInterfaceGenericSignature(); + for (const auto &req : opaqueSig.getRequirements()) { + switch (req.getKind()) { + case RequirementKind::SameShape: + llvm_unreachable("Same-shape requirement not supported here"); + + case RequirementKind::Conformance: + case RequirementKind::Layout: + continue; + + case RequirementKind::SameType: + info |= findGenericParameterReferencesRec( + genericSig, origParam, openedParam, req.getFirstType(), + TypePosition::Invariant, /*canBeCovariantResult=*/false); + + LLVM_FALLTHROUGH; + + case RequirementKind::Superclass: + info |= findGenericParameterReferencesRec( + genericSig, origParam, openedParam, req.getSecondType(), + TypePosition::Invariant, /*canBeCovariantResult=*/false); + break; + } + } + + return info; + } + + if (auto *existential = type->getAs()) + type = existential->getConstraintType(); + + // Protocol compositions are invariant. + if (auto *comp = type->getAs()) { + auto info = GenericParameterReferenceInfo(); + + for (auto member : comp->getMembers()) { + info |= findGenericParameterReferencesRec( + genericSig, origParam, openedParam, member, + TypePosition::Invariant, /*canBeCovariantResult=*/false); + } + + return info; + } + + // Packs are invariant. + if (auto *pack = type->getAs()) { + auto info = GenericParameterReferenceInfo(); + + for (auto arg : pack->getElementTypes()) { + info |= findGenericParameterReferencesRec( + genericSig, origParam, openedParam, arg, + TypePosition::Invariant, /*canBeCovariantResult=*/false); + } + + return info; + } + + // Pack expansions are invariant. + if (auto *expansion = type->getAs()) { + return findGenericParameterReferencesRec( + genericSig, origParam, openedParam, expansion->getPatternType(), + TypePosition::Invariant, /*canBeCovariantResult=*/false); + } + + // Specifically ignore parameterized protocols and existential + // metatypes because we can erase them to the upper bound. + if (type->is() || + type->is()) { + return GenericParameterReferenceInfo(); + } + + // Everything else should be a type parameter. + if (!type->isTypeParameter()) { + llvm::errs() << "Unhandled type:\n"; + type->dump(llvm::errs()); + abort(); + } + + if (!type->getRootGenericParam()->isEqual(origParam)) { + return GenericParameterReferenceInfo(); + } + + // A direct reference to 'Self'. + if (type->is()) { + if (position == TypePosition::Covariant && canBeCovariantResult) + return GenericParameterReferenceInfo::forCovariantResult(); + + return GenericParameterReferenceInfo::forSelfRef(position); + } + + if (origParam != openedParam) { + // Replace the original parameter with the parameter in the opened + // signature. + type = type.subst( + [&](SubstitutableType *type) { + ASSERT(type->isEqual(origParam)); + return openedParam; + }, + MakeAbstractConformanceForGenericType()); + } + + if (genericSig) { + // If the type parameter is beyond the domain of the opened + // signature, ignore it. + if (!genericSig->isValidTypeParameter(type)) { + return GenericParameterReferenceInfo(); + } + + if (auto reducedTy = genericSig.getReducedType(type)) { + if (!reducedTy->isEqual(type)) { + // Note: origParam becomes openedParam for the recursive call, + // because concreteTy is written in terms of genericSig and not + // the signature of the old origParam. + return findGenericParameterReferencesRec( + CanGenericSignature(), openedParam, openedParam, reducedTy, + position, canBeCovariantResult); + } + } + } + + // A reference to an associated type rooted on 'Self'. + return GenericParameterReferenceInfo::forAssocTypeRef(position); +} + +GenericParameterReferenceInfo +swift::findGenericParameterReferences(const ValueDecl *value, + CanGenericSignature sig, + GenericTypeParamType *origParam, + GenericTypeParamType *openedParam, + std::optional skipParamIndex) { + if (isa(value)) + return GenericParameterReferenceInfo(); + + auto type = value->getInterfaceType(); + + // Skip invalid declarations. + if (type->hasError()) + return GenericParameterReferenceInfo(); + + // For functions and subscripts, take skipParamIndex into account. + if (isa(value) || isa(value)) { + // And for a method, skip the 'self' parameter. + if (value->hasCurriedSelf()) + type = type->castTo()->getResult(); + + return ::findGenericParameterReferencesInFunction( + sig, origParam, openedParam, type->castTo(), + TypePosition::Covariant, /*canBeCovariantResult=*/true, + skipParamIndex); + } + + return ::findGenericParameterReferencesRec(sig, origParam, openedParam, type, + TypePosition::Covariant, + /*canBeCovariantResult=*/true); +} + +GenericParameterReferenceInfo swift::findExistentialSelfReferences( + const ValueDecl *value) { + auto *dc = value->getDeclContext(); + ASSERT(dc->getSelfProtocolDecl()); + + auto sig = dc->getGenericSignatureOfContext().getCanonicalSignature(); + auto genericParam = dc->getSelfInterfaceType()->castTo(); + + return findGenericParameterReferences(value, sig, genericParam, genericParam, + std::nullopt); +} + +bool HasSelfOrAssociatedTypeRequirementsRequest::evaluate( + Evaluator &evaluator, ProtocolDecl *decl) const { + // ObjC protocols do not require `any`. + if (decl->isObjC()) + return false; + + for (auto member : decl->getMembers()) { + // Existential types require `any` if the protocol has an associated type. + if (isa(member)) + return true; + + // For value members, look at their type signatures. + if (auto valueMember = dyn_cast(member)) { + const auto info = findExistentialSelfReferences(valueMember); + if (info.selfRef > TypePosition::Covariant || info.assocTypeRef) { + return true; + } + } + } + + // Check whether any of the inherited protocols require `any`. + for (auto proto : decl->getInheritedProtocols()) { + if (proto->hasSelfOrAssociatedTypeRequirements()) + return true; + } + + return false; +} + +/// A protocol member accessed with an existential value might have generic +/// constraints that require the ability to spell an opened archetype in order +/// to be satisfied. Such are +/// - superclass requirements, when the object is a non-'Self'-rooted type +/// parameter, and the subject is dependent on 'Self', e.g. U : G +/// - same-type requirements, when one side is dependent on 'Self', and the +/// other is a non-'Self'-rooted type parameter, e.g. U.Element == Self. +/// +/// Because opened archetypes are not part of the surface language, these +/// constraints render the member inaccessible. +static bool doesMemberHaveUnfulfillableConstraintsWithExistentialBase( + OpenedExistentialSignature existentialSig, const ValueDecl *member) { + const auto sig = + member->getInnermostDeclContext()->getGenericSignatureOfContext(); + + // Fast path: the member is generic only over 'Self'. + if (sig.getGenericParams().size() == 1) { + return false; + } + + class IsDependentOnOpenedExistentialSelf : public TypeWalker { + OpenedExistentialSignature existentialSig; + + public: + explicit IsDependentOnOpenedExistentialSelf(OpenedExistentialSignature existentialSig) + : existentialSig(existentialSig) {} + + Action walkToTypePre(Type ty) override { + // We're looking at the interface type of a protocol member, so it's written + // in terms of `Self` (tau_0_0) and possibly type parameters at higher depth: + // + // + if (!ty->isTypeParameter()) { + return Action::Continue; + } + + if (ty->getRootGenericParam()->getDepth() > 0) { + return Action::SkipNode; + } + + // Ok, we found a type parameter rooted in `Self`. Replace `Self` with the + // opened Self type in the existential signature, which looks like this: + // + // <..., Self where ..., Self: P> + ty = ty.subst( + [&](SubstitutableType *type) -> Type { + return existentialSig.SelfType; + }, + MakeAbstractConformanceForGenericType()); + + // Make sure this is valid first. + if (!existentialSig.OpenedSig->isValidTypeParameter(ty)) { + return Action::SkipNode; + } + + // If the existential type constrains Self.U to a type from the outer + // context, then the reduced type of Self.U in the existential signature + // will no longer contain Self. + ty = existentialSig.OpenedSig.getReducedType(ty); + + if (!ty.findIf([&](Type t) -> bool { + if (auto *paramTy = t->getAs()) + return paramTy->isEqual(existentialSig.SelfType); + return false; + })) { + return Action::SkipNode; + } + + // Ok, we found a type that depends on the opened existential Self. + return Action::Stop; + } + } isDependentOnSelf(existentialSig); + + for (const auto &req : sig.getRequirements()) { + switch (req.getKind()) { + case RequirementKind::Superclass: { + if (req.getFirstType()->getRootGenericParam()->getDepth() > 0 && + req.getSecondType().walk(isDependentOnSelf)) { + return true; + } + + break; + } + case RequirementKind::SameType: + case RequirementKind::SameShape: { + const auto isNonSelfRootedTypeParam = [](Type ty) { + return ty->isTypeParameter() && + ty->getRootGenericParam()->getDepth() > 0; + }; + + if ((isNonSelfRootedTypeParam(req.getFirstType()) && + req.getSecondType().walk(isDependentOnSelf)) || + (isNonSelfRootedTypeParam(req.getSecondType()) && + req.getFirstType().walk(isDependentOnSelf))) { + return true; + } + + break; + } + case RequirementKind::Conformance: + case RequirementKind::Layout: + break; + } + } + + return false; +} + +bool swift::isMemberAvailableOnExistential( + Type baseTy, const ValueDecl *member) { + + auto &ctx = member->getASTContext(); + auto existentialSig = ctx.getOpenedExistentialSignature(baseTy); + + auto *dc = member->getDeclContext(); + ASSERT(dc->getSelfProtocolDecl()); + + auto origParam = dc->getSelfInterfaceType()->castTo(); + auto openedParam = existentialSig.SelfType->castTo(); + + auto info = findGenericParameterReferences( + member, existentialSig.OpenedSig, origParam, openedParam, + std::nullopt); + + if (info.selfRef > TypePosition::Covariant || + info.assocTypeRef > TypePosition::Covariant) { + return false; + } + + // FIXME: Appropriately diagnose assignments instead. + if (auto *const storageDecl = dyn_cast(member)) { + if (info.hasCovariantSelfResult && storageDecl->supportsMutation()) + return false; + } + + if (doesMemberHaveUnfulfillableConstraintsWithExistentialBase(existentialSig, + member)) { + return false; + } + + return true; +} + +std::optional> +swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx, + Type paramTy, Type argTy) { + if (!callee) + return std::nullopt; + + // Only applies to functions and subscripts. + if (!isa(callee) && !isa(callee)) + return std::nullopt; + + // Special semantics prohibit opening existentials. + switch (TypeChecker::getDeclTypeCheckingSemantics(callee)) { + case DeclTypeCheckingSemantics::OpenExistential: + case DeclTypeCheckingSemantics::TypeOf: + // type(of:) and _openExistential handle their own opening. + return std::nullopt; + + case DeclTypeCheckingSemantics::Normal: + case DeclTypeCheckingSemantics::WithoutActuallyEscaping: + 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 std::nullopt; + + // The actual parameter type needs to involve a type variable, otherwise + // type inference won't be possible. + 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; + + // If the parameter is non-generic variadic, don't open. + if (param->isVariadic()) + return std::nullopt; + + // 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. + if (argTy->is()) { + formalParamTy = formalParamTy->getMetatypeInstanceType(); + paramTy = paramTy->getMetatypeInstanceType(); + } + + // 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(); + if (!paramTypeVar) + return std::nullopt; + + auto genericParam = formalParamTy->getAs(); + if (!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; + + Type existentialTy; + if (auto existentialMetaTy = argTy->getAs()) + existentialTy = existentialMetaTy->getInstanceType(); + else + existentialTy = argTy; + + ASSERT(existentialTy->isAnyExistentialType()); + + if (!existentialTy->isExistentialType()) + return std::nullopt; + + auto &ctx = callee->getASTContext(); + + // If the existential argument conforms to all of protocol requirements on + // the formal parameter's type, don't open unless ImplicitOpenExistentials is + // enabled. + + // If all of the conformance requirements on the formal parameter's type + // are self-conforming, don't open. + if (!ctx.LangOpts.hasFeature(Feature::ImplicitOpenExistentials)) { + bool containsNonSelfConformance = false; + for (auto proto : genericSig->getRequiredProtocols(genericParam)) { + auto conformance = lookupExistentialConformance( + existentialTy, proto); + if (conformance.isInvalid()) { + containsNonSelfConformance = true; + break; + } + } + + if (!containsNonSelfConformance) + return std::nullopt; + } + + auto existentialSig = ctx.getOpenedExistentialSignature(existentialTy); + + // Ensure that the formal parameter is only used in covariant positions, + // because it won't match anywhere else. + auto referenceInfo = findGenericParameterReferences( + callee, existentialSig.OpenedSig, genericParam, + existentialSig.SelfType->castTo(), + /*skipParamIdx=*/paramIdx); + if (referenceInfo.selfRef > TypePosition::Covariant || + referenceInfo.assocTypeRef > TypePosition::Covariant) + return std::nullopt; + + return std::make_tuple(genericParam, paramTypeVar, argTy, adjustments); +} + +/// For each occurrence of a type **type** in `refTy` that satisfies +/// `predicateFn` in covariant position, **type** is erased to an +/// existential using `eraseFn`. +static Type typeEraseExistentialSelfReferences( + Type refTy, TypePosition outermostPosition, + llvm::function_ref containsFn, + llvm::function_ref predicateFn, + llvm::function_ref eraseFn) { + if (!containsFn(refTy)) + return refTy; + + return refTy.transformWithPosition( + outermostPosition, + [&](TypeBase *t, TypePosition currPos) -> std::optional { + if (!containsFn(t)) { + return Type(t); + } + + if (t->is()) { + const auto instanceTy = t->getMetatypeInstanceType(); + auto erasedTy = typeEraseExistentialSelfReferences( + instanceTy, currPos, + containsFn, predicateFn, eraseFn); + if (instanceTy.getPointer() == erasedTy.getPointer()) { + return Type(t); + } + + // - If the output instance type is an existential, but the input is + // not, wrap the output in an existential metatype. + // + // X.Type → X → any Y → any Y.Type + // + // - Otherwise, both are existential or the output instance type is + // not existential; wrap the output in a singleton metatype. + if (erasedTy->isAnyExistentialType() && + !erasedTy->isConstraintType() && + !(instanceTy->isAnyExistentialType() && + !instanceTy->isConstraintType())) { + return Type(ExistentialMetatypeType::get(erasedTy)); + } + + return Type(MetatypeType::get(erasedTy)); + } + + // Opaque types whose substitutions involve this type parameter are + // erased to their upper bound. + if (auto opaque = dyn_cast(t)) { + for (auto replacementType : + opaque->getSubstitutions().getReplacementTypes()) { + auto erasedReplacementType = typeEraseExistentialSelfReferences( + replacementType, TypePosition::Covariant, + containsFn, predicateFn, eraseFn); + if (erasedReplacementType.getPointer() != + replacementType.getPointer()) + return opaque->getExistentialType(); + } + } + + // Parameterized protocol types whose arguments involve this type + // parameter are erased to the base type. + if (auto parameterized = dyn_cast(t)) { + for (auto argType : parameterized->getArgs()) { + auto erasedArgType = typeEraseExistentialSelfReferences( + argType, TypePosition::Covariant, + containsFn, predicateFn, eraseFn); + if (erasedArgType.getPointer() != argType.getPointer()) + return parameterized->getBaseType(); + } + } + /* + if (auto lvalue = dyn_cast(t)) { + auto objTy = lvalue->getObjectType(); + auto erasedTy = + typeEraseExistentialSelfReferences( + objTy, currPos, + containsFn, predicateFn, eraseFn); + + if (erasedTy.getPointer() == objTy.getPointer()) + return Type(lvalue); + + return erasedTy; + } + */ + + if (!predicateFn(t)) { + // Recurse. + return std::nullopt; + } + + auto erasedTy = eraseFn(t, currPos); + if (!erasedTy) + return Type(t); + + return erasedTy; + }); +} + +Type swift::typeEraseOpenedExistentialReference( + Type type, Type existentialBaseType, TypeVariableType *openedTypeVar, + TypePosition outermostPosition) { + auto existentialSig = + type->getASTContext().getOpenedExistentialSignature( + existentialBaseType); + + auto applyOuterSubstitutions = [&](Type t) -> Type { + if (t->hasTypeParameter()) { + auto outerSubs = existentialSig.Generalization; + unsigned depth = existentialSig.OpenedSig->getMaxDepth(); + OuterSubstitutions replacer{outerSubs, depth}; + return t.subst(replacer, replacer); + } + + return t; + }; + + auto erase = [&](Type paramTy, TypePosition currPos) -> Type { + switch (currPos) { + case TypePosition::Covariant: + break; + + case TypePosition::Contravariant: + case TypePosition::Invariant: + case TypePosition::Shape: + return Type(); + } + + // The upper bounds of 'Self' is the existential base type. + if (paramTy->is()) + return existentialBaseType; + + return applyOuterSubstitutions( + existentialSig.OpenedSig->getExistentialType(paramTy)); + }; + + return typeEraseExistentialSelfReferences( + type, + outermostPosition, + /*containsFn=*/[](Type t) { + return t->hasTypeVariable(); + }, + /*predicateFn=*/[](Type t) { + return t->isTypeVariableOrMember(); + }, + /*eraseFn=*/[&](Type t, TypePosition currPos) -> Type { + bool found = false; + auto paramTy = t.transformRec([&](Type t) -> std::optional { + if (t.getPointer() == openedTypeVar) { + found = true; + return existentialSig.SelfType; + } + return std::nullopt; + }); + + if (!found) + return Type(); + + assert(paramTy->isTypeParameter()); + + // This can happen with invalid code. + if (!existentialSig.OpenedSig->isValidTypeParameter(paramTy)) { + return Type(t); + } + + // Check if this existential fixes this `Self`-rooted type to something + // in the existential's outer generic signature. + Type reducedTy = existentialSig.OpenedSig.getReducedType(paramTy); + if (!reducedTy->isEqual(paramTy)) { + reducedTy = applyOuterSubstitutions(reducedTy); + + auto erasedTy = typeEraseExistentialSelfReferences( + reducedTy, currPos, + [&](Type t) { return t->hasTypeParameter(); }, + [&](Type t) { return t->isTypeParameter(); }, + [&](Type t, TypePosition currPos) { return erase(t, currPos); }); + if (erasedTy.getPointer() == reducedTy.getPointer()) { + return Type(t); + } + + return erasedTy; + } + + return erase(paramTy, currPos); + }); +} + +Type swift::typeEraseOpenedArchetypesFromEnvironment( + Type type, GenericEnvironment *env) { + assert(env->getKind() == GenericEnvironment::Kind::OpenedExistential); + + return typeEraseExistentialSelfReferences( + type, + TypePosition::Covariant, + /*containsFn=*/[](Type t) { + return t->hasOpenedExistential(); + }, + /*predicateFn=*/[](Type t) { + return t->is(); + }, + /*eraseFn=*/[&](Type t, TypePosition currPos) { + auto *openedTy = t->castTo(); + if (openedTy->getGenericEnvironment() == env) + return openedTy->getExistentialType(); + + return Type(); + }); +} \ No newline at end of file diff --git a/lib/Sema/OpenedExistentials.h b/lib/Sema/OpenedExistentials.h new file mode 100644 index 0000000000000..f0b28ab70c77f --- /dev/null +++ b/lib/Sema/OpenedExistentials.h @@ -0,0 +1,141 @@ +//===--- OpenedExistentials.h - Utilities for existential types -*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_OPENED_EXISTENTIALS_H +#define SWIFT_OPENED_EXISTENTIALS_H + +#include "swift/AST/Type.h" +#include "swift/Basic/OptionalEnum.h" +#include + +namespace swift { + +class CanGenericSignature; +class GenericTypeParamType; + +/// Describes the least favorable positions at which a requirement refers +/// 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: + /// Whether the uncurried interface type of the declaration, stripped of any + /// optionality, is a direct reference to the generic parameter at hand. For + /// example, "func foo(x: Int) -> () -> Self?" has a covariant 'Self' result. + bool hasCovariantSelfResult; + + OptionalTypePosition selfRef; + OptionalTypePosition assocTypeRef; + + /// A reference to 'Self'. + static GenericParameterReferenceInfo forSelfRef(TypePosition position) { + return GenericParameterReferenceInfo(false, position, std::nullopt); + } + + /// A reference to the generic parameter in covariant result position. + static GenericParameterReferenceInfo forCovariantResult() { + return GenericParameterReferenceInfo(true, TypePosition::Covariant, + std::nullopt); + } + + /// A reference to 'Self' through an associated type. + static GenericParameterReferenceInfo forAssocTypeRef(TypePosition position) { + return GenericParameterReferenceInfo(false, std::nullopt, position); + } + + GenericParameterReferenceInfo &operator|=(const GenericParameterReferenceInfo &other); + + explicit operator bool() const { + return hasCovariantSelfResult || selfRef || assocTypeRef; + } + + GenericParameterReferenceInfo() + : hasCovariantSelfResult(false), selfRef(std::nullopt), + assocTypeRef(std::nullopt) {} + +private: + GenericParameterReferenceInfo(bool hasCovariantSelfResult, OptionalTypePosition selfRef, + OptionalTypePosition assocTypeRef) + : hasCovariantSelfResult(hasCovariantSelfResult), selfRef(selfRef), + assocTypeRef(assocTypeRef) {} +}; + +/// Find references to the given generic parameter in the type signature of the +/// given declaration using the given generic signature. +/// +/// \param skipParamIndex If the value is a function or subscript declaration, +/// specifies the index of the parameter that shall be skipped. +GenericParameterReferenceInfo +findGenericParameterReferences(const ValueDecl *value, CanGenericSignature sig, + GenericTypeParamType *origParam, + GenericTypeParamType *openedParam, + std::optional skipParamIndex); + +/// Find references to 'Self' in the type signature of this declaration. +GenericParameterReferenceInfo findExistentialSelfReferences(const ValueDecl *value); + +/// Determine whether referencing the given member on the +/// given existential base type is supported. This is the case only if the +/// type of the member, spelled in the context of \p baseTy, does not contain +/// 'Self' or 'Self'-rooted dependent member types in non-covariant position. +bool 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. +/// +/// \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 generic parameter, 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> +canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx, + Type paramTy, Type argTy); + +/// 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. +Type typeEraseOpenedExistentialReference(Type type, Type existentialBaseType, + TypeVariableType *openedTypeVar, + TypePosition outermostPosition); + + +/// Given a type that includes opened existential archetypes derived from +/// the given generic environment, replace the archetypes with their upper +/// bounds. +Type typeEraseOpenedArchetypesFromEnvironment(Type type, + GenericEnvironment *env); + +} // end namespace swift + +#endif \ No newline at end of file diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index 1aec8fc05f73d..7cc6755c07a20 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -720,35 +720,6 @@ ExistentialConformsToSelfRequest::evaluate(Evaluator &evaluator, return true; } -bool HasSelfOrAssociatedTypeRequirementsRequest::evaluate( - Evaluator &evaluator, ProtocolDecl *decl) const { - // ObjC protocols do not require `any`. - if (decl->isObjC()) - return false; - - for (auto member : decl->getMembers()) { - // Existential types require `any` if the protocol has an associated type. - if (isa(member)) - return true; - - // For value members, look at their type signatures. - if (auto valueMember = dyn_cast(member)) { - const auto info = valueMember->findExistentialSelfReferences(); - if (info.selfRef > TypePosition::Covariant || info.assocTypeRef) { - return true; - } - } - } - - // Check whether any of the inherited protocols require `any`. - for (auto proto : decl->getInheritedProtocols()) { - if (proto->hasSelfOrAssociatedTypeRequirements()) - return true; - } - - return false; -} - ArrayRef PrimaryAssociatedTypesRequest::evaluate(Evaluator &evaluator, ProtocolDecl *decl) const { diff --git a/lib/Sema/TypeCheckEffects.cpp b/lib/Sema/TypeCheckEffects.cpp index eb166d1e8d336..e7bdef50fbd0a 100644 --- a/lib/Sema/TypeCheckEffects.cpp +++ b/lib/Sema/TypeCheckEffects.cpp @@ -16,6 +16,7 @@ //===----------------------------------------------------------------------===// #include "TypeChecker.h" +#include "OpenedExistentials.h" #include "TypeCheckConcurrency.h" #include "TypeCheckEffects.h" #include "swift/AST/ASTWalker.h" @@ -820,7 +821,7 @@ static Type typeEraseOpenedArchetypes(Type type) { if (!env) return type; - return constraints::typeEraseOpenedArchetypesFromEnvironment(type, env); + return typeEraseOpenedArchetypesFromEnvironment(type, env); } /// A type expressing the result of classifying whether a call or function diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index b88207a6f5c73..cb7ed932d4e5b 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -17,6 +17,7 @@ #include "TypeCheckProtocol.h" #include "DerivedConformances.h" #include "MiscDiagnostics.h" +#include "OpenedExistentials.h" #include "TypeAccessScopeChecker.h" #include "TypeCheckAccess.h" #include "TypeCheckAvailability.h" @@ -1132,7 +1133,7 @@ swift::matchWitness(WitnessChecker::RequirementEnvironmentCache &reqEnvCache, // defining a non-final class conforming to 'Collection' which uses // the default witness for 'Collection.Iterator', which is defined // as 'IndexingIterator'. - const auto selfRefInfo = req->findExistentialSelfReferences(); + const auto selfRefInfo = findExistentialSelfReferences(req); if (!selfRefInfo.assocTypeRef) { covariantSelf = classDecl; } @@ -4034,7 +4035,7 @@ void ConformanceChecker::checkNonFinalClassWitness(ValueDecl *requirement, // Check whether this requirement uses Self in a way that might // prevent conformance from succeeding. - const auto selfRefInfo = requirement->findExistentialSelfReferences(); + const auto selfRefInfo = findExistentialSelfReferences(requirement); if (selfRefInfo.selfRef == TypePosition::Invariant || (selfRefInfo.selfRef == TypePosition::Covariant && diff --git a/test/Constraints/opened_existentials.swift b/test/Constraints/opened_existentials.swift index f1a6e3b39f05e..45625fa3cc5bb 100644 --- a/test/Constraints/opened_existentials.swift +++ b/test/Constraints/opened_existentials.swift @@ -416,3 +416,10 @@ struct S { } } } + +func nestedMetatypeCallee(_ t: T) {} + +func nestedMetatypeCaller() { + let t = String.Type.Type.Type.self as (any Q.Type.Type.Type.Type) + nestedMetatypeCallee(t) +} From 7e4e79c69f1138543aa53f3bc73df547fb8d377c Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Wed, 4 Sep 2024 11:21:49 -0400 Subject: [PATCH 2/3] AST: Simplify SubstitutionMap::get() --- include/swift/AST/SubstitutionMap.h | 9 ++++++ include/swift/AST/TypeTransform.h | 4 +-- lib/AST/SubstitutionMap.cpp | 47 +++++++++++++++++------------ lib/AST/TypeSubstitution.cpp | 3 +- 4 files changed, 38 insertions(+), 25 deletions(-) diff --git a/include/swift/AST/SubstitutionMap.h b/include/swift/AST/SubstitutionMap.h index 822e0f1ad345c..fc5fa5c82ab06 100644 --- a/include/swift/AST/SubstitutionMap.h +++ b/include/swift/AST/SubstitutionMap.h @@ -104,6 +104,15 @@ class SubstitutionMap { ArrayRef replacementTypes, LookupConformanceFn lookupConformance); + /// Build a substitution map from the substitutions represented by + /// the given in-flight substitution. + /// + /// This function should generally only be used by the substitution + /// subsystem. + static SubstitutionMap get(GenericSignature genericSig, + ArrayRef replacementTypes, + InFlightSubstitution &IFS); + /// Build a substitution map from the substitutions represented by /// the given in-flight substitution. /// diff --git a/include/swift/AST/TypeTransform.h b/include/swift/AST/TypeTransform.h index 82df111ecb261..c80bc774e4386 100644 --- a/include/swift/AST/TypeTransform.h +++ b/include/swift/AST/TypeTransform.h @@ -998,9 +998,7 @@ case TypeKind::Id: return subs; auto sig = subs.getGenericSignature(); - return SubstitutionMap::get(sig, - QueryReplacementTypeArray{sig, newSubs}, - LookUpConformanceInModule()); + return SubstitutionMap::get(sig, newSubs, LookUpConformanceInModule()); } CanType transformSILField(CanType fieldTy, TypePosition pos) { diff --git a/lib/AST/SubstitutionMap.cpp b/lib/AST/SubstitutionMap.cpp index 019b881a6a868..d4e6699154706 100644 --- a/lib/AST/SubstitutionMap.cpp +++ b/lib/AST/SubstitutionMap.cpp @@ -168,9 +168,28 @@ SubstitutionMap SubstitutionMap::get(GenericSignature genericSig, SubstitutionMap SubstitutionMap::get(GenericSignature genericSig, ArrayRef types, LookupConformanceFn lookupConformance) { - return get(genericSig, - QueryReplacementTypeArray{genericSig, types}, - lookupConformance); + QueryReplacementTypeArray subs{genericSig, types}; + InFlightSubstitution IFS(subs, lookupConformance, std::nullopt); + return get(genericSig, types, IFS); +} + +SubstitutionMap SubstitutionMap::get(GenericSignature genericSig, + ArrayRef types, + InFlightSubstitution &IFS) { + // Form the stored conformances. + SmallVector conformances; + for (const auto &req : genericSig.getRequirements()) { + if (req.getKind() != RequirementKind::Conformance) continue; + + CanType depTy = req.getFirstType()->getCanonicalType(); + auto replacement = depTy.subst(IFS); + auto *proto = req.getProtocolDecl(); + auto conformance = IFS.lookupConformance(depTy, replacement, proto, + /*level=*/0); + conformances.push_back(conformance); + } + + return SubstitutionMap(genericSig, types, conformances); } SubstitutionMap SubstitutionMap::get(GenericSignature genericSig, @@ -185,29 +204,17 @@ SubstitutionMap SubstitutionMap::get(GenericSignature genericSig, for (auto *gp : genericSig.getGenericParams()) { // Record the replacement. - Type replacement = Type(gp).subst(IFS); - - assert((!replacement || replacement->hasError() || + Type replacement = IFS.substType(gp, /*level=*/0); + if (!replacement) + replacement = ErrorType::get(gp->getASTContext()); + assert((replacement->hasError() || gp->isParameterPack() == replacement->is()) && "replacement for pack parameter must be a pack type"); replacementTypes.push_back(replacement); } - // Form the stored conformances. - SmallVector conformances; - for (const auto &req : genericSig.getRequirements()) { - if (req.getKind() != RequirementKind::Conformance) continue; - - CanType depTy = req.getFirstType()->getCanonicalType(); - auto replacement = depTy.subst(IFS); - auto *proto = req.getProtocolDecl(); - auto conformance = IFS.lookupConformance(depTy, replacement, proto, - /*level=*/0); - conformances.push_back(conformance); - } - - return SubstitutionMap(genericSig, replacementTypes, conformances); + return SubstitutionMap::get(genericSig, replacementTypes, IFS); } Type SubstitutionMap::lookupSubstitution(GenericTypeParamType *genericParam) const { diff --git a/lib/AST/TypeSubstitution.cpp b/lib/AST/TypeSubstitution.cpp index b03b6223c6b50..c5fa774c6f069 100644 --- a/lib/AST/TypeSubstitution.cpp +++ b/lib/AST/TypeSubstitution.cpp @@ -808,8 +808,7 @@ SubstitutionMap TypeBase::getContextSubstitutionMap() { std::reverse(replacementTypes.begin(), replacementTypes.end()); auto subMap = SubstitutionMap::get( - genericSig, - QueryReplacementTypeArray{genericSig, replacementTypes}, + genericSig, replacementTypes, LookUpConformanceInModule()); nominalTy->ContextSubMap = subMap; From b1e0e776b43f7ca849a9231ba0800a2780d3d73d Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Mon, 19 Aug 2024 19:43:55 -0400 Subject: [PATCH 3/3] AST: Simplify SubstitutionMap::lookupConformance() --- include/swift/SIL/SILCloner.h | 5 +++++ lib/AST/SubstitutionMap.cpp | 26 ++++++-------------------- lib/AST/TypeSubstitution.cpp | 4 ++++ lib/SILOptimizer/Utils/ConstExpr.cpp | 3 ++- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/include/swift/SIL/SILCloner.h b/include/swift/SIL/SILCloner.h index d07c214a4a298..0293443319073 100644 --- a/include/swift/SIL/SILCloner.h +++ b/include/swift/SIL/SILCloner.h @@ -85,6 +85,11 @@ struct SubstitutionMapWithLocalArchetypes { ProtocolDecl *proto) { if (isa(origType)) return swift::lookupConformance(substType, proto); + + if (isa(origType) || + isa(origType)) + origType = origType->mapTypeOutOfContext()->getCanonicalType(); + if (SubsMap) return SubsMap->lookupConformance(origType, proto); diff --git a/lib/AST/SubstitutionMap.cpp b/lib/AST/SubstitutionMap.cpp index d4e6699154706..dab5f94f0cba3 100644 --- a/lib/AST/SubstitutionMap.cpp +++ b/lib/AST/SubstitutionMap.cpp @@ -11,14 +11,11 @@ //===----------------------------------------------------------------------===// // // This file defines the SubstitutionMap class. A SubstitutionMap packages -// together a set of replacement types and protocol conformances for -// specializing generic types. +// together a set of replacement types and protocol conformances, given by +// the generic parameters and conformance requirements of the substitution map's +// input generic signature. // -// SubstitutionMaps either have type parameters or archetypes as keys, -// based on whether they were built from a GenericSignature or a -// GenericEnvironment. -// -// To specialize a type, call Type::subst() with the right SubstitutionMap. +// To substitute a type, call Type::subst() with the right SubstitutionMap. // //===----------------------------------------------------------------------===// @@ -238,20 +235,9 @@ Type SubstitutionMap::lookupSubstitution(GenericTypeParamType *genericParam) con ProtocolConformanceRef SubstitutionMap::lookupConformance(CanType type, ProtocolDecl *proto) const { - if (empty()) - return ProtocolConformanceRef::forInvalid(); - - // If we have an archetype, map out of the context so we can compute a - // conformance access path. - if (auto archetype = dyn_cast(type)) { - if (!isa(archetype)) { - type = archetype->getInterfaceType()->getCanonicalType(); - } - } + ASSERT(type->isTypeParameter()); - // Error path: if we don't have a type parameter, there is no conformance. - // FIXME: Query concrete conformances in the generic signature? - if (!type->isTypeParameter()) + if (empty()) return ProtocolConformanceRef::forInvalid(); auto genericSig = getGenericSignature(); diff --git a/lib/AST/TypeSubstitution.cpp b/lib/AST/TypeSubstitution.cpp index c5fa774c6f069..65d7ff862ccea 100644 --- a/lib/AST/TypeSubstitution.cpp +++ b/lib/AST/TypeSubstitution.cpp @@ -206,6 +206,10 @@ operator()(CanType dependentType, Type conformingReplacementType, /*allowMissing=*/true); } + if (isa(dependentType) || + isa(dependentType)) + dependentType = dependentType->mapTypeOutOfContext()->getCanonicalType(); + auto result = Subs.lookupConformance(dependentType, conformedProtocol); if (!result.isInvalid()) return result; diff --git a/lib/SILOptimizer/Utils/ConstExpr.cpp b/lib/SILOptimizer/Utils/ConstExpr.cpp index 8cd60a4f018f2..31e3d5f81fd35 100644 --- a/lib/SILOptimizer/Utils/ConstExpr.cpp +++ b/lib/SILOptimizer/Utils/ConstExpr.cpp @@ -426,7 +426,8 @@ SymbolicValue ConstExprFunctionState::computeConstantValue(SILValue value) { // Try to resolve a witness method against our known conformances. if (auto *wmi = dyn_cast(value)) { auto conf = substitutionMap.lookupConformance( - wmi->getLookupType(), wmi->getConformance().getRequirement()); + wmi->getLookupType()->mapTypeOutOfContext()->getCanonicalType(), + wmi->getConformance().getRequirement()); if (conf.isInvalid()) return getUnknown(evaluator, value, UnknownReason::UnknownWitnessMethodConformance);