-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Sema: Support optional promotion of tuple patterns #63992
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
base: main
Are you sure you want to change the base?
Sema: Support optional promotion of tuple patterns #63992
Conversation
@swift-ci please smoke test macOS |
A general note: Any changes made to TypeCheckPattern.cpp need to be reflected in CSGen otherwise the behavior in multi-statement closures and result builder transformed closures would not change… |
FWIW I'm also hoping to eventually remove |
f3f6be7
to
85d8479
Compare
@xedin I took care of all the closure stuff; thanks for the reminder. |
Great! I'll try to take a look today. |
85d8479
to
9d9a385
Compare
@swift-ci please smoke test macOS |
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.
Some initial comments from a quick look through, I can look in more detail tomorrow. I think you'll need to adjust the logic in getTypeForPattern
rather than reversing the contextual conversion constraint though
// FIXME: '.a' is OK but 'E.a' is not? | ||
case E.a: () // expected-error {{enum case 'a' is not a member of type 'E?'}} |
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.
Heh yeah, this is something I'm planning on fixing
9d9a385
to
a53186e
Compare
@swift-ci please smoke test macOS |
@xedin How does this look? As of now the only known issue is a failure in the case of a |
contexts = ['do_block', 'closure', 'result_builder_closure'] | ||
def get_test_case_introducer(context): | ||
if context == 'do_block': | ||
return 'do' | ||
elif context == 'closure': | ||
return 'let _: () -> Void =' | ||
elif context == 'result_builder_closure': | ||
return '@DummyBuilder var x: Void' |
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.
Does anyone have an opinion on removing the result builder context now that constraint generation for closures has been unified? It makes these tests somewhat harder to codify.
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.
Fine with me, as long as you add a couple of simple test cases within result builders
@hamishknight could you please take a look? I will try to get some time for this next week. |
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.
Thanks! A few comments, but otherwise this LGTM. I'll let @xedin have the final say though.
// switch 0 { | ||
// case (int: 0): break | ||
// default: break | ||
// } | ||
// Do we really want/need to support this? |
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.
Urgh no, I don't think we want to support that. The constraint system will already reject such cases (including if case
, as that already goes through the constraint system even outside of closures). Hopefully we can drop it for switch
statements outside of closures without much fallout.
/// The first type is an optional of non-negative depth of the second type, | ||
/// ignoring lvalueness (because the constraint is currently applied only to | ||
/// pattern types, which are always rvalue). For example, given a RHS of | ||
/// 'Int', a LHS of 'Int' or 'Int??' would satisfy this constraint. | ||
EqualOrOptional, |
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 doesn't quite feel right to me to have Equal
in the name since the constraint isn't doing rvalue conversions, it's just asserting that no lvalues are present. Additionally, I think if we did ever want to apply this constraint to something else that could have lvalues, I think it would probably make most sense to preserve lvalueness like OptionalObject
, as lvalues are generally preserved through optional chains.
How about something like UnwrappingOptionals
? I think the plurality also helps make it clear it unwraps an arbitrary number of optionals.
a53186e
to
44b91a6
Compare
TVO_CanBindToNoEscape); | ||
CS.addConstraint( | ||
ConstraintKind::EqualOrOptional, tyVar, patternTy, | ||
locator.withPathElement(LocatorPathElt::PatternMatch(pattern))); |
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.
As far as I understand this problem, what we really want is not to look through optionals but disregard the structure of the tuple pattern and let context impose it. So how about instead of this new constraint we actually model it this way:
- Create a "tuple" pattern type variable (just like you did);
- For each pattern in the tuple create a member constraint to i.e.
<tuple var>[member: .<index or name>] == <element-var>
; - Set tuple variable as a type of the tuple pattern.
This scheme should make it possible to infer tuple type from context and look through optionals in pattern matching context while resolving individual tuple elements.
WDYT?
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.
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.
Sorry for the late reply here! I'm curious what the benefit of that approach would be, as wouldn't the new tuple member constraint need to handle the same optional unwrapping that the EqualOrOptional
constraint currently handles, plus it would need to have tuple matching logic? Or is there something I'm missing here?
Or is the aim to have the <tuple var>
be inferred as a non-optional? I should note that the constraint which attaches the tuple var to the surrounding pattern would fail with that, as it should only allow conversions that propagate into pattern, not out of them (though I'm becoming increasingly of the opinion that we should limit those to equality constraints where we can).
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.
There are no new constraints and existing member already handles unwrapping of optional base type.
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, I see! Sorry, I totally missed that. I guess one worry with that approach is we don't want to allow e.g (x: Int, y: Int)
to match with (y: Int, x: Int)
, or even worse some nominal type T
that just so happens to have the right members.
One other thing that we have to contend with is that we currently can use the tuple type of the pattern to introduce conversions, e.g this is currently allowed:
if case (x: 0, y: 0) = (0, 0) {}
Also just discovered that we currently allow duplicate labels in tuple patterns :(
if case (x: 0, x: 0) = (0, 0) {}
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 don’t think it should be too hard to maintain the structural matching, we can even add additional information about index/label to member constraint to make sure that the base tuple does indeed match structurally.
As with enum element patterns — at least the unqualified ones — this allows tuple patterns to omit syntactic noise in the form of enclosing
.some(...)
or trailing?
.