-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[CSRanking] Favour concrete members over protocol requirements #18927
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
9cb3e36
[test] Expand AnyObject ambiguity test to cover methods and subscripts
hamishknight 15ada53
[CSRanking] Swift 4.1 compatibility hack for favouring properties on …
hamishknight 8867b9d
[CSRanking] Favour members on concrete types over protocol members
hamishknight 2e6ba53
[CSRanking] Exclude concrete-over-protocol rule from dynamic lookup r…
hamishknight 0dabb11
Fix Linux failure for concrete-over-protocol
hamishknight 685ec32
[Revert Me] Disable Swift 5 version check for concrete-over-protocol
hamishknight File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -389,15 +389,18 @@ static bool paramIsIUO(Decl *decl, int paramNum) { | |
/// | ||
/// "Specialized" is essentially a form of subtyping, defined below. | ||
static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc, | ||
ValueDecl *decl1, ValueDecl *decl2) { | ||
ValueDecl *decl1, ValueDecl *decl2, | ||
bool isDynamicOverloadComparison = false) { | ||
|
||
if (tc.getLangOpts().DebugConstraintSolver) { | ||
auto &log = tc.Context.TypeCheckerDebug->getStream(); | ||
log << "Comparing declarations\n"; | ||
decl1->print(log); | ||
log << "\nand\n"; | ||
decl2->print(log); | ||
log << "\n"; | ||
log << "\n(isDynamicOverloadComparison: "; | ||
log << isDynamicOverloadComparison; | ||
log << ")\n"; | ||
} | ||
|
||
auto *innerDC1 = decl1->getInnermostDeclContext(); | ||
|
@@ -406,7 +409,9 @@ static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc, | |
auto *outerDC1 = decl1->getDeclContext(); | ||
auto *outerDC2 = decl2->getDeclContext(); | ||
|
||
if (!tc.specializedOverloadComparisonCache.count({decl1, decl2})) { | ||
auto overloadComparisonKey = | ||
std::make_tuple(decl1, decl2, isDynamicOverloadComparison); | ||
if (!tc.specializedOverloadComparisonCache.count(overloadComparisonKey)) { | ||
|
||
auto compareSpecializations = [&] () -> bool { | ||
// If the kinds are different, there's nothing we can do. | ||
|
@@ -446,6 +451,32 @@ static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc, | |
return inProtocolExtension2; | ||
} | ||
|
||
// A concrete type member is always more specialised than a protocol | ||
// member (bearing in mind that we have already handled the case where | ||
// exactly one member is in a protocol extension). Only apply this rule in | ||
// Swift 5 mode to better maintain source compatibility under Swift 4 | ||
// mode. | ||
// | ||
// Don't apply this rule when comparing two overloads found through | ||
// dynamic lookup to ensure we keep cases like this ambiguous: | ||
// | ||
// @objc protocol P { | ||
// var i: String { get } | ||
// } | ||
// class C { | ||
// @objc var i: Int { return 0 } | ||
// } | ||
// func foo(_ x: AnyObject) { | ||
// x.i // ensure ambiguous. | ||
// } | ||
// | ||
if (true && !isDynamicOverloadComparison) { | ||
auto *proto1 = dyn_cast<ProtocolDecl>(outerDC1); | ||
auto *proto2 = dyn_cast<ProtocolDecl>(outerDC2); | ||
if (proto1 != proto2) | ||
return proto2; | ||
} | ||
|
||
Type type1 = decl1->getInterfaceType(); | ||
Type type2 = decl2->getInterfaceType(); | ||
|
||
|
@@ -697,21 +728,21 @@ static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc, | |
return false; | ||
}; | ||
|
||
tc.specializedOverloadComparisonCache[{decl1, decl2}] = | ||
tc.specializedOverloadComparisonCache[overloadComparisonKey] = | ||
compareSpecializations(); | ||
} else if (tc.getLangOpts().DebugConstraintSolver) { | ||
auto &log = tc.Context.TypeCheckerDebug->getStream(); | ||
log << "Found cached comparison: " | ||
<< tc.specializedOverloadComparisonCache[{decl1, decl2}] << "\n"; | ||
log << "Found cached comparison: " | ||
<< tc.specializedOverloadComparisonCache[overloadComparisonKey] << "\n"; | ||
} | ||
|
||
if (tc.getLangOpts().DebugConstraintSolver) { | ||
auto &log = tc.Context.TypeCheckerDebug->getStream(); | ||
auto result = tc.specializedOverloadComparisonCache[{decl1, decl2}]; | ||
auto result = tc.specializedOverloadComparisonCache[overloadComparisonKey]; | ||
log << "comparison result: " << (result ? "better" : "not better") << "\n"; | ||
} | ||
|
||
return tc.specializedOverloadComparisonCache[{decl1, decl2}]; | ||
return tc.specializedOverloadComparisonCache[overloadComparisonKey]; | ||
} | ||
|
||
Comparison TypeChecker::compareDeclarations(DeclContext *dc, | ||
|
@@ -756,6 +787,9 @@ SolutionCompareResult ConstraintSystem::compareSolutions( | |
bool isStdlibOptionalMPlusOperator1 = false; | ||
bool isStdlibOptionalMPlusOperator2 = false; | ||
|
||
bool isSwift4ConcreteOverProtocolVar1 = false; | ||
bool isSwift4ConcreteOverProtocolVar2 = false; | ||
|
||
auto getWeight = [&](ConstraintLocator *locator) -> unsigned { | ||
if (auto *anchor = locator->getAnchor()) { | ||
auto weight = weights.find(anchor); | ||
|
@@ -849,15 +883,23 @@ SolutionCompareResult ConstraintSystem::compareSolutions( | |
case OverloadChoiceKind::DynamicMemberLookup: | ||
break; | ||
} | ||
|
||
|
||
// We don't apply some ranking rules to overloads found through dynamic | ||
// lookup in order to keep a few potentially ill-formed cases ambiguous. | ||
bool isDynamicOverloadComparison = | ||
choice1.getKind() == OverloadChoiceKind::DeclViaDynamic && | ||
choice2.getKind() == OverloadChoiceKind::DeclViaDynamic; | ||
|
||
// Determine whether one declaration is more specialized than the other. | ||
bool firstAsSpecializedAs = false; | ||
bool secondAsSpecializedAs = false; | ||
if (isDeclAsSpecializedAs(tc, cs.DC, decl1, decl2)) { | ||
if (isDeclAsSpecializedAs(tc, cs.DC, decl1, decl2, | ||
isDynamicOverloadComparison)) { | ||
score1 += weight; | ||
firstAsSpecializedAs = true; | ||
} | ||
if (isDeclAsSpecializedAs(tc, cs.DC, decl2, decl1)) { | ||
if (isDeclAsSpecializedAs(tc, cs.DC, decl2, decl1, | ||
isDynamicOverloadComparison)) { | ||
score2 += weight; | ||
secondAsSpecializedAs = true; | ||
} | ||
|
@@ -962,6 +1004,30 @@ SolutionCompareResult ConstraintSystem::compareSolutions( | |
} | ||
} | ||
|
||
// Swift 4.1 compatibility hack: If everything else is considered equal, | ||
// favour a property on a concrete type over a protocol property member. | ||
// | ||
// This hack is required due to changes in shadowing behaviour where a | ||
// protocol property member will no longer shadow a property on a concrete | ||
// type, which created unintentional ambiguities in 4.2. This hack ensures | ||
// we at least keep these cases unambiguous in Swift 5 under Swift 4 | ||
// compatibility mode. Don't however apply this hack for decls found through | ||
// dynamic lookup, as we want the user to have to disambiguate those. | ||
// | ||
// This is intentionally narrow in order to best preserve source | ||
// compatibility under Swift 4 mode by ensuring we don't introduce any new | ||
// ambiguities. This will become a more general "is more specialised" rule | ||
// in Swift 5 mode. | ||
if (false && !isDynamicOverloadComparison && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this mean that this code shouldn't exist? |
||
isa<VarDecl>(decl1) && isa<VarDecl>(decl2)) { | ||
auto *nominal1 = dc1->getSelfNominalTypeDecl(); | ||
auto *nominal2 = dc2->getSelfNominalTypeDecl(); | ||
if (nominal1 && nominal2 && nominal1 != nominal2) { | ||
isSwift4ConcreteOverProtocolVar1 = isa<ProtocolDecl>(nominal2); | ||
isSwift4ConcreteOverProtocolVar2 = isa<ProtocolDecl>(nominal1); | ||
} | ||
} | ||
|
||
// FIXME: Lousy hack for ?? to prefer the catamorphism (flattening) | ||
// over the mplus (non-flattening) overload if all else is equal. | ||
if (decl1->getBaseName() == "??") { | ||
|
@@ -1118,6 +1184,14 @@ SolutionCompareResult ConstraintSystem::compareSolutions( | |
score1 += isStdlibOptionalMPlusOperator2; | ||
} | ||
|
||
// All other things being equal, apply the Swift 4 compatibility hack for | ||
// preferring var members in concrete types over a protocol requirement | ||
// (see the comment above for the rationale of this hack). | ||
if (false && score1 == score2) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here? |
||
score1 += isSwift4ConcreteOverProtocolVar1; | ||
score2 += isSwift4ConcreteOverProtocolVar2; | ||
} | ||
|
||
// FIXME: There are type variables and overloads not common to both solutions | ||
// that haven't been considered. They make the systems different, but don't | ||
// affect ranking. We need to handle this. | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should probably be simplified to
if (!isDynamicOverloadComparison)
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@xedin I removed the version checks temporarily in 685ec32 to allow the compat suite to exercise the Swift 5 path.
true
should betc.Context.isSwiftVersionAtLeast(5)
, and below,false
should be!tc.Context.isSwiftVersionAtLeast(5)
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, gotcha!