Skip to content

Fix source compatibility problems with conforming to multiple Collection axes at once. #7136

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

Merged

Conversation

jckarter
Copy link
Contributor


Sema: Improve handling of potential witnesses during associated type inference.

The associated type inference is a bit unusual, since we want to be able to infer associated types from protocol extension implementations that themselves depend on the types being inferred a certain way, e.g.:

protocol Runcible { associatedtype Spoon; func runce(_: Spoon) }
extension Runcible where Spoon == Hat {
  func runce(_ hat: Hat) {}
}

For this reason, we would collect potential witnesses from all possible extensions of the type without immediately checking the constraints on each extension. This could lead to an inconsistency where, when ranking potential witnesses, associated type inference would end up picking the "best" candidate as something that can't even be used by the type we're inferring the witness for, leading to incomprehensible behavior. The ranking criterion itself is also flawed, because TC.compareDeclarations is normally comparing candidates for the same well-typed function invocation, but during associated type inference we haven't even established the type system we're comparing candidates within. Witness candidates from extensions with mutually exclusive associated type constraints but otherwise more specific constraints would be unnecessarily dismissed as ambiguous because neither candidate's generic signature is a match for the other's.

Both problems can be mitigated by looking at only the constraints for protocol extensions that specifically constrain the Self type, since those conformances are always explicit in the source code and therefore independent of associated type inference. We can pre-filter witness candidates by rejecting candidates for which the conforming type doesn't match the extension's Self constraints. We can score multiple witness candidates by looking only at the relative constraints they put on Self, considering a witness from a context that more specifically constrains Self to be a better candidate; we should ignore the associated type constraints for this scoring because we don't even know if they apply given the solution we'll ultimately pick. There's probably a more systemic way to go about this, but targeted fixes for both of these problems, along with a following patch to the standard library, restores source compatibility for Collection conformers, fixing rdar://problem/30228957.


stdlib: Adding missing default implementations of subscript(Range) for combinations of [Mutable][RangeReplaceable][Bidirectional|RandomAccess]Collection.

These were overlooked, and somehow code that attempted to make a minimal collection conform to RangeReplaceableCollection and RandomAccessCollection managed to compile successfully in Swift 3.0, but in Swift 3.1…something changed to reject a type that conforms to both due to the lack of a suitable default slicing subscript implementation in the stdlib that provided all the requirements. Fill in these missing implementations. The type checker still fails to pick the right witness without an explicit typealias SubSequence, but this is progress toward rdar://problem/30228957.


@jckarter
Copy link
Contributor Author

@swift-ci Please test

@jckarter
Copy link
Contributor Author

@airspeedswift and @dabrahams, how do the standard library changes here look?

@DougGregor or @rudkx, do the type checker changes look alright for 3.1?


// Make sure that solution is better than any of the other solutions
// Make sure that solution is better than any of the other solutionsa
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

solutionsa

@swift-ci
Copy link
Contributor

Build failed
Jenkins build - Swift Test Linux Platform
Git Commit - 7774d9e231ff14698bd585d6ba19f68b8bc0a2d2
Test requested by - @jckarter

@airspeedswift
Copy link
Member

I suspect PR #4825 might be what surfaced this problem. This is the first time that change has made it off master into a full release.

public subscript(bounds: Range<Index>) -> MutableRandomAccessSlice<Self> {
get {
_failEarlyRangeCheck(bounds, bounds: startIndex..<endIndex)
return .init(base: self, bounds: bounds)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

never thought about calling init like this, very cute.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying hard not to decide it's too cute by half. Maybe it's just a new idiom we should be using everywhere, but at first glance it appears to harm readability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My main concern was gyb-ability. I can expand out the name if you prefer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it, especially for short functions. Why repeat the name. And makes the fact that you're creating the thing you're returning clearer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@airspeedswift The reason why repeat it is that despite years of Swift I still think of .xxx as looking up xxx in the current scope instead of the type context. It feels as though it's chaining to Self.init. Of course I'm projecting, but I have a hard time seeing how other people don't make the same mistake. Finally, I don't see how it clarifies anything useful: CapitalizedName(...) is always creating something.

@jckarter how does "gyb-ability" come into play?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only that I judged init(...) to be clearer than repeating the complicated ${foo}${bar}${bas}Slice expansion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess gyb makes the .init thing more appealing since you have to return an ugly gybbed typename like RangeReplaceable${capability}Slice (not in this example, but in some others).

Re the working of .xxx... that seems like just something to get over, since it's pervasive – this is just one more example. Sure, CapitalizedName(...) means creation but I think the bare .init does make it clearer what it's doing "at a glance" once you're used to it, to me at least, and also eliminates any chance of there being an additional subtype conversion you could be overlooking.

Copy link
Member

@airspeedswift airspeedswift left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes LGTM. There are probably additional tests needed, like to test that slices have the expected properties, but we can merge without then add them later, will raise a Jira for that. We’ll need that kind of test for testing the conditional conformance refactoring too.

where C.Iterator.Element == Int
{}
% end
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice thorough testing, Joe!

Copy link
Contributor

@dabrahams dabrahams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modulo a decision about the .init(...) cuteness, it looks great.

@dabrahams
Copy link
Contributor

dabrahams commented Jan 30, 2017 via email

Copy link
Member

@DougGregor DougGregor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks fantastic, @jckarter!

@@ -2976,7 +2977,54 @@ InferredAssociatedTypesByWitnesses
ConformanceChecker::inferTypeWitnessesViaValueWitnesses(ValueDecl *req) {
InferredAssociatedTypesByWitnesses result;

auto isExtensionUsableForInference = [&](ExtensionDecl *extension) -> bool {
// Assume concrete extensions we found witnesses in are always viable.
if (!extension->getExtendedType()->isAnyExistentialType())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assumption isn't quite true: constrained extensions of concrete types won't be able to provide suitable witnesses, so that return true; could more accurately be:

return !extension->isConstrainedExtension();

for now. When conditional conformances come in, we'll need an "isAtLeastAsSpecializedAs" kind of check.

//
// TODO: This seems like it shouldn't happen, but without this check, we
// crash building the standard library.
if (!extension->getGenericSignature())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's it's recursion breaker.

for (auto witness : lookupValueWitnesses(req, /*ignoringNames=*/nullptr)) {
// If the potential witness came from a protocol extension, and our `Self`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can drop the "protocol" part of this comment, if you take my suggestion above regarding constrained extensions of concrete types.

@@ -3780,6 +3850,154 @@ void ConformanceChecker::resolveTypeWitnesses() {
// If we (still) have more than one solution, find the one with
// more-specialized witnesses.
if (solutions.size() > 1) {
auto compareDeclsForInference =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you pull this out into a static function? It's pretty large to be inline.

…inference.

The associated type inference is a bit unusual, since we want to be able to infer associated types from protocol extension implementations that themselves depend on the types being inferred a certain way, e.g.:

```
protocol Runcible { associatedtype Spoon; func runce(_: Spoon) }
extension Runcible where Spoon == Hat {
  func runce(_ hat: Hat) {}
}
```

For this reason, we would collect potential witnesses from all possible extensions of the type without immediately checking the constraints on each extension. This could lead to an inconsistency where, when ranking potential witnesses, associated type inference would end up picking the "best" candidate as something that can't even be used by the type we're inferring the witness for, leading to incomprehensible behavior. The ranking criterion itself is also flawed, because `TC.compareDeclarations` is normally comparing candidates for the same well-typed function invocation, but during associated type inference we haven't even established the type system we're comparing candidates within. Witness candidates from extensions with mutually exclusive associated type constraints but otherwise more specific constraints would be unnecessarily dismissed as ambiguous because neither candidate's generic signature is a match for the other's.

Both problems can be mitigated by looking at only the constraints for protocol extensions that specifically constrain the `Self` type, since those conformances are always explicit in the source code and therefore independent of associated type inference. We can pre-filter witness candidates by rejecting candidates for which the conforming type doesn't match the extension's Self constraints. We can score multiple witness candidates by looking only at the relative constraints they put on `Self`, considering a witness from a context that more specifically constrains Self to be a better candidate; we should ignore the associated type constraints for this scoring because we don't even know if they apply given the solution we'll ultimately pick. There's probably a more systemic way to go about this, but targeted fixes for both of these problems, along with a following patch to the standard library, restores source compatibility for Collection conformers, fixing rdar://problem/30228957.
…ex>) for combinations of [Mutable][RangeReplaceable][Bidirectional|RandomAccess]Collection.

These were overlooked, and somehow code that attempted to make a minimal collection conform to RangeReplaceableCollection and RandomAccessCollection managed to compile successfully in Swift 3.0, but in Swift 3.1…*something* changed to reject a type that conforms to both due to the lack of a suitable default slicing subscript implementation in the stdlib that provided all the requirements. Fill in these missing implementations, fixing rdar://problem/30228957.
@jckarter jckarter force-pushed the missing-slice-default-implementations branch from 7774d9e to 34ec424 Compare January 30, 2017 17:55
@jckarter
Copy link
Contributor Author

@airspeedswift @dabrahams I went ahead and expanded out the uses of .init shorthand. Not worth holding up this fix on a style debate, IMO.

@DougGregor I also made the type checker changes you requested. How does this look?

@jckarter
Copy link
Contributor Author

@swift-ci Please test

1 similar comment
@jckarter
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Jenkins build - Swift Test Linux Platform
Git Commit - 34ec424
Test requested by - @jckarter

Copy link
Member

@DougGregor DougGregor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@jckarter
Copy link
Contributor Author

@swift-ci Please test Linux

@jckarter jckarter merged commit fd91396 into swiftlang:master Jan 30, 2017
@rudkx
Copy link
Contributor

rudkx commented Jan 30, 2017

Awesome, thanks!

@jckarter jckarter deleted the missing-slice-default-implementations branch January 31, 2017 01:56
% for rangeReplaceable in ['', 'RangeReplaceable']:
% for capability in ['', 'Bidirectional', 'RandomAccess']:

struct ${mutable}${rangeReplaceable}${capability}Butt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put some 👖on that identifier!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants