Skip to content

Commit 04d1123

Browse files
Add friendly FES error for dimension mismatch on shared variable assignment (#8812)
1 parent e34e5ab commit 04d1123

6 files changed

Lines changed: 135 additions & 8 deletions

File tree

src/strands/ir_builders.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,11 @@ export function swizzleTrap(id, dimension, strandsContext, onRebind) {
572572
scalars.push(createStrandsNode(id, dimension, strandsContext));
573573
}
574574
} else {
575-
FES.userError('type error', `Swizzle assignment: RHS vector does not match LHS vector (need ${chars.length}, got ${value.dimension}).`);
575+
FES.dimensionMismatchError(
576+
chars.length,
577+
value.dimension,
578+
`${target._originalIdentifier || 'value'}.${property}`
579+
);
576580
}
577581
} else if (Array.isArray(value)) {
578582
const flat = value.flat(Infinity);

src/strands/strands_FES.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,11 @@ export function internalError(errorMessage) {
66
export function userError(errorType, errorMessage) {
77
const prefixedMessage = `[p5.strands ${errorType}]: ${errorMessage}`;
88
throw new Error(prefixedMessage);
9+
}
10+
11+
export function dimensionMismatchError(declaredDim,actualDim,varName){
12+
userError(
13+
'dimension mismatch',
14+
`Cannot assign a value of dimension ${actualDim} to \`${varName}\`, which expects dimension ${declaredDim}.`
15+
);
916
}

src/strands/strands_api.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,14 @@ function createHookArguments(strandsContext, parameters){
671671
return createStrandsNode(propNode.id, propNode.dimension, strandsContext, onRebind);
672672
},
673673
set(val) {
674+
675+
if(val?.isStrandsNode&&val.dimension!==propertyType.dataType.dimension){
676+
FES.dimensionMismatchError(
677+
propertyType.dataType.dimension,
678+
val.dimension,
679+
`${param.name}.${propertyType.name}`
680+
);
681+
}
674682
const oldDependsOn = dag.dependsOn[structNode.id];
675683
const newDependsOn = [...oldDependsOn];
676684
let newValueID;

src/strands/strands_node.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { swizzleTrap, primitiveConstructorNode, variableNode, arrayAccessNode, a
22
import { BaseType, NodeType, OpCode } from './ir_types';
33
import { getNodeDataFromID, createNodeData, getOrCreateNode } from './ir_dag';
44
import { recordInBasicBlock } from './ir_cfg';
5+
import { dimensionMismatchError } from './strands_FES';
56
export class StrandsNode {
67
constructor(id, dimension, strandsContext) {
78
this.id = id;
@@ -56,6 +57,13 @@ export class StrandsNode {
5657

5758
// For varying variables, we need both assignment generation AND a way to reference by identifier
5859
if (this._originalIdentifier) {
60+
if(value?.isStrandsNode && value.dimension!==this._originalDimension){
61+
dimensionMismatchError(
62+
this._originalDimension,
63+
value.dimension,
64+
this._originalIdentifier
65+
);
66+
}
5967
// Create a variable node for the target (the varying variable)
6068
const { id: targetVarID } = variableNode(
6169
this.strandsContext,
@@ -108,6 +116,13 @@ export class StrandsNode {
108116

109117
// For varying variables, create swizzle assignment
110118
if (this._originalIdentifier) {
119+
if(value?.isStrandsNode && value.dimension!==swizzlePattern.length){
120+
dimensionMismatchError(
121+
swizzlePattern.length,
122+
value.dimension,
123+
`${this._originalIdentifier}.${swizzlePattern}`
124+
);
125+
}
111126
// Create a variable node for the target with swizzle
112127
const { id: targetVarID } = variableNode(
113128
this.strandsContext,

test/unit/webgl/p5.Shader.js

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
import p5 from '../../../src/app.js';
2-
import { vi } from 'vitest';
2+
import { beforeEach, vi } from 'vitest';
33

44
const mockUserError = vi.fn();
5-
vi.mock('../../../src/strands/strands_FES', () => ({
6-
userError: (...args) => {
5+
vi.mock('../../../src/strands/strands_FES', () => {
6+
const userError = (...args) => {
77
mockUserError(...args);
88
const prefixedMessage = `[p5.strands ${args[0]}]: ${args[1]}`;
99
throw new Error(prefixedMessage);
10-
},
11-
internalError: (msg) => { throw new Error(`[p5.strands internal error]: ${msg}`); }
12-
}));
10+
};
11+
return {
12+
userError,
13+
internalError: (msg) => { throw new Error(`[p5.strands internal error]: ${msg}`); },
14+
dimensionMismatchError: (declaredDim, actualDim, varName) => {
15+
userError(
16+
'dimension mismatch',
17+
`Cannot assign a value of dimension ${actualDim} to \`${varName}\`, which expects dimension ${declaredDim}.`
18+
);
19+
},
20+
};
21+
});
1322

1423
suite('p5.Shader', function() {
1524
var myp5;
@@ -2608,6 +2617,48 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca
26082617
assert.approximately(pixelColor[1], 0, 5);
26092618
assert.approximately(pixelColor[2], 0, 5);
26102619
});
2620+
2621+
test('reports a friendly error when assigning a scalar to a sharedVec3 (bridge)', async () => {
2622+
await myp5.createCanvas(5, 5, myp5.WEBGL);
2623+
2624+
expect(() => {
2625+
myp5.baseMaterialShader().modify(() => {
2626+
let worldPosX = myp5.sharedVec3();
2627+
myp5.getWorldInputs(inputs => {
2628+
worldPosX = inputs.position.x; // scalar → vec3 mismatch
2629+
return inputs;
2630+
});
2631+
},{myp5});
2632+
}).toThrow(/dimension mismatch/);
2633+
});
2634+
2635+
test('reports a friendly error on dimension mismatch via swizzle write (bridgeSwizzle)', async () => {
2636+
await myp5.createCanvas(5, 5, myp5.WEBGL);
2637+
2638+
expect(() => {
2639+
myp5.baseMaterialShader().modify(() => {
2640+
let myVec = myp5.sharedVec3();
2641+
myp5.getWorldInputs(inputs => {
2642+
myVec.xy = inputs.position; // vec3 → 2-component swizzle mismatch
2643+
return inputs;
2644+
});
2645+
},{myp5});
2646+
}).toThrow(/dimension mismatch/);
2647+
});
2648+
2649+
test('does not error when shared variable assignment dimensions match', async () => {
2650+
await myp5.createCanvas(5, 5, myp5.WEBGL);
2651+
2652+
expect(() => {
2653+
myp5.baseMaterialShader().modify(() => {
2654+
let myVec = myp5.sharedVec3();
2655+
myp5.getWorldInputs(inputs => {
2656+
myVec = inputs.position; // vec3 → vec3, OK
2657+
return inputs;
2658+
});
2659+
},{myp5});
2660+
}).not.toThrow();
2661+
});
26112662
});
26122663

26132664
suite('p5.strands error messages', () => {
@@ -2625,7 +2676,7 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca
26252676
assert.include(err.message, '// noprotect');
26262677
};
26272678

2628-
afterEach(() => {
2679+
beforeEach(() => {
26292680
mockUserError.mockClear();
26302681
});
26312682

test/unit/webgpu/p5.Shader.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,6 +1361,48 @@ suite('WebGPU p5.Shader', function() {
13611361
myp5.compute(s4, 4);
13621362
}).not.toThrow();
13631363
});
1364+
1365+
test('reports a friendly error when assigning a scalar to a sharedVec3 (bridge)', async () => {
1366+
await myp5.createCanvas(5, 5, myp5.WEBGPU);
1367+
1368+
expect(() => {
1369+
myp5.baseMaterialShader().modify(() => {
1370+
let worldPosX = myp5.sharedVec3();
1371+
myp5.getWorldInputs(inputs => {
1372+
worldPosX = inputs.position.x; // scalar → vec3 mismatch
1373+
return inputs;
1374+
});
1375+
},{myp5});
1376+
}).toThrow(/dimension mismatch/);
1377+
});
1378+
1379+
test('reports a friendly error on dimension mismatch via swizzle write (bridgeSwizzle)', async () => {
1380+
await myp5.createCanvas(5, 5, myp5.WEBGPU);
1381+
1382+
expect(() => {
1383+
myp5.baseMaterialShader().modify(() => {
1384+
let myVec = myp5.sharedVec3();
1385+
myp5.getWorldInputs(inputs => {
1386+
myVec.xy = inputs.position; // vec3 → 2-component swizzle mismatch
1387+
return inputs;
1388+
});
1389+
},{myp5});
1390+
}).toThrow(/dimension mismatch/);
1391+
});
1392+
1393+
test('does not error when shared variable assignment dimensions match', async () => {
1394+
await myp5.createCanvas(5, 5, myp5.WEBGPU);
1395+
1396+
expect(() => {
1397+
myp5.baseMaterialShader().modify(() => {
1398+
let myVec = myp5.sharedVec3();
1399+
myp5.getWorldInputs(inputs => {
1400+
myVec = inputs.position; // vec3 → vec3, OK
1401+
return inputs;
1402+
});
1403+
},{myp5});
1404+
}).not.toThrow();
1405+
});
13641406
});
13651407
});
13661408
});

0 commit comments

Comments
 (0)