Opt-In Reflection metadata
- Proposal: SE-NNNN
- Authors: Max Ovtsin
- Review Manager: TBD
- Status: Pitch
- Implementation: Swift Opt-In Reflection metadata by maxovtsin · Pull Request #34199 · apple/swift · GitHub
Introduction
Reflection can be a useful thing to create convenient and concise APIs for libraries. This proposal seeks to improve the safety of such APIs and to tackle the binary size problem by introducing a mechanism to selectively enable reflection metadata only for types that need it and add a way to express a requirement of reflection metadata for APIs developers.
Motivation
Binary Size
Currently, the only available way to enable/disable reflection metadata is to do that for a whole module at once.
However, enabling reflection for a whole module can be quite expensive especially in cases when an API’s surface is quite big. There are some libraries and APIs that require reflection metadata (like SwiftUI, many JSON and DI frameworks, etc). Therefore if an application contains a lot of modules that are users of such API, all of them must have reflection metadata enabled and the compiler will emit the metadata for all of the declarations inside even if not all of them are used in the API.
Type safety
Another important reason is the current inability to express reflection metadata requirements in API.
Mirror/SwiftUI use the metadata under the hood, but they don't have an option to express that and sometimes it becomes a surprise for developers. It makes such API is less safe since it becomes a runtime issue rather than compile-time. For example, SwiftUI internal implementation uses reflection metadata to trigger re-rendering of the view hierarchy when a state has changed, but if by some reason a user module compiled with the metadata disabled, changing of the state won't trigger that behaviour and will cause inconsistency between a state and a representation.
Proposed solution
Introducing a new attribute @reflectable
for nominal types will help to solve two issues mentioned above at once.
Firstly, it will allow libraries to express a requirement to have reflection metadata for types on the source-code level which will make such APIs safer. If a declaration is marked with @reflectable
or inherits it, but the opt-in reflection metadata is not enabled, the compiler will emit a compile-time error which will make an API's user enable the feature and the compiler subsequently will generate metadata only for a type that need to have it.
Secondly, it will help to reduce the binary size overhead from the metadata that is not used at runtime.
Only classes, structs, enums, and protocols that are marked with that attribute or inherit it will get reflection metadata generated which will make the existing API more efficient.
Study Case:
SwiftUI Framework:
@reflectable
public protocol SwiftUI.View {}
User module:
import SwiftUI
struct SomeView: SwiftUI.View {
var body: some View {
Text("Hello, World!").frame(...)
}
}
struct SomeModel {}
window.contentView = NSHostingView(rootView: SomeView())
The compiler will emit reflection metadata for SomeView
because it inherits the attribute from SwiftUI.View protocol, and skip emission for SomeModel
struct. If an API's user module gets compiled without the -enable-opt-in-reflection-metadata
flag, the compiler will emit a warning.
Detailed design
One more level of reflection metadata is introduced in addition to two existing ones:
- Full Reflection metadata is enabled by default.
- Opt-In reflection metadata is enabled with a
-enable-opt-in-reflection-metadata
flag. - Reflection metadata is completely disabled with
-disable-reflection-metadata
flag.
A new attribute for nominal types @reflectable
marks nominal types for which the compiler will emit the reflection metadata. The attribute can be inherited from parents for classes and protocols.
Example 1:
@reflectable
protocol Ref {}
// Class A will inherit the attribute from the protocol conformance.
class A: Ref {}
Example 2:
@reflectable
class B {}
// Class C will inherit the protocol from the parent class.
class C: B {}
The type’s reflectability is computed during Sema and cached in the Evaluator. If a declaration is marked @reflectable
, but the reflection is disabled or enabled in full, the compiler will emit a warning. A new flag -enable-opt-in-reflection-metadata
should be used to enable that behaviour explicitly.
Source compatibility
The change won’t break source compatibility due to the purely additive nature of the change.
Effect on ABI stability
The proposal doesn’t change the existing ABI but introduces new guarantees between libraries and clients.
Effect on API resilience
Adding/removing the @reflectable
attribute won’t affect API resilience and break any existing guarantees. However, it’s still possible that some reflection metadata might be unavailable for cases when a library was updated with the new attribute, but a client wasn’t recompiled against a new version of the library.
Future direction
If this proposal is accepted, it will allow extending the Swift Standard library with a new protocol Reflectable
that will be marked with @reflectable
attribute. All existing APIs that require reflection metadata will be extended to accept objects conforming to that protocol. It will help to improve the safety of such API because the compiler will ensure that for all types, conforming to the Reflectable
protocol, the required reflection metadata is emitted.
@reflectable
public protocol Reflectable {}
public struct Mirror {
// A new constructor that takes an object conforming to Reflectable protocol.
public init(reflecting subject: Reflectable) {
...
}
}
Alternatives considered
An implementation with a magic protocol also was considered. However, It seems that an attribute will be a more generic approach in this case.