mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
This is similar to SuppressedAssociatedTypes, but infers default requirements when primary associated types of protocols are suppressed. This defaulting for the primary associated types happens in extensions of the protocol, along with generic parameters, whenever a source-written requirement states a conformance requirement for the protocol. Thus, the current scheme for this defaulting is a simplistic, driven by source-written requirements, rather than facts that are inferred while building generic signatures. Defaults are not expanded for infinitely many associated types. rdar://135168163
279 lines
9.9 KiB
C++
279 lines
9.9 KiB
C++
//===--- ApplyInverses.cpp - Resolve `~Protocol` anti-constraints ---------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2021 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// The `applyInverses` function takes the syntactic representation of a generic
|
|
// signature and applies implicit default constraints on generic parameters for
|
|
// core type capabilities like `Copyable` and `Escapable`. In doing so, it
|
|
// looks for explicit constraint suppression "requirements" like `T: ~Copyable`
|
|
// or same-type constraints that would contradict the implicit requirements and
|
|
// filters out unwanted default requirements.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "RequirementLowering.h"
|
|
#include "swift/AST/ASTContext.h"
|
|
#include "swift/AST/ConformanceLookup.h"
|
|
#include "swift/AST/Decl.h"
|
|
#include "swift/AST/DiagnosticsSema.h"
|
|
#include "swift/AST/Requirement.h"
|
|
#include "swift/AST/RequirementSignature.h"
|
|
#include "swift/AST/TypeCheckRequests.h"
|
|
#include "swift/AST/TypeMatcher.h"
|
|
#include "swift/AST/TypeRepr.h"
|
|
#include "swift/Basic/Assertions.h"
|
|
#include "swift/Basic/Defer.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/SetVector.h"
|
|
#include "Diagnostics.h"
|
|
#include "RewriteContext.h"
|
|
#include "NameLookup.h"
|
|
|
|
using namespace swift;
|
|
using namespace rewriting;
|
|
|
|
void swift::rewriting::applyInverses(
|
|
ASTContext &ctx,
|
|
ArrayRef<Type> gps,
|
|
ArrayRef<InverseRequirement> inverseList,
|
|
ArrayRef<StructuralRequirement> explicitRequirements,
|
|
SmallVectorImpl<StructuralRequirement> &result,
|
|
SmallVectorImpl<RequirementError> &errors) {
|
|
|
|
// Are there even any inverses or same-type requirements to validate?
|
|
if (inverseList.empty() && explicitRequirements.empty()) {
|
|
return;
|
|
}
|
|
|
|
const bool allowInverseOnAssocType =
|
|
ctx.LangOpts.hasFeature(Feature::SuppressedAssociatedTypes) ||
|
|
ctx.LangOpts.hasFeature(Feature::SuppressedAssociatedTypesWithDefaults);
|
|
|
|
llvm::DenseMap<CanType, CanType> representativeGPs;
|
|
|
|
// Start with an identity mapping.
|
|
for (auto gp : gps) {
|
|
auto canGP = stripBoundDependentMemberTypes(gp)->getCanonicalType();
|
|
representativeGPs.insert({canGP, canGP});
|
|
}
|
|
bool hadSameTypeConstraintInScope = false;
|
|
|
|
// Return the in-scope generic parameter that represents the equivalence class
|
|
// for `gp`, or return null if the parameter is constrained out of scope.
|
|
auto representativeGPFor = [&](CanType gp) -> CanType {
|
|
while (true) {
|
|
auto found = representativeGPs.find(gp);
|
|
if (found == representativeGPs.end()) {
|
|
return CanType();
|
|
}
|
|
if (found->second == CanType()) {
|
|
return CanType();
|
|
}
|
|
|
|
if (found->second == gp) {
|
|
return gp;
|
|
}
|
|
|
|
gp = found->second;
|
|
}
|
|
};
|
|
|
|
// Look for same-type constraints that equate multiple generic parameters
|
|
// within the scope so we can treat the equivalence class as a unit.
|
|
for (auto &explicitReqt : explicitRequirements) {
|
|
if (explicitReqt.req.getKind() != RequirementKind::SameType) {
|
|
continue;
|
|
}
|
|
|
|
// If one end of the same-type requirement is in scope, and the other is
|
|
// a concrete type or out-of-scope generic parameter, then the other
|
|
// parameter is also effectively out of scope.
|
|
auto firstTy =
|
|
stripBoundDependentMemberTypes(explicitReqt.req.getFirstType())
|
|
->getCanonicalType();
|
|
auto secondTy =
|
|
stripBoundDependentMemberTypes(explicitReqt.req.getSecondType())
|
|
->getCanonicalType();
|
|
if (!representativeGPs.count(firstTy)
|
|
&& !representativeGPs.count(secondTy)) {
|
|
// Same type constraint doesn't involve any in-scope generic parameters.
|
|
continue;
|
|
}
|
|
|
|
CanType typeInScope;
|
|
CanType typeOutOfScope;
|
|
|
|
if (representativeGPs.count(firstTy)
|
|
&& !representativeGPs.count(secondTy)){
|
|
// First type is constrained out of scope.
|
|
typeInScope = firstTy;
|
|
typeOutOfScope = secondTy;
|
|
} else if (!representativeGPs.count(firstTy)
|
|
&& representativeGPs.count(secondTy)) {
|
|
// Second type is constrained out of scope.
|
|
typeInScope = secondTy;
|
|
typeOutOfScope = firstTy;
|
|
} else {
|
|
// Otherwise, both ends of the same-type constraint are in scope.
|
|
|
|
// Choose one parameter to be the representative for the other, using
|
|
// a notion of "order" where we prefer the lexicographically smaller
|
|
// type to be the representative for the larger one.
|
|
const int sign = compareDependentTypes(firstTy, secondTy);
|
|
if (sign == 0) {
|
|
// `T == T` has no effect.
|
|
continue;
|
|
}
|
|
|
|
CanType smaller, larger;
|
|
if (sign < 0) {
|
|
smaller = firstTy;
|
|
larger = secondTy;
|
|
} else {
|
|
smaller = secondTy;
|
|
larger = firstTy;
|
|
}
|
|
|
|
hadSameTypeConstraintInScope = true;
|
|
representativeGPs.insert_or_assign(larger, representativeGPFor(smaller));
|
|
continue;
|
|
}
|
|
|
|
// If the out-of-scope type is another type parameter or associated type,
|
|
// then ignore this same-type constraint and allow defaulting to continue.
|
|
//
|
|
// It would probably have been more principled to suppress any defaulting
|
|
// in this case, but this behavior shipped in Swift 6.0 and 6.1, so we
|
|
// need to maintain source compatibility.
|
|
if (typeOutOfScope->isTypeParameter()) {
|
|
continue;
|
|
}
|
|
|
|
// If the out-of-scope type contains errors, then similarly, ignore the
|
|
// same type constraint. Any additional diagnostics arising from the type
|
|
// parameter being left ~Copyable or ~Escapable might be misleading if the
|
|
// corrected code is attempting to refer to a Copyable or Escapable type.
|
|
if (typeOutOfScope->hasError()) {
|
|
continue;
|
|
}
|
|
|
|
representativeGPs.insert_or_assign(representativeGPFor(typeInScope),
|
|
CanType());
|
|
hadSameTypeConstraintInScope = true;
|
|
}
|
|
|
|
// Summarize the inverses and diagnose ones that are incorrect.
|
|
llvm::DenseMap<CanType, InvertibleProtocolSet> inverses;
|
|
for (auto inverse : inverseList) {
|
|
auto canSubject =
|
|
stripBoundDependentMemberTypes(inverse.subject)->getCanonicalType();
|
|
|
|
// Inverses on associated types are experimental.
|
|
if (!allowInverseOnAssocType && canSubject->is<DependentMemberType>()) {
|
|
// Special exception: allow if we're building the stdlib.
|
|
if (!ctx.MainModule->isStdlibModule()) {
|
|
errors.push_back(RequirementError::forInvalidInverseSubject(inverse));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Noncopyable checking support for parameter packs is not implemented yet.
|
|
if (canSubject->isParameterPack()) {
|
|
errors.push_back(RequirementError::forInvalidInverseSubject(inverse));
|
|
continue;
|
|
}
|
|
|
|
// Value generics never have inverse requirements (or the positive thereof).
|
|
if (canSubject->isValueParameter()) {
|
|
continue;
|
|
}
|
|
|
|
// If the inverse is on a subject that wasn't permitted by our caller, then
|
|
// remove and diagnose as an error. This can happen when an inner context
|
|
// has a constraint on some outer generic parameter, e.g.,
|
|
//
|
|
// protocol P {
|
|
// func f() where Self: ~Copyable
|
|
// }
|
|
//
|
|
if (representativeGPs.find(canSubject) == representativeGPs.end()) {
|
|
errors.push_back(
|
|
RequirementError::forInvalidInverseOuterSubject(inverse));
|
|
continue;
|
|
}
|
|
|
|
auto representativeSubject = representativeGPFor(canSubject);
|
|
|
|
// If the subject is in scope, but same-type constrained to a type out of
|
|
// scope, then allow inverses to be stated even though they are redundant.
|
|
// This is because older versions of Swift not only accepted but required
|
|
// `extension Foo where T == NonCopyableType, T: ~Copyable {}` to be
|
|
// written, so we need to continue to accept that formulation for source
|
|
// compatibility.
|
|
if (!representativeSubject) {
|
|
continue;
|
|
}
|
|
|
|
auto &state = inverses[representativeSubject];
|
|
|
|
// Check if this inverse has already been seen.
|
|
auto inverseKind = inverse.getKind();
|
|
if (state.contains(inverseKind))
|
|
continue;
|
|
|
|
state.insert(inverseKind);
|
|
inverses[representativeSubject] = state;
|
|
}
|
|
|
|
// Fast-path: if there are no valid inverses or same-type constraints, then
|
|
// there are no requirements to be removed.
|
|
if (inverses.empty() && !hadSameTypeConstraintInScope) {
|
|
return;
|
|
}
|
|
|
|
// Scan the structural requirements and cancel out any inferred requirements
|
|
// based on the inverses we saw.
|
|
result.erase(llvm::remove_if(result, [&](StructuralRequirement structReq) {
|
|
auto req = structReq.req;
|
|
|
|
if (req.getKind() != RequirementKind::Conformance)
|
|
return false;
|
|
|
|
// Only consider requirements involving an invertible protocol.
|
|
auto proto = req.getProtocolDecl()->getInvertibleProtocolKind();
|
|
if (!proto) {
|
|
return false;
|
|
}
|
|
|
|
// See if this subject is in-scope.
|
|
auto subject = stripBoundDependentMemberTypes(req.getFirstType())->getCanonicalType();
|
|
auto representative = representativeGPs.find(subject);
|
|
if (representative == representativeGPs.end()) {
|
|
return false;
|
|
}
|
|
|
|
// If this type is same-type constrained into another equivalence class,
|
|
// then it doesn't need its own defaulted requirements.
|
|
if (representative->second != subject) {
|
|
return true;
|
|
}
|
|
|
|
// We now have found the inferred constraint 'Subject : Proto'.
|
|
// So, remove it if we have recorded a 'Subject : ~Proto'.
|
|
auto foundInverses = inverses.find(subject);
|
|
if (foundInverses == inverses.end()) {
|
|
return false;
|
|
}
|
|
auto recordedInverses = foundInverses->getSecond();
|
|
return recordedInverses.contains(*proto);
|
|
}), result.end());
|
|
}
|