Skip to content

Commit 1912d28

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 286f164 commit 1912d28

File tree

3 files changed

+46
-16
lines changed

3 files changed

+46
-16
lines changed

lib/Sema/CSRanking.cpp

Lines changed: 39 additions & 12 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,9 @@ 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+
auto overloadComparisonKey =
413+
std::make_tuple(decl1, decl2, isDynamicOverloadComparison);
414+
if (!tc.specializedOverloadComparisonCache.count(overloadComparisonKey)) {
410415

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

712-
tc.specializedOverloadComparisonCache[{decl1, decl2}] =
731+
tc.specializedOverloadComparisonCache[overloadComparisonKey] =
713732
compareSpecializations();
714733
} else if (tc.getLangOpts().DebugConstraintSolver) {
715734
auto &log = tc.Context.TypeCheckerDebug->getStream();
716-
log << "Found cached comparison: "
717-
<< tc.specializedOverloadComparisonCache[{decl1, decl2}] << "\n";
735+
log << "Found cached comparison: "
736+
<< tc.specializedOverloadComparisonCache[overloadComparisonKey] << "\n";
718737
}
719738

720739
if (tc.getLangOpts().DebugConstraintSolver) {
721740
auto &log = tc.Context.TypeCheckerDebug->getStream();
722-
auto result = tc.specializedOverloadComparisonCache[{decl1, decl2}];
741+
auto result = tc.specializedOverloadComparisonCache[overloadComparisonKey];
723742
log << "comparison result: " << (result ? "better" : "not better") << "\n";
724743
}
725744

726-
return tc.specializedOverloadComparisonCache[{decl1, decl2}];
745+
return tc.specializedOverloadComparisonCache[overloadComparisonKey];
727746
}
728747

729748
Comparison TypeChecker::compareDeclarations(DeclContext *dc,
@@ -861,15 +880,23 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
861880
case OverloadChoiceKind::DynamicMemberLookup:
862881
break;
863882
}
864-
883+
884+
// We don't apply some ranking rules to overloads found through dynamic
885+
// lookup in order to keep a few potentially ill-formed cases ambiguous.
886+
bool isDynamicOverloadComparison =
887+
choice1.getKind() == OverloadChoiceKind::DeclViaDynamic &&
888+
choice2.getKind() == OverloadChoiceKind::DeclViaDynamic;
889+
865890
// Determine whether one declaration is more specialized than the other.
866891
bool firstAsSpecializedAs = false;
867892
bool secondAsSpecializedAs = false;
868-
if (isDeclAsSpecializedAs(tc, cs.DC, decl1, decl2)) {
893+
if (isDeclAsSpecializedAs(tc, cs.DC, decl1, decl2,
894+
isDynamicOverloadComparison)) {
869895
score1 += weight;
870896
firstAsSpecializedAs = true;
871897
}
872-
if (isDeclAsSpecializedAs(tc, cs.DC, decl2, decl1)) {
898+
if (isDeclAsSpecializedAs(tc, cs.DC, decl2, decl1,
899+
isDynamicOverloadComparison)) {
873900
score2 += weight;
874901
secondAsSpecializedAs = true;
875902
}

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)