TSL: Add type promotion in operators and math functions#33695
TSL: Add type promotion in operators and math functions#33695shotamatsuda wants to merge 9 commits into
Conversation
|
This can be a different issue (because it can be fixed separately), but equality operators on vectors have odd behavior as well. This PR also addresses it. vec3(1.5).equal(ivec3(1)) // false
ivec3(1).equal(vec3(1.5)) // true! |
|
It is arguable whether the conversion from u32 to i32 to f32 is actually "promotion". In most cases it is, but it does drop information if the value exceeds safe integer limits... Though the same applies to the current behavior. |
|
@shotamatsuda I still need to analyze your PR but regarding your comment on the issue:
I just want to say that your comment reflects the same point of view as mine. We should prioritize more common and broader types, even to make this more similar to JS. I’d love to support any improvement in that direction. |
|
After fixing the failed examples, though fewer than I thought, it revealed the problem I anticipated: operations on JS numbers. Because JS numbers are considered f32 (and rightly so), all operations with integer types will result in float types, and that will be the source of most breakages, I presume. |
There was a problem hiding this comment.
When we have an uint * number, the number could be handled as a weak type instead of always handling it as float(). I think we can handle this in this PR.
In this case, 64 should automatically be converted to uint, prioritizing the type of output defined by the user. This should also be consistent with the use of array indexes.
Explicit definitions like float() and uint() should be handled differently as a strong type, maintaining the current logic.
instanceIndex.div( 64 ) // uint x uint ( number <-> weak type )
instanceIndex.div( uint( 64 ) ) // uint x uint
instanceIndex.div( float( 64 ) ) // uint x floatGetting it from the PR description, we should still keep the logic of prioritizing broader types:
float( 0.5 ).mul( int( 1 ) ) // 0.5
int( 1 ).mul( float( 0.5 ) ) // 0.5I think we can add a boolean flag to ConstNode when this is converted from a nodeObject() to define that it is a weak type.
There was a problem hiding this comment.
It took some time to consider, but I agree that your idea is good. It could be formalized into a couple of rules. The potentially non-intuitive parts would be:
weak × uint -> uintholds, whileweak × weak -> floatshould as well.uint(1).mul(0.5)yields1, but this is just a matter of how we see the node type of ConstNode; at least it is less confusing.
I will make the changes within this PR.

Related: three-types/three-ts-types#2116 (comment)
Description
This PR adds type promotion in TSL's operators and math functions.
Many programming languages that I know have implicit type promotion in operators and function overloads in math functions, like C and Java, while more modern languages (with a strict type system) tend not to allow the use of operators and functions with different types, including WGSL and GLSL. TSL behaves like the former, but its behavior is rather odd, I would say.
For instance, the result of commutative operators differs by operand order, and the result of functions differs by parameter order:
With this PR, operands and parameters of math functions are implicitly coerced to broader common types, instead of being coerced to the narrower types in some cases. Those are breaking changes, and I am not entirely confident about this, but I do not think the above behavior is ideal either.
The E2E tests will utterly fail and need to be fixed if these changes (or the direction of this PR) make sense.