-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[ConstraintSystem] Inference of inout for closure parameters in generic contexts #5533
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
Conversation
@swift-ci please test |
@@ -1410,6 +1410,35 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind, | |||
case ConstraintKind::ArgumentConversion: | |||
case ConstraintKind::OperatorArgumentTupleConversion: | |||
case ConstraintKind::OperatorArgumentConversion: | |||
if (type1->is<InOutType>() && !type2->is<InOutType>() && typeVar2) { |
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.
typeVar2
will only be non-null when type2
is a type variable, so the !type2->is<InOutType>()
check here is redundant.
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.
Yeah, that's true. Good spot
|
||
TypeMatchOptions subFlags = flags; | ||
subFlags -= TMF_GenerateConstraints; | ||
// First, see if we can actually replace typeVar2 with an InOutType. |
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.
matchTypes()
isn't permitted to tentatively do testing this way... when it fails, that branch of the solution space is invalid. In cases where it isn't obvious which way to go, we end up having to introduce a disjunction so the solver itself can explore both paths (and use the scope RAII objects to allow some of them to fail). I wonder if you could tackle this by adding a TVO_CanBeInOut
flag to specific type variables, and only allow type variables with that flag set to bind to inout
types, similarly to the way we have the TVO_MustBeMaterializable
flag.
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.
OK, that was the part I wasn't quite sure about in my approach, looking at the implementation of matchTypes
I couldn't see a reason for the CS to get changed if you remove TMF_GenerateConstraints
, but you know the part far better than me ;-). I'll have a look at adding a new flag to type variable then
@@ -137,3 +137,8 @@ func unsafeRawBufferPointerConversions( | |||
_ = UnsafeMutableRawBufferPointer(start: orp, count: 1) // expected-error {{cannot convert value of type 'UnsafeRawPointer?' to expected argument type 'UnsafeMutableRawPointer?'}} | |||
_ = UnsafeRawBufferPointer(start: orp, count: 1) | |||
} | |||
|
|||
func pointerInoutEquality(i: Int, p: UnsafeMutablePointer<Int>?) { |
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.
This is a bit of an odd test.
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.
I kept having issues with this kind of expression (found code of this kind in swiftpm or stdlib somewhere) so I thought I might as well add it to the test suite.
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.
Okay, but please add an SR- reference so we can more easily look up why the test exists.
7c4448b
to
8f39c78
Compare
…ntexts This fixes SR-1976
Having an invalid assignment in a closure could make the type checker fail without providing a diagnostic, eventually crashing the compiler. Now a diagnostic is provided. This resolves SR-3053/SR-3073
8f39c78
to
63c1f8d
Compare
Thanks for the feedback. I now annotate closure parameters with Regarding the @swift-ci please test |
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.
Definitely getting closer and simpler!
@@ -1410,6 +1410,22 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind, | |||
case ConstraintKind::ArgumentConversion: | |||
case ConstraintKind::OperatorArgumentTupleConversion: | |||
case ConstraintKind::OperatorArgumentConversion: | |||
if (type1->is<InOutType>() && typeVar2 && | |||
typeVar2->getImpl().canBeInOut()) { |
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.
When type1
is an InOutType
, we have two cases for typeVar2
: either it allows binding to inout
(in which case we do the binding, as it's doing below), or it does not allow binding to inout
(in which case we should immediately fail). The immediate-failure case might not be strictly necessary, but it would be nice to early-exit with the failure here rather than go through the entirety of matchTypes
.
Also, could this happen the other way, where type2
is an InOutType
and typeVar1
needs to be checked for canBeInOut
?
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.
Yeah, that's correct. I wasn't quite sure if the inout
to UnsafeMutablePointer
conversion could still happen but just verified that it can't, so early exit should be possible.
I couldn't come up with any scenario that needs a conversion the other way round but can't disprove it either.
auto inOutType1 = type1->getAs<InOutType>(); | ||
|
||
unsigned typeVar3Options = typeVar2->getImpl().getOptions(); | ||
typeVar3Options &= ~TVO_CanBeInOut; |
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.
I think we should add TVO_MustBeMaterializable
to typeVar3Options
, because one cannot have an inout
of a non-materializable type. (I'm not sure I understand why we need the new type variable at all... see below).
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.
OK, I must admit I didn't quite understand what TVO_MustBeMaterializable
was for and thought that passing the options on was a sensible thing to do.
auto inOutType3 = InOutType::get(typeVar3); | ||
|
||
addConstraint(ConstraintKind::Equal, typeVar2, inOutType3, locator); | ||
return matchTypes(inOutType1->getObjectType(), typeVar3, kind, flags, |
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.
This seems unnecessarily complex, with the introduction of a new type variable that is immediately bound. The effect of this code is, basically, binding typeVar2
to InOutType::get(inOutType1->getObjectType())
, i.e., type1
. Why can't we directly allow that type binding once we've checked that the type variable can bind to an InOutType
?
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.
I also thought about directly binding typeVar2
to type1
(which is an InOutType
) (that is call setFixedType(typeVar2, type1)
). It works on the test cases I have, but I thought that by doing this, we lose the flexibility of the subtype (or lower) constraint that we actually need to enforce. I thought there may be constraint systems that are solvable when typeVar1
is a proper subtype of type2
but type2
still needs to be upgraded to an InOutType
. However, I couldn't come up with an example.
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.
Even if that were the case this is going to bind the extra typevar anyway in the course of solving the system. I think the new TV Option can be removed in favor of a materializable constraint and a fixed binding.
@@ -137,3 +137,8 @@ func unsafeRawBufferPointerConversions( | |||
_ = UnsafeMutableRawBufferPointer(start: orp, count: 1) // expected-error {{cannot convert value of type 'UnsafeRawPointer?' to expected argument type 'UnsafeMutableRawPointer?'}} | |||
_ = UnsafeRawBufferPointer(start: orp, count: 1) | |||
} | |||
|
|||
func pointerInoutEquality(i: Int, p: UnsafeMutablePointer<Int>?) { |
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.
Okay, but please add an SR- reference so we can more easily look up why the test exists.
Looks like this was never merged? |
No, it wasn't. Uni work caught up with me last month so I couldn't work on it anymore. I am currently working on #5537 and was hoping to finish this PR sometime next week. Or is the issue very urgent at the moment? |
@ahoppen Not urgent, just making sure we're not dropping user contributions on the floor! |
FWIW @xedin is looking at similar issues. |
Heh, I wish I've seen this earlier!... Thanks, @slavapestov! This re-enforces my hepothesys that binding instead of argv between type of different materializability is a way to go about it, since potential bindings should be able to create disjunction with and without inout for related bind param, I will try it out tonight and let you guys know. |
@xedin Does your PR supersede this one? |
@slavapestov yes, it does, mine also covers tests from this one. |
[wasm] Revert unnecessary TaskGroup.cpp include directive
Problem
In generic contexts, the
inout
annotation for closure parameters was not inferred anymore, e.g. the following code failed to compile (SR-1976):while the non-generic version
compiles fine.
If the closure included a member access of the
inout
argument, this even a compiler crash (SR-3053/SR-3073):Solution
When encountering a subtype constraint (
type1 subtype type2
) wheretype1
is aninout
type andtype2
is not, we introduce a new type variabletype3
and check iftype2 == inout type3
can hold. This guards against cases like the following where we need the asymetricinout
relationship.Otherwise, we replace the original constraint by the two constraints
type2 == inout type3
andremovedInOutLayerFromType1 subtype type3
that upgradetype2
toinout
.Some notes
CSSimplify
is the right place to make the choice if a type needs to beinout
or not, but couldn't find a way to do it inCSSolve
. Using a disjunction constraint would require having a conjunction constraint as well but I couldn't find such a thing.UnsafeMutablePointer
in the example above.ConstraintGraphScope
but that didn't work for me and doesn't seem to be used anymore -> we might remove the class