Skip to content

Commit a822ff5

Browse files
committed
Support ToolbarContentBuilder
1 parent 12d8ccf commit a822ff5

File tree

4 files changed

+71
-4
lines changed

4 files changed

+71
-4
lines changed

Sources/LiveViewNative/Stylesheets/ViewReference.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ struct _TextReferenceObserver: ViewModifier {
182182
/// <ToolbarItem template="item2" />
183183
/// ```
184184
@_documentation(visibility: public)
185-
struct ToolbarContentReference: @preconcurrency Decodable {
185+
struct ToolbarContentReference: @preconcurrency Decodable, @preconcurrency AttributeDecodable {
186186
let value: [String]
187187

188188
init(from decoder: any Decoder) throws {
@@ -195,6 +195,12 @@ struct ToolbarContentReference: @preconcurrency Decodable {
195195
}
196196
}
197197

198+
init(from attribute: Attribute?, on element: ElementNode) throws {
199+
guard let value = attribute?.value
200+
else { throw AttributeDecodingError.missingAttribute(Self.self) }
201+
self.value = [value]
202+
}
203+
198204
@MainActor
199205
func resolve<R: RootRegistry>(on element: ElementNode, in context: LiveContext<R>) -> some ToolbarContent {
200206
ToolbarTreeBuilder<R>().fromNodes(
@@ -223,7 +229,7 @@ struct ToolbarContentReference: @preconcurrency Decodable {
223229
/// <ToolbarItem template="item2" id="item2" />
224230
/// ```
225231
@_documentation(visibility: public)
226-
struct CustomizableToolbarContentReference: @preconcurrency Decodable {
232+
struct CustomizableToolbarContentReference: @preconcurrency Decodable, @preconcurrency AttributeDecodable {
227233
let value: [String]
228234

229235
init(from decoder: any Decoder) throws {
@@ -236,6 +242,12 @@ struct CustomizableToolbarContentReference: @preconcurrency Decodable {
236242
}
237243
}
238244

245+
init(from attribute: Attribute?, on element: ElementNode) throws {
246+
guard let value = attribute?.value
247+
else { throw AttributeDecodingError.missingAttribute(Self.self) }
248+
self.value = [value]
249+
}
250+
239251
@MainActor
240252
func resolve<R: RootRegistry>(on element: ElementNode, in context: LiveContext<R>) -> some CustomizableToolbarContent {
241253
CustomizableToolbarTreeBuilder<R>().fromNodes(

Sources/ModifierGenerator/StyleDefinitionGenerator/StyleDefinitionGenerator.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,14 @@ public final class StyleDefinitionGenerator: SyntaxVisitor {
188188
.filter(\.isPublic)
189189
.filter({ !denylist.contains($0.name.text) })
190190
{
191+
// special case for `toolbar` to exclude `ViewReference` clauses.
192+
if modifier.name.text == "toolbar" {
193+
guard !modifier.signature.parameterClause.parameters.contains(where: {
194+
return $0.type.as(IdentifierTypeSyntax.self)?.genericArgumentClause?.arguments.first?.argument.as(IdentifierTypeSyntax.self)?.name.text == "ViewReference"
195+
})
196+
else { continue }
197+
}
198+
191199
// the 2nd member is always the enum decl
192200
// 1st member is the body(content:) block
193201
let offset = modifiers[modifier.name.text]?.memberBlock.members

Sources/SwiftSyntaxExtensions/NormalizableDeclSyntax.swift

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public extension NormalizableDeclSyntax {
3838
?? parameter.type.as(AttributedTypeSyntax.self)?.baseType.as(FunctionTypeSyntax.self)
3939
?? (parameter.type.as(AttributedTypeSyntax.self)?.baseType ?? parameter.type.as(OptionalTypeSyntax.self)?.wrappedType)?.as(TupleTypeSyntax.self)?.elements.first?.type.as(FunctionTypeSyntax.self)
4040
{
41-
guard functionType.returnClause.type.isVoid || parameter.isViewBuilder
41+
guard functionType.returnClause.type.isVoid || parameter.isViewBuilder || parameter.isToolbarContentBuilder
4242
else { return false }
4343
}
4444

@@ -134,6 +134,31 @@ public extension NormalizableDeclSyntax {
134134
.with(\.type, TypeSyntax(IdentifierTypeSyntax(name: .identifier("ViewReference"))))
135135
.makeAttributeReference()
136136
}
137+
if parameter.isToolbarContentBuilder {
138+
if let returnType = parameter.type.as(FunctionTypeSyntax.self)?.returnClause.type.as(IdentifierTypeSyntax.self),
139+
let genericType = genericWhereClause?.requirements.lazy.compactMap({ requirement -> TypeSyntax? in
140+
switch requirement.requirement {
141+
case let .conformanceRequirement(conformance):
142+
guard conformance.leftType.as(IdentifierTypeSyntax.self)?.name.text == returnType.name.text
143+
else { return nil }
144+
return conformance.rightType
145+
default:
146+
return nil
147+
}
148+
}).first,
149+
genericType.as(MemberTypeSyntax.self)?.name.text == "CustomizableToolbarContent"
150+
{
151+
return parameter
152+
.with(\.attributes, AttributeListSyntax())
153+
.with(\.type, TypeSyntax(IdentifierTypeSyntax(name: .identifier("CustomizableToolbarContentReference"))))
154+
.makeAttributeReference()
155+
} else {
156+
return parameter
157+
.with(\.attributes, AttributeListSyntax())
158+
.with(\.type, TypeSyntax(IdentifierTypeSyntax(name: .identifier("ToolbarContentReference"))))
159+
.makeAttributeReference()
160+
}
161+
}
137162
if let functionType = parameter.type.as(FunctionTypeSyntax.self) {
138163
// closures that return values other than `Void` are not supported.
139164
// we *might* be able to support those if the closure is `async`.

Sources/SwiftSyntaxExtensions/ValueResolution.swift

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,27 @@ public extension FunctionParameterSyntax {
2929
})
3030
}
3131

32+
/// Checks if this parameter is augmented with the `@ToolbarContentBuilder` result builder.
33+
var isToolbarContentBuilder: Bool {
34+
return attributes.contains(where: {
35+
switch $0 {
36+
case let .attribute(attribute):
37+
if let memberType = attribute.attributeName.as(MemberTypeSyntax.self) {
38+
// @SwiftUI.ToolbarContentBuilder
39+
return memberType.baseType.as(IdentifierTypeSyntax.self)?.name.text == "SwiftUI"
40+
&& memberType.name.text == "ToolbarContentBuilder"
41+
} else if let type = attribute.attributeName.as(IdentifierTypeSyntax.self) {
42+
// @ToolbarContentBuilder
43+
return type.name.text == "ToolbarContentBuilder"
44+
} else {
45+
return false
46+
}
47+
default:
48+
return false
49+
}
50+
})
51+
}
52+
3253
/// Wraps this parameter's type with `AttributeReference<_>`.
3354
func makeAttributeReference() -> Self {
3455
return with(\.type, TypeSyntax(
@@ -115,7 +136,8 @@ public extension FunctionParameterSyntax {
115136
{
116137
resolvedAttribute = FunctionCallExprSyntax.resolveAttributeReference(resolvedAttribute)
117138
}
118-
if attributeReferenceType.genericArgumentClause!.arguments.first!.argument.as(IdentifierTypeSyntax.self)?.name.text == "ViewReference" {
139+
if let typeName = attributeReferenceType.genericArgumentClause!.arguments.first!.argument.as(IdentifierTypeSyntax.self)?.name.text,
140+
typeName == "ViewReference" || typeName == "ToolbarContentReference" || typeName == "CustomizableToolbarContentReference" {
119141
// `ViewReference` should be converted into a closure that resolves the `ViewReference`.
120142
return ExprSyntax(
121143
ClosureExprSyntax {

0 commit comments

Comments
 (0)