Skip to content

Commit 3ac1026

Browse files
committed
[CSOptimizer] Relax candidate type requirements from equality to set of no-impact conversions
Candidate is viable (with some score) if: - Candidate is exactly equal to a parameter type - Candidate type differs from a parameter type only in optionality - Parameter is a generic parameter type and all conformances are matched by a candidate type - Candidate tuples matches a parameter tuple on arity - Candidate is an `Array<T>` and parameter is an `Unsafe*Pointer` - Candidate is a subclass of a parameter class type - Candidate is a concrete type and parameter is its existential value (except Any)
1 parent e166197 commit 3ac1026

File tree

3 files changed

+161
-55
lines changed

3 files changed

+161
-55
lines changed

lib/Sema/CSOptimizer.cpp

Lines changed: 154 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616

1717
#include "TypeChecker.h"
1818
#include "swift/AST/GenericSignature.h"
19+
#include "swift/Basic/OptionSet.h"
1920
#include "swift/Sema/ConstraintGraph.h"
2021
#include "swift/Sema/ConstraintSystem.h"
2122
#include "llvm/ADT/BitVector.h"
2223
#include "llvm/ADT/DenseMap.h"
2324
#include "llvm/ADT/SmallVector.h"
2425
#include "llvm/ADT/TinyPtrVector.h"
26+
#include "llvm/Support/SaveAndRestore.h"
2527
#include "llvm/Support/raw_ostream.h"
2628
#include <cstddef>
2729
#include <functional>
@@ -187,6 +189,144 @@ static void determineBestChoicesInContext(
187189
/*allow fixes*/ false, listener, None);
188190
};
189191

192+
// Determine whether the candidate type is a subclass of the superclass
193+
// type.
194+
auto isSubclassOf = [&](Type candidateType, Type superclassType) {
195+
// Conversion from a concrete type to its existential value.
196+
if (superclassType->isExistentialType() && !superclassType->isAny()) {
197+
llvm::SaveAndRestore<ConstraintSystemOptions> options(
198+
cs.Options, cs.Options - ConstraintSystemFlags::AllowFixes);
199+
200+
auto result = cs.matchExistentialTypes(
201+
candidateType, superclassType, ConstraintKind::SelfObjectOfProtocol,
202+
/*options=*/{}, cs.getConstraintLocator({}));
203+
return !result.isFailure();
204+
}
205+
206+
auto *subclassDecl = candidateType->getClassOrBoundGenericClass();
207+
auto *superclassDecl = superclassType->getClassOrBoundGenericClass();
208+
209+
if (!(subclassDecl && superclassDecl))
210+
return false;
211+
212+
return superclassDecl->isSuperclassOf(subclassDecl);
213+
};
214+
215+
enum class MatchFlag {
216+
OnParam,
217+
Literal,
218+
};
219+
220+
using MatchOptions = OptionSet<MatchFlag>;
221+
222+
// Perform a limited set of checks to determine whether the candidate
223+
// could possibly match the parameter type:
224+
//
225+
// - Equality
226+
// - Protocol conformance(s)
227+
// - Optional injection
228+
// - Superclass conversion
229+
// - Array-to-pointer conversion
230+
// - Value to existential conversion
231+
// - Exact match on top-level types
232+
std::function<double(GenericSignature, Type, Type, MatchOptions)>
233+
scoreCandidateMatch = [&](GenericSignature genericSig,
234+
Type candidateType, Type paramType,
235+
MatchOptions options) -> double {
236+
// Dependent members cannot be handled here because
237+
// they require substitution of the base type which
238+
// could come from a different argument.
239+
if (paramType->getAs<DependentMemberType>())
240+
return 0;
241+
242+
// Exact match between candidate and parameter types.
243+
if (candidateType->isEqual(paramType))
244+
return options.contains(MatchFlag::Literal) ? 0.3 : 1;
245+
246+
if (options.contains(MatchFlag::Literal))
247+
return 0;
248+
249+
// Check whether match would require optional injection.
250+
{
251+
SmallVector<Type, 2> candidateOptionals;
252+
SmallVector<Type, 2> paramOptionals;
253+
254+
candidateType =
255+
candidateType->lookThroughAllOptionalTypes(candidateOptionals);
256+
paramType = paramType->lookThroughAllOptionalTypes(paramOptionals);
257+
258+
if (!candidateOptionals.empty() || !paramOptionals.empty()) {
259+
if (paramOptionals.size() >= candidateOptionals.size())
260+
return scoreCandidateMatch(genericSig, candidateType, paramType,
261+
options);
262+
263+
// Optionality mismatch.
264+
return 0;
265+
}
266+
}
267+
268+
// Candidate could be injected into optional parameter type
269+
// or converted to a superclass.
270+
if (isSubclassOf(candidateType, paramType))
271+
return 1;
272+
273+
// Possible Array<T> -> Unsafe*Pointer conversion.
274+
if (options.contains(MatchFlag::OnParam)) {
275+
if (cs.isArrayType(candidateType) &&
276+
paramType->getAnyPointerElementType())
277+
return 1;
278+
}
279+
280+
// If both argument and parameter are tuples of the same arity,
281+
// it's a match.
282+
{
283+
if (auto *candidateTuple = candidateType->getAs<TupleType>()) {
284+
auto *paramTuple = paramType->getAs<TupleType>();
285+
if (paramTuple &&
286+
candidateTuple->getNumElements() == paramTuple->getNumElements())
287+
return 1;
288+
}
289+
}
290+
291+
// Check protocol requirement(s) if this parameter is a
292+
// generic parameter type.
293+
GenericSignature::RequiredProtocols protocolRequirements;
294+
if (genericSig) {
295+
if (auto *GP = paramType->getAs<GenericTypeParamType>()) {
296+
protocolRequirements = genericSig->getRequiredProtocols(GP);
297+
// It's a generic parameter which might be connected via
298+
// same-type constraints to other generic parameters but
299+
// we cannot check that here, so let's add a tiny score
300+
// just to acknowledge that it could possibly match.
301+
if (protocolRequirements.empty()) {
302+
return 0.01;
303+
}
304+
305+
if (llvm::all_of(protocolRequirements, [&](ProtocolDecl *protocol) {
306+
return TypeChecker::conformsToProtocol(candidateType, protocol,
307+
cs.DC->getParentModule(),
308+
/*allowMissing=*/false);
309+
}))
310+
return 0.7;
311+
}
312+
}
313+
314+
// Parameter is generic, let's check whether top-level
315+
// types match i.e. Array<Element> as a parameter.
316+
//
317+
// This is slightly better than all of the conformances matching
318+
// because the parameter is concrete and could split the graph.
319+
if (paramType->hasTypeParameter()) {
320+
auto *candidateDecl = candidateType->getAnyNominal();
321+
auto *paramDecl = paramType->getAnyNominal();
322+
323+
if (candidateDecl && paramDecl && candidateDecl == paramDecl)
324+
return 0.8;
325+
}
326+
327+
return 0;
328+
};
329+
190330
// The choice with the best score.
191331
double bestScore = 0.0;
192332
SmallVector<std::pair<Constraint *, double>, 2> favoredChoices;
@@ -256,23 +396,6 @@ static void determineBestChoicesInContext(
256396
if (paramType->is<FunctionType>())
257397
continue;
258398

259-
// Check protocol requirement(s) if this parameter is a
260-
// generic parameter type.
261-
GenericSignature::RequiredProtocols protocolRequirements;
262-
if (genericSig) {
263-
if (auto *GP = paramType->getAs<GenericTypeParamType>()) {
264-
protocolRequirements = genericSig->getRequiredProtocols(GP);
265-
// It's a generic parameter which might be connected via
266-
// same-type constraints to other generic parameters but
267-
// we cannot check that here, so let's ignore it.
268-
if (protocolRequirements.empty())
269-
continue;
270-
}
271-
272-
if (paramType->getAs<DependentMemberType>())
273-
return;
274-
}
275-
276399
// The idea here is to match the parameter type against
277400
// all of the argument candidate types and pick the best
278401
// match (i.e. exact equality one).
@@ -306,32 +429,14 @@ static void determineBestChoicesInContext(
306429
// The specifier only matters for `inout` check.
307430
candidateType = candidateType->getWithoutSpecifierType();
308431

309-
// We don't check generic requirements against literal default
310-
// types because it creates more noise than signal for operators.
311-
if (!protocolRequirements.empty() && !isLiteralDefault) {
312-
if (llvm::all_of(
313-
protocolRequirements, [&](ProtocolDecl *protocol) {
314-
return TypeChecker::conformsToProtocol(
315-
candidateType, protocol, cs.DC->getParentModule(),
316-
/*allowMissing=*/false);
317-
})) {
318-
// Score is lower here because we still prefer concrete
319-
// overloads over the generic ones when possible.
320-
bestCandidateScore = std::max(bestCandidateScore, 0.7);
321-
continue;
322-
}
323-
} else if (paramType->hasTypeParameter()) {
324-
// i.e. Array<Element> or Optional<Wrapped> as a parameter.
325-
// This is slightly better than all of the conformances matching
326-
// because the parameter is concrete and could split the graph.
327-
if (paramType->getAnyNominal() == candidateType->getAnyNominal()) {
328-
bestCandidateScore = std::max(bestCandidateScore, 0.8);
329-
continue;
330-
}
331-
} else if (candidateType->isEqual(paramType)) {
332-
// Exact match on one of the candidate bindings.
333-
bestCandidateScore =
334-
std::max(bestCandidateScore, isLiteralDefault ? 0.3 : 1.0);
432+
MatchOptions options(MatchFlag::OnParam);
433+
if (isLiteralDefault)
434+
options |= MatchFlag::Literal;
435+
436+
auto score = scoreCandidateMatch(genericSig, candidateType,
437+
paramType, options);
438+
if (score > 0) {
439+
bestCandidateScore = std::max(bestCandidateScore, score);
335440
continue;
336441
}
337442

@@ -365,11 +470,12 @@ static void determineBestChoicesInContext(
365470
if (score > 0 ||
366471
(decl->isOperator() &&
367472
!decl->getBaseIdentifier().isStandardComparisonOperator())) {
368-
if (llvm::any_of(
369-
resultTypes, [&overloadType](const Type candidateResultTy) {
370-
auto overloadResultTy = overloadType->getResult();
371-
return candidateResultTy->isEqual(overloadResultTy);
372-
})) {
473+
if (llvm::any_of(resultTypes, [&](const Type candidateResultTy) {
474+
return scoreCandidateMatch(genericSig,
475+
overloadType->getResult(),
476+
candidateResultTy,
477+
/*options=*/{}) > 0;
478+
})) {
373479
score += 1.0;
374480
}
375481
}

test/Constraints/diag_ambiguities.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ C(g) // expected-error{{ambiguous use of 'g'}}
2929
func h<T>(_ x: T) -> () {}
3030
_ = C(h) // OK - init(_: (Int) -> ())
3131

32-
func rdar29691909_callee(_ o: AnyObject?) -> Any? { return o }
33-
func rdar29691909_callee(_ o: AnyObject) -> Any { return o }
32+
func rdar29691909_callee(_ o: AnyObject?) -> Any? { return o } // expected-note {{found this candidate}}
33+
func rdar29691909_callee(_ o: AnyObject) -> Any { return o } // expected-note {{found this candidate}}
3434

3535
func rdar29691909(o: AnyObject) -> Any? {
36-
return rdar29691909_callee(o)
36+
return rdar29691909_callee(o) // expected-error{{ambiguous use of 'rdar29691909_callee'}}
3737
}
3838

3939
func rdar29907555(_ value: Any!) -> String {

test/expr/expressions.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -759,10 +759,10 @@ func invalidDictionaryLiteral() {
759759
//===----------------------------------------------------------------------===//
760760
// nil/metatype comparisons
761761
//===----------------------------------------------------------------------===//
762-
_ = Int.self == nil // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns false}}
763-
_ = nil == Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns false}}
764-
_ = Int.self != nil // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns true}}
765-
_ = nil != Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns true}}
762+
_ = Int.self == nil // expected-warning {{comparing non-optional value of type 'Int.Type' to 'nil' always returns false}}
763+
_ = nil == Int.self // expected-warning {{comparing non-optional value of type 'Int.Type' to 'nil' always returns false}}
764+
_ = Int.self != nil // expected-warning {{comparing non-optional value of type 'Int.Type' to 'nil' always returns true}}
765+
_ = nil != Int.self // expected-warning {{comparing non-optional value of type 'Int.Type' to 'nil' always returns true}}
766766

767767
_ = Int.self == .none // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns false}}
768768
_ = .none == Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns false}}

0 commit comments

Comments
 (0)