Skip to content
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

[ConstraintSystem][SE-0249] Key Path Expressions as Functions #26054

Open
wants to merge 12 commits into
base: master
from

Conversation

Projects
None yet
3 participants
@gregomni
Copy link
Collaborator

commented Jul 10, 2019

This builds on @brentdax's work in #23435 to handle optional chaining key paths as both KeyPath BGTs and as function types, and also does away with explicit disjunctions to determine which kind of type to turn the key path literal into, so ought to be more performant.

There is a single test diagnosis that gets worse here (a contextual type which matches root/value but specifies an incorrect writability is now ambiguous because the diagnostic path goes into the old CSDiag stuff, and the first step is to discard contextual type, and then the remainder is entirely ambiguous). The solution is future work to improve key path diagnoses in general, I think.

gregomni and others added some commits Sep 21, 2018

Implicit conversion of KeyPath<Root,Value> to (Root) -> Value.
Fix autoclosure param interface type when it involves archetypes.

Had some repeated locals from moving this block of code around - cleaned up.

Alternate implementation where KeyPathExpr is essentially a literal type and can be either a KeyPath(R,V) or (R)->V.

Some unneccessary code now.

Implicit closure pieces need valid sourceLocs in case a coerce expr needs to refer to the beginning of the closure in `\.foo as (A) -> B`.
Removed explicit noescape from function type so `let a: (A) -> B = \.foo` is valid.
Remove optimization that optional path is always read-only KP type in CSGen, since it can also now be of function type.
[ConstraintSolver] Favor keypaths over functions
Necessary to keep SE-0249 from failing tests.
Handle simplification of optional-chaining key path literals, disting…
…uish function type and keypath BGT type without explicit disjunctions.

@gregomni gregomni requested a review from brentdax Jul 10, 2019

@gregomni

This comment has been minimized.

Copy link
Collaborator Author

commented Jul 10, 2019

@swift-ci Please smoke test.

@brentdax

This comment has been minimized.

Copy link
Collaborator

commented Jul 10, 2019

From the smoke test, it looks like that didn't regress after all.

@@ -1139,17 +1139,6 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
continue;
}

// If one is a function type and the other is a key path, prefer the other.
if (type1->is<FunctionType>() != type2->is<FunctionType>()) {

This comment has been minimized.

Copy link
@brentdax

brentdax Jul 10, 2019

Collaborator

How do we avoid needing a scoring rule?

This comment has been minimized.

Copy link
@gregomni

gregomni Jul 10, 2019

Author Collaborator

The change in the implementation of simplifyKeyPathConstraint avoids creating a disjunction between the two forms now, so there shouldn't ever be two solutions. It leaves a single KeyPathConstraint until there's enough info so that the simplify can pick either function or KeyPath nominal.

I went this way because the constraint system isn't smart enough / has too much ambiguity to figure out key paths with optional chaining without additional help. Thus the somewhat obscure condition at the end of simplify'ing !anyComponentsUnresolved || (definitelyKeyPathType && capability == ReadOnly) -- even if we haven't figured out all the key path component overloads, if we can tell that the literal path will result in a KeyPath nominal and that it'll be read-only (as in an optional chain), then we can immediately bind to the correct nominal type (because no possible component overload can make it non-read-only once a component specifies that it is).

That's essentially a replacement for the logic that used to be in CSGen that immediately bound to a type if there was an optional chain involved instead of creating a KeyPathConstraint at all. The constraint system needs that top-down info in order to figure out the bottom-up overloads for the components.

This comment has been minimized.

Copy link
@brentdax

brentdax Jul 11, 2019

Collaborator

What happens if you write a key path in a context with no type information at all, like this?

let kp = \A.property
let _: KeyPath<A, Prop> = kp

(There should probably be a test like this if there isn't one already.)

This comment has been minimized.

Copy link
@gregomni

gregomni Jul 11, 2019

Author Collaborator

If there isn't context that definitively asks for function type vs nominal type, then once the components are all resolved, simplification defaults to the nominal type. (And I'll specifically add tests like that in a moment.)

This comment has been minimized.

Copy link
@brentdax

brentdax Jul 12, 2019

Collaborator

Hmm...could we end up in a situation where we currently don't have any context, but the typechecker would discover more context in the future if we left the constraint unsolved? @xedin, any thoughts?

@gregomni

This comment has been minimized.

Copy link
Collaborator Author

commented Jul 10, 2019

From the smoke test, it looks like that didn't regress after all.

Woo! Some other recent change must've improved this, nice!

@gregomni

This comment has been minimized.

Copy link
Collaborator Author

commented Jul 10, 2019

@swift-ci Please smoke test.

gregomni added some commits Jul 11, 2019

These two diagnoses changed for the better, not sure why.
(Happened when merging latest master into this branch.)
@gregomni

This comment has been minimized.

Copy link
Collaborator Author

commented Jul 11, 2019

@swift-ci Please smoke test.

@gregomni

This comment has been minimized.

Copy link
Collaborator Author

commented Jul 11, 2019

@swift-ci Please smoke test Linux.

@xedin xedin self-requested a review Jul 12, 2019

@xedin
Copy link
Member

left a comment

I left a couple of comments inline. But I really want for us to consider a meta-question here - should keypath handling be re-designed in a way where keypath type drives access to the components instead of components determining what access is going to be? That way it should be much easier to diagnose invalid writable calls and avoid creating all of the conversions from l-value result to r-value result types.

While generating constraints check whether there is a contextual type - if so, everything is easy, otherwise start with assumption that it's a WritableKeyPath generate constraints for components and try to solve (I think it might make sense to introduce <baseType> KeyPathComponent #N <resultType>), if that fails try read-only keypath and/or function type.

Solving keypath would mean solving all of the associated components so we no longer have to wait for overload choices to be made and re-generate the same constraint or iterate over components multiple times.

@@ -5514,6 +5519,26 @@ ConstraintSystem::simplifyKeyPathConstraint(Type keyPathTy,
return SolutionKind::Solved;
};

auto tryMatchRootAndValueFromFunctionType =
[&](FunctionType *fnTy) -> SolutionKind {

This comment has been minimized.

Copy link
@xedin

xedin Jul 18, 2019

Member

Could you please reuse logic for matching for root/value types between here and tryMatchRootAndValueFromKeyPathType?

This comment has been minimized.

Copy link
@xedin

xedin Jul 18, 2019

Member

Even better would be to make it so tryMatchRootAndValueFromKeyPathType accepts a Type and then figures out whether it's a function or bound generic, or something else. That way we wouldn't need special cases for function type vs. bound generic in multiple places.

// type variable that's some sort of conversion to a function type, use that
// constraint's second type.
if (keyPathTy->isTypeVariableOrMember()) {
for (auto constraint : getActiveConstraints()) {

This comment has been minimized.

Copy link
@xedin

xedin Jul 18, 2019

Member

Can we delay solving this constraint until keyPathTy is property resolved instead of trying to guess what it might be?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.