[CSOptimizer] Make a light-weight generic overload check if some requirements are unsatisfiable

If some of the requirements of a generic overload reference other
generic parameters, the optimizer won't be able to satisfy them
because it only has candidates for one (current) parameter. In
cases like that, let's fallback to a light-weight protocol conformance
check instead of skipping an overload choice altogether.
This commit is contained in:
Pavel Yaskevich
2024-11-11 09:55:09 -08:00
parent c2a55886f0
commit 15c773b9d7
2 changed files with 69 additions and 18 deletions

View File

@@ -555,11 +555,9 @@ static void determineBestChoicesInContext(
// Check protocol requirement(s) if this parameter is a
// generic parameter type.
if (genericSig && paramType->isTypeParameter()) {
// If candidate is not fully resolved or is matched against a
// dependent member type (i.e. `Self.T`), let's check conformances
// only and lower the score.
if (candidateType->hasTypeVariable() ||
paramType->is<DependentMemberType>()) {
// Light-weight check if cases where `checkRequirements` is not
// applicable.
auto checkProtocolRequirementsOnly = [&]() -> double {
auto protocolRequirements =
genericSig->getRequiredProtocols(paramType);
if (llvm::all_of(protocolRequirements, [&](ProtocolDecl *protocol) {
@@ -574,29 +572,64 @@ static void determineBestChoicesInContext(
}
return 0;
};
// If candidate is not fully resolved or is matched against a
// dependent member type (i.e. `Self.T`), let's check conformances
// only and lower the score.
if (candidateType->hasTypeVariable() ||
paramType->is<DependentMemberType>()) {
return checkProtocolRequirementsOnly();
}
// Cannot match anything but generic type parameters here.
if (!paramType->is<GenericTypeParamType>())
return std::nullopt;
bool hasUnsatisfiableRequirements = false;
SmallVector<Requirement, 4> requirements;
for (const auto &requirement : genericSig.getRequirements()) {
if (hasUnsatisfiableRequirements)
break;
llvm::SmallPtrSet<GenericTypeParamType *, 2> toExamine;
auto recordReferencesGenericParams = [&toExamine](Type type) {
type.visit([&toExamine](Type innerTy) {
if (auto *GP = innerTy->getAs<GenericTypeParamType>())
toExamine.insert(GP);
});
};
recordReferencesGenericParams(requirement.getFirstType());
if (requirement.getKind() != RequirementKind::Layout)
recordReferencesGenericParams(requirement.getSecondType());
if (llvm::any_of(toExamine, [&](GenericTypeParamType *GP) {
return paramType->isEqual(GP);
})) {
requirements.push_back(requirement);
// If requirement mentions other generic parameters
// `checkRequirements` would because we don't have
// candidate substitutions for anything but the current
// parameter type.
hasUnsatisfiableRequirements |= toExamine.size() > 1;
}
}
// If some of the requirements cannot be satisfied, because
// they reference other generic parameters, for example:
// `<T, U, where T.Element == U.Element>`, let's perform a
// light-weight check instead of skipping this overload choice.
if (hasUnsatisfiableRequirements)
return checkProtocolRequirementsOnly();
// If the candidate type is fully resolved, let's check all of
// the requirements that are associated with the corresponding
// parameter, if all of them are satisfied this candidate is
// an exact match.
auto isParameterType = [&paramType](Type type) {
return type->isEqual(paramType);
};
SmallVector<Requirement, 4> requirements;
for (const auto &requirement : genericSig.getRequirements()) {
if (requirement.getFirstType().findIf(isParameterType) ||
(requirement.getKind() != RequirementKind::Layout &&
requirement.getSecondType().findIf(isParameterType)))
requirements.push_back(requirement);
}
auto result = checkRequirements(
requirements,
[&paramType, &candidateType](SubstitutableType *type) -> Type {

View File

@@ -0,0 +1,18 @@
// RUN: %scale-test --invert-result --begin 1 --end 5 --step 1 --select NumLeafScopes %s -Xfrontend=-typecheck
// REQUIRES: asserts, no_asan
struct Value: RandomAccessCollection, RangeReplaceableCollection {
let startIndex = 0
let endIndex = 0
subscript(_: Int) -> Int { 0 }
func replaceSubrange<C: Collection>(_: Range<Int>, with: C) {}
}
func f(v: Value) {
let _ = v
%for i in range(0, N):
+ v
%end
}