Skip to content

Commit d37b6bc

Browse files
authored
[Observation] Reduce observation scope to only apply to tracking cases and re-work the macro to utilize more robust emissions (#65528)
1 parent 1bf9424 commit d37b6bc

13 files changed

+997
-1315
lines changed

lib/Macros/Sources/ObservationMacros/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
add_swift_macro_library(ObservationMacros
1414
ObservableMacro.swift
15+
Extensions.swift
1516
SWIFT_DEPENDENCIES
1617
SwiftSyntax::SwiftDiagnostics
1718
SwiftSyntax::SwiftOperators
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
//===----------------------------------------------------------------------===//
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+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
import SwiftSyntax
13+
import SwiftSyntaxMacros
14+
15+
@_implementationOnly import SwiftDiagnostics
16+
@_implementationOnly import SwiftOperators
17+
@_implementationOnly import SwiftSyntaxBuilder
18+
19+
extension VariableDeclSyntax {
20+
var identifierPattern: IdentifierPatternSyntax? {
21+
bindings.first?.pattern.as(IdentifierPatternSyntax.self)
22+
}
23+
24+
var isInstance: Bool {
25+
if let modifiers {
26+
for modifier in modifiers {
27+
for token in modifier.tokens(viewMode: .all) {
28+
if token.tokenKind == .keyword(.static) || token.tokenKind == .keyword(.class) {
29+
return false
30+
}
31+
}
32+
}
33+
}
34+
return true
35+
}
36+
37+
var identifier: TokenSyntax? {
38+
identifierPattern?.identifier
39+
}
40+
41+
var type: TypeSyntax? {
42+
bindings.first?.typeAnnotation?.type
43+
}
44+
45+
func accessorsMatching(_ predicate: (TokenKind) -> Bool) -> [AccessorDeclSyntax] {
46+
let patternBindings = bindings.compactMap { binding in
47+
binding.as(PatternBindingSyntax.self)
48+
}
49+
let accessors: [AccessorListSyntax.Element] = patternBindings.compactMap { patternBinding in
50+
switch patternBinding.accessor {
51+
case .accessors(let accessors):
52+
return accessors
53+
default:
54+
return nil
55+
}
56+
}.flatMap { $0.accessors }
57+
return accessors.compactMap { accessor in
58+
guard let decl = accessor.as(AccessorDeclSyntax.self) else {
59+
return nil
60+
}
61+
if predicate(decl.accessorKind.tokenKind) {
62+
return decl
63+
} else {
64+
return nil
65+
}
66+
}
67+
}
68+
69+
var willSetAccessors: [AccessorDeclSyntax] {
70+
accessorsMatching { $0 == .keyword(.willSet) }
71+
}
72+
var didSetAccessors: [AccessorDeclSyntax] {
73+
accessorsMatching { $0 == .keyword(.didSet) }
74+
}
75+
76+
var isComputed: Bool {
77+
if accessorsMatching({ $0 == .keyword(.get) }).count > 0 {
78+
return true
79+
} else {
80+
return bindings.compactMap { binding in
81+
binding.as(PatternBindingSyntax.self)?.accessor?.as(CodeBlockSyntax.self)
82+
}.count > 0
83+
}
84+
}
85+
86+
87+
var isImmutable: Bool {
88+
return bindingKeyword.tokenKind == .keyword(.let)
89+
}
90+
91+
func isEquivalent(to other: VariableDeclSyntax) -> Bool {
92+
if isInstance != other.isInstance {
93+
return false
94+
}
95+
return identifier?.text == other.identifier?.text
96+
}
97+
98+
var initializer: InitializerClauseSyntax? {
99+
bindings.first?.initializer
100+
}
101+
102+
func hasMacroApplication(_ name: String) -> Bool {
103+
guard let attributes else { return false }
104+
for attribute in attributes {
105+
switch attribute {
106+
case .attribute(let attr):
107+
if attr.attributeName.tokens(viewMode: .all).map({ $0.tokenKind }) == [.identifier(name)] {
108+
return true
109+
}
110+
default:
111+
break
112+
}
113+
}
114+
return false
115+
}
116+
}
117+
118+
extension TypeSyntax {
119+
var identifier: String? {
120+
for token in tokens(viewMode: .all) {
121+
switch token.tokenKind {
122+
case .identifier(let identifier):
123+
return identifier
124+
default:
125+
break
126+
}
127+
}
128+
return nil
129+
}
130+
131+
func genericSubstitution(_ parameters: GenericParameterListSyntax?) -> String? {
132+
var genericParameters = [String : TypeSyntax?]()
133+
if let parameters {
134+
for parameter in parameters {
135+
genericParameters[parameter.name.text] = parameter.inheritedType
136+
}
137+
}
138+
var iterator = self.asProtocol(TypeSyntaxProtocol.self).tokens(viewMode: .sourceAccurate).makeIterator()
139+
guard let base = iterator.next() else {
140+
return nil
141+
}
142+
143+
if let genericBase = genericParameters[base.text] {
144+
if let text = genericBase?.identifier {
145+
return "some " + text
146+
} else {
147+
return nil
148+
}
149+
}
150+
var substituted = base.text
151+
152+
while let token = iterator.next() {
153+
switch token.tokenKind {
154+
case .leftAngle:
155+
substituted += "<"
156+
case .rightAngle:
157+
substituted += ">"
158+
case .comma:
159+
substituted += ","
160+
case .identifier(let identifier):
161+
let type: TypeSyntax = "\(raw: identifier)"
162+
guard let substituedType = type.genericSubstitution(parameters) else {
163+
return nil
164+
}
165+
substituted += substituedType
166+
break
167+
default:
168+
// ignore?
169+
break
170+
}
171+
}
172+
173+
return substituted
174+
}
175+
}
176+
177+
extension FunctionDeclSyntax {
178+
var isInstance: Bool {
179+
if let modifiers {
180+
for modifier in modifiers {
181+
for token in modifier.tokens(viewMode: .all) {
182+
if token.tokenKind == .keyword(.static) || token.tokenKind == .keyword(.class) {
183+
return false
184+
}
185+
}
186+
}
187+
}
188+
return true
189+
}
190+
191+
struct SignatureStandin: Equatable {
192+
var isInstance: Bool
193+
var identifier: String
194+
var parameters: [String]
195+
var returnType: String
196+
}
197+
198+
var signatureStandin: SignatureStandin {
199+
var parameters = [String]()
200+
for parameter in signature.input.parameterList {
201+
parameters.append(parameter.firstName.text + ":" + (parameter.type.genericSubstitution(genericParameterClause?.genericParameterList) ?? "" ))
202+
}
203+
let returnType = signature.output?.returnType.genericSubstitution(genericParameterClause?.genericParameterList) ?? "Void"
204+
return SignatureStandin(isInstance: isInstance, identifier: identifier.text, parameters: parameters, returnType: returnType)
205+
}
206+
207+
func isEquivalent(to other: FunctionDeclSyntax) -> Bool {
208+
return signatureStandin == other.signatureStandin
209+
}
210+
}
211+
212+
extension DeclGroupSyntax {
213+
var memberFunctionStandins: [FunctionDeclSyntax.SignatureStandin] {
214+
var standins = [FunctionDeclSyntax.SignatureStandin]()
215+
for member in memberBlock.members {
216+
if let function = member.as(MemberDeclListItemSyntax.self)?.decl.as(FunctionDeclSyntax.self) {
217+
standins.append(function.signatureStandin)
218+
}
219+
}
220+
return standins
221+
}
222+
223+
func hasMemberFunction(equvalentTo other: FunctionDeclSyntax) -> Bool {
224+
for member in memberBlock.members {
225+
if let function = member.as(MemberDeclListItemSyntax.self)?.decl.as(FunctionDeclSyntax.self) {
226+
if function.isEquivalent(to: other) {
227+
return true
228+
}
229+
}
230+
}
231+
return false
232+
}
233+
234+
func hasMemberProperty(equivalentTo other: VariableDeclSyntax) -> Bool {
235+
for member in memberBlock.members {
236+
if let variable = member.as(MemberDeclListItemSyntax.self)?.decl.as(VariableDeclSyntax.self) {
237+
if variable.isEquivalent(to: other) {
238+
return true
239+
}
240+
}
241+
}
242+
return false
243+
}
244+
245+
var definedVariables: [VariableDeclSyntax] {
246+
memberBlock.members.compactMap { member in
247+
if let variableDecl = member.as(MemberDeclListItemSyntax.self)?.decl.as(VariableDeclSyntax.self) {
248+
return variableDecl
249+
}
250+
return nil
251+
}
252+
}
253+
254+
func addIfNeeded(_ decl: DeclSyntax?, to declarations: inout [DeclSyntax]) {
255+
guard let decl else { return }
256+
if let fn = decl.as(FunctionDeclSyntax.self) {
257+
if !hasMemberFunction(equvalentTo: fn) {
258+
declarations.append(decl)
259+
}
260+
} else if let property = decl.as(VariableDeclSyntax.self) {
261+
if !hasMemberProperty(equivalentTo: property) {
262+
declarations.append(decl)
263+
}
264+
}
265+
}
266+
267+
var isClass: Bool {
268+
return self.is(ClassDeclSyntax.self)
269+
}
270+
271+
var isActor: Bool {
272+
return self.is(ActorDeclSyntax.self)
273+
}
274+
275+
var isEnum: Bool {
276+
return self.is(EnumDeclSyntax.self)
277+
}
278+
}

0 commit comments

Comments
 (0)