{"version":3,"sources":["/Users/konradreczko/TypeGPU/wigsill/packages/typegpu/dist/index.cjs","../src/extractGpuValueGetter.ts","../src/shared/utilityTypes.ts","../src/tgsl/generationHelpers.ts","../src/core/valueProxyUtils.ts","../src/core/function/fnCore.ts","../src/core/resolve/resolveData.ts","../src/data/compiledIO.ts","../src/data/dataIO.ts","../src/tgsl/wgslGenerator.ts","../src/resolutionCtx.ts"],"names":["extractGpuValueGetter","object","$gpuValueOf","assertExhaustive","x","location"],"mappings":"AAAA,q5CAAiI,wDAA0C,wDAAkrB,SCK70BA,EAAAA,CACdC,CAAAA,CAC4B,CAE5B,EAAA,CAAI,uBAAQA,CAAAA,4BAAAA,CAAiBC,mBAAW,GAAA,EAAM,UAAA,CAC5C,OAAQD,CAAAA,CAA6CC,mBAAW,CAAA,CAAE,IAAA,CAChED,CACF,CAGJ,CCgDO,SAASE,EAAAA,CAAiBC,CAAAA,CAAUC,CAAAA,CAAyB,CAClE,MAAM,IAAI,KAAA,CAAM,CAAA,iBAAA,EAAoBD,CAAC,CAAA,IAAA,EAAOC,CAAQ,CAAA,CAAA;AC2d1C;AAEN;AAAoC,KAAA;ACngBpB,4CAAA;ACkGd;ACIa;AACV,OAAA;AAKT;AAwBmB;AACV,OAAA;AAYT;AAAA;AAoG8B;AC1KpB;AAeN;AAsBE;AASsB;ACrBV;AC/BoD;AAAK;AA4GzC;AAyWxB;AACH;AAPgB;AA4FjB;AAwBuB;ACvgBf;AAubiC;AA+GjC","file":"/Users/konradreczko/TypeGPU/wigsill/packages/typegpu/dist/index.cjs","sourcesContent":[null,"import { $gpuValueOf } from './shared/symbols.ts';\nimport type { ResolutionCtx } from './types.ts';\n\nexport type GpuValueGetter = (ctx: ResolutionCtx) => unknown;\n\nexport function extractGpuValueGetter(\n  object: unknown,\n): GpuValueGetter | undefined {\n  // biome-ignore lint/suspicious/noExplicitAny: we're inspecting the value\n  if (typeof (object as any)?.[$gpuValueOf] === 'function') {\n    return (object as { [$gpuValueOf]: GpuValueGetter })[$gpuValueOf].bind(\n      object,\n    );\n  }\n  return undefined;\n}\n","export type Default<T, TDefault> = unknown extends T ? TDefault\n  : T extends undefined ? TDefault\n  : T;\n\nexport type UnionToIntersection<U> =\n  // biome-ignore lint/suspicious/noExplicitAny: <had to be done>\n  (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I\n    : never;\n\nexport type Prettify<T> =\n  & {\n    [K in keyof T]: T[K];\n  }\n  & {};\n\n/**\n * Removes properties from record type that extend `Prop`\n */\nexport type OmitProps<T extends Record<string, unknown>, Prop> = Pick<\n  T,\n  {\n    [Key in keyof T]: T[Key] extends Prop ? never : Key;\n  }[keyof T]\n>;\n\n/**\n * Removes properties from record type that equal `Prop`\n */\nexport type OmitPropsExact<T extends Record<string, unknown>, Prop> = Pick<\n  T,\n  {\n    [Key in keyof T]: [T[Key], Prop] extends [Prop, T[Key]] ? never : Key;\n  }[keyof T]\n>;\n\nexport type NullableToOptional<T> =\n  & {\n    // Props where the value extends `null` -> make them optional and remove null from the type\n    [K in keyof T as T[K] extends null ? K : never]?: T[K];\n  }\n  & {\n    // All other props remain unchanged\n    [K in keyof T as T[K] extends null ? never : K]: T[K];\n  };\n\n/**\n * The opposite of Readonly<T>\n */\nexport type Mutable<T> = {\n  -readonly [P in keyof T]: T[P];\n};\n\n/**\n * Any typed array\n */\nexport type TypedArray =\n  | Uint8Array\n  | Uint16Array\n  | Uint32Array\n  | Int32Array\n  | Float32Array\n  | Float64Array;\n\nexport function assertExhaustive(x: never, location: string): never {\n  throw new Error(`Failed to handle ${x} at ${location}`);\n}\n","import { arrayOf } from '../data/array.ts';\nimport {\n  type AnyData,\n  isDisarray,\n  isSnippet,\n  isUnstruct,\n  snip,\n  type Snippet,\n  UnknownData,\n} from '../data/dataTypes.ts';\nimport { mat2x2f, mat3x3f, mat4x4f } from '../data/matrix.ts';\nimport {\n  abstractFloat,\n  abstractInt,\n  bool,\n  f16,\n  f32,\n  i32,\n  u32,\n} from '../data/numeric.ts';\nimport {\n  vec2b,\n  vec2f,\n  vec2h,\n  vec2i,\n  vec2u,\n  vec3b,\n  vec3f,\n  vec3h,\n  vec3i,\n  vec3u,\n  vec4b,\n  vec4f,\n  vec4h,\n  vec4i,\n  vec4u,\n  vecTypeToPrimitive,\n} from '../data/vector.ts';\nimport {\n  type AnyWgslData,\n  type AnyWgslStruct,\n  type F16,\n  type F32,\n  hasInternalDataType,\n  type I32,\n  isMat,\n  isMatInstance,\n  isVec,\n  isVecInstance,\n  isWgslArray,\n  isWgslStruct,\n  type U32,\n} from '../data/wgslTypes.ts';\nimport { invariant } from '../errors.ts';\nimport { getResolutionCtx } from '../gpuMode.ts';\nimport { $wgslDataType } from '../shared/symbols.ts';\nimport { assertExhaustive } from '../shared/utilityTypes.ts';\nimport { isNumericSchema } from '../std/numeric.ts';\nimport type { ResolutionCtx } from '../types.ts';\n\ntype SwizzleableType = 'f' | 'h' | 'i' | 'u' | 'b';\ntype SwizzleLength = 1 | 2 | 3 | 4;\n\nconst swizzleLenToType: Record<\n  SwizzleableType,\n  Record<SwizzleLength, AnyData>\n> = {\n  f: {\n    1: f32,\n    2: vec2f,\n    3: vec3f,\n    4: vec4f,\n  },\n  h: {\n    1: f16,\n    2: vec2h,\n    3: vec3h,\n    4: vec4h,\n  },\n  i: {\n    1: i32,\n    2: vec2i,\n    3: vec3i,\n    4: vec4i,\n  },\n  u: {\n    1: u32,\n    2: vec2u,\n    3: vec3u,\n    4: vec4u,\n  },\n  b: {\n    1: bool,\n    2: vec2b,\n    3: vec3b,\n    4: vec4b,\n  },\n} as const;\n\nconst kindToSchema = {\n  vec2f: vec2f,\n  vec2h: vec2h,\n  vec2i: vec2i,\n  vec2u: vec2u,\n  'vec2<bool>': vec2b,\n  vec3f: vec3f,\n  vec3h: vec3h,\n  vec3i: vec3i,\n  vec3u: vec3u,\n  'vec3<bool>': vec3b,\n  vec4f: vec4f,\n  vec4h: vec4h,\n  vec4i: vec4i,\n  vec4u: vec4u,\n  'vec4<bool>': vec4b,\n  mat2x2f: mat2x2f,\n  mat3x3f: mat3x3f,\n  mat4x4f: mat4x4f,\n} as const;\n\nconst indexableTypeToResult = {\n  vec2f: f32,\n  vec2h: f16,\n  vec2i: i32,\n  vec2u: u32,\n  'vec2<bool>': bool,\n  vec3f: f32,\n  vec3h: f16,\n  vec3i: i32,\n  vec3u: u32,\n  'vec3<bool>': bool,\n  vec4f: f32,\n  vec4h: f16,\n  vec4i: i32,\n  vec4u: u32,\n  'vec4<bool>': bool,\n  mat2x2f: vec2f,\n  mat3x3f: vec3f,\n  mat4x4f: vec4f,\n} as const;\n\nexport function getTypeForPropAccess(\n  targetType: AnyData,\n  propName: string,\n): AnyData | UnknownData {\n  if (isWgslStruct(targetType) || isUnstruct(targetType)) {\n    return targetType.propTypes[propName] as AnyData ?? UnknownData;\n  }\n\n  if (targetType === bool || isNumericSchema(targetType)) {\n    // No props to be accessed here\n    return UnknownData;\n  }\n\n  const propLength = propName.length;\n  if (\n    isVec(targetType) &&\n    propLength >= 1 &&\n    propLength <= 4\n  ) {\n    const swizzleTypeChar = targetType.type.includes('bool')\n      ? 'b'\n      : (targetType.type[4] as SwizzleableType);\n    const swizzleType =\n      swizzleLenToType[swizzleTypeChar][propLength as SwizzleLength];\n    if (swizzleType) {\n      return swizzleType;\n    }\n  }\n\n  return UnknownData;\n}\n\nexport function getTypeForIndexAccess(\n  dataType: AnyData,\n): AnyData | UnknownData {\n  // array\n  if (isWgslArray(dataType) || isDisarray(dataType)) {\n    return dataType.elementType as AnyData;\n  }\n\n  // vector or matrix\n  if (dataType.type in indexableTypeToResult) {\n    return indexableTypeToResult[\n      dataType.type as keyof typeof indexableTypeToResult\n    ];\n  }\n\n  return UnknownData;\n}\n\nexport function numericLiteralToSnippet(value: string): Snippet | undefined {\n  // Hex literals\n  if (/^0x[0-9a-f]+$/i.test(value)) {\n    return snip(value, abstractInt);\n  }\n\n  // Binary literals\n  if (/^0b[01]+$/i.test(value)) {\n    return snip(`${Number.parseInt(value.slice(2), 2)}`, abstractInt);\n  }\n\n  // Floating point literals\n  if (/^-?(?:\\d+\\.\\d*|\\d*\\.\\d+)$/i.test(value)) {\n    return snip(value, abstractFloat);\n  }\n\n  // Floating point literals with scientific notation\n  if (/^-?\\d+(?:\\.\\d+)?e-?\\d+$/i.test(value)) {\n    return snip(value, abstractFloat);\n  }\n\n  // Integer literals\n  if (/^-?\\d+$/i.test(value)) {\n    return snip(value, abstractInt);\n  }\n\n  return undefined;\n}\n\ntype ConversionAction = 'ref' | 'deref' | 'cast' | 'none';\n\ntype ConversionRankInfo =\n  | { rank: number; action: 'cast'; targetType: AnyData }\n  | { rank: number; action: Exclude<ConversionAction, 'cast'> };\n\nconst INFINITE_RANK: ConversionRankInfo = {\n  rank: Number.POSITIVE_INFINITY,\n  action: 'none',\n};\n\nfunction unwrapDecorated(data: AnyData): AnyData {\n  if (data.type === 'decorated') {\n    return data.inner as AnyData;\n  }\n  return data;\n}\n\nfunction getVectorComponent(type: AnyData): AnyData | undefined {\n  return isVec(type) ? vecTypeToPrimitive[type.type] : undefined;\n}\n\nfunction getAutoConversionRank(\n  src: AnyData,\n  dest: AnyData,\n): ConversionRankInfo {\n  const trueSrc = unwrapDecorated(src);\n  const trueDst = unwrapDecorated(dest);\n\n  if (trueSrc.type === trueDst.type) {\n    return { rank: 0, action: 'none' };\n  }\n\n  if (trueSrc.type === 'abstractFloat') {\n    if (trueDst.type === 'f32') return { rank: 1, action: 'none' };\n    if (trueDst.type === 'f16') return { rank: 2, action: 'none' };\n  }\n\n  if (trueSrc.type === 'abstractInt') {\n    if (trueDst.type === 'i32') return { rank: 3, action: 'none' };\n    if (trueDst.type === 'u32') return { rank: 4, action: 'none' };\n    if (trueDst.type === 'abstractFloat') return { rank: 5, action: 'none' };\n    if (trueDst.type === 'f32') return { rank: 6, action: 'none' };\n    if (trueDst.type === 'f16') return { rank: 7, action: 'none' };\n  }\n\n  if (isVec(trueSrc) && isVec(trueDst)) {\n    const compSrc = getVectorComponent(trueSrc);\n    const compDest = getVectorComponent(trueDst);\n    if (compSrc && compDest) {\n      return getAutoConversionRank(compSrc, compDest);\n    }\n  }\n\n  if (isMat(trueSrc) && isMat(trueDst)) {\n    // Matrix conversion rank depends only on component type (always f32 for now)\n    return { rank: 0, action: 'none' };\n  }\n\n  return INFINITE_RANK;\n}\n\nfunction getImplicitConversionRank(\n  src: AnyData,\n  dest: AnyData,\n): ConversionRankInfo {\n  const trueSrc = unwrapDecorated(src);\n  const trueDst = unwrapDecorated(dest);\n\n  if (\n    trueSrc.type === 'ptr' &&\n    getAutoConversionRank(trueSrc.inner as AnyData, trueDst).rank <\n      Number.POSITIVE_INFINITY\n  ) {\n    return { rank: 0, action: 'deref' };\n  }\n\n  if (\n    trueDst.type === 'ptr' &&\n    getAutoConversionRank(trueSrc, trueDst.inner as AnyData).rank <\n      Number.POSITIVE_INFINITY\n  ) {\n    return { rank: 1, action: 'ref' };\n  }\n\n  const primitivePreference = {\n    f32: 0,\n    f16: 1,\n    i32: 2,\n    u32: 3,\n    bool: 4,\n  } as const;\n  type PrimitiveType = keyof typeof primitivePreference;\n\n  if (\n    trueSrc.type in primitivePreference &&\n    trueDst.type in primitivePreference\n  ) {\n    const srcType = trueSrc.type as PrimitiveType;\n    const destType = trueDst.type as PrimitiveType;\n\n    if (srcType !== destType) {\n      const srcPref = primitivePreference[srcType];\n      const destPref = primitivePreference[destType];\n\n      const rank = destPref < srcPref ? 10 : 20;\n\n      return { rank: rank, action: 'cast', targetType: trueDst };\n    }\n  }\n\n  return INFINITE_RANK;\n}\n\nfunction getConversionRank(\n  src: AnyData,\n  dest: AnyData,\n  allowImplicit: boolean,\n): ConversionRankInfo {\n  const autoRank = getAutoConversionRank(src, dest);\n  if (autoRank.rank < Number.POSITIVE_INFINITY) {\n    return autoRank;\n  }\n  if (allowImplicit) {\n    return getImplicitConversionRank(src, dest);\n  }\n  return INFINITE_RANK;\n}\n\nexport type ConversionResultAction = {\n  sourceIndex: number;\n  action: ConversionAction;\n  targetType?: U32 | F32 | I32 | F16;\n};\n\nexport type ConversionResult = {\n  targetType: AnyData;\n  actions: ConversionResultAction[];\n  hasImplicitConversions?: boolean;\n};\n\nfunction findBestType(\n  types: AnyData[],\n  uniqueTypes: AnyData[],\n  allowImplicit: boolean,\n): ConversionResult | undefined {\n  let bestType: AnyData | undefined;\n  let minSum = Number.POSITIVE_INFINITY;\n  const conversionDetails = new Map<AnyData, ConversionRankInfo[]>();\n\n  for (const targetType of uniqueTypes) {\n    let currentSum = 0;\n    const currentDetails: ConversionRankInfo[] = [];\n    let possible = true;\n\n    for (const sourceType of types) {\n      const conversion = getConversionRank(\n        sourceType,\n        targetType,\n        allowImplicit,\n      );\n      if (conversion.rank === Number.POSITIVE_INFINITY) {\n        possible = false;\n        break;\n      }\n      currentSum += conversion.rank;\n      currentDetails.push(conversion);\n    }\n\n    if (possible && currentSum < minSum) {\n      minSum = currentSum;\n      bestType = targetType;\n      conversionDetails.set(bestType, currentDetails);\n    }\n  }\n\n  if (!bestType) {\n    return undefined;\n  }\n\n  const bestDetails = conversionDetails.get(bestType) as ConversionRankInfo[];\n  const actions: ConversionResultAction[] = bestDetails.map(\n    (detail, index) => ({\n      sourceIndex: index,\n      action: detail.action,\n      ...(detail.action === 'cast' && {\n        targetType: detail.targetType as U32 | F32 | I32 | F16,\n      }),\n    }),\n  );\n\n  const hasCasts = actions.some((action) => action.action === 'cast');\n\n  return { targetType: bestType, actions, hasImplicitConversions: hasCasts };\n}\n\nexport function concretize(type: AnyWgslData): AnyWgslData {\n  if (type.type === 'abstractFloat') {\n    return f32;\n  }\n\n  if (type.type === 'abstractInt') {\n    return i32;\n  }\n\n  return type;\n}\n\nexport function getBestConversion(\n  types: AnyData[],\n  targetTypes?: AnyData[],\n): ConversionResult | undefined {\n  if (types.length === 0) return undefined;\n\n  const uniqueTypes = [...new Set(types.map(unwrapDecorated))];\n  const uniqueTargetTypes = targetTypes\n    ? [...new Set(targetTypes.map(unwrapDecorated))]\n    : uniqueTypes;\n\n  const explicitResult = findBestType(types, uniqueTargetTypes, false);\n  if (explicitResult) {\n    return explicitResult;\n  }\n\n  const implicitResult = findBestType(types, uniqueTargetTypes, true);\n  if (implicitResult) {\n    implicitResult.hasImplicitConversions = implicitResult.actions.some(\n      (action) => action.action === 'cast',\n    );\n    return implicitResult;\n  }\n\n  return undefined;\n}\n\nexport function convertType(\n  sourceType: AnyData,\n  targetType: AnyData,\n  allowImplicit = true,\n): ConversionResult | undefined {\n  const conversion = getConversionRank(sourceType, targetType, allowImplicit);\n\n  if (conversion.rank < Number.POSITIVE_INFINITY) {\n    const actionDetail: ConversionResultAction = {\n      sourceIndex: 0,\n      action: conversion.action,\n    };\n    if (conversion.action === 'cast') {\n      actionDetail.targetType = conversion.targetType as U32 | F32 | I32 | F16;\n    }\n    return {\n      targetType: unwrapDecorated(targetType),\n      actions: [actionDetail],\n      hasImplicitConversions: conversion.action === 'cast',\n    };\n  }\n\n  return undefined;\n}\n\nexport type GenerationCtx = ResolutionCtx & {\n  readonly pre: string;\n  readonly callStack: unknown[];\n  indent(): string;\n  dedent(): string;\n  pushBlockScope(): void;\n  popBlockScope(): void;\n  getById(id: string): Snippet | null;\n  defineVariable(id: string, dataType: AnyWgslData | UnknownData): Snippet;\n};\n\nfunction applyActionToSnippet(\n  ctx: GenerationCtx,\n  value: Snippet,\n  action: ConversionResultAction,\n  targetType: AnyData,\n): Snippet {\n  if (action.action === 'none') {\n    return snip(value.value, targetType);\n  }\n\n  const resolvedValue = ctx.resolve(value.value);\n\n  switch (action.action) {\n    case 'ref':\n      return snip(`&${resolvedValue}`, targetType);\n    case 'deref':\n      return snip(`*${resolvedValue}`, targetType);\n    case 'cast': {\n      return snip(`${ctx.resolve(targetType)}(${resolvedValue})`, targetType);\n    }\n    default: {\n      assertExhaustive(action.action, 'applyActionToSnippet');\n    }\n  }\n}\n\nexport function convertToCommonType(\n  ctx: GenerationCtx,\n  values: Snippet[],\n  restrictTo?: AnyData[],\n): Snippet[] | undefined {\n  const types = values.map((value) => value.dataType);\n\n  if (types.some((type) => type === UnknownData)) {\n    return undefined;\n  }\n\n  const conversion = getBestConversion(types as AnyData[], restrictTo);\n  if (!conversion) {\n    return undefined;\n  }\n\n  if (conversion.hasImplicitConversions) {\n    console.warn(\n      `Implicit conversions from [\\n${\n        values\n          .map((v) => `  ${v.value}: ${v.dataType.type}`)\n          .join(\n            ',\\n',\n          )\n      }\\n] to ${conversion.targetType.type} are supported, but not recommended.\nConsider using explicit conversions instead.`,\n    );\n  }\n\n  return values.map((value, index) => {\n    const action = conversion.actions[index];\n    invariant(action, 'Action should not be undefined');\n    return applyActionToSnippet(ctx, value, action, conversion.targetType);\n  });\n}\n\nexport function convertStructValues(\n  ctx: GenerationCtx,\n  structType: AnyWgslStruct,\n  values: Record<string, Snippet>,\n): Snippet[] {\n  const propKeys = Object.keys(structType.propTypes);\n\n  return propKeys.map((key) => {\n    const val = values[key];\n    if (!val) {\n      throw new Error(`Missing property ${key}`);\n    }\n\n    const targetType = structType.propTypes[key];\n    const converted = convertToCommonType(ctx, [val], [targetType as AnyData]);\n    return converted?.[0] ?? val;\n  });\n}\n\nexport function coerceToSnippet(value: unknown): Snippet {\n  if (isSnippet(value)) {\n    // Already a snippet\n    return value;\n  }\n\n  if (hasInternalDataType(value)) {\n    // The value knows better about what type it is\n    return snip(value, value[$wgslDataType] as AnyData);\n  }\n\n  if (isVecInstance(value) || isMatInstance(value)) {\n    return snip(value, kindToSchema[value.kind]);\n  }\n\n  if (Array.isArray(value)) {\n    const coerced = value.map(coerceToSnippet).filter(Boolean);\n    const context = getResolutionCtx() as GenerationCtx | undefined;\n    if (!context) {\n      throw new Error('Tried to coerce array without a context');\n    }\n\n    const converted = convertToCommonType(context, coerced as Snippet[]);\n    const commonType = getBestConversion(\n      coerced.map((v) => v.dataType as AnyData),\n    )?.targetType as AnyWgslData | undefined;\n\n    if (!converted || !commonType) {\n      return snip(value, UnknownData);\n    }\n\n    return snip(\n      converted.map((v) => v.value).join(', '),\n      arrayOf(concretize(commonType), value.length),\n    );\n  }\n\n  if (\n    typeof value === 'string' || typeof value === 'function' ||\n    typeof value === 'object' || typeof value === 'symbol' ||\n    typeof value === 'undefined' || value === null\n  ) {\n    // Nothing representable in WGSL as-is, so unknown\n    return snip(value, UnknownData);\n  }\n\n  if (typeof value === 'number' || typeof value === 'bigint') {\n    return snip(\n      value,\n      numericLiteralToSnippet(String(value))?.dataType ?? UnknownData,\n    );\n  }\n\n  if (typeof value === 'boolean') {\n    return snip(value, bool);\n  }\n\n  return snip(value, UnknownData);\n}\n","import type { AnyData } from '../data/dataTypes.ts';\nimport type { BaseData } from '../data/wgslTypes.ts';\nimport {\n  extractGpuValueGetter,\n  type GpuValueGetter,\n} from '../extractGpuValueGetter.ts';\nimport { getName } from '../shared/meta.ts';\nimport { $providing, $wgslDataType } from '../shared/symbols.ts';\nimport { getTypeForPropAccess } from '../tgsl/generationHelpers.ts';\nimport type { ResolutionCtx, SelfResolvable } from '../types.ts';\n\nexport const valueProxyHandler: ProxyHandler<\n  & SelfResolvable\n  & { readonly [$wgslDataType]: BaseData }\n> = {\n  get(target, prop) {\n    if (prop in target) {\n      return Reflect.get(target, prop);\n    }\n\n    if (prop === $providing) {\n      return undefined;\n    }\n\n    if (\n      prop === 'toString' ||\n      prop === Symbol.toStringTag ||\n      prop === Symbol.toPrimitive\n    ) {\n      return () => target.toString();\n    }\n\n    return new Proxy(\n      {\n        '~resolve': (ctx: ResolutionCtx) =>\n          `${ctx.resolve(target)}.${String(prop)}`,\n\n        toString: () =>\n          `.value(...).${String(prop)}:${getName(target) ?? '<unnamed>'}`,\n\n        [$wgslDataType]: getTypeForPropAccess(\n          target[$wgslDataType] as AnyData,\n          String(prop),\n        ) as BaseData,\n      },\n      valueProxyHandler,\n    );\n  },\n};\n\nexport function getGpuValueRecursively<T>(\n  ctx: ResolutionCtx,\n  value: unknown,\n): T {\n  let unwrapped = value;\n  let valueGetter: GpuValueGetter | undefined;\n\n  // biome-ignore lint/suspicious/noAssignInExpressions: it's exactly what we want biome\n  while (valueGetter = extractGpuValueGetter(unwrapped)) {\n    unwrapped = valueGetter(ctx);\n  }\n\n  return unwrapped as T;\n}\n","import { FuncParameterType } from 'tinyest';\nimport { getAttributesString } from '../../data/attributes.ts';\nimport { type AnyData, snip } from '../../data/dataTypes.ts';\nimport {\n  type AnyWgslStruct,\n  isWgslData,\n  isWgslStruct,\n  Void,\n} from '../../data/wgslTypes.ts';\nimport { MissingLinksError } from '../../errors.ts';\nimport { getMetaData, getName, setName } from '../../shared/meta.ts';\nimport type { ResolutionCtx } from '../../types.ts';\nimport {\n  applyExternals,\n  type ExternalMap,\n  replaceExternalsInWgsl,\n} from '../resolve/externals.ts';\nimport { extractArgs } from './extractArgs.ts';\nimport type { Implementation } from './fnTypes.ts';\n\nexport interface FnCore {\n  applyExternals(newExternals: ExternalMap): void;\n  resolve(\n    ctx: ResolutionCtx,\n    argTypes: AnyData[],\n    returnType: AnyData,\n  ): string;\n}\n\nexport function createFnCore(\n  implementation: Implementation,\n  fnAttribute = '',\n): FnCore {\n  /**\n   * External application has to be deferred until resolution because\n   * some externals can reference the owner function which has not been\n   * initialized yet (like when accessing the Output struct of a vertex\n   * entry fn).\n   */\n  const externalsToApply: ExternalMap[] = [];\n\n  const core = {\n    applyExternals(newExternals: ExternalMap): void {\n      externalsToApply.push(newExternals);\n    },\n\n    resolve(\n      ctx: ResolutionCtx,\n      argTypes: AnyData[],\n      returnType: AnyData,\n    ): string {\n      const externalMap: ExternalMap = {};\n\n      for (const externals of externalsToApply) {\n        applyExternals(externalMap, externals);\n      }\n\n      const id = ctx.names.makeUnique(getName(this));\n\n      if (typeof implementation === 'string') {\n        const replacedImpl = replaceExternalsInWgsl(\n          ctx,\n          externalMap,\n          implementation,\n        );\n\n        let header = '';\n        let body = '';\n\n        if (fnAttribute !== '') {\n          const input = isWgslStruct(argTypes[0])\n            ? `(in: ${ctx.resolve(argTypes[0])})`\n            : '()';\n\n          const attributes = isWgslData(returnType)\n            ? getAttributesString(returnType)\n            : '';\n          const output = returnType !== Void\n            ? isWgslStruct(returnType)\n              ? `-> ${ctx.resolve(returnType)}`\n              : `-> ${attributes !== '' ? attributes : '@location(0)'} ${\n                ctx.resolve(returnType)\n              }`\n            : '';\n\n          header = `${input} ${output} `;\n          body = replacedImpl;\n        } else {\n          const providedArgs = extractArgs(replacedImpl);\n\n          if (providedArgs.args.length !== argTypes.length) {\n            throw new Error(\n              `WGSL implementation has ${providedArgs.args.length} arguments, while the shell has ${argTypes.length} arguments.`,\n            );\n          }\n\n          const input = providedArgs.args.map((argInfo, i) =>\n            `${argInfo.identifier}: ${\n              checkAndReturnType(\n                ctx,\n                `parameter ${argInfo.identifier}`,\n                argInfo.type,\n                argTypes[i],\n              )\n            }`\n          ).join(', ');\n\n          const output = returnType === Void ? '' : `-> ${\n            checkAndReturnType(\n              ctx,\n              'return type',\n              providedArgs.ret?.type,\n              returnType,\n            )\n          }`;\n\n          header = `(${input}) ${output}`;\n\n          body = replacedImpl.slice(providedArgs.range.end);\n        }\n\n        ctx.addDeclaration(`${fnAttribute}fn ${id}${header}${body}`);\n      } else {\n        // get data generated by the plugin\n        const pluginData = getMetaData(implementation);\n\n        if (pluginData?.externals) {\n          const missing = Object.fromEntries(\n            Object.entries(pluginData.externals).filter(\n              ([name]) => !(name in externalMap),\n            ),\n          );\n\n          applyExternals(externalMap, missing);\n        }\n\n        const ast = pluginData?.ast;\n        if (!ast) {\n          throw new Error(\n            \"Missing metadata for tgpu.fn function body (either missing 'kernel' directive, or misconfigured `unplugin-typegpu`)\",\n          );\n        }\n\n        // verify all required externals are present\n        const missingExternals = ast.externalNames.filter(\n          (name) => !(name in externalMap),\n        );\n        if (missingExternals.length > 0) {\n          throw new MissingLinksError(getName(this), missingExternals);\n        }\n\n        // generate wgsl string\n        const { head, body } = ctx.fnToWgsl({\n          args: argTypes.map((arg, i) =>\n            snip(\n              ast.params[i]?.type === FuncParameterType.identifier\n                ? ast.params[i].name\n                : `_arg_${i}`,\n              arg,\n            )\n          ),\n          argAliases: Object.fromEntries(\n            ast.params.flatMap((param, i) =>\n              param.type === FuncParameterType.destructuredObject\n                ? param.props.map(({ name, alias }) => [\n                  alias,\n                  snip(\n                    `_arg_${i}.${name}`,\n                    (argTypes[i] as AnyWgslStruct)\n                      .propTypes[name],\n                  ),\n                ])\n                : []\n            ),\n          ),\n          returnType,\n          body: ast.body,\n          externalMap,\n        });\n\n        ctx.addDeclaration(\n          `${fnAttribute}fn ${id}${ctx.resolve(head)}${ctx.resolve(body)}`,\n        );\n      }\n\n      return id;\n    },\n  };\n\n  // The implementation could have been given a name by a bundler plugin,\n  // so we try to transfer it to the core.\n  const maybeName = getName(implementation);\n  if (maybeName !== undefined) {\n    setName(core, maybeName);\n  }\n\n  return core;\n}\n\nfunction checkAndReturnType(\n  ctx: ResolutionCtx,\n  name: string,\n  wgslType: string | undefined,\n  jsType: unknown,\n) {\n  const resolvedJsType = ctx.resolve(jsType).replace(/\\s/g, '');\n\n  if (!wgslType) {\n    return resolvedJsType;\n  }\n\n  const resolvedWgslType = wgslType.replace(/\\s/g, '');\n\n  if (resolvedWgslType !== resolvedJsType) {\n    throw new Error(\n      `Type mismatch between TGPU shell and WGSL code string: ${name}, JS type \"${resolvedJsType}\", WGSL type \"${resolvedWgslType}\".`,\n    );\n  }\n\n  return wgslType;\n}\n","import { getAttributesString } from '../../data/attributes.ts';\nimport {\n  type AnyData,\n  type Disarray,\n  isLooseData,\n  type Unstruct,\n} from '../../data/dataTypes.ts';\nimport { formatToWGSLType } from '../../data/vertexFormatData.ts';\nimport type {\n  AnyWgslData,\n  BaseData,\n  Bool,\n  F16,\n  F32,\n  I32,\n  Mat2x2f,\n  Mat3x3f,\n  Mat4x4f,\n  U32,\n  Vec2b,\n  Vec2f,\n  Vec2h,\n  Vec2i,\n  Vec2u,\n  Vec3b,\n  Vec3f,\n  Vec3h,\n  Vec3i,\n  Vec3u,\n  Vec4b,\n  Vec4f,\n  Vec4h,\n  Vec4i,\n  Vec4u,\n  WgslArray,\n  WgslStruct,\n} from '../../data/wgslTypes.ts';\nimport { getName } from '../../shared/meta.ts';\nimport { assertExhaustive } from '../../shared/utilityTypes.ts';\nimport type { ResolutionCtx } from '../../types.ts';\nimport { isAttribute } from '../vertexLayout/connectAttributesToShader.ts';\n\n/**\n * Schemas for which their `type` property directly\n * translates to the resulting WGSL code.\n */\nconst identityTypes = [\n  'bool',\n  'f32',\n  'f16',\n  'i32',\n  'u32',\n  'vec2f',\n  'vec3f',\n  'vec4f',\n  'vec2h',\n  'vec3h',\n  'vec4h',\n  'vec2i',\n  'vec3i',\n  'vec4i',\n  'vec2u',\n  'vec3u',\n  'vec4u',\n  'vec2<bool>',\n  'vec3<bool>',\n  'vec4<bool>',\n  'mat2x2f',\n  'mat3x3f',\n  'mat4x4f',\n];\n\ntype IdentityType =\n  | Bool\n  | F32\n  | F16\n  | I32\n  | U32\n  | Vec2f\n  | Vec3f\n  | Vec4f\n  | Vec2h\n  | Vec3h\n  | Vec4h\n  | Vec2i\n  | Vec3i\n  | Vec4i\n  | Vec2u\n  | Vec3u\n  | Vec4u\n  | Vec2b\n  | Vec3b\n  | Vec4b\n  | Mat2x2f\n  | Mat3x3f\n  | Mat4x4f;\n\nfunction isIdentityType(data: AnyWgslData): data is IdentityType {\n  return identityTypes.includes(data.type);\n}\n\n/**\n * Resolves a single property of a struct.\n * @param ctx - The resolution context.\n * @param key - The key of the property.\n * @param property - The property itself.\n *\n * @returns The resolved property string.\n */\nfunction resolveStructProperty(\n  ctx: ResolutionCtx,\n  [key, property]: [string, BaseData],\n) {\n  return `  ${getAttributesString(property)}${key}: ${\n    ctx.resolve(property as AnyWgslData)\n  },\\n`;\n}\n\n/**\n * Resolves a struct and adds its declaration to the resolution context.\n * @param ctx - The resolution context.\n * @param struct - The struct to resolve.\n *\n * @returns The resolved struct name.\n */\nfunction resolveStruct(ctx: ResolutionCtx, struct: WgslStruct) {\n  const id = ctx.names.makeUnique(getName(struct));\n\n  ctx.addDeclaration(`\nstruct ${id} {\n${\n    Object.entries(struct.propTypes)\n      .map((prop) => resolveStructProperty(ctx, prop))\n      .join('')\n  }\\\n}\\n`);\n\n  return id;\n}\n\n/**\n * Resolves an unstruct (struct that does not align data by default) to its struct data counterpart.\n * @param ctx - The resolution context.\n * @param unstruct - The unstruct to resolve.\n *\n * @returns The resolved unstruct name.\n *\n * @example\n * ```ts\n * resolveUnstruct(ctx, {\n *   uv: d.float16x2, // -> d.vec2f after resolution\n *   color: d.snorm8x4, -> d.vec4f after resolution\n * });\n * ```\n */\nfunction resolveUnstruct(ctx: ResolutionCtx, unstruct: Unstruct) {\n  const id = ctx.names.makeUnique(getName(unstruct));\n\n  ctx.addDeclaration(`\nstruct ${id} {\n${\n    Object.entries(unstruct.propTypes)\n      .map((prop) =>\n        isAttribute(prop[1])\n          ? resolveStructProperty(ctx, [\n            prop[0],\n            formatToWGSLType[prop[1].format],\n          ])\n          : resolveStructProperty(ctx, prop)\n      )\n      .join('')\n  }\n}\\n`);\n\n  return id;\n}\n\n/**\n * Resolves an array.\n * @param ctx - The resolution context.\n * @param array - The array to resolve.\n *\n * @returns The resolved array name along with its element type and count (if not runtime-sized).\n *\n * @example\n * ```ts\n * resolveArray(ctx, d.arrayOf(d.u32, 0)); // 'array<u32>' (not a real pattern, a function is preferred)\n * resolveArray(ctx, d.arrayOf(d.u32, 5)); // 'array<u32, 5>'\n * ```\n */\nfunction resolveArray(ctx: ResolutionCtx, array: WgslArray) {\n  const element = ctx.resolve(array.elementType as AnyWgslData);\n\n  return array.elementCount === 0\n    ? `array<${element}>`\n    : `array<${element}, ${array.elementCount}>`;\n}\n\nfunction resolveDisarray(ctx: ResolutionCtx, disarray: Disarray) {\n  const element = ctx.resolve(\n    isAttribute(disarray.elementType)\n      ? formatToWGSLType[disarray.elementType.format]\n      : (disarray.elementType as AnyWgslData),\n  );\n\n  return disarray.elementCount === 0\n    ? `array<${element}>`\n    : `array<${element}, ${disarray.elementCount}>`;\n}\n\n/**\n * Resolves a WGSL data-type schema to a string.\n * @param ctx - The resolution context.\n * @param data - The data-type to resolve.\n *\n * @returns The resolved data-type string.\n */\nexport function resolveData(ctx: ResolutionCtx, data: AnyData): string {\n  if (isLooseData(data)) {\n    if (data.type === 'unstruct') {\n      return resolveUnstruct(ctx, data);\n    }\n\n    if (data.type === 'disarray') {\n      return resolveDisarray(ctx, data);\n    }\n\n    if (data.type === 'loose-decorated') {\n      return ctx.resolve(\n        isAttribute(data.inner)\n          ? formatToWGSLType[data.inner.format]\n          : data.inner,\n      );\n    }\n\n    return ctx.resolve(formatToWGSLType[data.type]);\n  }\n\n  if (isIdentityType(data)) {\n    return data.type;\n  }\n\n  if (data.type === 'struct') {\n    return resolveStruct(ctx, data);\n  }\n\n  if (data.type === 'array') {\n    return resolveArray(ctx, data);\n  }\n\n  if (data.type === 'atomic') {\n    return `atomic<${resolveData(ctx, data.inner)}>`;\n  }\n\n  if (data.type === 'decorated') {\n    return ctx.resolve(data.inner as AnyWgslData);\n  }\n\n  if (data.type === 'ptr') {\n    if (data.addressSpace === 'storage') {\n      return `ptr<storage, ${ctx.resolve(data.inner)}, ${\n        data.access === 'read-write' ? 'read_write' : data.access\n      }>`;\n    }\n    return `ptr<${data.addressSpace}, ${ctx.resolve(data.inner)}>`;\n  }\n\n  if (\n    data.type === 'abstractInt' || data.type === 'abstractFloat' ||\n    data.type === 'void' || data.type === 'u16'\n  ) {\n    throw new Error(`${data.type} has no representation in WGSL`);\n  }\n\n  assertExhaustive(data, 'resolveData');\n}\n","import { roundUp } from '../mathUtils.ts';\nimport type { Infer } from '../shared/repr.ts';\nimport { alignmentOf } from './alignmentOf.ts';\nimport { isDisarray, isUnstruct } from './dataTypes.ts';\nimport { offsetsForProps } from './offsets.ts';\nimport { sizeOf } from './sizeOf.ts';\nimport * as wgsl from './wgslTypes.ts';\n\nexport const EVAL_ALLOWED_IN_ENV: boolean = (() => {\n  try {\n    new Function('return true');\n    return true;\n  } catch {\n    return false;\n  }\n})();\n\nconst compiledWriters = new WeakMap<\n  wgsl.BaseData,\n  (\n    output: DataView,\n    offset: number,\n    value: unknown,\n    littleEndian?: boolean,\n  ) => void\n>();\n\nconst typeToPrimitive = {\n  u32: 'u32',\n  vec2u: 'u32',\n  vec3u: 'u32',\n  vec4u: 'u32',\n  u16: 'u16',\n\n  i32: 'i32',\n  vec2i: 'i32',\n  vec3i: 'i32',\n  vec4i: 'i32',\n\n  f32: 'f32',\n  vec2f: 'f32',\n  vec3f: 'f32',\n  vec4f: 'f32',\n\n  vec2h: 'f32',\n  vec3h: 'f32',\n  vec4h: 'f32',\n\n  mat2x2f: 'f32',\n  mat3x3f: 'f32',\n  mat4x4f: 'f32',\n} as const;\n\nconst primitiveToWriteFunction = {\n  u32: 'setUint32',\n  i32: 'setInt32',\n  f32: 'setFloat32',\n  u16: 'setUint16',\n} as const;\n\nexport function buildWriter(\n  node: wgsl.BaseData,\n  offsetExpr: string,\n  valueExpr: string,\n): string {\n  if (wgsl.isAtomic(node) || wgsl.isDecorated(node)) {\n    return buildWriter(node.inner, offsetExpr, valueExpr);\n  }\n\n  if (wgsl.isWgslStruct(node) || isUnstruct(node)) {\n    const propOffsets = offsetsForProps(node);\n    const sortedProps = Object.entries(propOffsets).sort(\n      (a, b) => a[1].offset - b[1].offset,\n    );\n    let code = '';\n    for (const [key, propOffset] of sortedProps) {\n      const subSchema = node.propTypes[key];\n      if (!subSchema) continue;\n      code += buildWriter(\n        subSchema,\n        `(${offsetExpr} + ${propOffset.offset})`,\n        `${valueExpr}.${key}`,\n      );\n    }\n    return code;\n  }\n\n  if (wgsl.isWgslArray(node) || isDisarray(node)) {\n    const arrSchema = node as wgsl.WgslArray;\n    const elementSize = roundUp(\n      sizeOf(arrSchema.elementType),\n      alignmentOf(arrSchema.elementType),\n    );\n    let code = '';\n\n    code += `for (let i = 0; i < ${arrSchema.elementCount}; i++) {\\n`;\n    code += buildWriter(\n      arrSchema.elementType,\n      `(${offsetExpr} + i * ${elementSize})`,\n      `${valueExpr}[i]`,\n    );\n    code += '}\\n';\n\n    return code;\n  }\n\n  if (wgsl.isVec(node)) {\n    const primitive = typeToPrimitive[node.type];\n    let code = '';\n    const writeFunc = primitiveToWriteFunction[primitive];\n    const components = ['x', 'y', 'z', 'w'];\n    const count = wgsl.isVec2(node) ? 2 : wgsl.isVec3(node) ? 3 : 4;\n\n    for (let i = 0; i < count; i++) {\n      code += `output.${writeFunc}((${offsetExpr} + ${i * 4}), ${valueExpr}.${\n        components[i]\n      }, littleEndian);\\n`;\n    }\n    return code;\n  }\n\n  if (wgsl.isMat(node)) {\n    const primitive = typeToPrimitive[node.type];\n    const writeFunc = primitiveToWriteFunction[primitive];\n\n    const matSize = wgsl.isMat2x2f(node) ? 2 : wgsl.isMat3x3f(node) ? 3 : 4;\n    const elementCount = matSize * matSize;\n    const rowStride = roundUp(matSize * 4, 8);\n\n    let code = '';\n    for (let i = 0; i < elementCount; i++) {\n      const colIndex = Math.floor(i / matSize);\n      const rowIndex = i % matSize;\n      const byteOffset = colIndex * rowStride + rowIndex * 4;\n\n      code +=\n        `output.${writeFunc}((${offsetExpr} + ${byteOffset}), ${valueExpr}.columns[${colIndex}].${\n          ['x', 'y', 'z', 'w'][rowIndex]\n        }, littleEndian);\\n`;\n    }\n\n    return code;\n  }\n\n  const primitive = typeToPrimitive[node.type as keyof typeof typeToPrimitive];\n  return `output.${\n    primitiveToWriteFunction[primitive]\n  }(${offsetExpr}, ${valueExpr}, littleEndian);\\n`;\n}\n\nexport function getCompiledWriterForSchema<T extends wgsl.BaseData>(\n  schema: T,\n): (\n  output: DataView,\n  offset: number,\n  value: Infer<T>,\n  littleEndian?: boolean,\n) => void {\n  if (compiledWriters.has(schema)) {\n    return compiledWriters.get(schema) as (\n      output: DataView,\n      offset: number,\n      value: Infer<T>,\n      littleEndian?: boolean,\n    ) => void;\n  }\n\n  const body = buildWriter(schema, 'offset', 'value');\n\n  const fn = new Function(\n    'output',\n    'offset',\n    'value',\n    'littleEndian=true',\n    body,\n  ) as (\n    output: DataView,\n    offset: number,\n    value: Infer<T> | unknown,\n    littleEndian?: boolean,\n  ) => void;\n\n  compiledWriters.set(schema, fn);\n\n  return fn;\n}\n","import type { ISerialInput, ISerialOutput } from 'typed-binary';\nimport type { Infer, InferRecord } from '../shared/repr.ts';\nimport alignIO from './alignIO.ts';\nimport { alignmentOf, customAlignmentOf } from './alignmentOf.ts';\nimport type {\n  AnyConcreteData,\n  AnyData,\n  Disarray,\n  LooseDecorated,\n  Unstruct,\n} from './dataTypes.ts';\nimport { mat2x2f, mat3x3f, mat4x4f } from './matrix.ts';\nimport { sizeOf } from './sizeOf.ts';\nimport {\n  vec2f,\n  vec2h,\n  vec2i,\n  vec2u,\n  vec3f,\n  vec3h,\n  vec3i,\n  vec3u,\n  vec4f,\n  vec4h,\n  vec4i,\n  vec4u,\n} from './vector.ts';\nimport type * as wgsl from './wgslTypes.ts';\n\ntype DataWriter<TSchema extends wgsl.BaseData> = (\n  output: ISerialOutput,\n  schema: TSchema,\n  value: Infer<TSchema>,\n) => void;\n\ntype DataReader<TSchema extends wgsl.BaseData> = (\n  input: ISerialInput,\n  schema: TSchema,\n) => Infer<TSchema>;\n\ntype CompleteDataWriters = {\n  [TType in AnyConcreteData['type']]: DataWriter<\n    Extract<AnyData, { readonly type: TType }>\n  >;\n};\n\ntype CompleteDataReaders = {\n  [TType in AnyConcreteData['type']]: DataReader<\n    Extract<AnyData, { readonly type: TType }>\n  >;\n};\n\nconst dataWriters = {\n  bool() {\n    throw new Error('Booleans are not host-shareable');\n  },\n\n  f32(output, _schema: wgsl.F32, value: number) {\n    output.writeFloat32(value);\n  },\n\n  f16(output, _schema: wgsl.F16, value: number) {\n    output.writeFloat16(value);\n  },\n\n  i32(output, _schema: wgsl.I32, value: number) {\n    output.writeInt32(value);\n  },\n\n  u32(output, _schema: wgsl.U32, value: number) {\n    output.writeUint32(value);\n  },\n\n  u16(output, _schema: wgsl.U16, value: number) {\n    output.writeUint16(value);\n  },\n\n  vec2f(output, _, value: wgsl.v2f) {\n    output.writeFloat32(value.x);\n    output.writeFloat32(value.y);\n  },\n\n  vec2h(output, _, value: wgsl.v2h) {\n    output.writeFloat16(value.x);\n    output.writeFloat16(value.y);\n  },\n\n  vec2i(output, _, value: wgsl.v2i) {\n    output.writeInt32(value.x);\n    output.writeInt32(value.y);\n  },\n\n  vec2u(output, _, value: wgsl.v2u) {\n    output.writeUint32(value.x);\n    output.writeUint32(value.y);\n  },\n\n  'vec2<bool>'() {\n    throw new Error('Booleans are not host-shareable');\n  },\n\n  vec3f(output, _, value: wgsl.v3f) {\n    output.writeFloat32(value.x);\n    output.writeFloat32(value.y);\n    output.writeFloat32(value.z);\n  },\n\n  vec3h(output, _, value: wgsl.v3h) {\n    output.writeFloat16(value.x);\n    output.writeFloat16(value.y);\n    output.writeFloat16(value.z);\n  },\n\n  vec3i(output, _, value: wgsl.v3i) {\n    output.writeInt32(value.x);\n    output.writeInt32(value.y);\n    output.writeInt32(value.z);\n  },\n\n  vec3u(output, _, value: wgsl.v3u) {\n    output.writeUint32(value.x);\n    output.writeUint32(value.y);\n    output.writeUint32(value.z);\n  },\n\n  'vec3<bool>'() {\n    throw new Error('Booleans are not host-shareable');\n  },\n\n  vec4f(output, _, value: wgsl.v4f) {\n    output.writeFloat32(value.x);\n    output.writeFloat32(value.y);\n    output.writeFloat32(value.z);\n    output.writeFloat32(value.w);\n  },\n\n  vec4h(output, _, value: wgsl.v4h) {\n    output.writeFloat16(value.x);\n    output.writeFloat16(value.y);\n    output.writeFloat16(value.z);\n    output.writeFloat16(value.w);\n  },\n\n  vec4i(output, _, value: wgsl.v4i) {\n    output.writeInt32(value.x);\n    output.writeInt32(value.y);\n    output.writeInt32(value.z);\n    output.writeInt32(value.w);\n  },\n\n  vec4u(output, _, value: wgsl.v4u) {\n    output.writeUint32(value.x);\n    output.writeUint32(value.y);\n    output.writeUint32(value.z);\n    output.writeUint32(value.w);\n  },\n\n  'vec4<bool>'() {\n    throw new Error('Booleans are not host-shareable');\n  },\n\n  mat2x2f(output, _, value: wgsl.m2x2f) {\n    for (let i = 0; i < value.length; ++i) {\n      output.writeFloat32(value[i] as number);\n    }\n  },\n\n  mat3x3f(output, _, value: wgsl.m3x3f) {\n    for (let i = 0; i < value.length; ++i) {\n      output.writeFloat32(value[i] as number);\n    }\n  },\n\n  mat4x4f(output, _, value: wgsl.m4x4f) {\n    for (let i = 0; i < value.length; ++i) {\n      output.writeFloat32(value[i] as number);\n    }\n  },\n\n  struct(\n    output,\n    schema: wgsl.WgslStruct,\n    value: InferRecord<Record<string, wgsl.BaseData>>,\n  ) {\n    const alignment = alignmentOf(schema);\n    alignIO(output, alignment);\n\n    for (const [key, property] of Object.entries(schema.propTypes)) {\n      alignIO(output, alignmentOf(property));\n      writeData(output, property, value[key] as wgsl.BaseData);\n    }\n\n    alignIO(output, alignment);\n  },\n\n  array(output, schema: wgsl.WgslArray, value: Infer<wgsl.BaseData>[]) {\n    if (schema.elementCount === 0) {\n      throw new Error('Cannot write using a runtime-sized schema.');\n    }\n\n    const alignment = alignmentOf(schema);\n    alignIO(output, alignment);\n    const beginning = output.currentByteOffset;\n    for (let i = 0; i < Math.min(schema.elementCount, value.length); i++) {\n      alignIO(output, alignment);\n      writeData(output, schema.elementType, value[i]);\n    }\n    output.seekTo(beginning + sizeOf(schema));\n  },\n\n  ptr() {\n    throw new Error('Pointers are not host-shareable');\n  },\n\n  atomic(output, schema: wgsl.Atomic, value: number) {\n    dataWriters[schema.inner.type]?.(output, schema, value);\n  },\n\n  decorated(output, schema: wgsl.Decorated, value: unknown) {\n    const alignment = customAlignmentOf(schema);\n    alignIO(output, alignment);\n\n    const beginning = output.currentByteOffset;\n    dataWriters[(schema.inner as AnyData)?.type]?.(output, schema.inner, value);\n    output.seekTo(beginning + sizeOf(schema));\n  },\n\n  // Loose Types\n\n  uint8(output, _, value: number) {\n    output.writeUint8(value);\n  },\n  uint8x2(output, _, value: wgsl.v2u) {\n    output.writeUint8(value.x);\n    output.writeUint8(value.y);\n  },\n  uint8x4(output, _, value: wgsl.v4u) {\n    output.writeUint8(value.x);\n    output.writeUint8(value.y);\n    output.writeUint8(value.z);\n    output.writeUint8(value.w);\n  },\n  sint8(output, _, value: number) {\n    output.writeInt8(value);\n  },\n  sint8x2(output, _, value: wgsl.v2i) {\n    output.writeInt8(value.x);\n    output.writeInt8(value.y);\n  },\n  sint8x4(output, _, value: wgsl.v4i) {\n    output.writeInt8(value.x);\n    output.writeInt8(value.y);\n    output.writeInt8(value.z);\n    output.writeInt8(value.w);\n  },\n  unorm8(output, _, value: number) {\n    output.writeUint8(value * 255);\n  },\n  unorm8x2(output, _, value: wgsl.v2f) {\n    output.writeUint8(value.x * 255);\n    output.writeUint8(value.y * 255);\n  },\n  unorm8x4(output, _, value: wgsl.v4f) {\n    output.writeUint8(value.x * 255);\n    output.writeUint8(value.y * 255);\n    output.writeUint8(value.z * 255);\n    output.writeUint8(value.w * 255);\n  },\n  snorm8(output, _, value: number) {\n    output.writeUint8(value * 127 + 128);\n  },\n  snorm8x2(output, _, value: wgsl.v2f) {\n    output.writeUint8(value.x * 127 + 128);\n    output.writeUint8(value.y * 127 + 128);\n  },\n  snorm8x4(output, _, value: wgsl.v4f) {\n    output.writeUint8(value.x * 127 + 128);\n    output.writeUint8(value.y * 127 + 128);\n    output.writeUint8(value.z * 127 + 128);\n    output.writeUint8(value.w * 127 + 128);\n  },\n  uint16(output, _, value: number) {\n    output.writeUint16(value);\n  },\n  uint16x2(output, _, value: wgsl.v2u) {\n    output.writeUint16(value.x);\n    output.writeUint16(value.y);\n  },\n  uint16x4(output, _, value: wgsl.v4u) {\n    output.writeUint16(value.x);\n    output.writeUint16(value.y);\n    output.writeUint16(value.z);\n    output.writeUint16(value.w);\n  },\n  sint16(output, _, value: number) {\n    output.writeInt16(value);\n  },\n  sint16x2(output, _, value: wgsl.v2i) {\n    output.writeInt16(value.x);\n    output.writeInt16(value.y);\n  },\n  sint16x4(output, _, value: wgsl.v4i) {\n    output.writeInt16(value.x);\n    output.writeInt16(value.y);\n    output.writeInt16(value.z);\n    output.writeInt16(value.w);\n  },\n  unorm16(output, _, value: number) {\n    output.writeUint16(value * 65535);\n  },\n  unorm16x2(output, _, value: wgsl.v2f) {\n    output.writeUint16(value.x * 65535);\n    output.writeUint16(value.y * 65535);\n  },\n  unorm16x4(output, _, value: wgsl.v4f) {\n    output.writeUint16(value.x * 65535);\n    output.writeUint16(value.y * 65535);\n    output.writeUint16(value.z * 65535);\n    output.writeUint16(value.w * 65535);\n  },\n  snorm16(output, _, value: number) {\n    output.writeUint16(value * 32767 + 32768);\n  },\n  snorm16x2(output, _, value: wgsl.v2f) {\n    output.writeUint16(value.x * 32767 + 32768);\n    output.writeUint16(value.y * 32767 + 32768);\n  },\n  snorm16x4(output, _, value: wgsl.v4f) {\n    output.writeUint16(value.x * 32767 + 32768);\n    output.writeUint16(value.y * 32767 + 32768);\n    output.writeUint16(value.z * 32767 + 32768);\n    output.writeUint16(value.w * 32767 + 32768);\n  },\n  float16(output, _, value: number) {\n    output.writeFloat16(value);\n  },\n  float16x2(output, _, value: wgsl.v2f) {\n    output.writeFloat16(value.x);\n    output.writeFloat16(value.y);\n  },\n  float16x4(output, _, value: wgsl.v4f) {\n    output.writeFloat16(value.x);\n    output.writeFloat16(value.y);\n    output.writeFloat16(value.z);\n    output.writeFloat16(value.w);\n  },\n  float32(output, _, value: number) {\n    output.writeFloat32(value);\n  },\n  float32x2(output, _, value: wgsl.v2f) {\n    output.writeFloat32(value.x);\n    output.writeFloat32(value.y);\n  },\n  float32x3(output, _, value: wgsl.v3f) {\n    output.writeFloat32(value.x);\n    output.writeFloat32(value.y);\n    output.writeFloat32(value.z);\n  },\n  float32x4(output, _, value: wgsl.v4f) {\n    output.writeFloat32(value.x);\n    output.writeFloat32(value.y);\n    output.writeFloat32(value.z);\n    output.writeFloat32(value.w);\n  },\n  uint32(output, _, value: number) {\n    output.writeUint32(value);\n  },\n  uint32x2(output, _, value: wgsl.v2u) {\n    output.writeUint32(value.x);\n    output.writeUint32(value.y);\n  },\n  uint32x3(output, _, value: wgsl.v3u) {\n    output.writeUint32(value.x);\n    output.writeUint32(value.y);\n    output.writeUint32(value.z);\n  },\n  uint32x4(output, _, value: wgsl.v4u) {\n    output.writeUint32(value.x);\n    output.writeUint32(value.y);\n    output.writeUint32(value.z);\n    output.writeUint32(value.w);\n  },\n  sint32(output, _, value: number) {\n    output.writeInt32(value);\n  },\n  sint32x2(output, _, value: wgsl.v2i) {\n    output.writeInt32(value.x);\n    output.writeInt32(value.y);\n  },\n  sint32x3(output, _, value: wgsl.v3i) {\n    output.writeInt32(value.x);\n    output.writeInt32(value.y);\n    output.writeInt32(value.z);\n  },\n  sint32x4(output, _, value: wgsl.v4i) {\n    output.writeInt32(value.x);\n    output.writeInt32(value.y);\n    output.writeInt32(value.z);\n    output.writeInt32(value.w);\n  },\n  'unorm10-10-10-2'(output, _, value: wgsl.v4f) {\n    let packed = 0;\n    packed |= ((value.x * 1023) & 1023) << 22; // r (10 bits)\n    packed |= ((value.x * 1023) & 1023) << 12; // g (10 bits)\n    packed |= ((value.y * 1023) & 1023) << 2; // b (10 bits)\n    packed |= (value.z * 3) & 3; // a (2 bits)\n    output.writeUint32(packed);\n  },\n  'unorm8x4-bgra'(output, _, value: wgsl.v4f) {\n    output.writeUint8(value.z * 255);\n    output.writeUint8(value.y * 255);\n    output.writeUint8(value.x * 255);\n    output.writeUint8(value.w * 255);\n  },\n\n  disarray(output, schema: Disarray, value: unknown[]) {\n    const alignment = alignmentOf(schema);\n\n    alignIO(output, alignment);\n    const beginning = output.currentByteOffset;\n    for (let i = 0; i < Math.min(schema.elementCount, value.length); i++) {\n      alignIO(output, alignment);\n      dataWriters[(schema.elementType as AnyData)?.type]?.(\n        output,\n        schema.elementType,\n        value[i],\n      );\n    }\n\n    output.seekTo(beginning + sizeOf(schema));\n  },\n\n  unstruct(output, schema: Unstruct, value) {\n    for (const [key, property] of Object.entries(schema.propTypes)) {\n      dataWriters[property.type]?.(output, property, value[key]);\n    }\n  },\n\n  'loose-decorated'(output, schema: LooseDecorated, value: unknown) {\n    const alignment = customAlignmentOf(schema);\n    alignIO(output, alignment);\n\n    const beginning = output.currentByteOffset;\n    const writer = dataWriters[(schema.inner as AnyData)?.type];\n    writer?.(output, schema.inner, value);\n    output.seekTo(beginning + sizeOf(schema));\n    return value;\n  },\n} satisfies CompleteDataWriters as Record<\n  string,\n  (output: ISerialOutput, schema: unknown, value: unknown) => void\n>;\n\nexport function writeData<TData extends wgsl.BaseData>(\n  output: ISerialOutput,\n  schema: TData,\n  value: Infer<TData>,\n): void {\n  const writer = dataWriters[schema.type];\n  if (!writer) {\n    throw new Error(`Cannot write data of type '${schema.type}'.`);\n  }\n\n  writer(output, schema, value);\n}\n\nconst dataReaders = {\n  bool(): boolean {\n    throw new Error('Booleans are not host-shareable');\n  },\n\n  f32(input: ISerialInput): number {\n    return input.readFloat32();\n  },\n\n  f16(input: ISerialInput): number {\n    return input.readFloat16();\n  },\n\n  i32(input: ISerialInput): number {\n    return input.readInt32();\n  },\n\n  u32(input: ISerialInput): number {\n    return input.readUint32();\n  },\n\n  u16(input: ISerialInput): number {\n    return input.readUint16();\n  },\n\n  vec2f(input: ISerialInput): wgsl.v2f {\n    return vec2f(input.readFloat32(), input.readFloat32());\n  },\n\n  vec3f(input: ISerialInput): wgsl.v3f {\n    return vec3f(input.readFloat32(), input.readFloat32(), input.readFloat32());\n  },\n\n  vec4f(input: ISerialInput): wgsl.v4f {\n    return vec4f(\n      input.readFloat32(),\n      input.readFloat32(),\n      input.readFloat32(),\n      input.readFloat32(),\n    );\n  },\n\n  vec2h(input): wgsl.v2h {\n    return vec2h(input.readFloat16(), input.readFloat16());\n  },\n\n  vec3h(input: ISerialInput): wgsl.v3h {\n    return vec3h(input.readFloat16(), input.readFloat16(), input.readFloat16());\n  },\n\n  vec4h(input: ISerialInput): wgsl.v4h {\n    return vec4h(\n      input.readFloat16(),\n      input.readFloat16(),\n      input.readFloat16(),\n      input.readFloat16(),\n    );\n  },\n\n  vec2i(input): wgsl.v2i {\n    return vec2i(input.readInt32(), input.readInt32());\n  },\n\n  vec3i(input: ISerialInput): wgsl.v3i {\n    return vec3i(input.readInt32(), input.readInt32(), input.readInt32());\n  },\n\n  vec4i(input: ISerialInput): wgsl.v4i {\n    return vec4i(\n      input.readInt32(),\n      input.readInt32(),\n      input.readInt32(),\n      input.readInt32(),\n    );\n  },\n\n  vec2u(input): wgsl.v2u {\n    return vec2u(input.readUint32(), input.readUint32());\n  },\n\n  vec3u(input: ISerialInput): wgsl.v3u {\n    return vec3u(input.readUint32(), input.readUint32(), input.readUint32());\n  },\n\n  vec4u(input: ISerialInput): wgsl.v4u {\n    return vec4u(\n      input.readUint32(),\n      input.readUint32(),\n      input.readUint32(),\n      input.readUint32(),\n    );\n  },\n\n  'vec2<bool>'() {\n    throw new Error('Booleans are not host-shareable');\n  },\n\n  'vec3<bool>'() {\n    throw new Error('Booleans are not host-shareable');\n  },\n\n  'vec4<bool>'() {\n    throw new Error('Booleans are not host-shareable');\n  },\n\n  mat2x2f(input: ISerialInput): wgsl.m2x2f {\n    return mat2x2f(\n      input.readFloat32(),\n      input.readFloat32(),\n      input.readFloat32(),\n      input.readFloat32(),\n    );\n  },\n\n  mat3x3f(input: ISerialInput): wgsl.m3x3f {\n    const skipOneAfter = () => {\n      const value = input.readFloat32();\n      input.readFloat32(); // skipping;\n      return value;\n    };\n\n    return mat3x3f(\n      input.readFloat32(),\n      input.readFloat32(),\n      skipOneAfter(),\n      //\n      input.readFloat32(),\n      input.readFloat32(),\n      skipOneAfter(),\n      //\n      input.readFloat32(),\n      input.readFloat32(),\n      skipOneAfter(),\n    );\n  },\n\n  mat4x4f(input: ISerialInput): wgsl.m4x4f {\n    return mat4x4f(\n      input.readFloat32(),\n      input.readFloat32(),\n      input.readFloat32(),\n      input.readFloat32(),\n      //\n      input.readFloat32(),\n      input.readFloat32(),\n      input.readFloat32(),\n      input.readFloat32(),\n      //\n      input.readFloat32(),\n      input.readFloat32(),\n      input.readFloat32(),\n      input.readFloat32(),\n      //\n      input.readFloat32(),\n      input.readFloat32(),\n      input.readFloat32(),\n      input.readFloat32(),\n    );\n  },\n\n  struct(input: ISerialInput, schema: wgsl.WgslStruct) {\n    const alignment = alignmentOf(schema);\n    alignIO(input, alignment);\n    const result = {} as Record<string, unknown>;\n\n    for (const [key, property] of Object.entries(schema.propTypes)) {\n      alignIO(input, alignmentOf(property));\n      result[key] = readData(input, property);\n    }\n\n    alignIO(input, alignment);\n    return result as InferRecord<Record<string, wgsl.BaseData>>;\n  },\n\n  array(input, schema) {\n    if (schema.elementCount === 0) {\n      throw new Error('Cannot read using a runtime-sized schema.');\n    }\n\n    const alignment = alignmentOf(schema);\n    const elements: unknown[] = [];\n\n    for (let i = 0; i < schema.elementCount; i++) {\n      alignIO(input, alignment);\n      const elementType = schema.elementType as wgsl.AnyWgslData;\n      const value = readData(input, elementType);\n      elements.push(value);\n    }\n\n    alignIO(input, alignment);\n    return elements as never[];\n  },\n\n  ptr() {\n    throw new Error('Pointers are not host-shareable');\n  },\n\n  atomic(input, schema: wgsl.Atomic): number {\n    return readData(input, schema.inner);\n  },\n\n  decorated(input, schema: wgsl.Decorated) {\n    const alignment = customAlignmentOf(schema);\n    alignIO(input, alignment);\n\n    const beginning = input.currentByteOffset;\n    const value = readData(input, schema.inner);\n    input.seekTo(beginning + sizeOf(schema));\n    return value as never;\n  },\n\n  // Loose Types\n\n  uint8: (i) => i.readUint8(),\n  uint8x2: (i) => vec2u(i.readUint8(), i.readUint8()),\n  uint8x4: (i) =>\n    vec4u(i.readUint8(), i.readUint8(), i.readUint8(), i.readUint8()),\n  sint8: (i) => i.readInt8(),\n  sint8x2: (i) => {\n    return vec2i(i.readInt8(), i.readInt8());\n  },\n  sint8x4: (i) => vec4i(i.readInt8(), i.readInt8(), i.readInt8(), i.readInt8()),\n  unorm8: (i) => i.readUint8() / 255,\n  unorm8x2: (i) => vec2f(i.readUint8() / 255, i.readUint8() / 255),\n  unorm8x4: (i) =>\n    vec4f(\n      i.readUint8() / 255,\n      i.readUint8() / 255,\n      i.readUint8() / 255,\n      i.readUint8() / 255,\n    ),\n  snorm8: (i) => (i.readUint8() - 128) / 127,\n  snorm8x2: (i) =>\n    vec2f((i.readUint8() - 128) / 127, (i.readUint8() - 128) / 127),\n  snorm8x4: (i) =>\n    vec4f(\n      (i.readUint8() - 128) / 127,\n      (i.readUint8() - 128) / 127,\n      (i.readUint8() - 128) / 127,\n      (i.readUint8() - 128) / 127,\n    ),\n  uint16: (i) => i.readUint16(),\n  uint16x2: (i) => vec2u(i.readUint16(), i.readUint16()),\n  uint16x4: (i) =>\n    vec4u(i.readUint16(), i.readUint16(), i.readUint16(), i.readUint16()),\n  sint16: (i) => i.readInt16(),\n  sint16x2: (i) => vec2i(i.readInt16(), i.readInt16()),\n  sint16x4: (i) =>\n    vec4i(i.readInt16(), i.readInt16(), i.readInt16(), i.readInt16()),\n  unorm16: (i) => i.readUint16() / 65535,\n  unorm16x2: (i) => vec2f(i.readUint16() / 65535, i.readUint16() / 65535),\n  unorm16x4: (i) =>\n    vec4f(\n      i.readUint16() / 65535,\n      i.readUint16() / 65535,\n      i.readUint16() / 65535,\n      i.readUint16() / 65535,\n    ),\n  snorm16: (i) => (i.readUint16() - 32768) / 32767,\n  snorm16x2: (i): wgsl.v2f =>\n    vec2f(dataReaders.snorm16(i), dataReaders.snorm16(i)),\n  snorm16x4: (i): wgsl.v4f =>\n    vec4f(\n      dataReaders.snorm16(i),\n      dataReaders.snorm16(i),\n      dataReaders.snorm16(i),\n      dataReaders.snorm16(i),\n    ),\n  float16(i) {\n    return i.readFloat16();\n  },\n  float16x2: (i) => vec2f(i.readFloat16(), i.readFloat16()),\n  float16x4: (i) =>\n    vec4f(i.readFloat16(), i.readFloat16(), i.readFloat16(), i.readFloat16()),\n  float32: (i) => i.readFloat32(),\n  float32x2: (i) => vec2f(i.readFloat32(), i.readFloat32()),\n  float32x3: (i) => vec3f(i.readFloat32(), i.readFloat32(), i.readFloat32()),\n  float32x4: (i) =>\n    vec4f(i.readFloat32(), i.readFloat32(), i.readFloat32(), i.readFloat32()),\n  uint32: (i) => i.readUint32(),\n  uint32x2: (i) => vec2u(i.readUint32(), i.readUint32()),\n  uint32x3: (i) => vec3u(i.readUint32(), i.readUint32(), i.readUint32()),\n  uint32x4: (i) =>\n    vec4u(i.readUint32(), i.readUint32(), i.readUint32(), i.readUint32()),\n  sint32: (i) => i.readInt32(),\n  sint32x2: (i) => vec2i(i.readInt32(), i.readInt32()),\n  sint32x3: (i) => vec3i(i.readInt32(), i.readInt32(), i.readInt32()),\n  sint32x4: (i) =>\n    vec4i(i.readInt32(), i.readInt32(), i.readInt32(), i.readInt32()),\n  'unorm10-10-10-2'(i) {\n    const packed = i.readUint32();\n    const r = (packed >> 22) / 1023;\n    const g = ((packed >> 12) & 1023) / 1023;\n    const b = ((packed >> 2) & 1023) / 1023;\n    const a = (packed & 3) / 3;\n    return vec4f(r, g, b, a);\n  },\n  'unorm8x4-bgra'(i) {\n    const b = i.readByte() / 255;\n    const g = i.readByte() / 255;\n    const r = i.readByte() / 255;\n    const a = i.readByte() / 255;\n    return vec4f(r, g, b, a);\n  },\n\n  unstruct(input, schema: Unstruct) {\n    const result = {} as Record<string, unknown>;\n\n    for (const [key, property] of Object.entries(schema.propTypes)) {\n      result[key] = readData(input, property);\n    }\n\n    return result as InferRecord<Record<string, wgsl.BaseData>>;\n  },\n\n  disarray(input, schema: Disarray) {\n    const alignment = alignmentOf(schema);\n    const elements: unknown[] = [];\n\n    for (let i = 0; i < schema.elementCount; i++) {\n      alignIO(input, alignment);\n      elements.push(readData(input, schema.elementType));\n    }\n\n    alignIO(input, alignment);\n    return elements;\n  },\n\n  'loose-decorated'(input, schema: LooseDecorated) {\n    alignIO(input, customAlignmentOf(schema));\n\n    const beginning = input.currentByteOffset;\n    const value = readData(input, schema.inner);\n    input.seekTo(beginning + sizeOf(schema));\n    return value;\n  },\n} satisfies CompleteDataReaders;\n\nexport function readData<TData extends wgsl.BaseData>(\n  input: ISerialInput,\n  schema: TData,\n): Infer<TData> {\n  const reader = (dataReaders as Record<string, unknown>)[\n    schema.type\n  ] as DataReader<TData>;\n  if (!reader) {\n    throw new Error(`Cannot read data of type '${schema.type}'.`);\n  }\n\n  return reader(input, schema);\n}\n","import * as tinyest from 'tinyest';\nimport { arrayOf } from '../data/array.ts';\nimport {\n  type AnyData,\n  isData,\n  isLooseData,\n  snip,\n  type Snippet,\n  UnknownData,\n} from '../data/dataTypes.ts';\nimport * as d from '../data/index.ts';\nimport { abstractInt } from '../data/numeric.ts';\nimport * as wgsl from '../data/wgslTypes.ts';\nimport { getName } from '../shared/meta.ts';\nimport { $internal } from '../shared/symbols.ts';\nimport { type FnArgsConversionHint, isMarkedInternal } from '../types.ts';\nimport {\n  coerceToSnippet,\n  concretize,\n  convertStructValues,\n  convertToCommonType,\n  type GenerationCtx,\n  getTypeForIndexAccess,\n  getTypeForPropAccess,\n  numericLiteralToSnippet,\n} from './generationHelpers.ts';\nimport { ResolutionError } from '../errors.ts';\n\nconst { NodeTypeCatalog: NODE } = tinyest;\n\nconst parenthesizedOps = [\n  '==',\n  '!=',\n  '<',\n  '<=',\n  '>',\n  '>=',\n  '<<',\n  '>>',\n  '+',\n  '-',\n  '*',\n  '/',\n  '%',\n  '|',\n  '^',\n  '&',\n  '&&',\n  '||',\n];\n\nconst binaryLogicalOps = ['&&', '||', '==', '!=', '<', '<=', '>', '>='];\n\ntype Operator =\n  | tinyest.BinaryOperator\n  | tinyest.AssignmentOperator\n  | tinyest.LogicalOperator\n  | tinyest.UnaryOperator;\n\nfunction operatorToType<\n  TL extends AnyData | UnknownData,\n  TR extends AnyData | UnknownData,\n>(lhs: TL, op: Operator, rhs?: TR): TL | TR | wgsl.Bool {\n  if (!rhs) {\n    if (op === '!' || op === '~') {\n      return d.bool;\n    }\n\n    return lhs;\n  }\n\n  if (binaryLogicalOps.includes(op)) {\n    return d.bool;\n  }\n\n  if (op === '=') {\n    return rhs;\n  }\n\n  return lhs;\n}\n\nfunction assertExhaustive(value: never): never {\n  throw new Error(\n    `'${JSON.stringify(value)}' was not handled by the WGSL generator.`,\n  );\n}\n\nexport function generateBlock(\n  ctx: GenerationCtx,\n  [_, statements]: tinyest.Block,\n): string {\n  ctx.pushBlockScope();\n  try {\n    return `${ctx.indent()}{\n${statements.map((statement) => generateStatement(ctx, statement)).join('\\n')}\n${ctx.dedent()}}`;\n  } finally {\n    ctx.popBlockScope();\n  }\n}\n\nexport function registerBlockVariable(\n  ctx: GenerationCtx,\n  id: string,\n  dataType: wgsl.AnyWgslData | UnknownData,\n): Snippet {\n  return ctx.defineVariable(id, dataType);\n}\n\nexport function generateIdentifier(ctx: GenerationCtx, id: string): Snippet {\n  const res = ctx.getById(id);\n  if (!res) {\n    throw new Error(`Identifier ${id} not found`);\n  }\n\n  return res;\n}\n\nexport function generateExpression(\n  ctx: GenerationCtx,\n  expression: tinyest.Expression,\n): Snippet {\n  if (typeof expression === 'string') {\n    return generateIdentifier(ctx, expression);\n  }\n\n  if (typeof expression === 'boolean') {\n    return snip(expression ? 'true' : 'false', d.bool);\n  }\n\n  if (\n    expression[0] === NODE.logicalExpr ||\n    expression[0] === NODE.binaryExpr ||\n    expression[0] === NODE.assignmentExpr\n  ) {\n    // Logical/Binary/Assignment Expression\n    const [_, lhs, op, rhs] = expression;\n    const lhsExpr = generateExpression(ctx, lhs);\n    const rhsExpr = generateExpression(ctx, rhs);\n\n    const forcedType = expression[0] === NODE.assignmentExpr\n      ? [lhsExpr.dataType as AnyData]\n      : [];\n\n    const converted = convertToCommonType(\n      ctx,\n      [lhsExpr, rhsExpr],\n      forcedType,\n    ) as\n      | [Snippet, Snippet]\n      | undefined;\n    const [convLhs, convRhs] = converted || [lhsExpr, rhsExpr];\n\n    const lhsStr = ctx.resolve(convLhs.value);\n    const rhsStr = ctx.resolve(convRhs.value);\n    const type = operatorToType(convLhs.dataType, op, convRhs.dataType);\n\n    return snip(\n      parenthesizedOps.includes(op)\n        ? `(${lhsStr} ${op} ${rhsStr})`\n        : `${lhsStr} ${op} ${rhsStr}`,\n      type,\n    );\n  }\n\n  if (expression[0] === NODE.postUpdate) {\n    // Post-Update Expression\n    const [_, op, arg] = expression;\n    const argExpr = generateExpression(ctx, arg);\n    const argStr = ctx.resolve(argExpr.value);\n\n    return snip(`${argStr}${op}`, argExpr.dataType);\n  }\n\n  if (expression[0] === NODE.unaryExpr) {\n    // Unary Expression\n    const [_, op, arg] = expression;\n    const argExpr = generateExpression(ctx, arg);\n    const argStr = ctx.resolve(argExpr.value);\n\n    const type = operatorToType(argExpr.dataType, op);\n    return snip(`${op}${argStr}`, type);\n  }\n\n  if (expression[0] === NODE.memberAccess) {\n    // Member Access\n    const [_, targetNode, property] = expression;\n    const target = generateExpression(ctx, targetNode);\n\n    if (target.dataType.type === 'unknown') {\n      // No idea what the type is, so we act on the snippet's value and try to guess\n\n      // biome-ignore lint/suspicious/noExplicitAny: we're inspecting the value, and it could be any value\n      const propValue = (target.value as any)[property];\n\n      // We try to extract any type information based on the prop's value\n      return coerceToSnippet(propValue);\n    }\n\n    if (wgsl.isPtr(target.dataType)) {\n      return snip(\n        `(*${ctx.resolve(target.value)}).${property}`,\n        getTypeForPropAccess(target.dataType.inner as AnyData, property),\n      );\n    }\n\n    if (wgsl.isWgslArray(target.dataType) && property === 'length') {\n      if (target.dataType.elementCount === 0) {\n        // Dynamically-sized array\n        return snip(`arrayLength(&${ctx.resolve(target.value)})`, d.u32);\n      }\n\n      return snip(String(target.dataType.elementCount), abstractInt);\n    }\n\n    if (wgsl.isMat(target.dataType) && property === 'columns') {\n      return snip(target.value, target.dataType);\n    }\n\n    if (\n      wgsl.isVec(target.dataType) && wgsl.isVecInstance(target.value)\n    ) {\n      // We're operating on a vector that's known at resolution time\n      // biome-ignore lint/suspicious/noExplicitAny: it's probably a swizzle\n      return coerceToSnippet((target.value as any)[property]);\n    }\n\n    return snip(\n      `${ctx.resolve(target.value)}.${property}`,\n      getTypeForPropAccess(target.dataType, property),\n    );\n  }\n\n  if (expression[0] === NODE.indexAccess) {\n    // Index Access\n    const [_, targetNode, propertyNode] = expression;\n    const target = generateExpression(ctx, targetNode);\n    const property = generateExpression(ctx, propertyNode);\n    const targetStr = ctx.resolve(target.value);\n    const propertyStr = ctx.resolve(property.value);\n\n    if (target.dataType.type === 'unknown') {\n      // No idea what the type is, so we act on the snippet's value and try to guess\n\n      if (\n        Array.isArray(propertyNode) && propertyNode[0] === NODE.numericLiteral\n      ) {\n        return coerceToSnippet(\n          // biome-ignore lint/suspicious/noExplicitAny: we're inspecting the value, and it could be any value\n          (target.value as any)[propertyNode[1] as number],\n        );\n      }\n\n      throw new Error(\n        `Cannot index value ${targetStr} of unknown type with index ${propertyStr}`,\n      );\n    }\n\n    if (wgsl.isPtr(target.dataType)) {\n      return snip(\n        `(*${targetStr})[${propertyStr}]`,\n        getTypeForIndexAccess(target.dataType.inner as AnyData),\n      );\n    }\n\n    return snip(\n      `${targetStr}[${propertyStr}]`,\n      isData(target.dataType)\n        ? getTypeForIndexAccess(target.dataType)\n        : UnknownData,\n    );\n  }\n\n  if (expression[0] === NODE.numericLiteral) {\n    // Numeric Literal\n    const type = numericLiteralToSnippet(expression[1]);\n    if (!type) {\n      throw new Error(`Invalid numeric literal ${expression[1]}`);\n    }\n    return type;\n  }\n\n  if (expression[0] === NODE.call) {\n    // Function Call\n    const [_, callee, args] = expression;\n    const id = generateExpression(ctx, callee);\n\n    ctx.callStack.push(id.value);\n\n    const argSnippets = args.map((arg) => generateExpression(ctx, arg));\n    const resolvedSnippets = argSnippets.map((res) =>\n      snip(ctx.resolve(res.value), res.dataType)\n    );\n    const argValues = resolvedSnippets.map((res) => res.value);\n\n    ctx.callStack.pop();\n\n    resolvedSnippets.forEach((sn, idx) => {\n      if (sn.dataType === UnknownData) {\n        throw new Error(\n          `Tried to pass '${sn.value}' of unknown type as argument #${idx} to '${\n            typeof id.value === 'string'\n              ? id.value\n              : getName(id.value) ?? '<unnamed>'\n          }()'`,\n        );\n      }\n    });\n\n    if (typeof id.value === 'string') {\n      return snip(`${id.value}(${argValues.join(', ')})`, id.dataType);\n    }\n\n    if (wgsl.isWgslStruct(id.value)) {\n      const resolvedId = ctx.resolve(id.value);\n\n      return snip(\n        `${resolvedId}(${argValues.join(', ')})`,\n        // Unintuitive, but the type of the return value is the struct itself\n        id.value,\n      );\n    }\n\n    if (!isMarkedInternal(id.value)) {\n      throw new Error(\n        `Function ${String(id.value)} ${\n          getName(id.value)\n        } has not been created using TypeGPU APIs. Did you mean to wrap the function with tgpu.fn(args, return)(...) ?`,\n      );\n    }\n\n    const argTypes = id.value[$internal]?.argTypes as\n      | FnArgsConversionHint\n      | undefined;\n    let convertedResources: Snippet[];\n    try {\n      if (!argTypes || argTypes === 'keep') {\n        convertedResources = resolvedSnippets;\n      } else if (argTypes === 'coerce') {\n        convertedResources = convertToCommonType(ctx, resolvedSnippets) ??\n          resolvedSnippets;\n      } else {\n        const pairs =\n          (Array.isArray(argTypes) ? argTypes : (argTypes(...resolvedSnippets)))\n            .map((type, i) => [type, resolvedSnippets[i] as Snippet] as const);\n\n        convertedResources = pairs.map(([type, sn]) => {\n          if (sn.dataType.type === 'unknown') {\n            console.warn(\n              `Internal error: unknown type when generating expression: ${expression}`,\n            );\n            return sn;\n          }\n\n          const conv = convertToCommonType(ctx, [sn], [type])?.[0];\n          if (!conv) {\n            throw new ResolutionError(\n              `Cannot convert argument of type '${sn.dataType.type}' to '${type.type}' for function ${\n                getName(id.value)\n              }`,\n              [{\n                function: id.value,\n                callStack: ctx.callStack,\n                error:\n                  `Cannot convert argument of type '${sn.dataType.type}' to '${type.type}'`,\n                toString: () => getName(id.value),\n              }],\n            );\n          }\n          return conv;\n        });\n      }\n\n      // Assuming that `id` is callable\n      const fnRes = (id.value as unknown as (...args: unknown[]) => unknown)(\n        ...convertedResources,\n      ) as Snippet;\n      return snip(ctx.resolve(fnRes.value), fnRes.dataType);\n    } catch (error) {\n      throw new ResolutionError(error, [{\n        toString: () => getName(id.value),\n      }]);\n    }\n  }\n\n  if (expression[0] === NODE.objectExpr) {\n    // Object Literal\n    const obj = expression[1];\n    const callee = ctx.callStack[ctx.callStack.length - 1];\n\n    if (wgsl.isWgslStruct(callee)) {\n      const propKeys = Object.keys(callee.propTypes);\n      const entries = Object.fromEntries(\n        propKeys.map((key) => {\n          const val = obj[key];\n          if (val === undefined) {\n            throw new Error(\n              `Missing property ${key} in object literal for struct ${callee}`,\n            );\n          }\n          return [key, generateExpression(ctx, val)];\n        }),\n      );\n\n      const convertedValues = convertStructValues(ctx, callee, entries);\n\n      return snip(\n        convertedValues.map((v) => ctx.resolve(v.value)).join(', '),\n        callee,\n      );\n    }\n\n    if (isMarkedInternal(callee)) {\n      const argTypes = callee[$internal]?.argTypes;\n\n      if (typeof argTypes === 'object' && argTypes !== null) {\n        const propKeys = Object.keys(argTypes);\n        const snippets: Record<string, Snippet> = {};\n\n        for (const key of propKeys) {\n          const val = obj[key];\n          if (val === undefined) {\n            throw new Error(\n              `Missing property ${key} in object literal for function ${callee}`,\n            );\n          }\n          const expr = generateExpression(ctx, val);\n          const targetType = argTypes[key as keyof typeof argTypes];\n          const converted = convertToCommonType(ctx, [expr], [targetType]);\n          snippets[key] = converted?.[0] ?? expr;\n        }\n\n        return snip(snippets, UnknownData);\n      }\n    }\n\n    throw new Error(\n      'Object expressions are only allowed as return values of functions or as arguments to structs.',\n    );\n  }\n\n  if (expression[0] === NODE.arrayExpr) {\n    const [_, valuesRaw] = expression;\n    // Array Expression\n    const values = valuesRaw.map((value) =>\n      generateExpression(ctx, value as tinyest.Expression)\n    );\n    if (values.length === 0) {\n      throw new Error('Cannot create empty array literal.');\n    }\n\n    const convertedValues = convertToCommonType(ctx, values);\n    if (!convertedValues) {\n      throw new Error(\n        'The given values cannot be automatically converted to a common type. Consider explicitly casting them.',\n      );\n    }\n\n    const targetType = convertedValues[0]?.dataType as AnyData;\n    const type = targetType.type === 'abstractFloat'\n      ? d.f32\n      : targetType.type === 'abstractInt'\n      ? d.i32\n      : targetType;\n\n    const typeId = ctx.resolve(type);\n\n    const arrayType = `array<${typeId}, ${values.length}>`;\n    const arrayValues = convertedValues.map((sn) => ctx.resolve(sn.value));\n\n    return snip(\n      `${arrayType}( ${arrayValues.join(', ')} )`,\n      arrayOf(\n        type as wgsl.AnyWgslData,\n        values.length,\n      ) as wgsl.AnyWgslData,\n    );\n  }\n\n  if (expression[0] === NODE.stringLiteral) {\n    throw new Error('Cannot use string literals in TGSL.');\n  }\n\n  if (expression[0] === NODE.preUpdate) {\n    throw new Error('Cannot use pre-updates in TGSL.');\n  }\n\n  assertExhaustive(expression);\n}\n\nfunction blockifySingleStatement(statement: tinyest.Statement): tinyest.Block {\n  return typeof statement !== 'object' ||\n      statement[0] !== NODE.block\n    ? [NODE.block, [statement]]\n    : statement;\n}\n\nexport function generateStatement(\n  ctx: GenerationCtx,\n  statement: tinyest.Statement,\n): string {\n  if (typeof statement === 'string') {\n    return `${ctx.pre}${\n      ctx.resolve(generateIdentifier(ctx, statement).value)\n    };`;\n  }\n\n  if (typeof statement === 'boolean') {\n    return `${ctx.pre}${statement ? 'true' : 'false'};`;\n  }\n\n  if (statement[0] === NODE.return) {\n    const returnNode = statement[1];\n    const returnValue = returnNode !== undefined\n      ? ctx.resolve(generateExpression(ctx, returnNode).value)\n      : undefined;\n\n    // check if the thing at the top of the call stack is a struct and the statement is a plain JS object\n    // if so wrap the value returned in a constructor of the struct (its resolved name)\n    if (\n      wgsl.isWgslStruct(ctx.callStack[ctx.callStack.length - 1]) &&\n      typeof returnNode === 'object' &&\n      returnNode[0] === NODE.objectExpr\n    ) {\n      const resolvedStruct = ctx.resolve(\n        ctx.callStack[ctx.callStack.length - 1],\n      );\n      return `${ctx.pre}return ${resolvedStruct}(${returnValue});`;\n    }\n\n    return returnValue\n      ? `${ctx.pre}return ${returnValue};`\n      : `${ctx.pre}return;`;\n  }\n\n  if (statement[0] === NODE.if) {\n    const [_, cond, cons, alt] = statement;\n    const condExpr = generateExpression(ctx, cond);\n    let condSnippet = condExpr;\n    const converted = convertToCommonType(ctx, [condExpr], [d.bool]);\n    if (converted?.[0]) {\n      [condSnippet] = converted;\n    }\n    const condition = ctx.resolve(condSnippet.value);\n\n    ctx.indent(); // {\n    const consequent = generateStatement(ctx, blockifySingleStatement(cons));\n    ctx.dedent(); // }\n\n    ctx.indent(); // {\n    const alternate = alt\n      ? generateStatement(ctx, blockifySingleStatement(alt))\n      : undefined;\n    ctx.dedent(); // }\n\n    if (!alternate) {\n      return `\\\n${ctx.pre}if (${condition})\n${consequent}`;\n    }\n\n    return `\\\n${ctx.pre}if (${condition})\n${consequent}\n${ctx.pre}else\n${alternate}`;\n  }\n\n  if (statement[0] === NODE.let || statement[0] === NODE.const) {\n    const [_, rawId, rawValue] = statement;\n    const eq = rawValue !== undefined\n      ? generateExpression(ctx, rawValue)\n      : undefined;\n\n    if (!eq) {\n      throw new Error(\n        `Cannot create variable '${rawId}' without an initial value.`,\n      );\n    }\n\n    if (isLooseData(eq.dataType)) {\n      throw new Error(\n        `Cannot create variable '${rawId}' with loose data type.`,\n      );\n    }\n\n    registerBlockVariable(\n      ctx,\n      rawId,\n      concretize(eq.dataType as wgsl.AnyWgslData),\n    );\n    const id = ctx.resolve(generateIdentifier(ctx, rawId).value);\n\n    // If the value is a plain JS object it has to be an output struct\n    if (\n      typeof rawValue === 'object' &&\n      rawValue[0] === NODE.objectExpr &&\n      wgsl.isWgslStruct(ctx.callStack[ctx.callStack.length - 1])\n    ) {\n      const structType = ctx.callStack[\n        ctx.callStack.length - 1\n      ] as wgsl.WgslStruct;\n      const obj = rawValue[1];\n\n      const entries: Record<string, Snippet> = {};\n      for (const [key, value] of Object.entries(obj)) {\n        if (!value) {\n          throw new Error(`Missing property ${key} in object literal`);\n        }\n        entries[key] = generateExpression(ctx, value);\n      }\n\n      const convertedValues = convertStructValues(ctx, structType, entries);\n      const resolvedStruct = ctx.resolve(structType);\n      return `${ctx.pre}var ${id} = ${resolvedStruct}(${\n        convertedValues.map((sn) => ctx.resolve(sn.value)).join(', ')\n      });`;\n    }\n\n    return `${ctx.pre}var ${id} = ${ctx.resolve(eq.value)};`;\n  }\n\n  if (statement[0] === NODE.block) {\n    return generateBlock(ctx, statement);\n  }\n\n  if (statement[0] === NODE.for) {\n    const [_, init, condition, update, body] = statement;\n\n    const initStatement = init ? generateStatement(ctx, init) : undefined;\n    const initStr = initStatement ? initStatement.slice(0, -1) : '';\n\n    const conditionExpr = condition\n      ? generateExpression(ctx, condition)\n      : undefined;\n    let condSnippet = conditionExpr;\n    if (conditionExpr) {\n      const converted = convertToCommonType(ctx, [conditionExpr], [d.bool]);\n      if (converted?.[0]) {\n        [condSnippet] = converted;\n      }\n    }\n    const conditionStr = condSnippet ? ctx.resolve(condSnippet.value) : '';\n\n    const updateStatement = update ? generateStatement(ctx, update) : undefined;\n    const updateStr = updateStatement ? updateStatement.slice(0, -1) : '';\n\n    ctx.indent();\n    const bodyStr = generateStatement(ctx, blockifySingleStatement(body));\n    ctx.dedent();\n\n    return `\\\n${ctx.pre}for (${initStr}; ${conditionStr}; ${updateStr})\n${bodyStr}`;\n  }\n\n  if (statement[0] === NODE.while) {\n    const [_, condition, body] = statement;\n    const condExpr = generateExpression(ctx, condition);\n    let condSnippet = condExpr;\n    if (condExpr) {\n      const converted = convertToCommonType(ctx, [condExpr], [d.bool]);\n      if (converted?.[0]) {\n        [condSnippet] = converted;\n      }\n    }\n    const conditionStr = ctx.resolve(condSnippet.value);\n\n    ctx.indent();\n    const bodyStr = generateStatement(ctx, blockifySingleStatement(body));\n    ctx.dedent();\n\n    return `\\\n${ctx.pre}while (${conditionStr})\n${bodyStr}`;\n  }\n\n  if (statement[0] === NODE.continue) {\n    return `${ctx.pre}continue;`;\n  }\n\n  if (statement[0] === NODE.break) {\n    return `${ctx.pre}break;`;\n  }\n\n  return `${ctx.pre}${ctx.resolve(generateExpression(ctx, statement).value)};`;\n}\n\nexport function generateFunction(\n  ctx: GenerationCtx,\n  body: tinyest.Block,\n): string {\n  return generateBlock(ctx, body);\n}\n","import { resolveData } from './core/resolve/resolveData.ts';\nimport {\n  type Eventual,\n  isDerived,\n  isProviding,\n  isSlot,\n  type SlotValuePair,\n  type TgpuDerived,\n  type TgpuSlot,\n} from './core/slot/slotTypes.ts';\nimport { getAttributesString } from './data/attributes.ts';\nimport {\n  type AnyData,\n  isData,\n  snip,\n  type Snippet,\n  type UnknownData,\n} from './data/dataTypes.ts';\nimport { type BaseData, isWgslArray, isWgslStruct } from './data/wgslTypes.ts';\nimport { MissingSlotValueError, ResolutionError } from './errors.ts';\nimport { popMode, provideCtx, pushMode, RuntimeMode } from './gpuMode.ts';\nimport type { NameRegistry } from './nameRegistry.ts';\nimport { naturalsExcept } from './shared/generators.ts';\nimport type { Infer } from './shared/repr.ts';\nimport { $internal, $providing } from './shared/symbols.ts';\nimport {\n  bindGroupLayout,\n  type TgpuBindGroup,\n  TgpuBindGroupImpl,\n  type TgpuBindGroupLayout,\n  type TgpuLayoutEntry,\n} from './tgpuBindGroupLayout.ts';\nimport { coerceToSnippet } from './tgsl/generationHelpers.ts';\nimport { generateFunction } from './tgsl/wgslGenerator.ts';\nimport type {\n  FnToWgslOptions,\n  ItemLayer,\n  ItemStateStack,\n  ResolutionCtx,\n  Wgsl,\n} from './types.ts';\nimport { isSelfResolvable, isWgsl } from './types.ts';\n\n/**\n * Inserted into bind group entry definitions that belong\n * to the automatically generated catch-all bind group.\n *\n * A non-occupied group index can only be determined after\n * every resource has been resolved, so this acts as a placeholder\n * to be replaced with an actual numeric index at the very end\n * of the resolution process.\n */\nconst CATCHALL_BIND_GROUP_IDX_MARKER = '#CATCHALL#';\n\nexport type ResolutionCtxImplOptions = {\n  readonly names: NameRegistry;\n};\n\ntype SlotToValueMap = Map<TgpuSlot<unknown>, unknown>;\n\ntype SlotBindingLayer = {\n  type: 'slotBinding';\n  bindingMap: WeakMap<TgpuSlot<unknown>, unknown>;\n};\n\ntype FunctionScopeLayer = {\n  type: 'functionScope';\n  args: Snippet[];\n  argAliases: Record<string, Snippet>;\n  externalMap: Record<string, unknown>;\n  returnType: AnyData;\n};\n\ntype BlockScopeLayer = {\n  type: 'blockScope';\n  declarations: Map<string, AnyData | UnknownData>;\n};\n\nclass ItemStateStackImpl implements ItemStateStack {\n  private _stack: (\n    | ItemLayer\n    | SlotBindingLayer\n    | FunctionScopeLayer\n    | BlockScopeLayer\n  )[] = [];\n  private _itemDepth = 0;\n\n  get itemDepth(): number {\n    return this._itemDepth;\n  }\n\n  get topItem(): ItemLayer {\n    const state = this._stack[this._stack.length - 1];\n    if (!state || state.type !== 'item') {\n      throw new Error('Internal error, expected item layer to be on top.');\n    }\n    return state;\n  }\n\n  pushItem() {\n    this._itemDepth++;\n    this._stack.push({\n      type: 'item',\n      usedSlots: new Set(),\n    });\n  }\n\n  popItem() {\n    this.pop('item');\n  }\n\n  pushSlotBindings(pairs: SlotValuePair<unknown>[]) {\n    this._stack.push({\n      type: 'slotBinding',\n      bindingMap: new WeakMap(pairs),\n    });\n  }\n\n  popSlotBindings() {\n    this.pop('slotBinding');\n  }\n\n  pushFunctionScope(\n    args: Snippet[],\n    argAliases: Record<string, Snippet>,\n    returnType: AnyData,\n    externalMap: Record<string, unknown>,\n  ) {\n    this._stack.push({\n      type: 'functionScope',\n      args,\n      argAliases,\n      returnType,\n      externalMap,\n    });\n  }\n\n  popFunctionScope() {\n    this.pop('functionScope');\n  }\n\n  pushBlockScope() {\n    this._stack.push({\n      type: 'blockScope',\n      declarations: new Map<string, AnyData | UnknownData>(),\n    });\n  }\n\n  popBlockScope() {\n    this.pop('blockScope');\n  }\n\n  pop(type?: (typeof this._stack)[number]['type']) {\n    const layer = this._stack[this._stack.length - 1];\n    if (!layer || (type && layer.type !== type)) {\n      throw new Error(`Internal error, expected a ${type} layer to be on top.`);\n    }\n\n    this._stack.pop();\n    if (type === 'item') {\n      this._itemDepth--;\n    }\n  }\n\n  readSlot<T>(slot: TgpuSlot<T>): T | undefined {\n    for (let i = this._stack.length - 1; i >= 0; --i) {\n      const layer = this._stack[i];\n      if (layer?.type === 'item') {\n        // Binding not available yet, so this layer is dependent on the slot's value.\n        layer.usedSlots.add(slot);\n      } else if (layer?.type === 'slotBinding') {\n        const boundValue = layer.bindingMap.get(slot);\n\n        if (boundValue !== undefined) {\n          return boundValue as T;\n        }\n      } else if (\n        layer?.type === 'functionScope' ||\n        layer?.type === 'blockScope'\n      ) {\n        // Skip\n      } else {\n        throw new Error('Unknown layer type.');\n      }\n    }\n\n    return slot.defaultValue;\n  }\n\n  getSnippetById(id: string): Snippet | undefined {\n    for (let i = this._stack.length - 1; i >= 0; --i) {\n      const layer = this._stack[i];\n\n      if (layer?.type === 'functionScope') {\n        const arg = layer.args.find((a) => a.value === id);\n        if (arg !== undefined) {\n          return arg;\n        }\n\n        if (layer.argAliases[id]) {\n          return layer.argAliases[id];\n        }\n\n        const external = layer.externalMap[id];\n\n        if (external !== undefined && external !== null) {\n          return coerceToSnippet(external);\n        }\n\n        // Since functions cannot access resources from the calling scope, we\n        // return early here.\n        return undefined;\n      }\n\n      if (layer?.type === 'blockScope') {\n        const declarationType = layer.declarations.get(id);\n        if (declarationType !== undefined) {\n          return snip(id, declarationType);\n        }\n      } else {\n        // Skip\n      }\n    }\n\n    return undefined;\n  }\n\n  defineBlockVariable(id: string, type: AnyData | UnknownData): Snippet {\n    if (type.type === 'unknown') {\n      throw Error(`Tried to define variable '${id}' of unknown type`);\n    }\n\n    for (let i = this._stack.length - 1; i >= 0; --i) {\n      const layer = this._stack[i];\n\n      if (layer?.type === 'blockScope') {\n        layer.declarations.set(id, type);\n\n        return snip(id, type);\n      }\n    }\n\n    throw new Error('No block scope found to define a variable in.');\n  }\n}\n\nconst INDENT = [\n  '', // 0\n  '  ', // 1\n  '    ', // 2\n  '      ', // 3\n  '        ', // 4\n  '          ', // 5\n  '            ', // 6\n  '              ', // 7\n  '                ', // 8\n];\n\nconst N = INDENT.length - 1;\n\nexport class IndentController {\n  private identLevel = 0;\n\n  get pre(): string {\n    return (\n      INDENT[this.identLevel] ??\n        (INDENT[N] as string).repeat(this.identLevel / N) +\n          INDENT[this.identLevel % N]\n    );\n  }\n\n  indent(): string {\n    const str = this.pre;\n    this.identLevel++;\n    return str;\n  }\n\n  dedent(): string {\n    this.identLevel--;\n    return this.pre;\n  }\n}\n\ninterface FixedBindingConfig {\n  layoutEntry: TgpuLayoutEntry;\n  resource: object;\n}\n\nexport class ResolutionCtxImpl implements ResolutionCtx {\n  private readonly _memoizedResolves = new WeakMap<\n    // WeakMap because if the item does not exist anymore,\n    // apart from this map, there is no way to access the cached value anyway.\n    object,\n    { slotToValueMap: SlotToValueMap; result: string }[]\n  >();\n  private readonly _memoizedDerived = new WeakMap<\n    // WeakMap because if the \"derived\" does not exist anymore,\n    // apart from this map, there is no way to access the cached value anyway.\n    TgpuDerived<unknown>,\n    { slotToValueMap: SlotToValueMap; result: unknown }[]\n  >();\n\n  private readonly _indentController = new IndentController();\n  private readonly _itemStateStack = new ItemStateStackImpl();\n  private readonly _declarations: string[] = [];\n  private _varyingLocations: Record<string, number> | undefined;\n\n  get varyingLocations() {\n    return this._varyingLocations;\n  }\n\n  readonly [$internal] = {\n    itemStateStack: this._itemStateStack,\n  };\n\n  // -- Bindings\n  /**\n   * A map from registered bind group layouts to random strings put in\n   * place of their group index. The whole tree has to be traversed to\n   * collect every use of a typed bind group layout, since they can be\n   * explicitly imposed group indices, and they cannot collide.\n   */\n  public readonly bindGroupLayoutsToPlaceholderMap = new Map<\n    TgpuBindGroupLayout,\n    string\n  >();\n  private _nextFreeLayoutPlaceholderIdx = 0;\n  public readonly fixedBindings: FixedBindingConfig[] = [];\n  // --\n\n  public readonly callStack: unknown[] = [];\n  public readonly names: NameRegistry;\n\n  constructor(opts: ResolutionCtxImplOptions) {\n    this.names = opts.names;\n  }\n\n  get pre(): string {\n    return this._indentController.pre;\n  }\n\n  indent(): string {\n    return this._indentController.indent();\n  }\n\n  dedent(): string {\n    return this._indentController.dedent();\n  }\n\n  getById(id: string): Snippet | null {\n    const item = this._itemStateStack.getSnippetById(id);\n\n    if (item === undefined) {\n      return null;\n    }\n\n    return item;\n  }\n\n  defineVariable(id: string, dataType: AnyData | UnknownData): Snippet {\n    return this._itemStateStack.defineBlockVariable(id, dataType);\n  }\n\n  pushBlockScope() {\n    this._itemStateStack.pushBlockScope();\n  }\n\n  popBlockScope() {\n    this._itemStateStack.popBlockScope();\n  }\n\n  fnToWgsl(options: FnToWgslOptions): { head: Wgsl; body: Wgsl } {\n    this._itemStateStack.pushFunctionScope(\n      options.args,\n      options.argAliases,\n      options.returnType,\n      options.externalMap,\n    );\n\n    try {\n      return {\n        head: resolveFunctionHeader(this, options.args, options.returnType),\n        body: generateFunction(this, options.body),\n      };\n    } finally {\n      this._itemStateStack.popFunctionScope();\n    }\n  }\n\n  addDeclaration(declaration: string): void {\n    this._declarations.push(declaration);\n  }\n\n  allocateLayoutEntry(layout: TgpuBindGroupLayout): string {\n    const memoMap = this.bindGroupLayoutsToPlaceholderMap;\n    let placeholderKey = memoMap.get(layout);\n\n    if (!placeholderKey) {\n      placeholderKey = `#BIND_GROUP_LAYOUT_${this\n        ._nextFreeLayoutPlaceholderIdx++}#`;\n      memoMap.set(layout, placeholderKey);\n    }\n\n    return placeholderKey;\n  }\n\n  allocateFixedEntry(\n    layoutEntry: TgpuLayoutEntry,\n    resource: object,\n  ): { group: string; binding: number } {\n    const binding = this.fixedBindings.length;\n    this.fixedBindings.push({ layoutEntry, resource });\n\n    return {\n      group: CATCHALL_BIND_GROUP_IDX_MARKER,\n      binding,\n    };\n  }\n\n  readSlot<T>(slot: TgpuSlot<T>): T {\n    const value = this._itemStateStack.readSlot(slot);\n\n    if (value === undefined) {\n      throw new MissingSlotValueError(slot);\n    }\n\n    return value;\n  }\n\n  withSlots<T>(pairs: SlotValuePair<unknown>[], callback: () => T): T {\n    this._itemStateStack.pushSlotBindings(pairs);\n\n    try {\n      return callback();\n    } finally {\n      this._itemStateStack.popSlotBindings();\n    }\n  }\n\n  withVaryingLocations<T>(\n    locations: Record<string, number>,\n    callback: () => T,\n  ): T {\n    this._varyingLocations = locations;\n\n    try {\n      return callback();\n    } finally {\n      this._varyingLocations = undefined;\n    }\n  }\n\n  unwrap<T>(eventual: Eventual<T>): T {\n    if (isProviding(eventual)) {\n      return this.withSlots(\n        eventual[$providing].pairs,\n        () => this.unwrap(eventual[$providing].inner) as T,\n      );\n    }\n\n    let maybeEventual = eventual;\n\n    // Unwrapping all layers of slots.\n    while (true) {\n      if (isSlot(maybeEventual)) {\n        maybeEventual = this.readSlot(maybeEventual);\n      } else if (isDerived(maybeEventual)) {\n        maybeEventual = this._getOrCompute(maybeEventual);\n      } else {\n        break;\n      }\n    }\n\n    return maybeEventual;\n  }\n\n  _getOrCompute<T>(derived: TgpuDerived<T>): T {\n    // All memoized versions of `derived`\n    const instances = this._memoizedDerived.get(derived) ?? [];\n\n    this._itemStateStack.pushItem();\n\n    try {\n      for (const instance of instances) {\n        const slotValuePairs = [...instance.slotToValueMap.entries()];\n\n        if (\n          slotValuePairs.every(([slot, expectedValue]) =>\n            slot.areEqual(this._itemStateStack.readSlot(slot), expectedValue)\n          )\n        ) {\n          return instance.result as T;\n        }\n      }\n\n      // If we got here, no item with the given slot-to-value combo exists in cache yet\n      // Derived computations are always done on the CPU\n      pushMode(RuntimeMode.CPU);\n\n      let result: T;\n      try {\n        result = derived['~compute']();\n      } finally {\n        popMode(RuntimeMode.CPU);\n      }\n\n      // We know which slots the item used while resolving\n      const slotToValueMap = new Map<TgpuSlot<unknown>, unknown>();\n      for (const usedSlot of this._itemStateStack.topItem.usedSlots) {\n        slotToValueMap.set(usedSlot, this._itemStateStack.readSlot(usedSlot));\n      }\n\n      instances.push({ slotToValueMap, result });\n      this._memoizedDerived.set(derived, instances);\n      return result;\n    } catch (err) {\n      if (err instanceof ResolutionError) {\n        throw err.appendToTrace(derived);\n      }\n\n      throw new ResolutionError(err, [derived]);\n    } finally {\n      this._itemStateStack.popItem();\n    }\n  }\n\n  /**\n   * @param item The item whose resolution should be either retrieved from the cache (if there is a cache hit), or resolved.\n   */\n  _getOrInstantiate(item: object): string {\n    // All memoized versions of `item`\n    const instances = this._memoizedResolves.get(item) ?? [];\n\n    this._itemStateStack.pushItem();\n\n    try {\n      for (const instance of instances) {\n        const slotValuePairs = [...instance.slotToValueMap.entries()];\n\n        if (\n          slotValuePairs.every(([slot, expectedValue]) =>\n            slot.areEqual(this._itemStateStack.readSlot(slot), expectedValue)\n          )\n        ) {\n          return instance.result;\n        }\n      }\n\n      // If we got here, no item with the given slot-to-value combo exists in cache yet\n      let result: string;\n      if (isData(item)) {\n        result = resolveData(this, item);\n      } else if (isDerived(item) || isSlot(item)) {\n        result = this.resolve(this.unwrap(item));\n      } else if (isSelfResolvable(item)) {\n        result = item['~resolve'](this);\n      } else {\n        result = this.resolveValue(item);\n      }\n\n      // We know which slots the item used while resolving\n      const slotToValueMap = new Map<TgpuSlot<unknown>, unknown>();\n      for (const usedSlot of this._itemStateStack.topItem.usedSlots) {\n        slotToValueMap.set(usedSlot, this._itemStateStack.readSlot(usedSlot));\n      }\n\n      instances.push({ slotToValueMap, result });\n      this._memoizedResolves.set(item, instances);\n\n      return result;\n    } catch (err) {\n      if (err instanceof ResolutionError) {\n        throw err.appendToTrace(item);\n      }\n\n      throw new ResolutionError(err, [item]);\n    } finally {\n      this._itemStateStack.popItem();\n    }\n  }\n\n  resolve(item: unknown): string {\n    if (isProviding(item)) {\n      return this.withSlots(\n        item[$providing].pairs,\n        () => this.resolve(item[$providing].inner),\n      );\n    }\n\n    if ((item && typeof item === 'object') || typeof item === 'function') {\n      if (this._itemStateStack.itemDepth === 0) {\n        try {\n          pushMode(RuntimeMode.GPU);\n          const result = provideCtx(this, () => this._getOrInstantiate(item));\n          return `${[...this._declarations].join('\\n\\n')}${result}`;\n        } finally {\n          popMode(RuntimeMode.GPU);\n        }\n      }\n\n      return this._getOrInstantiate(item);\n    }\n\n    return String(item);\n  }\n\n  resolveValue<T extends BaseData>(\n    value: Infer<T>,\n    schema?: T | undefined,\n  ): string {\n    if (isWgsl(value)) {\n      return this.resolve(value);\n    }\n\n    if (schema && isWgslArray(schema)) {\n      return `array(${\n        (value as unknown[]).map((element) =>\n          this.resolveValue(element, schema.elementType)\n        )\n      })`;\n    }\n\n    if (Array.isArray(value)) {\n      return `array(${value.map((element) => this.resolveValue(element))})`;\n    }\n\n    if (schema && isWgslStruct(schema)) {\n      return `${this.resolve(schema)}(${\n        Object.entries(schema.propTypes).map(([key, type_]) =>\n          this.resolveValue((value as Infer<typeof schema>)[key], type_)\n        )\n      })`;\n    }\n\n    throw new Error(\n      `Value ${value} (as json: ${\n        JSON.stringify(value)\n      }) of schema ${schema} is not resolvable to WGSL`,\n    );\n  }\n}\n\n/**\n * The results of a WGSL resolution.\n *\n * @param code - The resolved code.\n * @param usedBindGroupLayouts - List of used `tgpu.bindGroupLayout`s.\n * @param catchall - Automatically constructed bind group for buffer usages and buffer shorthands, preceded by its index.\n */\nexport interface ResolutionResult {\n  code: string;\n  usedBindGroupLayouts: TgpuBindGroupLayout[];\n  catchall: [number, TgpuBindGroup] | undefined;\n}\n\nexport function resolve(\n  item: Wgsl,\n  options: ResolutionCtxImplOptions,\n): ResolutionResult {\n  const ctx = new ResolutionCtxImpl(options);\n  let code = ctx.resolve(item);\n\n  const memoMap = ctx.bindGroupLayoutsToPlaceholderMap;\n  const usedBindGroupLayouts: TgpuBindGroupLayout[] = [];\n  const takenIndices = new Set<number>(\n    [...memoMap.keys()]\n      .map((layout) => layout.index)\n      .filter((v): v is number => v !== undefined),\n  );\n\n  const automaticIds = naturalsExcept(takenIndices);\n\n  const layoutEntries = ctx.fixedBindings.map(\n    (binding, idx) =>\n      [String(idx), binding.layoutEntry] as [string, TgpuLayoutEntry],\n  );\n\n  const createCatchallGroup = () => {\n    const catchallIdx = automaticIds.next().value;\n    const catchallLayout = bindGroupLayout(Object.fromEntries(layoutEntries));\n    usedBindGroupLayouts[catchallIdx] = catchallLayout;\n    code = code.replaceAll(CATCHALL_BIND_GROUP_IDX_MARKER, String(catchallIdx));\n\n    return [\n      catchallIdx,\n      new TgpuBindGroupImpl(\n        catchallLayout,\n        Object.fromEntries(\n          ctx.fixedBindings.map(\n            (binding, idx) =>\n              // biome-ignore lint/suspicious/noExplicitAny: <it's fine>\n              [String(idx), binding.resource] as [string, any],\n          ),\n        ),\n      ),\n    ] as [number, TgpuBindGroup];\n  };\n\n  // Retrieving the catch-all binding index first, because it's inherently\n  // the least swapped bind group (fixed and cannot be swapped).\n  const catchall = layoutEntries.length > 0 ? createCatchallGroup() : undefined;\n\n  for (const [layout, placeholder] of memoMap.entries()) {\n    const idx = layout.index ?? automaticIds.next().value;\n    usedBindGroupLayouts[idx] = layout;\n    code = code.replaceAll(placeholder, String(idx));\n  }\n\n  return {\n    code,\n    usedBindGroupLayouts,\n    catchall,\n  };\n}\n\nexport function resolveFunctionHeader(\n  ctx: ResolutionCtx,\n  args: Snippet[],\n  returnType: AnyData,\n) {\n  const argList = args\n    .map((arg) => `${arg.value}: ${ctx.resolve(arg.dataType as AnyData)}`)\n    .join(', ');\n\n  return returnType.type !== 'void'\n    ? `(${argList}) -> ${getAttributesString(returnType)} ${\n      ctx.resolve(returnType)\n    }`\n    : `(${argList})`;\n}\n"]}