Type Checking Macro Arguments Against Declarations

I'm trying to implement SwiftUI's @Environment as a macro (similar to this question). Here's the property wrapper API I am trying to replicate:

@propertyWrapper
public struct Environment<Value>: DynamicProperty {
    public init(_ keyPath: KeyPath<EnvironmentValues, Value>)

    public var wrappedValue: Value { get }
}

So I tried using an accessor macro that provides the getter and a peer macro that generates the Environment<Value> storage variable:

@attached(accessor)
@attached(peer, names: prefixed(_))
public macro Environment<V>(_ keyPath: _const KeyPath<EnvironmentValues, V>) = ...

The problem is that clients can invoke the macro with a key path whose value type is, say, Int, yet the variable declaration can have String type. For instance:

extension EnvironmentValues {
  var myInt: Int { 0 }
}
struct MyType {
  @Environment(\.myInt) var myString: String // no errors here
}

Obviously, clients get an error in the expanded macro code, but that's a really bad UX. Ideally, I'd like to have Swift type-check the given key-path literal to ensure its value matches the variable declaration's type. Otherwise, I'd like a way for my macro to check for type equality between the key-path value and the variable declaration. I think the latter was proposed here.

So I wonder if there's an existing way/hack to get unambiguous type-checking on the declaration itself, or if I have to wait for features like context.getType(of:). Thanks in advance!

The hack I use may not meet your requirements, but it can at least convey some designated messages to the users when they get wrong:

  1. prepare 2 overloaded dummy functions.
func _secretTypeCheck<T>(_ lhs: KeyPath<EnvironmentValues, T>, _ rhs: T.Type) {
}

// put the message here
@available(*, unavailable, message: "Environment macro is attached to a property of a wrong type!")
func _secretTypeCheck<T, S>(_ lhs: KeyPath<EnvironmentValues, T>, _ rhs: S.Type) {
}
  1. invoke that function in the expanded code:
struct MyType {

  var myString: String {
    _secretTypeCheck(\.myInt, String.self)

    /* ... actual code follows ... */
  }
}
3 Likes

Thanks for your reply! Yeah, I mean this approach provides a good diagnostic message, it’s just not localized since it’s in the generated macro code. I also wonder if this type-overload resolution negatively impacts compile time.