Skip to content

Commit ca63a09

Browse files
authored
feat: Variance annotation overrides to achieve better type behavior (#2073)
Review fixes
1 parent 3c0e71d commit ca63a09

File tree

19 files changed

+98
-83
lines changed

19 files changed

+98
-83
lines changed

packages/typegpu/src/core/buffer/bufferShorthand.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ interface TgpuBufferShorthandBase<TData extends BaseData> extends TgpuNamable {
3131
// ---
3232
}
3333

34-
export interface TgpuMutable<TData extends BaseData>
34+
export interface TgpuMutable<out TData extends BaseData>
3535
extends TgpuBufferShorthandBase<TData> {
3636
readonly resourceType: 'mutable';
3737
readonly buffer: TgpuBuffer<TData> & StorageFlag;
@@ -45,7 +45,7 @@ export interface TgpuMutable<TData extends BaseData>
4545
// ---
4646
}
4747

48-
export interface TgpuReadonly<TData extends BaseData>
48+
export interface TgpuReadonly<out TData extends BaseData>
4949
extends TgpuBufferShorthandBase<TData> {
5050
readonly resourceType: 'readonly';
5151
readonly buffer: TgpuBuffer<TData> & StorageFlag;
@@ -59,7 +59,7 @@ export interface TgpuReadonly<TData extends BaseData>
5959
// ---
6060
}
6161

62-
export interface TgpuUniform<TData extends BaseData>
62+
export interface TgpuUniform<out TData extends BaseData>
6363
extends TgpuBufferShorthandBase<TData> {
6464
readonly resourceType: 'uniform';
6565
readonly buffer: TgpuBuffer<TData> & UniformFlag;

packages/typegpu/src/core/buffer/bufferUsage.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ class TgpuFixedBufferImpl<
237237
}
238238

239239
export class TgpuLaidOutBufferImpl<
240-
TData extends BaseData,
240+
TData extends AnyWgslData,
241241
TUsage extends BindableBufferUsage,
242242
> implements TgpuBufferUsage<TData, TUsage>, SelfResolvable {
243243
/** Type-token, not available at runtime */
@@ -257,21 +257,20 @@ export class TgpuLaidOutBufferImpl<
257257
}
258258

259259
[$resolve](ctx: ResolutionCtx): ResolvedSnippet {
260-
const dataType = this.dataType as unknown as AnyData;
261260
const id = ctx.getUniqueName(this);
262261
const group = ctx.allocateLayoutEntry(this.#membership.layout);
263262
const usage = usageToVarTemplateMap[this.usage];
264263

265264
ctx.addDeclaration(
266265
`@group(${group}) @binding(${this.#membership.idx}) var<${usage}> ${id}: ${
267-
ctx.resolve(dataType).value
266+
ctx.resolve(this.dataType).value
268267
};`,
269268
);
270269

271270
return snip(
272271
id,
273-
dataType,
274-
isNaturallyEphemeral(dataType) ? 'runtime' : this.usage,
272+
this.dataType,
273+
isNaturallyEphemeral(this.dataType) ? 'runtime' : this.usage,
275274
);
276275
}
277276

@@ -280,7 +279,7 @@ export class TgpuLaidOutBufferImpl<
280279
}
281280

282281
get [$gpuValueOf](): InferGPU<TData> {
283-
const schema = this.dataType as AnyData;
282+
const schema = this.dataType;
284283
const usage = this.usage;
285284

286285
return new Proxy({

packages/typegpu/src/core/function/dualImpl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function dualImpl<T extends (...args: never[]) => unknown>(
5454

5555
const argSnippets = args as MapValueToSnippet<Parameters<T>>;
5656
const converted = argSnippets.map((s, idx) => {
57-
const argType = argTypes[idx] as AnyData | undefined;
57+
const argType = argTypes[idx];
5858
if (!argType) {
5959
throw new Error('Function called with invalid arguments');
6060
}

packages/typegpu/src/core/resolve/resolveData.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
import { formatToWGSLType } from '../../data/vertexFormatData.ts';
1717
import type {
1818
AnyWgslData,
19-
BaseData,
2019
Bool,
2120
F16,
2221
F32,
@@ -119,10 +118,10 @@ function isIdentityType(data: AnyWgslData): data is IdentityType {
119118
*/
120119
function resolveStructProperty(
121120
ctx: ResolutionCtx,
122-
[key, property]: [string, BaseData],
121+
[key, property]: [string, AnyData],
123122
) {
124123
return ` ${getAttributesString(property)}${key}: ${
125-
ctx.resolve(property as AnyWgslData).value
124+
ctx.resolve(property).value
126125
},\n`;
127126
}
128127

@@ -142,7 +141,7 @@ function resolveStruct(ctx: ResolutionCtx, struct: WgslStruct) {
142141
ctx.addDeclaration(`\
143142
struct ${id} {
144143
${
145-
Object.entries(struct.propTypes as Record<string, BaseData>)
144+
Object.entries(struct.propTypes)
146145
.map((prop) => resolveStructProperty(ctx, prop))
147146
.join('')
148147
}\
@@ -172,7 +171,7 @@ function resolveUnstruct(ctx: ResolutionCtx, unstruct: Unstruct) {
172171
ctx.addDeclaration(`\
173172
struct ${id} {
174173
${
175-
Object.entries(unstruct.propTypes as Record<string, BaseData>)
174+
Object.entries(unstruct.propTypes)
176175
.map((prop) =>
177176
isAttribute(prop[1])
178177
? resolveStructProperty(ctx, [

packages/typegpu/src/core/root/init.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
import type { AnyData, Disarray } from '../../data/dataTypes.ts';
1212
import type {
1313
AnyWgslData,
14-
BaseData,
1514
U16,
1615
U32,
1716
v3u,
@@ -657,7 +656,7 @@ class TgpuRootImpl extends WithBindingImpl
657656
TgpuVertexLayout,
658657
{
659658
buffer:
660-
| (TgpuBuffer<WgslArray<BaseData> | Disarray<BaseData>> & VertexFlag)
659+
| (TgpuBuffer<WgslArray | Disarray> & VertexFlag)
661660
| GPUBuffer;
662661
offset?: number | undefined;
663662
size?: number | undefined;

packages/typegpu/src/data/attributes.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
isLooseData,
2626
isLooseDecorated,
2727
type LooseDecorated,
28-
type LooseTypeLiteral,
2928
type Undecorate,
3029
} from './dataTypes.ts';
3130
import { sizeOf } from './sizeOf.ts';
@@ -50,7 +49,6 @@ import {
5049
type PerspectiveOrLinearInterpolationType,
5150
type Size,
5251
type Vec4f,
53-
type WgslTypeLiteral,
5452
} from './wgslTypes.ts';
5553

5654
// ----------
@@ -113,9 +111,9 @@ export type ExtractAttributes<T> = T extends {
113111
export type Decorate<
114112
TData extends BaseData,
115113
TAttrib extends AnyAttribute,
116-
> = TData['type'] extends WgslTypeLiteral
114+
> = TData extends AnyWgslData
117115
? Decorated<Undecorate<TData>, [TAttrib, ...ExtractAttributes<TData>]>
118-
: TData['type'] extends LooseTypeLiteral
116+
: TData extends AnyLooseData
119117
? LooseDecorated<Undecorate<TData>, [TAttrib, ...ExtractAttributes<TData>]>
120118
: never;
121119

packages/typegpu/src/data/dataTypes.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import type { WgslComparisonSampler, WgslSampler } from './sampler.ts';
3838
* unless they are explicitly decorated with the custom align attribute
3939
* via `d.align` function.
4040
*/
41-
export interface Disarray<TElement extends wgsl.BaseData = wgsl.BaseData>
41+
export interface Disarray<out TElement extends wgsl.BaseData = wgsl.BaseData>
4242
extends wgsl.BaseData {
4343
<T extends TElement>(elements: Infer<T>[]): Infer<T>[];
4444
(): Infer<TElement>[];
@@ -66,8 +66,8 @@ export interface Disarray<TElement extends wgsl.BaseData = wgsl.BaseData>
6666
* via `d.align` function.
6767
*/
6868
export interface Unstruct<
69-
// biome-ignore lint/suspicious/noExplicitAny: the widest type that works with both covariance and contravariance
70-
TProps extends Record<string, wgsl.BaseData> = any,
69+
// @ts-expect-error: Override variance, as we want unstructs to behave like objects
70+
out TProps extends Record<string, AnyData> = Record<string, AnyData>,
7171
> extends wgsl.BaseData, TgpuNamable {
7272
(props: Prettify<InferRecord<TProps>>): Prettify<InferRecord<TProps>>;
7373
(): Prettify<InferRecord<TProps>>;
@@ -93,8 +93,8 @@ export interface Unstruct<
9393
export type AnyUnstruct = Unstruct;
9494

9595
export interface LooseDecorated<
96-
TInner extends wgsl.BaseData = wgsl.BaseData,
97-
TAttribs extends unknown[] = unknown[],
96+
out TInner extends wgsl.BaseData = wgsl.BaseData,
97+
out TAttribs extends unknown[] = unknown[],
9898
> extends wgsl.BaseData {
9999
readonly type: 'loose-decorated';
100100
readonly inner: TInner;

packages/typegpu/src/data/deepEqual.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ export function deepEqual(a: AnyData, b: AnyData): boolean {
5555
const keyB = bKeys[i];
5656
if (
5757
keyA !== keyB || !keyA || !keyB ||
58-
!deepEqual(aProps[keyA], bProps[keyB])
58+
// biome-ignore lint/style/noNonNullAssertion: they exist
59+
!deepEqual(aProps[keyA]!, bProps[keyB]!)
5960
) {
6061
return false;
6162
}
@@ -74,12 +75,12 @@ export function deepEqual(a: AnyData, b: AnyData): boolean {
7475
return (
7576
a.addressSpace === b.addressSpace &&
7677
a.access === b.access &&
77-
deepEqual(a.inner as AnyData, b.inner as AnyData)
78+
deepEqual(a.inner, b.inner)
7879
);
7980
}
8081

8182
if (isAtomic(a) && isAtomic(b)) {
82-
return deepEqual(a.inner as AnyData, b.inner as AnyData);
83+
return deepEqual(a.inner, b.inner);
8384
}
8485

8586
if (

packages/typegpu/src/data/offsets.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import alignIO from './alignIO.ts';
44
import { alignmentOf, customAlignmentOf } from './alignmentOf.ts';
55
import { isUnstruct, type Unstruct } from './dataTypes.ts';
66
import { sizeOf } from './sizeOf.ts';
7-
import type { BaseData, WgslStruct } from './wgslTypes.ts';
7+
import type { WgslStruct } from './wgslTypes.ts';
88

99
export interface OffsetInfo {
1010
offset: number;
@@ -17,16 +17,18 @@ const cachedOffsets = new WeakMap<
1717
Record<string, OffsetInfo>
1818
>();
1919

20-
export function offsetsForProps<T extends Record<string, BaseData>>(
21-
struct: WgslStruct<T> | Unstruct<T>,
22-
): Record<keyof T, OffsetInfo> {
20+
export function offsetsForProps<T extends WgslStruct | Unstruct>(
21+
struct: T,
22+
): Record<keyof T['propTypes'], OffsetInfo> {
23+
type Key = keyof T['propTypes'];
24+
2325
const cached = cachedOffsets.get(struct);
2426
if (cached) {
25-
return cached as Record<keyof T, OffsetInfo>;
27+
return cached as Record<Key, OffsetInfo>;
2628
}
2729

2830
const measurer = new Measurer();
29-
const offsets = {} as Record<keyof T, OffsetInfo>;
31+
const offsets = {} as Record<Key, OffsetInfo>;
3032
let lastEntry: OffsetInfo | undefined;
3133

3234
for (const key in struct.propTypes) {
@@ -47,7 +49,7 @@ export function offsetsForProps<T extends Record<string, BaseData>>(
4749
}
4850

4951
const propSize = sizeOf(prop);
50-
offsets[key] = { offset: measurer.size, size: propSize };
52+
offsets[key as Key] = { offset: measurer.size, size: propSize };
5153
lastEntry = offsets[key];
5254
measurer.add(propSize);
5355
}
@@ -57,12 +59,6 @@ export function offsetsForProps<T extends Record<string, BaseData>>(
5759
measurer.size;
5860
}
5961

60-
cachedOffsets.set(
61-
struct as
62-
| WgslStruct<Record<string, BaseData>>
63-
| Unstruct<Record<string, BaseData>>,
64-
offsets,
65-
);
66-
62+
cachedOffsets.set(struct, offsets);
6763
return offsets;
6864
}

packages/typegpu/src/data/struct.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { isValidProp } from '../nameRegistry.ts';
22
import { getName, setName } from '../shared/meta.ts';
33
import { $internal } from '../shared/symbols.ts';
4-
import type { AnyData } from './dataTypes.ts';
54
import { schemaCallWrapper } from './schemaCallWrapper.ts';
6-
import type { AnyWgslData, BaseData, WgslStruct } from './wgslTypes.ts';
5+
import type { AnyWgslData, WgslStruct } from './wgslTypes.ts';
76

87
// ----------
98
// Public API
@@ -26,7 +25,7 @@ export function struct<TProps extends Record<string, AnyWgslData>>(
2625
return INTERNAL_createStruct(props, false);
2726
}
2827

29-
export function abstruct<TProps extends Record<string, BaseData>>(
28+
export function abstruct<TProps extends Record<string, AnyWgslData>>(
3029
props: TProps,
3130
): WgslStruct<TProps> {
3231
return INTERNAL_createStruct(props, true);
@@ -36,7 +35,7 @@ export function abstruct<TProps extends Record<string, BaseData>>(
3635
// Implementation
3736
// --------------
3837

39-
function INTERNAL_createStruct<TProps extends Record<string, BaseData>>(
38+
function INTERNAL_createStruct<TProps extends Record<string, AnyWgslData>>(
4039
props: TProps,
4140
isAbstruct: boolean,
4241
): WgslStruct<TProps> {
@@ -54,7 +53,7 @@ function INTERNAL_createStruct<TProps extends Record<string, BaseData>>(
5453
Object.fromEntries(
5554
Object.entries(props).map(([key, schema]) => [
5655
key,
57-
schemaCallWrapper(schema as AnyData, instanceProps?.[key]),
56+
schemaCallWrapper(schema, instanceProps?.[key]),
5857
]),
5958
);
6059

0 commit comments

Comments
 (0)