Skip to content

Commit 2e6ba53

Browse files
committed
[CSRanking] Exclude concrete-over-protocol rule from dynamic lookup ranking
This rule caused us to lose ambiguities in places where we really want ambiguity for `AnyObject` lookup, so only apply it when not comparing such overloads. This whole situation is a bit of a hack – really we shouldn't be applying any type-based or context-based overload ranking rules to overloads found through `AnyObject` lookup, but unfortunately we don't have syntax to precisely disambiguate overloads. This commit can be reverted if/when we ever remove `AnyObject` lookup.
1 parent 8867b9d commit 2e6ba53

File tree

3 files changed

+50
-17
lines changed

3 files changed

+50
-17
lines changed

lib/Sema/CSRanking.cpp

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -389,15 +389,18 @@ static bool paramIsIUO(Decl *decl, int paramNum) {
389389
///
390390
/// "Specialized" is essentially a form of subtyping, defined below.
391391
static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc,
392-
ValueDecl *decl1, ValueDecl *decl2) {
392+
ValueDecl *decl1, ValueDecl *decl2,
393+
bool isDynamicOverloadComparison = false) {
393394

394395
if (tc.getLangOpts().DebugConstraintSolver) {
395396
auto &log = tc.Context.TypeCheckerDebug->getStream();
396397
log << "Comparing declarations\n";
397398
decl1->print(log);
398399
log << "\nand\n";
399400
decl2->print(log);
400-
log << "\n";
401+
log << "\n(isDynamicOverloadComparison: ";
402+
log << isDynamicOverloadComparison;
403+
log << ")\n";
401404
}
402405

403406
auto *innerDC1 = decl1->getInnermostDeclContext();
@@ -406,7 +409,8 @@ static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc,
406409
auto *outerDC1 = decl1->getDeclContext();
407410
auto *outerDC2 = decl2->getDeclContext();
408411

409-
if (!tc.specializedOverloadComparisonCache.count({decl1, decl2})) {
412+
if (!tc.specializedOverloadComparisonCache.count(
413+
{decl1, decl2, isDynamicOverloadComparison})) {
410414

411415
auto compareSpecializations = [&] () -> bool {
412416
// If the kinds are different, there's nothing we can do.
@@ -451,7 +455,21 @@ static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc,
451455
// exactly one member is in a protocol extension). Only apply this rule in
452456
// Swift 5 mode to better maintain source compatibility under Swift 4
453457
// mode.
454-
if (tc.Context.isSwiftVersionAtLeast(5)) {
458+
//
459+
// Don't apply this rule when comparing two overloads found through
460+
// dynamic lookup to ensure we keep cases like this ambiguous:
461+
//
462+
// @objc protocol P {
463+
// var i: String { get }
464+
// }
465+
// class C {
466+
// @objc var i: Int { return 0 }
467+
// }
468+
// func foo(_ x: AnyObject) {
469+
// x.i // ensure ambiguous.
470+
// }
471+
//
472+
if (tc.Context.isSwiftVersionAtLeast(5) && !isDynamicOverloadComparison) {
455473
auto *proto1 = dyn_cast<ProtocolDecl>(outerDC1);
456474
auto *proto2 = dyn_cast<ProtocolDecl>(outerDC2);
457475
if (proto1 != proto2)
@@ -709,21 +727,25 @@ static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc,
709727
return false;
710728
};
711729

712-
tc.specializedOverloadComparisonCache[{decl1, decl2}] =
713-
compareSpecializations();
730+
tc.specializedOverloadComparisonCache[{
731+
decl1, decl2, isDynamicOverloadComparison}] = compareSpecializations();
714732
} else if (tc.getLangOpts().DebugConstraintSolver) {
715733
auto &log = tc.Context.TypeCheckerDebug->getStream();
716-
log << "Found cached comparison: "
717-
<< tc.specializedOverloadComparisonCache[{decl1, decl2}] << "\n";
734+
log << "Found cached comparison: "
735+
<< tc.specializedOverloadComparisonCache[{decl1, decl2,
736+
isDynamicOverloadComparison}]
737+
<< "\n";
718738
}
719739

720740
if (tc.getLangOpts().DebugConstraintSolver) {
721741
auto &log = tc.Context.TypeCheckerDebug->getStream();
722-
auto result = tc.specializedOverloadComparisonCache[{decl1, decl2}];
742+
auto result = tc.specializedOverloadComparisonCache[{
743+
decl1, decl2, isDynamicOverloadComparison}];
723744
log << "comparison result: " << (result ? "better" : "not better") << "\n";
724745
}
725746

726-
return tc.specializedOverloadComparisonCache[{decl1, decl2}];
747+
return tc.specializedOverloadComparisonCache[{decl1, decl2,
748+
isDynamicOverloadComparison}];
727749
}
728750

729751
Comparison TypeChecker::compareDeclarations(DeclContext *dc,
@@ -864,15 +886,23 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
864886
case OverloadChoiceKind::DynamicMemberLookup:
865887
break;
866888
}
867-
889+
890+
// We don't apply some ranking rules to overloads found through dynamic
891+
// lookup in order to keep a few potentially ill-formed cases ambiguous.
892+
bool isDynamicOverloadComparison =
893+
choice1.getKind() == OverloadChoiceKind::DeclViaDynamic &&
894+
choice2.getKind() == OverloadChoiceKind::DeclViaDynamic;
895+
868896
// Determine whether one declaration is more specialized than the other.
869897
bool firstAsSpecializedAs = false;
870898
bool secondAsSpecializedAs = false;
871-
if (isDeclAsSpecializedAs(tc, cs.DC, decl1, decl2)) {
899+
if (isDeclAsSpecializedAs(tc, cs.DC, decl1, decl2,
900+
isDynamicOverloadComparison)) {
872901
score1 += weight;
873902
firstAsSpecializedAs = true;
874903
}
875-
if (isDeclAsSpecializedAs(tc, cs.DC, decl2, decl1)) {
904+
if (isDeclAsSpecializedAs(tc, cs.DC, decl2, decl1,
905+
isDynamicOverloadComparison)) {
876906
score2 += weight;
877907
secondAsSpecializedAs = true;
878908
}

lib/Sema/TypeChecker.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -555,9 +555,11 @@ class TypeChecker final : public LazyResolver {
555555
SmallVector<NominalTypeDecl*, 8> DelayedCircularityChecks;
556556

557557
// Caches whether a given declaration is "as specialized" as another.
558-
llvm::DenseMap<std::pair<ValueDecl*, ValueDecl*>, bool>
559-
specializedOverloadComparisonCache;
560-
558+
llvm::DenseMap<std::tuple<ValueDecl *, ValueDecl *,
559+
/*isDynamicOverloadComparison*/ unsigned>,
560+
bool>
561+
specializedOverloadComparisonCache;
562+
561563
/// A list of closures for the most recently type-checked function, which we
562564
/// will need to compute captures for.
563565
std::vector<AnyFunctionRef> ClosuresWithUncomputedCaptures;

test/Constraints/dynamic_lookup.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// RUN: %empty-directory(%t)
22
// RUN: %target-swift-frontend -emit-module %S/Inputs/PrivateObjC.swift -o %t
3-
// RUN: %target-typecheck-verify-swift -I %t -verify-ignore-unknown
3+
// RUN: %target-typecheck-verify-swift -swift-version 4 -I %t -verify-ignore-unknown
4+
// RUN: %target-typecheck-verify-swift -swift-version 5 -I %t -verify-ignore-unknown
45

56
// REQUIRES: objc_interop
67
import Foundation

0 commit comments

Comments
 (0)