Sorry the title is a mouthful.
Basically this is what I'm trying to do:
@propertyWrapper @dynamicMemberLookup
struct Wrapper<Wrapped> {
var wrappedValue: Wrapped
subscript<Member>(
dynamicMember keyPath: WritableKeyPath<Wrapped, Member>
) -> Wrapper<Member> {
get {
Wrapper(wrappedValue: wrappedValue[keyPath: keyPath])
// error: cannot convert return expression of type 'Wrapper<Wrapped>' to return type 'Wrapper<Member>'
// error: cannot convert value of type 'Member' to expected argument type 'Wrapped'
// fix-it: Insert ' as! Wrapped'
}
set { wrappedValue[keyPath: keyPath] = newValue.wrappedValue }
}
}
I think what I'm trying to do should be possible, because SwiftUI does it. Starting from 38:04 in the talk "Modern Swift API Design" from WWDC 2019, it shows that SwiftUI's Binding's dynamic member lookup returns a new Binding:
@propertyWrapper @dynamicMemberLookup
public struct Binding<Value> {
...
public subscript<Property>(
dynamicMember keyPath: WritableKeyPath<Value, Property>
) -> Binding<Property> {
...
}
}
What am I doing wrong?
xAlien95
(Stefano De Carolis)
2
Weird, if you explicitly add the type parameter it compiles, right?
@propertyWrapper @dynamicMemberLookup
struct Wrapper<Wrapped> {
var wrappedValue: Wrapped
subscript<Member>(
dynamicMember keyPath: WritableKeyPath<Wrapped, Member>
) -> Wrapper<Member> {
// here: use Wrapper<Member> explicitly
get { Wrapper<Member>(wrappedValue: wrappedValue[keyPath: keyPath]) }
set { wrappedValue[keyPath: keyPath] = newValue.wrappedValue }
}
}
Lantua
5
For generic declarations, using the self type (Wrapper) or its member type (Wrapper.XXX) infers the generic parameter (Wrapped) to match. This has pretty high priority and usually cause the typechecking to fail when the generic parameter is meant to be something else, Wrapped <~ Member in this case.
Can't remember if it's intentional, though.
1 Like
It seems that this is the case for all generic types, not just generic property wrappers:
@dynamicMemberLookup
struct T<Foo> {
let foo: Foo
subscript<Bar>(
dynamicMember keyPath: WritableKeyPath<Foo, Bar>
) -> T<Bar> {
T(foo: foo[keyPath: keyPath])
// error: Cannot convert return expression of type 'T<Foo>' to return type 'T<Bar>'
// error: Cannot convert value of type 'Bar' to expected argument type 'Foo'
// fix-it: Insert ' as! Foo'
}
}
This is interesting. Is there a benefit from the high priority?
Lantua
7
I'm sure I asked the same question years back, if only I could find it... IIRC, it was due to how this inferred default got applied before anything else in the typechecker.
My personal take is that you need to be watchful anyway if you're in a context with Wrapped, but Wrapped means something else (Member).
Lantua
8
To illustrate my point:
struct Foo<Value> {
var value: Value
typealias Member = Binding<Value>
// Error
func fail1() -> Foo<Int> { Foo(value: 3) }
func fail2() -> Foo<Int>.Member { Member.constant(3) }
// Ok
func ok1() -> Foo<Int> { Foo<Int>(value: 3) }
func ok2() -> Foo<Int>.Member {Foo<Int>.Member.constant(3)}
}
That said, you can get away pretty easily if you avoid using the name Wrapper:
get { .init(wrappedValue: wrappedValue[keyPath: keyPath]) }
Thanks! The example makes it much easier to understand what is going on. So basically, everywhere in Foo<Value> itself, Foo is always interpreted as Self. This seems really like a bug to me.
ole
(Ole Begemann)
10
This issue was previously discussed in Using the bare name of a generic type within itself.
Money quote from @Douglas_Gregor:
It seemed like a nice convenience when I cargo-culture it years ago (we didn’t have the general Self at the time). I’d love to phase this out of the language.
— https://forums.swift.org/t/using-the-bare-name-of-a-generic-type-within-itself/24778/26
3 Likes