From 219577eedebec19a4f4a79f3fe8e790019a10267 Mon Sep 17 00:00:00 2001 From: skyash-dev Date: Fri, 11 Sep 2026 17:03:01 +0530 Subject: [PATCH 1/4] check and warn incorrect glsl comparisons --- src/strands/ir_builders.js | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/strands/ir_builders.js b/src/strands/ir_builders.js index 2dcbf3debf..e9444bbce6 100644 --- a/src/strands/ir_builders.js +++ b/src/strands/ir_builders.js @@ -214,6 +214,46 @@ export function binaryOpNode( } } + const leftDim = dag.dimensions[finalLeftNodeID]; + const rightDim = dag.dimensions[finalRightNodeID]; + const leftBase = dag.baseTypes[finalLeftNodeID]; + const rightBase = dag.baseTypes[finalRightNodeID]; + + const isOrdering = [ + OpCode.Binary.LESS_THAN, + OpCode.Binary.LESS_EQUAL, + OpCode.Binary.GREATER_THAN, + OpCode.Binary.GREATER_EQUAL, + ].includes(opCode); + const isEquality = opCode === OpCode.Binary.EQUAL || opCode === OpCode.Binary.NOT_EQUAL; + const isLogical = opCode === OpCode.Binary.LOGICAL_AND || opCode === OpCode.Binary.LOGICAL_OR; + + if (isOrdering) { + if (leftDim > 1 || rightDim > 1) { + FES.userError( + 'type error', + `${OpCodeToSymbol[opCode]} is only defined for scalars. ` + + `Got ${leftBase}${leftDim} ${OpCodeToSymbol[opCode]} ${rightBase}${rightDim}.` + ); + } + } else if (isEquality) { + if ((leftDim > 1 || rightDim > 1) && (leftDim !== rightDim || leftBase !== rightBase)) { + FES.userError( + 'type error', + `Equality comparisons between vectors require matching dimensions and base types. ` + + `Got ${leftBase}${leftDim} ${OpCodeToSymbol[opCode]} ${rightBase}${rightDim}.` + ); + } + } else if (isLogical) { + if (leftBase !== BaseType.BOOL || rightBase !== BaseType.BOOL || leftDim !== 1 || rightDim !== 1) { + FES.userError( + 'type error', + `${OpCodeToSymbol[opCode]} requires two bool scalars. ` + + `Got ${leftBase}${leftDim} ${OpCodeToSymbol[opCode]} ${rightBase}${rightDim}.` + ); + } + } + if (booleanOpCode[opCode]) { cast.toType.baseType = BaseType.BOOL; cast.toType.dimension = 1; From 1d881082b6501f13cbc737e36791f248af50da02 Mon Sep 17 00:00:00 2001 From: skyash-dev Date: Fri, 11 Sep 2026 19:01:55 +0530 Subject: [PATCH 2/4] add unit tests --- test/unit/webgl/p5.Shader.js | 112 +++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/test/unit/webgl/p5.Shader.js b/test/unit/webgl/p5.Shader.js index 93ef6be721..a3735e17a8 100644 --- a/test/unit/webgl/p5.Shader.js +++ b/test/unit/webgl/p5.Shader.js @@ -3781,6 +3781,118 @@ suite('p5.Shader', function () { assert.include(errMsg, 'float4'); }); + test('ordering comparison with a vector operand throws a clear strands type error', () => { + myp5.createCanvas(50, 50, myp5.WEBGL); + + try { + myp5.baseMaterialShader().modify( + () => { + myp5.getFinalColor(color => { + if (color < 2) { + color = [1, 1, 1, 1]; + } + return color; + }); + }, + { myp5 } + ); + } catch (e) { + /* expected */ + } + + assert.isAbove( + mockUserError.mock.calls.length, + 0, + 'FES.userError should have been called' + ); + const errMsg = mockUserError.mock.calls[0][1]; + assert.include(errMsg, '<'); + assert.include(errMsg, 'only defined for scalars'); + }); + + test('ordering comparison between scalars is allowed', () => { + myp5.createCanvas(50, 50, myp5.WEBGL); + + myp5.baseMaterialShader().modify( + () => { + myp5.getFinalColor(color => { + if (color.r < 0.5) { + color = [1, 1, 1, 1]; + } + return color; + }); + }, + { myp5 } + ); + + assert.equal(mockUserError.mock.calls.length, 0); + }); + + test('equality comparison between matching vectors is allowed', () => { + myp5.createCanvas(50, 50, myp5.WEBGL); + + myp5.baseMaterialShader().modify( + () => { + myp5.getFinalColor(color => { + if (color.equalTo([1, 1, 1, 1])) { + color = [1, 1, 1, 1]; + } + return color; + }); + }, + { myp5 } + ); + + assert.equal(mockUserError.mock.calls.length, 0); + }); + + test('logical and with non-boolean operands throws a clear strands type error', () => { + myp5.createCanvas(50, 50, myp5.WEBGL); + + try { + myp5.baseMaterialShader().modify( + () => { + myp5.getFinalColor(color => { + if (color.r && color.g) { + color = [1, 1, 1, 1]; + } + return color; + }); + }, + { myp5 } + ); + } catch (e) { + /* expected */ + } + + assert.isAbove( + mockUserError.mock.calls.length, + 0, + 'FES.userError should have been called' + ); + const errMsg = mockUserError.mock.calls[0][1]; + assert.include(errMsg, '&&'); + assert.include(errMsg, 'requires two bool scalars'); + }); + + test('logical and between two boolean scalars is allowed', () => { + myp5.createCanvas(50, 50, myp5.WEBGL); + + myp5.baseMaterialShader().modify( + () => { + myp5.getFinalColor(color => { + if (color.r < 0.5 && color.g > 0.5) { + color = [1, 1, 1, 1]; + } + return color; + }); + }, + { myp5 } + ); + + assert.equal(mockUserError.mock.calls.length, 0); + }); + test('shows a helpful error for web editor loop protection', () => { myp5.createCanvas(50, 50, myp5.WEBGL); From 1c1447c2b2b23a86ff86040ae428b5918e3fc7f0 Mon Sep 17 00:00:00 2001 From: yash <52105266+skyyash@users.noreply.github.com> Date: Fri, 18 Sep 2026 13:24:13 +0530 Subject: [PATCH 3/4] add bool ordering and bool/number equality checks --- src/strands/ir_builders.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/strands/ir_builders.js b/src/strands/ir_builders.js index edbd86cf61..1c9ef8f0d9 100644 --- a/src/strands/ir_builders.js +++ b/src/strands/ir_builders.js @@ -235,6 +235,12 @@ export function binaryOpNode( `${OpCodeToSymbol[opCode]} is only defined for scalars. ` + `Got ${leftBase}${leftDim} ${OpCodeToSymbol[opCode]} ${rightBase}${rightDim}.` ); + } else if (leftBase === BaseType.BOOL || rightBase === BaseType.BOOL) { + FES.userError( + 'type error', + `${OpCodeToSymbol[opCode]} is not defined for boolean values. ` + + `Got ${leftBase}${leftDim} ${OpCodeToSymbol[opCode]} ${rightBase}${rightDim}.` + ); } } else if (isEquality) { if ((leftDim > 1 || rightDim > 1) && (leftDim !== rightDim || leftBase !== rightBase)) { @@ -243,6 +249,12 @@ export function binaryOpNode( `Equality comparisons between vectors require matching dimensions and base types. ` + `Got ${leftBase}${leftDim} ${OpCodeToSymbol[opCode]} ${rightBase}${rightDim}.` ); + } else if ((leftBase === BaseType.BOOL) !== (rightBase === BaseType.BOOL)) { + FES.userError( + 'type error', + `Equality comparisons between boolean and numeric types are not allowed. ` + + `Got ${leftBase}${leftDim} ${OpCodeToSymbol[opCode]} ${rightBase}${rightDim}.` + ); } } else if (isLogical) { if (leftBase !== BaseType.BOOL || rightBase !== BaseType.BOOL || leftDim !== 1 || rightDim !== 1) { @@ -253,7 +265,7 @@ export function binaryOpNode( ); } } - + if (booleanOpCode[opCode]) { cast.toType.baseType = BaseType.BOOL; cast.toType.dimension = 1; From f887d7c9a4c9bdb559f02ddd5355bb2e451e2935 Mon Sep 17 00:00:00 2001 From: yash <52105266+skyyash@users.noreply.github.com> Date: Fri, 18 Sep 2026 13:38:09 +0530 Subject: [PATCH 4/4] add bool ordering and equality tests --- test/unit/webgl/p5.Shader.js | 134 +++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/test/unit/webgl/p5.Shader.js b/test/unit/webgl/p5.Shader.js index 97ebb49427..bc21e6b1b9 100644 --- a/test/unit/webgl/p5.Shader.js +++ b/test/unit/webgl/p5.Shader.js @@ -3908,6 +3908,140 @@ suite('p5.Shader', function () { assert.equal(mockUserError.mock.calls.length, 0); }); + test('ordering comparison with a boolean operand throws a clear strands type error', () => { + myp5.createCanvas(50, 50, myp5.WEBGL); + + try { + myp5.baseMaterialShader().modify( + () => { + myp5.getFinalColor(color => { + if (myp5.bool(true) < 0.5) { + color = [1, 1, 1, 1]; + } + return color; + }); + }, + { myp5 } + ); + } catch (e) { + /* expected */ + } + + assert.isAbove( + mockUserError.mock.calls.length, + 0, + 'FES.userError should have been called' + ); + const errMsg = mockUserError.mock.calls[0][1]; + assert.include(errMsg, '<'); + assert.include(errMsg, 'not defined for boolean values'); + }); + + test('ordering comparison between two booleans throws a clear strands type error', () => { + myp5.createCanvas(50, 50, myp5.WEBGL); + + try { + myp5.baseMaterialShader().modify( + () => { + myp5.getFinalColor(color => { + if (myp5.bool(true) > myp5.bool(false)) { + color = [1, 1, 1, 1]; + } + return color; + }); + }, + { myp5 } + ); + } catch (e) { + /* expected */ + } + + assert.isAbove( + mockUserError.mock.calls.length, + 0, + 'FES.userError should have been called' + ); + const errMsg = mockUserError.mock.calls[0][1]; + assert.include(errMsg, '>'); + assert.include(errMsg, 'not defined for boolean values'); + }); + + test('equality comparison between boolean and numeric types throws a clear strands type error', () => { + myp5.createCanvas(50, 50, myp5.WEBGL); + + try { + myp5.baseMaterialShader().modify( + () => { + myp5.getFinalColor(color => { + if (myp5.bool(true).equalTo(1.0)) { + color = [1, 1, 1, 1]; + } + return color; + }); + }, + { myp5 } + ); + } catch (e) { + /* expected */ + } + + assert.isAbove( + mockUserError.mock.calls.length, + 0, + 'FES.userError should have been called' + ); + const errMsg = mockUserError.mock.calls[0][1]; + assert.include(errMsg, '=='); + assert.include(errMsg, 'between boolean and numeric'); + }); + + test('inequality comparison between numeric and boolean types throws a clear strands type error', () => { + myp5.createCanvas(50, 50, myp5.WEBGL); + + try { + myp5.baseMaterialShader().modify( + () => { + myp5.getFinalColor(color => { + if (myp5.float(1.0).notEqual(myp5.bool(false))) { + color = [1, 1, 1, 1]; + } + return color; + }); + }, + { myp5 } + ); + } catch (e) { + /* expected */ + } + + assert.isAbove( + mockUserError.mock.calls.length, + 0, + 'FES.userError should have been called' + ); + const errMsg = mockUserError.mock.calls[0][1]; + assert.include(errMsg, '!='); + assert.include(errMsg, 'between boolean and numeric'); + }); + + test('equality comparison between two booleans is allowed', () => { + myp5.createCanvas(50, 50, myp5.WEBGL); + + myp5.baseMaterialShader().modify( + () => { + myp5.getFinalColor(color => { + if (myp5.bool(true).equalTo(myp5.bool(false))) { + color = [1, 1, 1, 1]; + } + return color; + }); + }, + { myp5 } + ); + + assert.equal(mockUserError.mock.calls.length, 0); + }); + test('shows a helpful error for web editor loop protection', () => { myp5.createCanvas(50, 50, myp5.WEBGL);