Skip to content

Commit afe06f4

Browse files
committed
Observation and associated macros
1 parent 8a7f600 commit afe06f4

28 files changed

+1050
-1001
lines changed

include/swift/Threading/Impl/Darwin.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ inline tls_key_t tls_get_key(tls_key k) {
238238
return __PTK_FRAMEWORK_SWIFT_KEY4;
239239
case tls_key::concurrency_fallback:
240240
return __PTK_FRAMEWORK_SWIFT_KEY5;
241+
case tls_key::observation_transaction:
242+
return __PTK_FRAMEWORK_SWIFT_KEY6;
241243
}
242244
}
243245

include/swift/Threading/TLSKeys.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ enum class tls_key {
2121
compatibility50,
2222
concurrency_task,
2323
concurrency_executor_tracking_info,
24-
concurrency_fallback
24+
concurrency_fallback,
25+
observation_transaction
2526
};
2627

2728
} // namespace swift

lib/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ add_subdirectory(Immediate)
3535
add_subdirectory(IRGen)
3636
add_subdirectory(LLVMPasses)
3737
add_subdirectory(Localization)
38+
add_subdirectory(Macros)
3839
add_subdirectory(Markup)
3940
add_subdirectory(Migrator)
4041
add_subdirectory(Option)

lib/Macros/CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#===--- CMakeLists.txt - Macro support libraries ------------------------===#
2+
#
3+
# This source file is part of the Swift.org open source project
4+
#
5+
# Copyright (c) 2023 Apple Inc. and the Swift project authors
6+
# Licensed under Apache License v2.0 with Runtime Library Exception
7+
#
8+
# See https://swift.org/LICENSE.txt for license information
9+
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
#
11+
#===----------------------------------------------------------------------===#
12+
13+
add_subdirectory(Sources/ObservationMacros)
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#===--- CMakeLists.txt - Observation macros library ----------------------===#
2+
#
3+
# This source file is part of the Swift.org open source project
4+
#
5+
# Copyright (c) 2023 Apple Inc. and the Swift project authors
6+
# Licensed under Apache License v2.0 with Runtime Library Exception
7+
#
8+
# See https://swift.org/LICENSE.txt for license information
9+
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
#
11+
#===----------------------------------------------------------------------===#
12+
13+
if (SWIFT_SWIFT_PARSER)
14+
add_library(ObservationMacros SHARED
15+
ObservableMacro.swift)
16+
17+
set_target_properties(ObservationMacros
18+
PROPERTIES
19+
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/swift/host/plugins"
20+
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/swift/host/plugins"
21+
)
22+
23+
target_compile_options(ObservationMacros PRIVATE
24+
$<$<COMPILE_LANGUAGE:Swift>:-runtime-compatibility-version>
25+
$<$<COMPILE_LANGUAGE:Swift>:none>)
26+
27+
# Set the appropriate target triple.
28+
if(SWIFT_HOST_VARIANT_SDK IN_LIST SWIFT_DARWIN_PLATFORMS)
29+
set(DEPLOYMENT_VERSION "${SWIFT_SDK_${SWIFT_HOST_VARIANT_SDK}_DEPLOYMENT_VERSION}")
30+
endif()
31+
32+
if(SWIFT_HOST_VARIANT_SDK STREQUAL ANDROID)
33+
set(DEPLOYMENT_VERSION ${SWIFT_ANDROID_API_LEVEL})
34+
endif()
35+
36+
get_target_triple(target target_variant "${SWIFT_HOST_VARIANT_SDK}" "${SWIFT_HOST_VARIANT_ARCH}"
37+
MACCATALYST_BUILD_FLAVOR ""
38+
DEPLOYMENT_VERSION "${DEPLOYMENT_VERSION}")
39+
40+
target_compile_options(ObservationMacros PRIVATE $<$<COMPILE_LANGUAGE:Swift>:-target;${target}>)
41+
42+
# Workaround a cmake bug, see the corresponding function in swift-syntax
43+
function(force_target_macros_link_libraries TARGET)
44+
cmake_parse_arguments(ARGS "" "" "PUBLIC" ${ARGN})
45+
46+
foreach(DEPENDENCY ${ARGS_PUBLIC})
47+
target_link_libraries(${TARGET} PRIVATE
48+
${DEPENDENCY}
49+
)
50+
add_dependencies(${TARGET} ${DEPENDENCY})
51+
52+
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/forced-${DEPENDENCY}-dep.swift
53+
COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/forced-${DEPENDENCY}-dep.swift
54+
DEPENDS ${DEPENDENCY}
55+
)
56+
target_sources(${TARGET} PRIVATE
57+
${CMAKE_CURRENT_BINARY_DIR}/forced-${DEPENDENCY}-dep.swift
58+
)
59+
endforeach()
60+
endfunction()
61+
62+
set(SWIFT_SYNTAX_MODULES
63+
SwiftSyntax
64+
SwiftSyntaxMacros
65+
)
66+
67+
# Compute the list of SwiftSyntax targets
68+
list(TRANSFORM SWIFT_SYNTAX_MODULES PREPEND "SwiftSyntax::"
69+
OUTPUT_VARIABLE SWIFT_SYNTAX_TARGETS)
70+
71+
# TODO: Change to target_link_libraries when cmake is fixed
72+
force_target_link_libraries(ObservationMacros PUBLIC
73+
${SWIFT_SYNTAX_TARGETS}
74+
)
75+
76+
set(SWIFT_SYNTAX_LIBRARIES_SOURCE_DIR
77+
"${SWIFT_PATH_TO_EARLYSWIFTSYNTAX_BUILD_DIR}/lib/swift/host")
78+
set(SWIFT_SYNTAX_LIBRARIES_DEST_DIR
79+
"${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/swift/host")
80+
81+
# Determine the SwiftSyntax shared library files that were built as
82+
# part of earlyswiftsyntax.
83+
list(TRANSFORM SWIFT_SYNTAX_MODULES PREPEND ${CMAKE_SHARED_LIBRARY_PREFIX}
84+
OUTPUT_VARIABLE SWIFT_SYNTAX_SHARED_LIBRARIES)
85+
list(TRANSFORM SWIFT_SYNTAX_SHARED_LIBRARIES APPEND
86+
${CMAKE_SHARED_LIBRARY_SUFFIX}
87+
OUTPUT_VARIABLE SWIFT_SYNTAX_SHARED_LIBRARIES)
88+
89+
# Copy over all of the shared libraries from earlyswiftsyntax so they can
90+
# be found via RPATH.
91+
foreach (sharedlib ${SWIFT_SYNTAX_SHARED_LIBRARIES})
92+
add_custom_command(
93+
TARGET ObservationMacros PRE_BUILD
94+
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SWIFT_SYNTAX_LIBRARIES_SOURCE_DIR}/${sharedlib} ${SWIFT_SYNTAX_LIBRARIES_DEST_DIR}/${sharedlib}
95+
COMMENT "Copying ${sharedlib}"
96+
)
97+
endforeach()
98+
99+
# Copy all of the Swift modules from earlyswiftsyntax so they can be found
100+
# in the same relative place within the build directory as in the final
101+
# toolchain.
102+
list(TRANSFORM SWIFT_SYNTAX_MODULES APPEND ".swiftmodule"
103+
OUTPUT_VARIABLE SWIFT_SYNTAX_MODULE_DIRS)
104+
foreach(module_dir ${SWIFT_SYNTAX_MODULE_DIRS})
105+
file(GLOB module_files
106+
"${SWIFT_SYNTAX_LIBRARIES_SOURCE_DIR}/${module_dir}/*.swiftinterface")
107+
add_custom_command(
108+
TARGET ObservationMacros PRE_BUILD
109+
COMMAND ${CMAKE_COMMAND} -E make_directory ${SWIFT_SYNTAX_LIBRARIES_DEST_DIR}/${module_dir}
110+
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${module_files} ${SWIFT_SYNTAX_LIBRARIES_DEST_DIR}/${module_dir}/
111+
COMMENT "Copying ${module_dir}"
112+
)
113+
endforeach()
114+
115+
target_include_directories(ObservationMacros PUBLIC
116+
${SWIFT_SYNTAX_LIBRARIES_DEST_DIR})
117+
118+
# Ensure the install directory exists before everything gets started
119+
add_custom_command(
120+
TARGET ObservationMacros PRE_BUILD
121+
COMMAND ${CMAKE_COMMAND} -E make_directory "lib${LLVM_LIBDIR_SUFFIX}/host/plugins"
122+
)
123+
124+
swift_install_in_component(TARGETS ObservationMacros
125+
LIBRARY
126+
DESTINATION "lib${LLVM_LIBDIR_SUFFIX}/host/plugins"
127+
COMPONENT compiler
128+
ARCHIVE
129+
DESTINATION "lib${LLVM_LIBDIR_SUFFIX}/host/plugins"
130+
COMPONENT compiler)
131+
132+
set_property(GLOBAL APPEND PROPERTY SWIFT_EXPORTS ObservationMacros)
133+
134+
endif()
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import SwiftSyntax
2+
import SwiftSyntaxMacros
3+
4+
@_implementationOnly import SwiftDiagnostics
5+
@_implementationOnly import SwiftOperators
6+
@_implementationOnly import SwiftSyntaxBuilder
7+
8+
private extension DeclSyntaxProtocol {
9+
var isObservableStoredProperty: Bool {
10+
guard let property = self.as(VariableDeclSyntax.self),
11+
let binding = property.bindings.first
12+
else {
13+
return false
14+
}
15+
16+
return binding.accessor == nil
17+
}
18+
}
19+
20+
public struct ObservableMacro: MemberMacro, MemberAttributeMacro, ConformanceMacro {
21+
// MARK: - ConformanceMacro
22+
public static func expansion<
23+
Declaration: DeclGroupSyntax,
24+
Context: MacroExpansionContext
25+
>(
26+
of node: AttributeSyntax,
27+
providingConformancesOf declaration: Declaration,
28+
in context: Context
29+
) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {
30+
let protocolName: TypeSyntax = "Observable"
31+
return [(protocolName, nil)]
32+
}
33+
34+
// MARK: - MemberMacro
35+
public static func expansion<
36+
Declaration: DeclGroupSyntax,
37+
Context: MacroExpansionContext
38+
>(
39+
of node: AttributeSyntax,
40+
providingMembersOf declaration: Declaration,
41+
in context: Context
42+
) throws -> [DeclSyntax] {
43+
guard let identified = declaration.asProtocol(IdentifiedDeclSyntax.self) else {
44+
return []
45+
}
46+
47+
let parentName = identified.identifier
48+
49+
let registrar: DeclSyntax =
50+
"""
51+
let _registrar = ObservationRegistrar<\(parentName)>()
52+
"""
53+
54+
let transactions: DeclSyntax =
55+
"""
56+
public nonisolated func transactions<Delivery>(for keyPaths: KeyPaths<\(parentName)>, isolation: Delivery) -> ObservedTransactions<\(parentName), Delivery> where Delivery: Actor {
57+
_registrar.transactions(for: keyPaths, isolation: isolation)
58+
}
59+
"""
60+
61+
let changes: DeclSyntax =
62+
"""
63+
public nonisolated func changes<Member>(for keyPath: KeyPath<\(parentName), Member>) -> ObservedChanges<\(parentName), Member> where Member: Sendable {
64+
_registrar.changes(for: keyPath)
65+
}
66+
"""
67+
68+
let memberList = MemberDeclListSyntax(
69+
declaration.members.members.filter {
70+
$0.decl.isObservableStoredProperty
71+
}
72+
)
73+
74+
let storageStruct: DeclSyntax =
75+
"""
76+
private struct _Storage {
77+
\(memberList)
78+
}
79+
"""
80+
81+
let storage: DeclSyntax =
82+
"""
83+
private var _storage = _Storage()
84+
"""
85+
86+
return [
87+
registrar,
88+
transactions,
89+
changes,
90+
storageStruct,
91+
storage,
92+
]
93+
}
94+
95+
// MARK: - MemberAttributeMacro
96+
97+
public static func expansion<
98+
Declaration: DeclGroupSyntax,
99+
MemberDeclaration: DeclSyntaxProtocol,
100+
Context: MacroExpansionContext
101+
>(
102+
of node: AttributeSyntax,
103+
attachedTo declaration: Declaration,
104+
providingAttributesFor member: MemberDeclaration,
105+
in context: Context
106+
) throws -> [AttributeSyntax] {
107+
guard member.isObservableStoredProperty else {
108+
return []
109+
}
110+
111+
return [
112+
AttributeSyntax(
113+
attributeName: SimpleTypeIdentifierSyntax(
114+
name: .identifier("ObservableProperty")
115+
)
116+
)
117+
]
118+
}
119+
}
120+
121+
public struct ObservablePropertyMacro: AccessorMacro {
122+
public static func expansion<
123+
Context: MacroExpansionContext,
124+
Declaration: DeclSyntaxProtocol
125+
>(
126+
of node: AttributeSyntax,
127+
providingAccessorsOf declaration: Declaration,
128+
in context: Context
129+
) throws -> [AccessorDeclSyntax] {
130+
guard let property = declaration.as(VariableDeclSyntax.self),
131+
let binding = property.bindings.first,
132+
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
133+
binding.accessor == nil
134+
else {
135+
return []
136+
}
137+
138+
if identifier.text == "_registrar" || identifier.text == "_storage" { return [] }
139+
140+
let getAccessor: AccessorDeclSyntax =
141+
"""
142+
get {
143+
_registrar.access(self, keyPath: \\.\(identifier))
144+
return _storage.\(identifier)
145+
}
146+
"""
147+
148+
let setAccessor: AccessorDeclSyntax =
149+
"""
150+
set {
151+
_registrar.withMutation(of: self, keyPath: \\.\(identifier)) {
152+
_storage.\(identifier) = newValue
153+
}
154+
}
155+
"""
156+
157+
return [getAccessor, setAccessor]
158+
}
159+
}

stdlib/public/Observation/CMakeLists.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#===--- CMakeLists.txt - Reflection support library ---------------------===#
1+
#===--- CMakeLists.txt - Observation support library ---------------------===#
22
#
33
# This source file is part of the Swift.org open source project
44
#
@@ -10,5 +10,4 @@
1010
#
1111
#===----------------------------------------------------------------------===#
1212

13-
add_subdirectory(Sources/_ObservationRuntime)
1413
add_subdirectory(Sources/Observation)

0 commit comments

Comments
 (0)