-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Add root type constraint between KeyPath expressions and applications #17094
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
include/swift/AST/Expr.h
Outdated
@@ -2300,7 +2300,7 @@ class SubscriptExpr final : public LookupExpr, | |||
return Bits.SubscriptExpr.HasArgLabelLocs; | |||
} | |||
|
|||
/// Whether this call with written with a trailing closure. | |||
/// Whether this call was written with a trailing closure. |
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 should be a separate patch - if the other fix gets reverted for whatever reason your typo fix will live on :)
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.
Submitted as #17098.
@jckarter Want to take a look? |
lib/Sema/CSSimplify.cpp
Outdated
for (auto constraint : InactiveConstraints) { | ||
if (constraint.getKind() == ConstraintKind::KeyPath && | ||
constraint.getLocator()->getAnchor() == keyPathExpr) { | ||
addConstraint(ConstraintKind::Subtype, root, constraint.getSecondType(), 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.
This typing rule seems to be duplicating the existing rule in CSGen (after drilling). Try looking at what visitUnresolvedMemberExpr
is doing.
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 afraid I don't understand which existing rule you're referring to or what specifically in visitUnresolvedMemberExpr
you're pointing me to. 😞 I'll need some more help to understand. Sorry!
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 try our best to do this in CSGen because we are trying to avoid dealing with expressions in the solver at all costs...
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.
In CSGen you don't know that this is a KeyPath application—it's just a subscript. Because this is inside the subscript overload, I don't think it can be in CSGen. Or maybe I missing something?
But it should be possible to move it out a level to ConstraintSystem::resolveOverload
if that's preferable.
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.
In CSGen you don't know that this is a KeyPath application
I think it can be done. A naive way would be to add an extra "faux root" expression node where you can stash the type variable generated for the root
expression in the KeyPath. Then in the post-walk, you can recreate this logic (minus the locator digging) and directly bind the subscript's base to the faux root. For example (don't actually do this, please clean it up):
// ConstraintWalker::walkToExprPost
if (auto subscript = dyn_cast<SubscriptExpr>(expr)) {
if (auto indexTuple = dyn_cast<TupleExpr>(subscript->getIndex())) {
if (indexTuple->getNumElements() == 1) {
if (auto keyPathExpr = dyn_cast<KeyPathExpr>(indexTuple->getElement(0))) {
auto type = CG.visit(expr);
auto &CS = CG.getConstraintSystem();
auto simplifiedType = CS.simplifyType(type);
CS.setType(expr, simplifiedType);
CS.addConstraint(ConstraintKind::Subtype,
CS.getType(subscript->getBase()),
CS.getType(keyPathExpr->getFauxRoot()),
CS.getConstraintLocator(expr));
return expr;
}
}
}
}
// ...
// ConstraintWalker::visitKeyPathExpr
case KeyPathExpr::Component::Kind::UnresolvedProperty:
E->setFauxRoot(/**/);
CS.setType(E->getFauxRoot(), root);
LLVM_FALLTHROUGH;
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.
Essentially, you've hit the nail on the head with the fix here: the constraint system we set up for unresolved member key paths doesn't actually bind the root type variable to anything useful - the value member constraint is just going to fail on an unbound type variable. Now we've just got to do some work to keep the required contextual type propagation clean.
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.
That assumes that a [keyPath: <key path>]
expression is a KeyPath application. I was assuming that shouldn't be hardcoded, but it actually is today. i.e., this fails to compile:
extension String {
subscript(keyPath: KeyPath<Int, String>) -> String {
return "🙈"
}
}
_ = ""[keyPath: \Int.description]
Since that's apparently not valid, it should be fine to add this constraint outside of the KeyPath application constraint. (Although that kinda seems like a bug to me. Why shouldn't you be able to overload a subscript that takes a KeyPath?)
Given that…
-
Is there any reason it's preferable to use
walkToExprPost
overvisitSubscriptExpr
? As long as we're going to assume that[keyPath: <key path>]
is a key path application, we might as well remove the overload and do this directly invisitSubscriptExpr
? -
Is it still preferable to create another expression instead of using the locator? Neither seem ideal to me. 😕 That would mean adding a new type like
KeyPathRootExpr
, right?
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.
Is there any reason it's preferable to use walkToExprPost over visitSubscriptExpr
I use it out of convenience. Anywhere in the walk that has access to the subscript and its children after they have had constraints generated will do.
As long as we're going to assume that [keyPath: ] is a key path application, we might as well remove the overload and do this directly in visitSubscriptExpr
No such luck in the general case
extension String {
subscript<T>(keypath a: T) -> String {
return "🙈"
}
}
_ = ""[keypath: 5]
Is it still preferable to create another expression instead of using the locator
Digging in the locators is a hack, adding a fake AST node is a hack. Neither is desirable. You shouldn't need a new type for this, the whole goal of the example I gave was that you need to stash the type variable generated for root somewhere that's accessible to the rest of CSGen.
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.
The suggestion @CodaFi made it along the lines I was thinking as well, but I'm not sure if we really need to create a faux root through, because even if we still let root allocate new type variable but then connect it to something which can inferred that would still be good enough. Another idea, but I'm not sure how feasible that would be, is to actually make it so KeyPathExpr
, when created, knows what expression it's attached to, that would be very straightforward to get its type from the constraint system cache and relate it to root in visitKeyPathExpr
.
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 still need to take a closure look at the code changes.
func sr7380() { | ||
_ = ""[keyPath: \.count] | ||
} | ||
|
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.
Can you add a tests for array and dictionary example as well, with 'let', 'var', and collection literal variants, e.g.:
let arr = [1]
_ = arr[keyPath: \.[0]]
let dic = [1:"s"]
_ = dic[keyPath: \.[1]]
and then versions for var
rather than let
, as well as ones that use collection literals rather than the named collections?
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.
Great suggestion. The var
case wasn't working; I've fixed that up.
Looks good, thanks! When I tried this originally, I had issues getting the type system to keep working in the face of |
@swift-ci Please smoke test |
@swift-ci Please test source compatibility |
lib/Sema/CSSimplify.cpp
Outdated
for (auto constraint : InactiveConstraints) { | ||
if (constraint.getKind() == ConstraintKind::KeyPath && | ||
constraint.getLocator()->getAnchor() == keyPathExpr) { | ||
auto keyPathRootTy = constraint.getSecondType(); |
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.
Collapse this
addConstraint(ConstraintKind::Subtype, root->getWithoutSpecifierType(), keyPathRootTy, locator);
b76e6f8
to
2bf8470
Compare
I added a couple tests for this. This error was even worse before:
I added some tests for |
Oh, sorry, I meant that |
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. A couple small notes of things you should take a look at.
It would be great if we didn't have to iterate over constraints to do this, but offhand I cannot think of how we would do that.
lib/Sema/CSSimplify.cpp
Outdated
if (auto indexTuple = dyn_cast<TupleExpr>(subscript->getIndex())) { | ||
if (indexTuple->getNumElements() == 1) { | ||
if (auto keyPathExpr = dyn_cast<KeyPathExpr>(indexTuple->getElement(0))) { | ||
for (auto constraint : InactiveConstraints) { |
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.
Rather than iterating over all constraints here, and checking the anchor expression, you should be able to do something like:
auto *typevar = cs.getType(keyPathExpr)->getAs<TypeVariableType>();
if (typevar) {
CG.gatherConstraints(typevar, constraints, ConstraintGraph::GatheringKind::EquivalenceClass);
// iterate over only those looking for a ConstraintKind::KeyPath where the constraint.getFirstType()->isEqual(typevar)
}
lib/Sema/CSSimplify.cpp
Outdated
path[0].getKind() == ConstraintLocator::SubscriptMember) { | ||
if (auto indexTuple = dyn_cast<TupleExpr>(subscript->getIndex())) { | ||
if (indexTuple->getNumElements() == 1) { | ||
if (auto keyPathExpr = dyn_cast<KeyPathExpr>(indexTuple->getElement(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 suspect some of the above logic could be turned into cast<>()
and assert
s based on our expectations of what we know when we decide an overload is a key path application.
For whatever remains, it might be nice to remove some of the depth of control flow via the advice in https://www.llvm.org/docs/CodingStandards.html#use-early-exits-and-continue-to-simplify-code
You could for example make this a closure with early returns, a separate function, or a:
do {
// use break in places where we bail out
} while (0);
@swift-ci Please smoke test |
@swift-ci Please test source compatibility |
1fb5037
to
4525eaf
Compare
How does that look, @rudkx? |
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.
LGTM!
@swift-ci Please smoke test |
@swift-ci Please test source compatibility |
I had to switch the check for a tuple back to a |
@swift-ci Please smoke test |
Worth cherry-picking into 4.2? |
Sounds good to me! |
This key path application was failing to resolve the key path:
This adds a constraint between the type the KeyPath application is subscripting and the base type of the KeyPath expression inside of it.
Resolves SR-7380.