Compiler errors destructuring Parameter Pack return tuple

Ok, I'm stumped here. Am I holding this wrong somehow? I get compiler errors, or undefined symbol errors, when trying to access destructured tuples returned from using a parameter pack function. Simple repro below:

import Foundation

struct ResponseTuple<each Response: QueryResponseProtocol> {
	let responses: (repeat each Response)
}

protocol QueryResponseProtocol: Equatable {}

protocol QueryStateAccessing {
	func values<each T: QueryResponseProtocol>(for types: repeat (each T).Type) throws -> (repeat each T)
	// If I instead use a ResponseTuple type for the return, then the compiler is happy when I access the properties
	// 	func values<each T: QueryResponseProtocol>(for types: repeat (each T).Type) throws -> ResponseTuple<repeat each T>
}

struct ShippingResponse: QueryResponseProtocol {
	let service: String
}
struct AspectsResponse: QueryResponseProtocol {
	let aspects: [String]
}
struct PricingResponse: QueryResponseProtocol {
	let price: Decimal
}

struct ShippingServiceRequest {
	typealias ResponseModel = ShippingResponse
	
	init(queryState: QueryStateAccessing) throws {
		let (pricingResponse, aspectsResponse) = try queryState.values(for: PricingResponse.self, AspectsResponse.self)
		_ = pricingResponse.price // this works fine
		// _ = aspectsResponse.aspects // this is a compiler error - Undefined symbol

		// single value is a compiler error
		//		let (pricingResponse) = try queryState.values(for: PricingResponse.self) // this is a compiler error

		// this is a compiler error
		// let results = try queryState.values(for: PricingResponse.self, AspectsResponse.self)
		// _ = results.0

		// compiler happy when use the ResponseTuple type approach
		// let responseTuple = try queryState.values(for: PricingResponse.self, AspectsResponse.self)
		// _ = responseTuple.responses.1
	}
}
1 Like

No wronger than anyone else is. Parameter packs are still quite broken, but there are generally workarounds.

For example, sometimes, by hiding metatypes:

extension QueryStateAccessing {
  func values<each Response: QueryResponseProtocol>() throws -> (repeat each Response) {
    try values(for: repeat (each Response).self)
  }
}
let (pricingResponse, aspectsResponse): (PricingResponse, AspectsResponse)
    = try queryState.values()
_ = pricingResponse.price
_ = aspectsResponse.aspects

_ = try queryState.values() as (PricingResponse, AspectsResponse)

let _: ShippingResponse = try queryState.values()

let results: (PricingResponse, AspectsResponse) = try queryState.values()
_ = results.0

Note, however, that single values are not really handled properly:

// Error: Cannot convert value of type '()' to type 'ShippingResponse' in coercion
_ = try queryState.values() as ShippingResponse
1 Like

Even trying those workarounds I still get a compiler error.

Thanks for confirming! This is unfortunate after 2 years of releases.

Correction, it does work if I do it in an extension like you did. But not if I just put that method in the main protocol.

1 Like

And I didn't get a compiler error with your first line, though the rest were all problems here too. Are you using Xcode build 16C5032a?

This unit test passes with the code additions below.

@Test func test() throws {
  let request = try ShippingServiceRequest(queryState: QueryState())
  #expect(request.price == 0)
  #expect(request.service == "")
  #expect(request.aspects == [])
}
Supporting Code
protocol QueryResponseProtocol: Equatable {
  init()
}
struct ShippingResponse: QueryResponseProtocol {
  let service: String = .init()
}
struct AspectsResponse: QueryResponseProtocol {
  let aspects: [String] = .init()
}
struct PricingResponse: QueryResponseProtocol {
  let price: Decimal = .init()
}
struct QueryState: QueryStateAccessing {
  func values<each Response: QueryResponseProtocol>(
    for type: repeat (each Response).Type
  ) throws -> (repeat each Response) {
    (repeat (each Response)())
  }
}
struct ShippingServiceRequest {
  init(queryState: QueryStateAccessing) throws {
    let (pricingResponse, aspectsResponse, shippingResponse) = try queryState.values()
      as (PricingResponse, AspectsResponse, ShippingResponse)
    price = pricingResponse.price
    service = shippingResponse.service
    aspects = aspectsResponse.aspects
  }

  let price: Decimal
  let service: String
  let aspects: [String]
}
struct ShippingServiceRequest {
  init(queryState: QueryStateAccessing) throws {
    let (pricingResponse, aspectsResponse, shippingResponse) = try queryState.values()
      as (PricingResponse, AspectsResponse, ShippingResponse)
    price = pricingResponse.price
    service = shippingResponse.service
    aspects = aspectsResponse.aspects
  }

  let price: Decimal
  let service: String
  let aspects: [String]
}

Yeah, weird. I have to add your method without metatypes in an extension, and can't add the signature in the protocol, or else I get an error.

I'm using Xcode 16.1(16B40) and also tested in 16.2.

1 Like

lolol, in fact, if I add MY method in an extension instead of the protocol, it also works!

1 Like