@Builder<FlowContent> / @Builder<PhrasingContent> constraints break component composition
Problem
Many HTML tags enforce content model constraints at compile time by using typed builders:
// LiTag.swift, PTag.swift, TdTag.swift, SectionTag.swift, NavTag.swift, etc.
public init(
@Builder<FlowContent> _ block: () -> [any FlowContent]
)
This makes it impossible to embed reusable components that return [any SGML.Element] inside these tags.
Minimal Reproduction
// A typical reusable component
struct MyBadge: TemplateRepresentable {
@Builder<Element>
func render(_ request: Request) -> [Element] {
Span { Text("badge") }
}
}
// ❌ Compile error: "Argument type 'any Element' does not conform to expected type 'FlowContent'"
Li {
MyBadge().render(request) // returns [any SGML.Element]
}
Affected Tags
All tags with @Builder<FlowContent> or @Builder<PhrasingContent> constraints:
FlowContent: Li, P, Td, Th, Section, Nav, Main, A, Body, Article, Aside, Blockquote, Dd, Del, Dialog, Figcaption, Ins
PhrasingContent: H1, H2, H3, H4, H5, H6, Span, B, I, Em, Strong, Cite, Data, Kbd, Mark, Output, Pre, Q, Samp, Small, Sub, Sup, U, Var
Current Workaround (user-side, fragile)
extension Builder where Element == any FlowContent {
public static func buildExpression(
_ expression: [SGML.Element]
) -> [any FlowContent] {
expression.compactMap { $0 as? any FlowContent }
}
}
extension Builder where Element == any PhrasingContent {
public static func buildExpression(
_ expression: [SGML.Element]
) -> [any PhrasingContent] {
expression.compactMap { $0 as? any PhrasingContent }
}
}
This works but silently drops elements at runtime that don't conform to FlowContent — no compile-time or runtime warning.
Suggested Fixes
Option 1 (lowest risk) — Add an unconstrained @Builder<Element> overload alongside the typed one on all affected tags:
public init(@Builder<Element> _ block: () -> [Element]) {
self.init(children: block())
}
Option 2 — Make SGML.Element (or concrete tag types) conform to FlowContent and PhrasingContent, so arrays of Element are accepted without casting.
Option 3 — Add the buildExpression bridge in the library itself, so user code doesn't need the workaround.
Option 1 is the lowest-risk change — it adds an unconstrained overload while keeping the typed one for users who want compile-time content model checking.
Related Issue
AttributeStore.swift fails to compile under Swift 6 strict import visibility:
// Current (fails under Swift 6 #MemberImportVisibility):
import Collections
// Fix:
import OrderedCollections
Issue created together with AI :-)
@Builder<FlowContent>/@Builder<PhrasingContent>constraints break component compositionProblem
Many HTML tags enforce content model constraints at compile time by using typed builders:
This makes it impossible to embed reusable components that return
[any SGML.Element]inside these tags.Minimal Reproduction
Affected Tags
All tags with
@Builder<FlowContent>or@Builder<PhrasingContent>constraints:FlowContent:
Li,P,Td,Th,Section,Nav,Main,A,Body,Article,Aside,Blockquote,Dd,Del,Dialog,Figcaption,InsPhrasingContent:
H1,H2,H3,H4,H5,H6,Span,B,I,Em,Strong,Cite,Data,Kbd,Mark,Output,Pre,Q,Samp,Small,Sub,Sup,U,VarCurrent Workaround (user-side, fragile)
This works but silently drops elements at runtime that don't conform to
FlowContent— no compile-time or runtime warning.Suggested Fixes
Option 1 (lowest risk) — Add an unconstrained
@Builder<Element>overload alongside the typed one on all affected tags:Option 2 — Make
SGML.Element(or concrete tag types) conform toFlowContentandPhrasingContent, so arrays ofElementare accepted without casting.Option 3 — Add the
buildExpressionbridge in the library itself, so user code doesn't need the workaround.Option 1 is the lowest-risk change — it adds an unconstrained overload while keeping the typed one for users who want compile-time content model checking.
Related Issue
AttributeStore.swiftfails to compile under Swift 6 strict import visibility:Issue created together with AI :-)