Skip to content

Commit a8871c3

Browse files
Fix Show actual type in strands hook error messages
1 parent 626996e commit a8871c3

2 files changed

Lines changed: 100 additions & 6 deletions

File tree

src/strands/strands_api.js

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,12 @@ function enforceReturnTypeMatch(strandsContext, expectedType, returned, hookName
405405
}
406406
if (receivedType.dimension !== expectedType.dimension) {
407407
if (receivedType.dimension !== 1) {
408-
FES.userError('type error', `You have returned a vector with ${receivedType.dimension} components in ${hookName} when a ${expectedType.baseType + expectedType.dimension} was expected!`);
408+
const receivedTypeDisplay = receivedType.baseType + (receivedType.dimension > 1 ? receivedType.dimension : '');
409+
const expectedTypeDisplay = expectedType.baseType + expectedType.dimension;
410+
FES.userError('type error',
411+
`You have returned a ${receivedTypeDisplay} in ${hookName} when a ${expectedTypeDisplay} was expected!\n\n` +
412+
`Make sure your hook returns the correct type.`
413+
);
409414
}
410415
else {
411416
const result = build.primitiveConstructorNode(strandsContext, expectedType, returned);
@@ -494,7 +499,24 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) {
494499
if (retNode instanceof StrandsNode) {
495500
const returnedNode = getNodeDataFromID(strandsContext.dag, retNode.id);
496501
if (returnedNode.baseType !== expectedStructType.typeName) {
497-
FES.userError("type error", `You have returned a ${retNode.baseType} from ${hookType.name} when a ${expectedStructType.typeName} was expected.`);
502+
const receivedTypeName = returnedNode.baseType || 'undefined';
503+
const receivedDim = dag.dimensions[retNode.id];
504+
const receivedTypeDisplay = receivedDim > 1 ?
505+
`${receivedTypeName}${receivedDim}` : receivedTypeName;
506+
507+
const expectedProps = expectedStructType.properties
508+
.map(p => p.name).join(', ');
509+
FES.userError('type error',
510+
`You have returned a ${receivedTypeDisplay} from ${hookType.name} when a ${expectedStructType.typeName} was expected.\n\n` +
511+
`The ${expectedStructType.typeName} struct has these properties: { ${expectedProps} }\n\n` +
512+
`Instead of returning a different type, you should modify and return the ${expectedStructType.typeName} struct that was passed to your hook.\n\n` +
513+
`For example:\n` +
514+
`${hookType.name}((inputs) => {\n` +
515+
` // Modify properties of inputs\n` +
516+
` inputs.someProperty = ...;\n` +
517+
` return inputs; // Return the modified struct\n` +
518+
`})`
519+
);
498520
}
499521
const newDeps = returnedNode.dependsOn.slice();
500522
for (let i = 0; i < expectedStructType.properties.length; i++) {
@@ -513,10 +535,14 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) {
513535
const propName = expectedProp.name;
514536
const receivedValue = retNode[propName];
515537
if (receivedValue === undefined) {
516-
FES.userError('type error', `You've returned an incomplete struct from ${hookType.name}.\n` +
517-
`Expected: { ${expectedReturnType.properties.map(p => p.name).join(', ')} }\n` +
518-
`Received: { ${Object.keys(retNode).join(', ')} }\n` +
519-
`All of the properties are required!`);
538+
const expectedProps = expectedReturnType.properties.map(p => p.name).join(', ');
539+
const receivedProps = Object.keys(retNode).join(', ');
540+
FES.userError('type error',
541+
`You've returned an incomplete ${expectedStructType.typeName} struct from ${hookType.name}.\n\n` +
542+
`Expected properties: { ${expectedProps} }\n` +
543+
`Received properties: { ${receivedProps} }\n\n` +
544+
`All properties are required! Make sure to include all properties in the returned struct.`
545+
);
520546
}
521547
const expectedTypeInfo = expectedProp.dataType;
522548
const returnedPropID = enforceReturnTypeMatch(strandsContext, expectedTypeInfo, receivedValue, hookType.name);

test/unit/webgl/p5.Shader.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
11
import p5 from '../../../src/app.js';
2+
import { vi } from 'vitest';
3+
4+
const mockUserError = vi.fn();
5+
vi.mock('../../../src/strands/strands_FES', () => ({
6+
userError: (...args) => {
7+
mockUserError(...args);
8+
const prefixedMessage = `[p5.strands ${args[0]}]: ${args[1]}`;
9+
throw new Error(prefixedMessage);
10+
},
11+
internalError: (msg) => { throw new Error(`[p5.strands internal error]: ${msg}`); }
12+
}));
13+
214
suite('p5.Shader', function() {
315
var myp5;
416
beforeAll(function() {
@@ -1921,4 +1933,60 @@ suite('p5.Shader', function() {
19211933
assert.approximately(pixelColor[2], 0, 5);
19221934
});
19231935
});
1936+
1937+
suite('p5.strands error messages', () => {
1938+
afterEach(() => {
1939+
mockUserError.mockClear();
1940+
});
1941+
1942+
test('wrong type in struct hook shows actual type and expected properties', () => {
1943+
myp5.createCanvas(50, 50, myp5.WEBGL);
1944+
1945+
try {
1946+
myp5.baseMaterialShader().modify(() => {
1947+
myp5.getWorldInputs(() => [1, 2, 3, 4]); // vec4 instead of Vertex struct
1948+
}, { myp5 });
1949+
} catch (e) { /* expected */ }
1950+
1951+
assert.isAbove(mockUserError.mock.calls.length, 0, 'FES.userError should have been called');
1952+
const errMsg = mockUserError.mock.calls[0][1];
1953+
assert.notInclude(errMsg, 'a undefined'); // #8444
1954+
assert.include(errMsg, 'float4');
1955+
assert.include(errMsg, 'getWorldInputs');
1956+
assert.include(errMsg, 'Vertex');
1957+
assert.include(errMsg, 'properties');
1958+
});
1959+
1960+
test('vector dimension mismatch shows actual and expected types', () => {
1961+
myp5.createCanvas(50, 50, myp5.WEBGL);
1962+
1963+
try {
1964+
myp5.baseMaterialShader().modify(() => {
1965+
myp5.getFinalColor((c) => [c.r, c.g, c.b]); // vec3 instead of vec4
1966+
}, { myp5 });
1967+
} catch (e) { /* expected */ }
1968+
1969+
assert.isAbove(mockUserError.mock.calls.length, 0, 'FES.userError should have been called');
1970+
const errMsg = mockUserError.mock.calls[0][1];
1971+
assert.include(errMsg, 'float3');
1972+
assert.include(errMsg, 'float4');
1973+
});
1974+
1975+
test('incomplete struct shows expected vs received properties', () => {
1976+
myp5.createCanvas(50, 50, myp5.WEBGL);
1977+
1978+
try {
1979+
myp5.baseMaterialShader().modify(() => {
1980+
myp5.getWorldInputs((inputs) => {
1981+
return { position: inputs.position };
1982+
});
1983+
}, { myp5 });
1984+
} catch (e) { /* expected */ }
1985+
1986+
assert.isAbove(mockUserError.mock.calls.length, 0, 'FES.userError should have been called');
1987+
const errMsg = mockUserError.mock.calls[0][1];
1988+
assert.include(errMsg, 'Expected properties');
1989+
assert.include(errMsg, 'Received properties');
1990+
});
1991+
});
19241992
});

0 commit comments

Comments
 (0)