Skip to content

Generic function taking closure with inout parameter returns erroneous results #6586

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
merged 2 commits into from
Jan 9, 2017

Conversation

xedin
Copy link
Contributor

@xedin xedin commented Jan 5, 2017

Resolves SR-3520.
Resolves SR-1976.
Resolves SR-3073.
Resolves SR-3479.

@xedin
Copy link
Contributor Author

xedin commented Jan 5, 2017

/cc @rudkx @slavapestov @jtbandes

This is still work in process, but solves crasher related to the ticket and improves diagnostics related to result types of closures with anonymous parameters...

I'm going to check why doesn't following type-check correctly right now (I'm pretty sure it has to do with how constraint graph contraction works related to parameter binding):

func sr3520_2<T>(_ item: T, _ update: (inout T) -> Void) {
  var x = item
  update(&x)
}
var i = 42
sr3520_2(i) { $0 += 3 }

@xedin
Copy link
Contributor Author

xedin commented Jan 6, 2017

I've figured out what the problem is last night but i don't know yet how to solve it properly - the reason why it's failing to type-check is because update: parameter is (inout <tv_0>) (where <tv_0> must be materializable) and anonymous closure is going to allocate argument as just (<tv_1>) (which might be inout or might not be depending on the solver path) and according to parameter simplification rules following constraint is going to be created to relating parameter and argument (inout <tv_0>) arg conv (tv_1>) (keep in mind that tv_0 must be materializable because it's open generic) so now CG.optimize() is going to kick-in and merge equivalence classes of tv_0 and tv_1 which yields constraint system unsolvable when tv_1 actually has to be inout, because tv_0 must be materializable, but works in other cases e.g.

func f0<T, U>(_ t: T, _ f: (inout T) -> U) -> U {
  var t2 = t
  return f(&t2)
}

struct X2 {
  func g() -> Float { return 0 }
}

_ = f0(X2(), {$0.g()})

since first argument of closure doesn't have to be inout in this case, everything works perfectly fine. There is also rule in CG.optimize which prohibits such contraction if there is BindParam that binds to strict inout is involved but that's never true in either case.

I've tried to check materializability requirements on both sides and skip contraction if they are different but that breaks a bunch of other cases...

Edit: I've incorrectly stated that constraint kind is 'arg conv' where it's actually 'subtype', because closure parameter bindings are constructed in context of function call, which means them subtype constraints, see ConstraintSystem::matchFunctionTypes.

@slavapestov
Copy link
Contributor

@xedin We were discussing this problem yesterday because it also came up with #6608. @DougGregor and I had the idea that there should be one type variable for the internal type of each parameter (which is an lvalue type for inouts, an array type for varargs, etc) -- and another type variable for the external type of the parameter list (which need not be materializable). A constraint binds the outer type variable with a tuple type of inner type variables, enforcing the necessary conversions. What do you think?

@xedin
Copy link
Contributor Author

xedin commented Jan 6, 2017

@slavapestov That's what I was kind of thinking as well (but less general)...

I thought that maybe if one side of the contractable constraint must be materializable but the other isn't instead of contracting constraint it would make more sense to create a separate binding which is going to reverse the relationship e.g. -> (inout t0) arg conv (t1) becomes t1 bind (inout t0) this way instead of making type variables equivalent (which I think is wrong) it would establish other type of dependency relationship which is more appropriate.

All that said, I'm not yet sure if that's going to work at all, since I haven't tried it but in theory it should enable t1 to be bound properly and doesn't prevent related 'bind param' constraints from being contracted...

@jtbandes
Copy link
Contributor

jtbandes commented Jan 6, 2017

Perhaps @cwillmor would have thoughts on this; although no longer a maintainer I think he was the original author of BindParam constraint stuff...

@xedin xedin changed the title [WIP] SR-3520: Generic function taking closure with inout parameter returns erroneous results Generic function taking closure with inout parameter returns erroneous results Jan 8, 2017
@xedin
Copy link
Contributor Author

xedin commented Jan 8, 2017

@slavapestov @DougGregor I've pushed a fix which deals with inout T0 subtype T1 constraint contraction by ConstraintGraph.optimize which breaks system solving because it also contracts bind param bindings which are not supposed to be inout but rather lvalue. The change from subtype to fixed binding made early allows for correct 'bind param' contraction/solving because left-hand side of the constraint is going to be currently marked as 'inout' which marks right-hand side as 'lvalue'.

Please take a look and let me know what you think, I've added comments and tests for all related issues SR-3520, SR-1976, SR-3073, SR-3479.

@slavapestov
Copy link
Contributor

Awesome. So the vararg problem is not related after all?

@slavapestov
Copy link
Contributor

@swift-ci Please smoke test

@xedin
Copy link
Contributor Author

xedin commented Jan 9, 2017

Yeah, vararg is unrelated, it has to do with contraction of subtype constraints, generated by sub-declarations of trailing closures to be precise :)

// (if inout containts type var, see ConstraintGraph::contractEdges()),
// since right-hand side type variable must not be materializable
// it can simply get left-hand side as a fixed binding, otherwise fail.
if (type1->is<InOutType>() && type1->hasTypeVariable() && typeVar2) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this should be type1->getInOutObjectType()->isTypeVariableOrMember()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, let me change it to check for type variable directly instead of presence of one.

@slavapestov slavapestov self-assigned this Jan 9, 2017
Convert subtype constraints with inout on one side and type variable
on the other into fixed binding. Constriants like `inout T0 subtype T1`
where (T0 must be materializable) are created when closures are part
of the generic function parameters e.g. `func foo<T>(_ t: T, (inout T) -> Void) {}`
so when such function gets called e.g.
```
  var x = 42
  foo(x) { $0 = 0 }
```
it's going to try and map closure parameters type (inout T0) - where
is opened generic parameter T - to argument type (T1), which can
be 'inout' but it's uncertain at this stage, but since closure
'declaration' `{ $0 = 0 }` is wrapped inside of a function call,
it has to 'map' parameters to arguments instead of converting them,
see `ConstraintSystem::matchFunctionTypes`.

Resolves: SR-3520, SR-1976, SR-3073, SR-3479.
@xedin
Copy link
Contributor Author

xedin commented Jan 9, 2017

@slavapestov Force pushed the change for type variable checking, thanks!

@slavapestov
Copy link
Contributor

@swift-ci Please smoke test

@slavapestov
Copy link
Contributor

@swift-ci Please smoke test Linux

1 similar comment
@slavapestov
Copy link
Contributor

@swift-ci Please smoke test Linux

@xedin
Copy link
Contributor Author

xedin commented Jan 9, 2017

@slavapestov previous Linux build is still going, just got to run tests, I took a long time to compile llvm/clang and other pieces...

@slavapestov
Copy link
Contributor

@swift-ci Please smoke test Linux

@xedin
Copy link
Contributor Author

xedin commented Jan 9, 2017

\o/

@slavapestov slavapestov merged commit c04c6c9 into swiftlang:master Jan 9, 2017
@xedin
Copy link
Contributor Author

xedin commented Jan 9, 2017

@slavapestov Thanks again!

@xedin
Copy link
Contributor Author

xedin commented Jan 9, 2017

@slavapestov Will you resolve the tickets or should I?

@rudkx
Copy link
Contributor

rudkx commented Jan 9, 2017

@xedin I went ahead and resolved them, thanks!

@xedin
Copy link
Contributor Author

xedin commented Jan 9, 2017

@rudkx Awesome, thank you!

@timvermeulen
Copy link

This didn't make Swift 3.1?

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.

5 participants