-
Notifications
You must be signed in to change notification settings - Fork 10.4k
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
[SE-0287] [Sema] Implementation for implicit member chains #31679
[SE-0287] [Sema] Implementation for implicit member chains #31679
Conversation
1d01bd5
to
8670779
Compare
So, we can look at this problem from the inside out the way you’ve got it, but I wonder if it wouldn’t be cleaner to try it the other way around. The intent is to improve inference around an apply where the base of a sequence of member references is given contextually. So why not have the typing rules for applies cooperate here? We could try to look through an apply to bind the contextual type of whatever parameter to an implicit TypeExpr at the head of the chain. That should handle operator applies for you as well. |
@CodaFi Just to clarify that I'm understanding correctly, you're suggesting that in a construction like:
The type checking for That sounds like it could work out nicely, but I worry that it misses locations where type inference should be able to determine the contextual type but we forget to look through all the relevant expressions. Please correct me if I'm misunderstanding, but just focusing this on apply expressions would miss the inference in return statements, pattern binding decls, assign expressions, and possibly others. Moreover, whenever a new construct is added to the language, we'd have to make sure to consider the possibility of each sub-expression being part of an implicit member chain in order to bind the base type appropriately. With the current implementation, the base type of the chain is automatically bound to whatever contextual type we have by the expression at the end of the chain. Am I missing something? |
Right, as written the explanation I gave was... incomplete. Let me try to rephrase with the benefit of having puzzled through more of the details of the implementation here. Essentially, you have the right idea about the typing rules here, the question is how best we propagate contextual information to the heads of the member reference chains. Your implementation does so by modeling the backpropagation explicitly in the AST, whereas I was trying to request we instead specialize the typing rules in CSGen. I believe this would better model the heterogeneous version of this proposal anyways since you are only using the back-pointers to locate from "tail" to "head" as it were. Now, as I'm sure you've already noticed, this is pretty tough because syntactically the "head" of the chain with the syntactically leading At the end of the day, all we need to do is bridge the existing connected components here by getting an equality constraint between the tail and the enclosing contextual type. |
Thanks for your thoughtful feedback @CodaFi, that all makes sense. I agree with you that lifting this out of the AST is cleaner. There's a couple spots where I worry we lose the information about the "head" of the chain, specifically in the spots where we're calling |
Also, the latest commit has the member-chain stuff lifted out of the AST and into the constraint system. I've reworked CSGen (and |
lib/Sema/CSGen.cpp
Outdated
@@ -3298,7 +3337,7 @@ namespace { | |||
CS.addConstraint(ConstraintKind::OptionalObject, | |||
CS.getType(expr->getSubExpr()), objectTy, | |||
locator); | |||
return objectTy; | |||
return addUnresolvedMemberChainConstraints(expr, objectTy, locator); |
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.
We might be able, here and elsewhere, to lift this into the ConstraintWalker
itself in walkToExprPost
so that the logic for which expressions to "look through" for chains is contained entirely in getMemberChainSubExpr
. That would require us to pay the cost of the getMemberChainSubExpr(expr) == nullptr
on every expression, though, as opposed to just the ones that we know might be a part of an unresolved member chain. I think it would also require a new path element kind (or another function getMemberResultLocator
that gives us the locator we should use to match the member result to the whole chain result)...
1a91e4f
to
bbd4c27
Compare
@xedin I'd also love your feedback on this PR which implements multi-member chains (without changing the current rules for generic arguments at the head and tail matching). |
f8a3d85
to
d4e137b
Compare
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.
Only big question for me here is whether it would be possible to constrain recording of the base type and determining linked chain to the Constraint{Generator, Walker}
instead of constraint system. It seems like it should be possible to determine tail and head of the chain by lookahead in ConstraintWalker::walkToExprPre
and tie up the chain in ConstraintWalker::walkToExprPost
once tail expression is reached again. WDYT?
lib/Sema/CSGen.cpp
Outdated
// (represented with a new type variable) must equal the base type. | ||
if (CS.isMemberChainTail(expr)) { | ||
auto *chainBaseExpr = CS.getMemberChainBase(expr); | ||
if (auto *UME = dyn_cast<UnresolvedMemberExpr>(chainBaseExpr)) { |
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.
Should this be a castTo<UnresolvedMemberExpr>
instead since base of the member chain is always an UnresolvedMemberExpr
?
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 so—getMemberChainBase
works for all member chains, including, say foo?.bar!.baz()
(where the base is a DeclRefExpr
), so we do actually want to check here whether the member chain has a base which is an UnresolvedMemberExpr
, as opposed to anything else that could be sitting at the base.
Perhaps this method would be better named something like maybeAddUnresolvedMemberChainConstraints
, that just starts getting pretty long.
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'm asking because getMemberChainBase
is only used in combination with isMemberChainTail
which hints at it being tailored for unresolved member based chains. It seems like all of this functionality could be localized to ConstraintWalker
and chains like this determined upfront...
Thanks for circling back on this @xedin! I mentioned a similar possibility up-thread. I think something like that would be possible, with a couple caveats:
Regardless, I think it's perfectly reasonable to move If I've missed something let me know! Interested to hear your thoughts on the tradeoff here. |
It looks like
The problem I see with current approach is that the logic is scattered around constraint generator. I think regular expressions are better off not knowing whether they are a part of the chain or not. Having everything in one spot IMO going to help maintenance in the long run even if constraint walker has to lookahead to determine if expression is included in the chain. Just to clarify what I'm thinking - if constraint walker encounters an expression which could be a tail of the chain it would spin a separate walker to determine whether there is a chain from this member or any other sub-member, record such chains so all of the intermediate members could avoid the check, this is effectively a depth-first search of chains from a possible tail. |
Makes total sense. It's been a bit since I've touched this implementation but give me a bit and I'll see where your suggestions take me. Thank you! |
@Jumhyn No worries! Please re-request the review when you are ready otherwise there is a high chance that I'm going to loose track of this again :/ |
@xedin Will do! I realized we're also using (Also, FWIW, I don't think I can re-request your review until your current review request is cleared—looks like just a comment doesn't reset that state) |
Sorry I forgot to mention that in the review - I think this check is obsolete because diagnostics no longer re-typecheck sub-expressions. |
@xedin Unfortunately it's used for more than just the diagnostic—I thought that too initially, but we actually use |
Hm, I think we might be able to extract that from |
Ooh, nice. That looks promising. Thank you for your help! |
@xedin Unrelated to the above, it looks like some changes have gone in since I last worked on this that makes my model for the member chain constraints not quite work. When we generate the constraints for an expression like,
the current implementation here ends up with four type variables:
In the constraint system, the type variables actually associated with each expression looks like:
The extra level of indirection was introduced to handle the fact that a) the base of the chain can be an optional of the result type of the final member of the chain and b) the result of the entire chain might be an lvalue even though the base isn't. However, the fact that
The fact that Do you have any thoughts about a path forward? |
d4e137b
to
8aa3bac
Compare
We previously were not properly handling the diagnostics for using an rvalue implicit member on the left hand side of an assignment. This adds the proper handling and extends it for member chains.
Since the user can now write additional member accesses off of an UnresolvedMemberExpr, we should offer all available completions rather than just those that match the contextual type.
Instead, an expresison like `.foo()` is represented as an `UnresolvedMemberExpr` nested inside a `CallExpr`.
4ababaf
to
0af9cb4
Compare
@rintaro (or anyone) mind running tests again? Just rebased to fix failures. |
@swift-ci Please test |
Build failed |
Build failed |
0af9cb4
to
8905f97
Compare
@rintaro Once more? 😇 |
@swift-ci Please test |
Build failed |
Build failed |
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.
Looks good to me, turns out that idea to introduce an implicit result AST node paid off in more than one way!
@swift-ci please test source compatibility |
Build failed |
Hmm, the macOS failure seems like a flake to me, could someone with more familiarity with the CI confirm and kick off another run? |
@swift-ci please test macOS platform |
@swift-ci please test Windows platform |
Thank you Frederick! |
Thank you, @Jumhyn and happy to help! |
This is the implementation for a forthcoming proposal (see this discussion thread) that extends implicit member syntax to allow additional member accesses chained off of the base
UnresolvedMemberExpr
.Resolves: rdar://problem/57295228