Note: minor, may not be feasible/realistic to fix. Opening as part of addressing #1443 in #1683. We've also discussed this idea before, so worth having an issue for it.
Summary
The fixed-size overloads of getStructEncoder, getStructDecoder, and getStructCodec type
their result as FixedSize*<T> - i.e. with the default TSize of number - rather than
inferring the literal byte size from the sum of their fields. Even when every field has a
statically-known literal size, the struct's fixedSize is typed as the broad number.
Current behaviour
import { getStructEncoder } from '@solana/codecs-data-structures';
import { getU32Encoder } from '@solana/codecs-numbers';
const e = getStructEncoder([['value', getU32Encoder()]]);
// ^? FixedSizeEncoder<{ value: number }> (size is `number`, not the literal `4`)
Compare to the U32Encoder:
const u32 = getU32Encoder();
// ^? FixedSizeEncoder<number, 4>
Eg:
|
export function getStructEncoder<const TFields extends Fields<FixedSizeEncoder<any>>>( |
|
fields: TFields, |
|
): FixedSizeEncoder<GetEncoderTypeFromFields<TFields>>; |
Why this matters
Size-aware combinators - getUnion*, getPredicate*, getPatternMatch* decide between a
FixedSize* and a VariableSize* result by comparing the literal sizes of their branches. When
two struct branches have different real sizes but both report fixedSize: number, these
combinators can't tell the sizes apart and conservatively widen to a plain Encoder/Decoder/
Codec. Consumers are then forced to cast. If we knew the sizes are different we could return VariableSize*.
Suggested fix
Add a TSize type parameter to the fixed-size overloads and compute it as the literal sum of the
field sizes (mirroring how the runtime already sums getFixedSize over the fields).
Caveat / open question / why this might not make sense
Type-level addition in TypeScript is pretty hacky. We might be able to do it when the struct is very small?
The motivating example is v1 transaction config where the struct actually only has one field:
|
function getCompiledTransactionConfigValueEncoder(): VariableSizeEncoder<CompiledTransactionConfigValue> { |
|
return getPatternMatchEncoder<CompiledTransactionConfigValue>([ |
|
[value => value.kind === 'u32', getStructEncoder([['value', getU32Encoder()]])], |
|
[value => value.kind === 'u64', getStructEncoder([['value', getU64Encoder()]])], |
|
]) as unknown as VariableSizeEncoder<CompiledTransactionConfigValue>; |
|
} |
Acceptance criteria (typetest)
getStructEncoder([
['a', getU32Encoder()],
['b', getU8Encoder()],
]) satisfies FixedSizeEncoder<{ a: number; b: number }, 5>;
Note: minor, may not be feasible/realistic to fix. Opening as part of addressing #1443 in #1683. We've also discussed this idea before, so worth having an issue for it.
Summary
The fixed-size overloads of
getStructEncoder,getStructDecoder, andgetStructCodectypetheir result as
FixedSize*<T>- i.e. with the defaultTSizeofnumber- rather thaninferring the literal byte size from the sum of their fields. Even when every field has a
statically-known literal size, the struct's
fixedSizeis typed as the broadnumber.Current behaviour
Compare to the U32Encoder:
Eg:
kit/packages/codecs-data-structures/src/struct.ts
Lines 85 to 87 in 93191af
Why this matters
Size-aware combinators - getUnion*, getPredicate*, getPatternMatch* decide between a
FixedSize* and a VariableSize* result by comparing the literal sizes of their branches. When
two struct branches have different real sizes but both report
fixedSize: number, thesecombinators can't tell the sizes apart and conservatively widen to a plain Encoder/Decoder/
Codec. Consumers are then forced to cast. If we knew the sizes are different we could return
VariableSize*.Suggested fix
Add a
TSizetype parameter to the fixed-size overloads and compute it as the literal sum of thefield sizes (mirroring how the runtime already sums getFixedSize over the fields).
Caveat / open question / why this might not make sense
Type-level addition in TypeScript is pretty hacky. We might be able to do it when the struct is very small?
The motivating example is v1 transaction config where the struct actually only has one field:
kit/packages/transaction-messages/src/codecs/v1/config.ts
Lines 24 to 29 in 93191af
Acceptance criteria (typetest)