Skip to content

Commit 21b57f7

Browse files
committed
[fix][Observation]: further attempts to resolve macro expansion
interaction with comments Adds logic to insert newlines in various places to try and resolve the fact that the current expansion produces invalid code in some cases depending on comment location. Adds some basic tests of the expansion output.
1 parent 0d1e482 commit 21b57f7

File tree

2 files changed

+87
-3
lines changed

2 files changed

+87
-3
lines changed

lib/Macros/Sources/ObservationMacros/ObservableMacro.swift

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,18 +272,33 @@ extension PatternBindingListSyntax {
272272

273273
extension VariableDeclSyntax {
274274
func privatePrefixed(_ prefix: String, addingAttribute attribute: AttributeSyntax, removingAttribute toRemove: AttributeSyntax, in context: LocalMacroExpansionContext<some MacroExpansionContext>) -> VariableDeclSyntax {
275+
var newAttribute = attribute
276+
newAttribute.leadingTrivia = .newline
277+
275278
let newAttributes = attributes.filter { attribute in
276279
switch attribute {
277280
case .attribute(let attr):
278281
attr.attributeName.identifier != toRemove.attributeName.identifier
279282
default: true
280283
}
281-
} + [.attribute(attribute)]
284+
} + [.attribute(newAttribute)]
285+
286+
var newModifiers = modifiers.privatePrefixed(prefix, in: context)
287+
let hasModifiers = !newModifiers.isEmpty
288+
if hasModifiers {
289+
newModifiers.leadingTrivia += .newline
290+
}
291+
282292
return VariableDeclSyntax(
283293
leadingTrivia: leadingTrivia,
284294
attributes: newAttributes,
285-
modifiers: modifiers.privatePrefixed(prefix, in: context),
286-
bindingSpecifier: TokenSyntax(bindingSpecifier.tokenKind, leadingTrivia: .newline, trailingTrivia: .space, presence: .present),
295+
modifiers: newModifiers,
296+
bindingSpecifier: TokenSyntax(
297+
bindingSpecifier.tokenKind,
298+
leadingTrivia: hasModifiers ? .space : .newline,
299+
trailingTrivia: .space,
300+
presence: .present
301+
),
287302
bindings: bindings.privatePrefixed(prefix, in: context),
288303
trailingTrivia: trailingTrivia
289304
)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// REQUIRES: swift_swift_parser, asserts
2+
//
3+
// UNSUPPORTED: back_deploy_concurrency
4+
// REQUIRES: concurrency
5+
// REQUIRES: observation
6+
//
7+
// RUN: %empty-directory(%t)
8+
// RUN: %empty-directory(%t-scratch)
9+
10+
// RUN: %target-swift-frontend -swift-version 5 -typecheck -plugin-path %swift-plugin-dir -I %t -dump-macro-expansions %s 2>&1 | %FileCheck %s --color
11+
12+
import Observation
13+
14+
// Test cases for comment handling with Observable macro
15+
_ = 0 // absorbs trivia so file check expectations don't leak into macro expansions
16+
17+
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
18+
@Observable
19+
final class CommentAfterGlobalActorAnnotation {
20+
@MainActor // Innocent comment
21+
internal var it = 0
22+
}
23+
24+
// CHECK-LABEL: @__swiftmacro{{.*}}CommentAfterGlobalActorAnnotation{{.*}}ObservationTracked{{.*}}.swift
25+
// CHECK: @MainActor // Innocent comment
26+
// CHECK-NEXT: @ObservationIgnored
27+
// CHECK-NEXT: private var _it = 0
28+
_ = 0
29+
30+
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
31+
@Observable
32+
final class CommentAfterAvailabilityAnnotation {
33+
@available(*, deprecated) // Innocent comment
34+
internal var it = 0
35+
}
36+
37+
// CHECK-LABEL: @__swiftmacro{{.*}}CommentAfterAvailabilityAnnotation{{.*}}ObservationTracked{{.*}}.swift
38+
// CHECK-NEXT: {{-+}}
39+
// CHECK-NEXT: @available(*, deprecated) // Innocent comment
40+
// CHECK-NEXT: @ObservationIgnored
41+
// CHECK-NEXT: private var _it = 0
42+
_ = 0
43+
44+
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
45+
@Observable
46+
final class CommentOnSameLineAsOtherAnnotation {
47+
@MainActor /* Innocent comment */ public var it = 0
48+
}
49+
50+
// CHECK-LABEL: @__swiftmacro{{.*}}CommentOnSameLineAsOtherAnnotation{{.*}}ObservationTracked{{.*}}.swift
51+
// CHECK: @MainActor /* Innocent comment */
52+
// CHECK-NEXT: @ObservationIgnored
53+
// CHECK-NEXT: private var _it = 0
54+
_ = 0
55+
56+
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
57+
@Observable
58+
final class CommentOnSameLineNoAnnotation {
59+
/* Innocent comment */ public /*1*/ final /*2*/ var /*3*/ it /*4*/ = 0
60+
}
61+
62+
// Note: seems there's some weirdness with the existing macro eating/duplicating trivia in some
63+
// cases but we'll just test for the current behavior since it doesn't seem to be covered elsewhere:
64+
65+
// CHECK-LABEL: @__swiftmacro{{.*}}CommentOnSameLineNoAnnotation{{.*}}ObservationTracked{{.*}}.swift
66+
// CHECK: /* Innocent comment */
67+
// CHECK-NEXT: @ObservationIgnored
68+
// CHECK-NEXT: private final /*2*/ var _it /*4*/ /*4*/ = 0
69+
_ = 0

0 commit comments

Comments
 (0)