Skip to content

Commit 003b335

Browse files
authored
[Observation] Switch Observable to be a non-marker protocol (swiftlang#66993)
With support for redundant conformance declarations via macros, the `Observable` protocol can be a non-marker protocol, which provides more flexibility for evolution in the future. rdar://111463883 This change also switches to the new ExtensionMacro protocol, the requirement for which includes information about whether the conformance to the Observable protocol has already been added, either in the declaration or in a superclass to the macro-attributed type. This allows the @observable macro to be applied to subclasses of observable types without redundant-conformance errors.
1 parent 3a0b726 commit 003b335

File tree

6 files changed

+36
-36
lines changed

6 files changed

+36
-36
lines changed

lib/Macros/Sources/ObservationMacros/Extensions.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ extension VariableDeclSyntax {
5858
guard let decl = accessor.as(AccessorDeclSyntax.self) else {
5959
return nil
6060
}
61-
if predicate(decl.accessorKind.tokenKind) {
61+
if predicate(decl.accessorSpecifier.tokenKind) {
6262
return decl
6363
} else {
6464
return nil
@@ -85,7 +85,7 @@ extension VariableDeclSyntax {
8585

8686

8787
var isImmutable: Bool {
88-
return bindingKeyword.tokenKind == .keyword(.let)
88+
return bindingSpecifier.tokenKind == .keyword(.let)
8989
}
9090

9191
func isEquivalent(to other: VariableDeclSyntax) -> Bool {
@@ -198,9 +198,9 @@ extension FunctionDeclSyntax {
198198
var signatureStandin: SignatureStandin {
199199
var parameters = [String]()
200200
for parameter in signature.input.parameterList {
201-
parameters.append(parameter.firstName.text + ":" + (parameter.type.genericSubstitution(genericParameterClause?.genericParameterList) ?? "" ))
201+
parameters.append(parameter.firstName.text + ":" + (parameter.type.genericSubstitution(genericParameterClause?.parameters) ?? "" ))
202202
}
203-
let returnType = signature.output?.returnType.genericSubstitution(genericParameterClause?.genericParameterList) ?? "Void"
203+
let returnType = signature.output?.returnType.genericSubstitution(genericParameterClause?.parameters) ?? "Void"
204204
return SignatureStandin(isInstance: isInstance, identifier: identifier.text, parameters: parameters, returnType: returnType)
205205
}
206206

lib/Macros/Sources/ObservationMacros/ObservableMacro.swift

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public struct ObservableMacro {
7171
static var ignoredAttribute: AttributeSyntax {
7272
AttributeSyntax(
7373
leadingTrivia: .space,
74-
atSignToken: .atSignToken(),
74+
atSign: .atSignToken(),
7575
attributeName: SimpleTypeIdentifierSyntax(name: .identifier(ignoredMacroName)),
7676
trailingTrivia: .space
7777
)
@@ -173,12 +173,14 @@ extension PatternBindingListSyntax {
173173
}
174174

175175
extension VariableDeclSyntax {
176-
func privatePrefixed(_ prefix: String, addingAttribute attribute: AttributeSyntax) -> VariableDeclSyntax {
177-
VariableDeclSyntax(
176+
func privatePrefixed(_ prefix: String, addingAttribute attribute: AttributeSyntax) -> VariableDeclSyntax {
177+
let newAttributes = AttributeListSyntax(
178+
(attributes.map(Array.init) ?? []) + [.attribute(attribute)])
179+
return VariableDeclSyntax(
178180
leadingTrivia: leadingTrivia,
179-
attributes: attributes?.appending(.attribute(attribute)) ?? [.attribute(attribute)],
181+
attributes: newAttributes,
180182
modifiers: modifiers?.privatePrefixed(prefix) ?? ModifierListSyntax(keyword: .private),
181-
bindingKeyword: TokenSyntax(bindingKeyword.tokenKind, leadingTrivia: .space, trailingTrivia: .space, presence: .present),
183+
bindingSpecifier: TokenSyntax(bindingSpecifier.tokenKind, leadingTrivia: .space, trailingTrivia: .space, presence: .present),
182184
bindings: bindings.privatePrefixed(prefix),
183185
trailingTrivia: trailingTrivia
184186
)
@@ -265,30 +267,27 @@ extension ObservableMacro: MemberAttributeMacro {
265267
}
266268
}
267269

268-
extension ObservableMacro: ConformanceMacro {
269-
public static func expansion<Declaration: DeclGroupSyntax, Context: MacroExpansionContext>(
270+
extension ObservableMacro: ExtensionMacro {
271+
public static func expansion(
270272
of node: AttributeSyntax,
271-
providingConformancesOf declaration: Declaration,
272-
in context: Context
273-
) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {
274-
let inheritanceList: InheritedTypeListSyntax?
275-
if let classDecl = declaration.as(ClassDeclSyntax.self) {
276-
inheritanceList = classDecl.inheritanceClause?.inheritedTypeCollection
277-
} else if let structDecl = declaration.as(StructDeclSyntax.self) {
278-
inheritanceList = structDecl.inheritanceClause?.inheritedTypeCollection
279-
} else {
280-
inheritanceList = nil
281-
}
282-
283-
if let inheritanceList {
284-
for inheritance in inheritanceList {
285-
if inheritance.typeName.identifier == ObservableMacro.conformanceName {
286-
return []
287-
}
288-
}
273+
attachedTo declaration: some DeclGroupSyntax,
274+
providingExtensionsOf type: some TypeSyntaxProtocol,
275+
conformingTo protocols: [TypeSyntax],
276+
in context: some MacroExpansionContext
277+
) throws -> [ExtensionDeclSyntax] {
278+
// This method can be called twice - first with an empty `protocols` when
279+
// no conformance is needed, and second with a `MissingTypeSyntax` instance.
280+
if protocols.isEmpty {
281+
return []
289282
}
290-
291-
return [(ObservableMacro.observableConformanceType, nil)]
283+
284+
let decl: DeclSyntax = """
285+
extension \(raw: type.trimmedDescription): \(raw: qualifiedConformanceName) {}
286+
"""
287+
288+
return [
289+
decl.cast(ExtensionDeclSyntax.self)
290+
]
292291
}
293292
}
294293

stdlib/public/Observation/Sources/Observation/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ add_swift_target_library(swiftObservation ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS
2424
SWIFT_COMPILE_FLAGS
2525
${SWIFT_STANDARD_LIBRARY_SWIFT_FLAGS}
2626
"-enable-experimental-feature" "Macros"
27+
"-enable-experimental-feature" "ExtensionMacros"
2728
-Xfrontend -disable-implicit-string-processing-module-import
2829

2930
C_COMPILE_FLAGS

stdlib/public/Observation/Sources/Observation/Observable.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
/// the ``Observation/Observable-swift.macro`` macro when adding observation
1919
/// support to a type.
2020
@available(SwiftStdlib 5.9, *)
21-
@_marker public protocol Observable { }
21+
public protocol Observable { }
2222

2323
#if $Macros && hasAttribute(attached)
2424

@@ -46,7 +46,7 @@
4646
@attached(member, names: named(_$observationRegistrar), named(access), named(withMutation), arbitrary)
4747
#endif
4848
@attached(memberAttribute)
49-
@attached(conformance)
49+
@attached(extension, conformances: Observable)
5050
public macro Observable() =
5151
#externalMacro(module: "ObservationMacros", type: "ObservableMacro")
5252

test/stdlib/Observation/Observable.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// REQUIRES: swift_swift_parser, executable_test
22

3-
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -parse-as-library -enable-experimental-feature InitAccessors -enable-experimental-feature Macros -Xfrontend -plugin-path -Xfrontend %swift-host-lib-dir/plugins)
3+
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -parse-as-library -enable-experimental-feature InitAccessors -enable-experimental-feature Macros -enable-experimental-feature ExtensionMacros -Xfrontend -plugin-path -Xfrontend %swift-host-lib-dir/plugins)
44

55
// Run this test via the swift-plugin-server
6-
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -parse-as-library -enable-experimental-feature InitAccessors -enable-experimental-feature Macros -Xfrontend -external-plugin-path -Xfrontend %swift-host-lib-dir/plugins#%swift-plugin-server)
6+
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -parse-as-library -enable-experimental-feature InitAccessors -enable-experimental-feature Macros -enable-experimental-feature ExtensionMacros -Xfrontend -external-plugin-path -Xfrontend %swift-host-lib-dir/plugins#%swift-plugin-server)
77

88
// Asserts is required for '-enable-experimental-feature InitAccessors'.
99
// REQUIRES: asserts

test/stdlib/Observation/ObservableDidSetWillSet.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// REQUIRES: swift_swift_parser, executable_test
22

3-
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -enable-experimental-feature InitAccessors -enable-experimental-feature Macros -Xfrontend -plugin-path -Xfrontend %swift-host-lib-dir/plugins) | %FileCheck %s
3+
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -enable-experimental-feature InitAccessors -enable-experimental-feature Macros -enable-experimental-feature ExtensionMacros -Xfrontend -plugin-path -Xfrontend %swift-host-lib-dir/plugins) | %FileCheck %s
44

55
// Asserts is required for '-enable-experimental-feature InitAccessors'.
66
// REQUIRES: asserts

0 commit comments

Comments
 (0)