InlineArray pattern matching and destructuring

I have come across a need in pattern matching an InlineArray and was very surprised to find out that while I can do this:

@available(macOS 26, *)
mutating func names<let count: Int>() -> [count of Name] {
	InlineArray { _ in _names.next() }
}

I can't actually use this the way I wanted:

// doesn't work :(
let [x, y, z] = names()

forcing me to instead do either of these, which are suboptimal because I'm going to be writing hundreds of tests with that pattern:

// locals[0], locals[1], etc. I've lost the name :(
let locals = names()
// got the name back but "lost" my short-hand :(
let (x, y, z) = (locals[0], locals[1], locals[2])
// wonderful! except...
let (x, y, z) = names()
// ...now I've got this in my code and it keeps growing to like 30
	mutating func vars() -> (Var) {
		(_vars.next())
	}

	mutating func vars() -> (Var, Var) {
		(_vars.next(), _vars.next())
	}

	mutating func vars() -> (Var, Var, Var) {
		(_vars.next(), _vars.next(), _vars.next())
	}

	mutating func vars() -> (Var, Var, Var, Var) {
		(_vars.next(), _vars.next(), _vars.next(), _vars.next())
	}

	mutating func vars() -> (Var, Var, Var, Var, Var) {
		(_vars.next(), _vars.next(), _vars.next(), _vars.next(), _vars.next())
	}

	mutating func vars() -> (Var, Var, Var, Var, Var, Var) {
		(_vars.next(), _vars.next(), _vars.next(), _vars.next(), _vars.next(), _vars.next())
	}

	mutating func vars() -> (Var, Var, Var, Var, Var, Var, Var) {
		(_vars.next(), _vars.next(), _vars.next(), _vars.next(), _vars.next(), _vars.next(), _vars.next())
	}

	mutating func vars() -> (Var, Var, Var, Var, Var, Var, Var, Var) {
		(_vars.next(), _vars.next(), _vars.next(), _vars.next(), _vars.next(), _vars.next(), _vars.next(), _vars.next())
	}

I'd therefore like to suggest an InlineArray pattern matching.

let [x, y, z] = names()

switch names {
case let [x]: // ...
case let [x, y]: // ...
case let [x, y, z]: // ...
case let [_, y, z, w] where y * z == w: // ...
default: // ...
}

if case let [x] = names { /* ... */ }

and so forth.

Thoughts?

2 Likes

Why don't you use a tuple instead?

1 Like

Because then I have to make a names function for each possible arity, that’s what I’m doing right now but I have like 30 functions for returning tuples of 1-30 elements, I’d much rather be able to write 1 function that infers the count!

Can you explain your actual use-case in more detail? It's hard to imagine a situation where this would be meaningful:

let (x1, x2, x3, /* ... */, x30) = names()

... expression involving all of x1 ... x30

Unit tests for various passes of a toy compiler. I’m naming all those variables and building up the input and expected structure using them.

Also in unit tests for graphs where I name nodes to build up graphs.

Basically at this scale, unit tests!

Another option could be supporting same-type constraints on parameter packs, that would also support my use case.

1 Like

You could forgo type safety and just use strings to name your nodes, or you could make your graph data type generic over a NameType: Hashable that is used for the names, and then define local enums like this:

enum Name {
  case x
  case y
  case z
}

That could be an option, it would require large adjustments to the main code just for the tests which isn’t ideal but it would definitely have its advantages!

While I love this hacky solution, its still longer at call-site, I’ll keep my 30 functions for now!

1 Like

I have once again encountered this issue, this time with decompilers.
Here's what I'd like to be able to do:

func stride<let count: Int>(
	from offset: Offset,
	by stride: Offset,
	in space: AddressSpaceId,
	size: UInt32
) -> InlineArray<count, Varnode> {
	InlineArray(first: Varnode(at: offset, in: space, size: size)) { previous in
		previous.advanced(by: stride)
	}
}

let [RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI] = Varnode.stride(from: 0, by: 8, in: .reg, size: 8)
let [EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI] = Varnode.stride(from: 0, by: 8, in: .reg, size: 4)
let [AX, CX, DX, BX, SP, BP, SI, DI] = Varnode.stride(from: 0, by: 8, in: .reg, size: 2)
let [AL, CL, DL, BL, SPL, BPP, SIL, DIL] = Varnode.stride(from: 0, by: 8, in: .reg, size: 1)
let [AH, CH, DH, BH] = Varnode.stride(from: 1, by: 8, in: .reg, size: 1)

Here's what I'm forced to do:

let (RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI) = Varnode.stride(from: 0, by: 8, in: .reg, size: 8)
let (EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI) = Varnode.stride(from: 0, by: 8, in: .reg, size: 4)
let (AX, CX, DX, BX, SP, BP, SI, DI) = Varnode.stride(from: 0, by: 8, in: .reg, size: 2)
let (AL, CL, DL, BL, SPL, BPP, SIL, DIL) = Varnode.stride(from: 0, by: 8, in: .reg, size: 1)
let (AH, CH, DH, BH) = Varnode.stride(from: 1, by: 8, in: .reg, size: 1)

// then the functions, up to 32

func stride(
	from offset: Offset,
	by stride: Offset,
	in space: AddressSpaceId,
	size: UInt32
) -> (Varnode) {
	let names: [1 of Varnode] = Self.stride(from: offset, by: stride, in: space, size: size)
	return (names[0])
}

func stride(
	from offset: Offset,
	by stride: Offset,
	in space: AddressSpaceId,
	size: UInt32
) -> (Varnode, Varnode) {
	let names: [2 of Varnode] = Self.stride(from: offset, by: stride, in: space, size: size)
	return (names[0], names[1])
}

func stride(
	from offset: Offset,
	by stride: Offset,
	in space: AddressSpaceId,
	size: UInt32
) -> (Varnode, Varnode, Varnode) {
	let names: [3 of Varnode] = Self.stride(from: offset, by: stride, in: space, size: size)
	return (names[0], names[1], names[2])
}

// on and on...
1 Like