Interesting Integer Generic Parameters behavior

I've been using Integer Generic Parameters in my upcoming networking library without any issues for a while now but just came across an interesting compile error/diagnostic and was wondering if this is expected behavior or a bug.

If I try creating 2 values of the same value type that use integer generic parameters (and InlineArrays as underlying storage) with different underlying inline arrays I get compile errors for the inline array literals for the second value. It looks like the first value's integer generic parameters are applied to other values of the same type instead of each instance having their own generic parameters, which is how it is written in the source code and how I expected it to work.

Environment

OS

Arch Linux (6.14.6-arch1-1)

Toolchains

  • main-snapshot-2025-05-12
  • main-snapshot-2025-05-14

VS Code

1.100.0

Swift

Swift version 6.2-dev (LLVM be9c1978c3677f1, Swift 749e9ee090b6443)
Target: x86_64-unknown-linux-gnu
Build config: +assertions
(with Swift Language Mode 5)

Swift (VS Code Extension)

2.2.0


Destiny Example

Basically I have the following struct that stores socket responders:

struct RouterResponderStorage<
        ConcreteStaticResponderStorage: StaticResponderStorageProtocol,
        ConcreteDynamicResponderStorage: DynamicResponderStorageProtocol
    >

and have two different static responder storage types:

struct StaticResponderStorage: StaticResponderStorageProtocol {

    @usableFromInline var staticStrings:[DestinyRoutePathType:RouteResponses.StaticString]
    @usableFromInline var strings:[DestinyRoutePathType:RouteResponses.String]
    @usableFromInline var stringsWithDateHeader:[DestinyRoutePathType:RouteResponses.StringWithDateHeader]
    @usableFromInline var uint8Arrays:[DestinyRoutePathType:RouteResponses.UInt8Array]
    @usableFromInline var uint16Arrays:[DestinyRoutePathType:RouteResponses.UInt16Array]
}

struct CompiledStaticResponderStorage<
        let staticStringsCount: Int,
        let stringsCount: Int,
        let stringsWithDateHeaderCount: Int,
        let uint8ArraysCount: Int,
        let uint16ArraysCount: Int
    >: StaticResponderStorageProtocol {

    public let staticStrings:InlineArray<staticStringsCount, Route<RouteResponses.StaticString>>
    public let strings:InlineArray<stringsCount, Route<RouteResponses.String>>
    public let stringsWithDateHeader:InlineArray<stringsWithDateHeaderCount, Route<RouteResponses.StringWithDateHeader>>
    public let uint8Arrays:InlineArray<uint8ArraysCount, Route<RouteResponses.UInt8Array>>
    public let uint16Arrays:InlineArray<uint16ArraysCount, Route<RouteResponses.UInt16Array>>
}

and they are maintained by the following struct:

struct Router<
        ConcreteStaticResponderStorage: StaticResponderStorageProtocol,
        ConcreteDynamicResponderStorage: DynamicResponderStorageProtocol,
        ConcreteErrorResponder: ErrorResponderProtocol,
        ConcreteDynamicNotFoundResponder: DynamicRouteResponderProtocol,
        ConcreteStaticNotFoundResponder: StaticRouteResponderProtocol
    >: RouterProtocol {
    public private(set) var caseSensitiveResponders:RouterResponderStorage<ConcreteStaticResponderStorage, ConcreteDynamicResponderStorage>
    public private(set) var caseInsensitiveResponders:RouterResponderStorage<ConcreteStaticResponderStorage, ConcreteDynamicResponderStorage>
}

Finally we get to the problem if I create two separate CompiledStaticResponderStorage values in the same Router (I added comments where the errors show up).

let router = Router(
    ...,
    caseSensitiveResponders: RouterResponderStorage(
        static: CompiledStaticResponderStorage(
            staticStrings: [],
            strings: [
                // GET /redirectfrom HTTP/1.1
                .init(
                    path: SIMD64<UInt8>(),
                    responder: RouteResponses.String("HTTP/1.1 307\r\nLocation: /redirectto\r\n")
                )
            ],
            stringsWithDateHeader: [
                // GET /redirectto HTTP/1.1
                .init(
                    path: SIMD64<UInt8>(),
                    responder: RouteResponses.StringWithDateHeader("HTTP/1.1 200\r\nDate: Thu, 01 Jan 1970 00:00:00 GMT\r\nVersion: destiny1.1\r\nConnection: close\r\nServer: destiny\r\nYou-GET'd: true\r\nSet-Cookie: cookie1=yessir\r\nSet-Cookie: cookie2=pogchamp\r\nContent-Type: text/html\r\nContent-Length: 134\r\n\r\n<!DOCTYPE html><html><head><meta charset=\"UTF-8\"></head><body><h1>You've been redirected from /redirectfrom to here</h1></body></html>")
                ),
                // POST /post HTTP/1.1
                .init(
                    path: SIMD64<UInt8>(),
                    responder: RouteResponses.StringWithDateHeader("HTTP/1.1 501\r\nDate: Thu, 01 Jan 1970 00:00:00 GMT\r\nVersion: destiny1.1\r\nConnection: close\r\nServer: destiny\r\nYou-POST'd: true\r\nSet-Cookie: cookie1=yessir\r\nSet-Cookie: cookie2=pogchamp\r\nContent-Type: application/json\r\nContent-Length: 17\r\n\r\n{\"bing\":\"bonged\"}")
                ),
                // GET /bro?what=dude HTTP/1.1
                .init(
                    path: SIMD64<UInt8>(),
                    responder: RouteResponses.StringWithDateHeader("HTTP/1.1 200\r\nDate: Thu, 01 Jan 1970 00:00:00 GMT\r\nVersion: destiny1.1\r\nConnection: close\r\nServer: destiny\r\nYou-GET'd: true\r\nSet-Cookie: cookie1=yessir\r\nSet-Cookie: cookie2=pogchamp\r\nContent-Type: application/json\r\nContent-Length: 17\r\n\r\n{\"bing\":\"bonged\"}")
                ),
                // GET /html HTTP/1.1
                .init(
                    path: SIMD64<UInt8>(),
                    responder: RouteResponses.StringWithDateHeader("HTTP/1.1 200\r\nDate: Thu, 01 Jan 1970 00:00:00 GMT\r\nVersion: destiny1.1\r\nConnection: close\r\nServer: destiny\r\nYou-GET'd: true\r\nSet-Cookie: cookie1=yessir\r\nSet-Cookie: cookie2=pogchamp\r\nContent-Type: text/html\r\nContent-Length: 132\r\n\r\n<!DOCTYPE html><html><head><meta charset=\"UTF-8\"></head><body><h1>This outcome was inevitable; t'was your destiny</h1></body></html>")
                ),
                // GET /html2 HTTP/2.0
                .init(
                    path: SIMD64<UInt8>(),
                    responder: RouteResponses.StringWithDateHeader("HTTP/2.0 200\r\nDate: Thu, 01 Jan 1970 00:00:00 GMT\r\nVersion: destiny2.0\r\nServer: destiny\r\nYou-GET'd: true\r\nContent-Type: text/html\r\nContent-Length: 132\r\n\r\n<!DOCTYPE html><html><head><meta charset=\"UTF-8\"></head><body><h1>This outcome was inevitable; t'was your destiny</h1></body></html>")
                ),
                // GET /json HTTP/1.1
                .init(
                    path: SIMD64<UInt8>(),
                    responder: RouteResponses.StringWithDateHeader("HTTP/1.1 200\r\nDate: Thu, 01 Jan 1970 00:00:00 GMT\r\nVersion: destiny1.1\r\nConnection: close\r\nServer: destiny\r\nYou-GET'd: true\r\nSet-Cookie: cookie1=yessir\r\nSet-Cookie: cookie2=pogchamp\r\nContent-Type: application/json\r\nContent-Length: 57\r\n\r\n{\"this_outcome_was_inevitable_and_was_your_destiny\":true}")
                ),
                // GET /txt HTTP/1.1
                .init(
                    path: SIMD64<UInt8>(),
                    responder: RouteResponses.StringWithDateHeader("HTTP/1.1 200\r\nDate: Thu, 01 Jan 1970 00:00:00 GMT\r\nVersion: destiny1.1\r\nConnection: close\r\nServer: destiny\r\nYou-GET'd: true\r\nSet-Cookie: cookie1=yessir\r\nSet-Cookie: cookie2=pogchamp\r\nContent-Type: text/plain\r\nContent-Length: 43\r\n\r\njust a regular txt page; t'was your destiny")
                )
            ],
            uint8Arrays: [
                // GET /bytes HTTP/1.1
                .init(
                    path: SIMD64<UInt8>(),
                    responder: RouteResponses.UInt8Array([])
                ),
                // GET /bytes2 HTTP/1.1
                .init(
                    path: SIMD64<UInt8>(),
                    responder: RouteResponses.UInt8Array([])
                ),
                // GET /bytes3 HTTP/1.1
                .init(
                    path: SIMD64<UInt8>(),
                    responder: RouteResponses.UInt8Array([])
                )
            ],
            uint16Arrays: []
        ),
        ...
    ),
    caseInsensitiveResponders: RouterResponderStorage(
        static: CompiledStaticResponderStorage(
            staticStrings: [],
            strings: [], // ERROR: Expected '1' elements in inline array literal, but got '0'
            stringsWithDateHeader: [ // ERROR: Expected '7' elements in inline array literal, but got '1'
                // get /shoop http/1.1
                .init(
                    path: SIMD64<UInt8>(),
                    responder: RouteResponses.StringWithDateHeader("HTTP/1.1 200\r\nDate: Thu, 01 Jan 1970 00:00:00 GMT\r\nVersion: destiny1.1\r\nConnection: close\r\nServer: destiny\r\nYou-GET'd: true\r\nSet-Cookie: cookie1=yessir\r\nSet-Cookie: cookie2=pogchamp\r\nContent-Type: text/html\r\nContent-Length: 132\r\n\r\n<!DOCTYPE html><html><head><meta charset=\"UTF-8\"></head><body><h1>This outcome was inevitable; t'was your destiny</h1></body></html>")
                )
            ],
            uint8Arrays: [], // ERROR: Expected '3' elements in inline array literal, but got '0'
            uint16Arrays: []
        ),
       ...
    ),
    ...
)
1 Like

After much thought (and re-reading 0452) I think this is expected behavior as the generic parameters become static constant members of the type, with the same visibility as the type itself and they should be used like generic types. The simple solution to my problem is to use two different generic types for the type utilizing integer generic parameters, like the following.

struct Router<
        ConcreteCaseSensitiveStaticResponderStorage: StaticResponderStorageProtocol,
        ConcreteCaseInsensitiveStaticResponderStorage: StaticResponderStorageProtocol,
        ConcreteCaseSensitiveDynamicResponderStorage: DynamicResponderStorageProtocol,
        ConcreteCaseInsensitiveDynamicResponderStorage: DynamicResponderStorageProtocol
    >: RouterProtocol {
    public private(set) var caseSensitiveResponders:RouterResponderStorage<ConcreteCaseSensitiveStaticResponderStorage, ConcreteCaseSensitiveDynamicResponderStorage>
    public private(set) var caseInsensitiveResponders:RouterResponderStorage<ConcreteCaseInsensitiveStaticResponderStorage, ConcreteCaseInsensitiveDynamicResponderStorage>
}

I've reported a compile crash when trying to implement the solution: IRBuilder compiler crash with main-snapshot-2025-05-14 toolchain · Issue #81650 · swiftlang/swift · GitHub

1 Like