RValueAdjustment path element kind

While working on implicit member chains, I've noticed that in CSGen, visitUnresolvedMemberExpr makes use of the RValueAdjustment PathElementKind. The documentation for this kind is sparse ("RValue adjustment."), and it's only used for UnresolvedMemberExprs and DynamicTypeExprs AFAICT, so I wanted to ask if someone with more knowledge could provide more context on this kind.

@Douglas_Gregor, digging through git blame indicates that you added this kind to visitUnresolvedMemberExpr (7!) years ago, any chance you still remember what it's for? :sweat_smile:

cc @xedin who might also know more about this.

1 Like

That kind is used to indicate that equality/conversion constraint this element is associated with would always have r-value type on the right-hand side, left-hand side might be either l- or r-value. In particular, it's used in combination with UnresolvedMemberExpr because such expressions supposed to produce the same type as contextual but contextual type could be an l-value type when base type and result of the reference could only be an r-value e.g.

enum E { case foo }

func test(e: inout E) {
  if e == .foo { // although `e` is `l-value` both base and result of `.foo` are r-value types.
1 Like

Thanks @xedin, that makes sense!

@xedin Is there an appropriate construction that goes in the other direction, i.e. allows the left-hand side of a conversion to be an r-value while the right-hand side is maybe-an-l-value?

More specifically, I've been rethinking the model for UnresolvedMemberExprs which have more members hanging off of them. The current model has the member type convertible to the base type and the base type equal to the context type, which works for a single-element chain. However, straightforwardly extending this model to multi-member chains doesn't play nice with optionals, e.g.,

struct S {
    static var foo: S = S()
    var x: S { S() }

let _: S? = .foo.x

This results in a nonsense error about (non-optional) foo not being unwrapped, since its type was inferred as S?. Instead, I'd like to rework the model so that each element in the chain need only be convertible to the next member, e.g., in the above example we would have that the implicit base converts to the result of .foo, the result of .foo converts to the result of .x, the result of .x converts to the context type.

However, I'm hitting a wall with this approach since the type of the .foo member is actually @lvalue S, which doesn't allow for a conversion from S (unless I'm misunderstanding something?). What I'd like to express is "A convertible to B, ignoring any l-value-ness of B." Is such a conversion well-founded in this case?

Curious to know if this seems like a reasonable model for these chains, and if there's any other approach that you would suggest for modeling the type relationships here!

There is nothing in the language which could get from r-value to l-value type implicitly. I think only l-value -> r-value is possible by introducing an implicit load. In your example it seems like a result of .foo needs to be a base and a result of .x right? One way I see it could work is that each component should introduce a new result type variable which actual result of the component is equated to, this way other components could only chain against that r-value type similar to what ConstraintGenerator::visitKeyPathExpr does.

1 Like

Yep, that's right. That extra level of indirection is what I settled on as well after some experimentation. Glad to know that's not totally misguided. Thank you!

No, I think that's totally fine!