Skip to content

Commit e45b566

Browse files
committed
GSB: Try harder to prove derivation via redundant requirements
Consider a signature with a conformance requirement, and two identical superclass requirements, both of which make the conformance requirement redundant. The conformance will have two requirement sources, the explicit source and a derived source based on one of the two superclass requirements, whichever one was processed first. If we end up marking the *other* superclass requirement as redundant, we would incorrectly conclude that the conformance was not redundant. Instead, if a derived source is based on a redundant requirement, we can't just discard it right away; instead, we have to check if that source could have been derived some other way.
1 parent 3c136c0 commit e45b566

File tree

4 files changed

+135
-6
lines changed

4 files changed

+135
-6
lines changed

include/swift/Basic/Statistics.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,10 @@ FRONTEND_STATISTIC(Sema, NumAccessorBodiesSynthesized)
224224
/// amount of work the GSB does analyzing type signatures.
225225
FRONTEND_STATISTIC(Sema, NumGenericSignatureBuilders)
226226

227+
/// Number of steps in the GSB's redundant requirements algorithm, which is in
228+
/// the worst-case exponential.
229+
FRONTEND_STATISTIC(Sema, NumRedundantRequirementSteps)
230+
227231
/// Number of conformance access paths we had to compute.
228232
FRONTEND_STATISTIC(Sema, NumConformanceAccessPathsRecorded)
229233

lib/AST/GenericSignatureBuilder.cpp

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5878,6 +5878,8 @@ GenericSignatureBuilder::isValidRequirementDerivationPath(
58785878
const RequirementSource *otherSource,
58795879
RequirementRHS otherRHS,
58805880
const ProtocolDecl *requirementSignatureSelfProto) {
5881+
if (auto *Stats = Context.Stats)
5882+
++Stats->getFrontendCounters().NumRedundantRequirementSteps;
58815883

58825884
SmallVector<ExplicitRequirement, 2> result;
58835885
getBaseRequirements(
@@ -5893,18 +5895,116 @@ GenericSignatureBuilder::isValidRequirementDerivationPath(
58935895
if (visited.count(otherReq))
58945896
return None;
58955897

5896-
// Don't consider paths based on requirements that are already
5897-
// known to be redundant either, since those paths become
5898-
// invalid once redundant requirements are dropped.
5899-
if (isRedundantExplicitRequirement(otherReq))
5900-
return None;
5901-
59025898
SWIFT_DEFER {
59035899
visited.erase(otherReq);
59045900
};
59055901
visited.insert(otherReq);
59065902

59075903
auto otherSubjectType = otherReq.getSubjectType();
5904+
5905+
// If our requirement is based on a path involving some other
5906+
// redundant requirement, see if we can derive the redundant
5907+
// requirement using requirements we haven't visited yet.
5908+
// If not, we go ahead and drop it from consideration.
5909+
//
5910+
// We need to do this because sometimes, we don't record all possible
5911+
// requirement sources that derive a given requirement.
5912+
//
5913+
// For example, when a nested type of a type parameter is concretized by
5914+
// adding a superclass requirement, we only record the requirement source
5915+
// for the concrete conformance the first time. A subsequently-added
5916+
// superclass requirement on the same parent type does not record a
5917+
// redundant concrete conformance for the child type.
5918+
if (isRedundantExplicitRequirement(otherReq)) {
5919+
// If we have a redundant explicit requirement source, it really is
5920+
// redundant; there's no other derivation that would not be redundant.
5921+
if (!otherSource->isDerivedNonRootRequirement())
5922+
return None;
5923+
5924+
auto *equivClass = resolveEquivalenceClass(otherSubjectType,
5925+
ArchetypeResolutionKind::AlreadyKnown);
5926+
assert(equivClass &&
5927+
"Explicit requirement names an unknown equivalence class?");
5928+
5929+
switch (otherReq.getKind()) {
5930+
case RequirementKind::Conformance: {
5931+
auto *proto = otherReq.getRHS().get<ProtocolDecl *>();
5932+
5933+
auto found = equivClass->conformsTo.find(proto);
5934+
assert(found != equivClass->conformsTo.end());
5935+
5936+
bool foundValidDerivation = false;
5937+
for (const auto &constraint : found->second) {
5938+
if (isValidRequirementDerivationPath(
5939+
visited, otherReq.getKind(),
5940+
constraint.source, proto,
5941+
requirementSignatureSelfProto)) {
5942+
foundValidDerivation = true;
5943+
break;
5944+
}
5945+
}
5946+
5947+
if (!foundValidDerivation)
5948+
return None;
5949+
5950+
break;
5951+
}
5952+
5953+
case RequirementKind::Superclass: {
5954+
auto superclass = getCanonicalTypeInContext(
5955+
otherReq.getRHS().get<Type>(), { });
5956+
5957+
for (const auto &constraint : equivClass->superclassConstraints) {
5958+
auto otherSuperclass = getCanonicalTypeInContext(
5959+
constraint.value, { });
5960+
5961+
if (superclass->isExactSuperclassOf(otherSuperclass)) {
5962+
bool foundValidDerivation = false;
5963+
if (isValidRequirementDerivationPath(
5964+
visited, otherReq.getKind(),
5965+
constraint.source, otherSuperclass,
5966+
requirementSignatureSelfProto)) {
5967+
foundValidDerivation = true;
5968+
break;
5969+
}
5970+
5971+
if (!foundValidDerivation)
5972+
return None;
5973+
}
5974+
}
5975+
5976+
break;
5977+
}
5978+
5979+
case RequirementKind::Layout: {
5980+
auto layout = otherReq.getRHS().get<LayoutConstraint>();
5981+
5982+
for (const auto &constraint : equivClass->layoutConstraints) {
5983+
auto otherLayout = constraint.value;
5984+
5985+
if (layout == otherLayout) {
5986+
bool foundValidDerivation = false;
5987+
if (isValidRequirementDerivationPath(
5988+
visited, otherReq.getKind(),
5989+
constraint.source, otherLayout,
5990+
requirementSignatureSelfProto)) {
5991+
foundValidDerivation = true;
5992+
break;
5993+
}
5994+
5995+
if (!foundValidDerivation)
5996+
return None;
5997+
}
5998+
}
5999+
6000+
break;
6001+
}
6002+
6003+
case RequirementKind::SameType:
6004+
llvm_unreachable("Should not see same type requirements here");
6005+
}
6006+
}
6007+
59086008
if (auto *depMemType = otherSubjectType->getAs<DependentMemberType>()) {
59096009
// If 'req' is based on some other conformance requirement
59106010
// `T.[P.]A : Q', we want to make sure that we have a
@@ -5975,6 +6075,9 @@ void GenericSignatureBuilder::computeRedundantRequirements(
59756075
Impl->computedRedundantRequirements = true;
59766076
#endif
59776077

6078+
FrontendStatsTracer tracer(Context.Stats,
6079+
"compute-redundant-requirements");
6080+
59786081
// This sort preserves iteration order with the legacy algorithm.
59796082
SmallVector<ExplicitRequirement, 2> requirements(
59806083
Impl->ExplicitRequirements.begin(),
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// RUN: %scale-test --begin 1 --end 20 --step 1 --select NumRedundantRequirementSteps --polynomial-threshold 2 %s
2+
3+
protocol P {}
4+
5+
func f<T>(_: T) where
6+
%for i in range(0, N):
7+
T : P,
8+
%end
9+
T : P {}

test/Generics/superclass_constraint.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,16 @@ _ = G<Base & P>() // expected-error {{'G' requires that 'Base & P' inherit from
213213

214214
func badClassConstrainedType(_: G<Base & P>) {}
215215
// expected-error@-1 {{'G' requires that 'Base & P' inherit from 'Base'}}
216+
217+
// Reduced from CoreStore in source compat suite
218+
public protocol Pony {}
219+
220+
public class Teddy: Pony {}
221+
222+
public struct Paddock<P: Pony> {}
223+
224+
public struct Barn<T: Teddy> {
225+
// CHECK-DAG: Barn.foo@
226+
// CHECK: Generic signature: <T, S where T : Teddy>
227+
public func foo<S>(_: S, _: Barn<T>, _: Paddock<T>) {}
228+
}

0 commit comments

Comments
 (0)