// FeatureConfig.swift in module `Feature`
protocol FeatureConfig {
var flag: Bool { get }
var message: String { get }
}
// Feature.swift in module `Feature`, the type used across this module
public struct Feature {
let config: FeatureConfig
var flag: Bool { config.flag }
var message: String { config.message }
}
// FeatureMockConfig.swift in module `MockConfig`
struct FeatureMockConfig: FeatureConfig {
var flag: Bool
var message: String
}
// Somewhere in the main app
let feature = Feature(config: FeatureMockConfig())
In my case at work, we have a whole lot of those "projections" of config.* properties into the feature module like this. It would be amazing if we could generate them automatically like this or something:
public struct Feature {
let config: FeatureConfig
#features(of: config)
}
Hell I would even settle for this:
public struct Feature {
@config let config: FeatureConfig
#config(flag)
#config(message)
}
Is there no way SwiftSyntax could be modified to supply more information to macros for purposes like this?
@dynamicMemberLookup is not ideal for this; we want autocomplete to work… I also don't think that would actually eliminate as much duplication as I'd like. The following code doesn't even compile for some reason anyway:
public struct Feature {
let config: FeatureConfig
subscript<T>(dynamicMember member: String) -> T {
get {
switch member {
// Variable '<unknown>' captured by a closure before being initialized
case "flag": config.flag as! T
// Variable '<unknown>' captured by a closure before being initialized
case "message": config.message as! T
default: fatalError()
}
}
}
}
As for the macro, attaching it to the overall type should allow inspection of its properties, which may allow you to generate what you want.
Attaching it to... which type? The config, protocol, or feature?
I want to generate code on the feature. Where can I get it from?
Using the KeyPath version of @dynamicMemberLookup autocompletes just fine. And you'd attach the macro to the Feature type, which should let you inspect its properties.
Ah! I forgot about that. Thanks for clarifying, sorry :)
Attaching the macro to Feature doesn't help me here, because I want to enumerate the properties of something else. Feature doesn't have any properties of its own besides config; I want to examine either FeatureMockConfig or FeatureConfig and duplicate the properties declared in either of those decls.
As @Jon_Shier said, @dynamicMemberLookup does the job, it also autocompletes!
// FeatureConfig.swift in module `Feature`
protocol FeatureConfig {
var flag: Bool { get }
var message: String { get }
}
// Feature.swift in module `Feature`, the type used across this module
@dynamicMemberLookup
public struct Feature {
let config: any FeatureConfig
subscript<T>(dynamicMember keyPath: KeyPath<any FeatureConfig, T>) -> T {
config[keyPath: keyPath]
}
}
// FeatureMockConfig.swift in module `MockConfig`
struct FeatureMockConfig: FeatureConfig {
var flag = false
var message = "test"
}
// Somewhere in the main app
let feature = Feature(config: FeatureMockConfig())
print(feature.flag) // false
print(feature.message) // "test"
Whoa! Sick! I see now! That's awesome. Thanks y'all!
I do still wish this sort outer-decl introspection was available with macros, though As cool as this is, I suspect this won't enable satisfying requirements of a second protocol, would it? i.e.:
public protocol AFeatureDependencies {
var flag: Bool { get }
var message: String { get }
}
public strict AFeatureView: View {
typealias Dependencies = AFeatureDependencies
let dependencies: Dependencies
...
}
@dynamicMemberLookup
public struct Feature {
let config: any FeatureConfig
subscript<T>(dynamicMember keyPath: KeyPath<any FeatureConfig, T>) -> T {
config[keyPath: keyPath]
}
}
extension Feature: AFeatureView.Dependencies { }
If this works for that, then awesome! If not, problem persists
Edit: does not work, doesn't count as protocol conformance