Skip to content

Commit de8d7ac

Browse files
authored
Merge pull request #8505 from Anshumancanrock/fix-undefined-bug
Fix Show actual type in strands hook error messages
2 parents c538e7d + 05a0cb0 commit de8d7ac

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
@@ -485,7 +485,12 @@ function enforceReturnTypeMatch(strandsContext, expectedType, returned, hookName
485485
}
486486
if (receivedType.dimension !== expectedType.dimension) {
487487
if (receivedType.dimension !== 1) {
488-
FES.userError('type error', `You have returned a vector with ${receivedType.dimension} components in ${hookName} when a ${expectedType.baseType + expectedType.dimension} was expected!`);
488+
const receivedTypeDisplay = receivedType.baseType + (receivedType.dimension > 1 ? receivedType.dimension : '');
489+
const expectedTypeDisplay = expectedType.baseType + expectedType.dimension;
490+
FES.userError('type error',
491+
`You have returned a ${receivedTypeDisplay} in ${hookName} when a ${expectedTypeDisplay} was expected!\n\n` +
492+
`Make sure your hook returns the correct type.`
493+
);
489494
}
490495
else {
491496
const result = build.primitiveConstructorNode(strandsContext, expectedType, returned);
@@ -576,7 +581,24 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) {
576581
if (retNode instanceof StrandsNode) {
577582
const returnedNode = getNodeDataFromID(strandsContext.dag, retNode.id);
578583
if (returnedNode.baseType !== expectedStructType.typeName) {
579-
FES.userError("type error", `You have returned a ${retNode.baseType} from ${hookType.name} when a ${expectedStructType.typeName} was expected.`);
584+
const receivedTypeName = returnedNode.baseType || 'undefined';
585+
const receivedDim = dag.dimensions[retNode.id];
586+
const receivedTypeDisplay = receivedDim > 1 ?
587+
`${receivedTypeName}${receivedDim}` : receivedTypeName;
588+
589+
const expectedProps = expectedStructType.properties
590+
.map(p => p.name).join(', ');
591+
FES.userError('type error',
592+
`You have returned a ${receivedTypeDisplay} from ${hookType.name} when a ${expectedStructType.typeName} was expected.\n\n` +
593+
`The ${expectedStructType.typeName} struct has these properties: { ${expectedProps} }\n\n` +
594+
`Instead of returning a different type, you should modify and return the ${expectedStructType.typeName} struct that was passed to your hook.\n\n` +
595+
`For example:\n` +
596+
`${hookType.name}((inputs) => {\n` +
597+
` // Modify properties of inputs\n` +
598+
` inputs.someProperty = ...;\n` +
599+
` return inputs; // Return the modified struct\n` +
600+
`})`
601+
);
580602
}
581603
const newDeps = returnedNode.dependsOn.slice();
582604
for (let i = 0; i < expectedStructType.properties.length; i++) {
@@ -595,10 +617,14 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) {
595617
const propName = expectedProp.name;
596618
const receivedValue = retNode[propName];
597619
if (receivedValue === undefined) {
598-
FES.userError('type error', `You've returned an incomplete struct from ${hookType.name}.\n` +
599-
`Expected: { ${expectedReturnType.properties.map(p => p.name).join(', ')} }\n` +
600-
`Received: { ${Object.keys(retNode).join(', ')} }\n` +
601-
`All of the properties are required!`);
620+
const expectedProps = expectedReturnType.properties.map(p => p.name).join(', ');
621+
const receivedProps = Object.keys(retNode).join(', ');
622+
FES.userError('type error',
623+
`You've returned an incomplete ${expectedStructType.typeName} struct from ${hookType.name}.\n\n` +
624+
`Expected properties: { ${expectedProps} }\n` +
625+
`Received properties: { ${receivedProps} }\n\n` +
626+
`All properties are required! Make sure to include all properties in the returned struct.`
627+
);
602628
}
603629
const expectedTypeInfo = expectedProp.dataType;
604630
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() {
@@ -1942,4 +1954,60 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca
19421954
assert.approximately(pixelColor[2], 0, 5);
19431955
});
19441956
});
1957+
1958+
suite('p5.strands error messages', () => {
1959+
afterEach(() => {
1960+
mockUserError.mockClear();
1961+
});
1962+
1963+
test('wrong type in struct hook shows actual type and expected properties', () => {
1964+
myp5.createCanvas(50, 50, myp5.WEBGL);
1965+
1966+
try {
1967+
myp5.baseMaterialShader().modify(() => {
1968+
myp5.getWorldInputs(() => [1, 2, 3, 4]); // vec4 instead of Vertex struct
1969+
}, { myp5 });
1970+
} catch (e) { /* expected */ }
1971+
1972+
assert.isAbove(mockUserError.mock.calls.length, 0, 'FES.userError should have been called');
1973+
const errMsg = mockUserError.mock.calls[0][1];
1974+
assert.notInclude(errMsg, 'a undefined'); //
1975+
assert.include(errMsg, 'float4');
1976+
assert.include(errMsg, 'getWorldInputs');
1977+
assert.include(errMsg, 'Vertex');
1978+
assert.include(errMsg, 'properties');
1979+
});
1980+
1981+
test('vector dimension mismatch shows actual and expected types', () => {
1982+
myp5.createCanvas(50, 50, myp5.WEBGL);
1983+
1984+
try {
1985+
myp5.baseMaterialShader().modify(() => {
1986+
myp5.getFinalColor((c) => [c.r, c.g, c.b]); // vec3 instead of vec4
1987+
}, { myp5 });
1988+
} catch (e) { /* expected */ }
1989+
1990+
assert.isAbove(mockUserError.mock.calls.length, 0, 'FES.userError should have been called');
1991+
const errMsg = mockUserError.mock.calls[0][1];
1992+
assert.include(errMsg, 'float3');
1993+
assert.include(errMsg, 'float4');
1994+
});
1995+
1996+
test('incomplete struct shows expected vs received properties', () => {
1997+
myp5.createCanvas(50, 50, myp5.WEBGL);
1998+
1999+
try {
2000+
myp5.baseMaterialShader().modify(() => {
2001+
myp5.getWorldInputs((inputs) => {
2002+
return { position: inputs.position };
2003+
});
2004+
}, { myp5 });
2005+
} catch (e) { /* expected */ }
2006+
2007+
assert.isAbove(mockUserError.mock.calls.length, 0, 'FES.userError should have been called');
2008+
const errMsg = mockUserError.mock.calls[0][1];
2009+
assert.include(errMsg, 'Expected properties');
2010+
assert.include(errMsg, 'Received properties');
2011+
});
2012+
});
19452013
});

0 commit comments

Comments
 (0)