luau/tests/conformance/vector.luau
vegorov-rbx 55f3e00938
Sync to upstream/release/687 (#1954)
## What's Changed?

This week we have an update with an implementation for one of the RFCs
we had approved before, an improvement of the new type solver and a
small Lua 5.1 C API compatibility improvement.

* `@deprecated` attribute can now have a custom suggestion for a
replacement and a reason message as described in [deprecated attribute
parameters
RFC](https://rfcs.luau.org/syntax-attribute-functions-deprecated.html)

For example:
```luau
@[deprecated {reason = "foo suffers from performance issues", use = "bar"}]
local function foo()
    ...
end

-- Function 'foo' is deprecated, use 'bar' instead. foo suffers from performance issues
foo()
```

* `lua_cpcall` C API function has been restored both for compatibility
with Lua 5.1 and as a safe way to enter protected call environment to
work with Luau C API functions that may error

Instead of
```
if (!lua_checkstack(L, 2))
    return -1;
lua_pushcfunction(L, test, nullptr);
lua_pushlightuserdata(L, context);
int status = lua_pcall(L, 1, 0, 0);
```
you can simply do 
```
int status = lua_cpcall(L, test, context);
```

* In Luau CLI, required module return values can now have any type

## New Type Solver
- Additional improvements on type refinements used with external types
should fix some reported false positive errors where types refined to
`never`
- Fixed an issue in recursive refinement types in a form of `t1 where t1
= refine<t1, _>` getting 'stuck'
- Fixed an issue in subtyping of generic functions, it is now possible
to assign `<T>(T, (T) -> T) -> T` to `(number, <X>(X) -> X) -> number`
- Fixed an ICE caused by recursive types (Fixes #1686)
- Added additional iteration and recursion limits to stop the type
solver before system resources are used up

## Internal Contributors

Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Annie Tang <annietang@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Ilya Rezvov <irezvov@roblox.com>
Co-authored-by: Sora Kanosue <skanosue@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2025-08-15 11:48:43 -07:00

186 lines
6.8 KiB
Text

-- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
print('testing vectors')
-- detect vector size
local vector_size = if pcall(function() return vector.create(0, 0, 0).w end) then 4 else 3
function ecall(fn, ...)
local ok, err = pcall(fn, ...)
assert(not ok)
return err:sub((err:find(": ") or -1) + 2, #err)
end
-- equality
assert(vector.create(1, 2, 3) == vector.create(1, 2, 3))
assert(vector.create(0, 1, 2) == vector.create(-0, 1, 2))
assert(vector.create(1, 2, 3) ~= vector.create(1, 2, 4))
-- rawequal
assert(rawequal(vector.create(1, 2, 3), vector.create(1, 2, 3)))
assert(rawequal(vector.create(0, 1, 2), vector.create(-0, 1, 2)))
assert(not rawequal(vector.create(1, 2, 3), vector.create(1, 2, 4)))
-- type & tostring
assert(type(vector.create(1, 2, 3)) == "vector")
if vector_size == 4 then
assert(tostring(vector.create(1, 2, 3, 4)) == "1, 2, 3, 4")
assert(tostring(vector.create(-1, 2, 0.5, 0)) == "-1, 2, 0.5, 0")
else
assert(tostring(vector.create(1, 2, 3)) == "1, 2, 3")
assert(tostring(vector.create(-1, 2, 0.5)) == "-1, 2, 0.5")
end
local t = {}
-- basic table access
t[vector.create(1, 2, 3)] = 42
assert(t[vector.create(1, 2, 3)] == 42)
assert(t[vector.create(1, 2, 4)] == nil)
-- negative zero should hash the same as zero
assert(t[vector.create(0, 0, 0)] == nil)
t[vector.create(0, 0, 0)] = "hello"
assert(t[vector.create(0, 0, 0)] == "hello")
assert(t[vector.create(0, -0, 0)] == "hello")
-- test arithmetic instructions
assert(vector.create(1, 2, 4) + vector.create(8, 16, 24) == vector.create(9, 18, 28));
assert(vector.create(1, 2, 4) - vector.create(8, 16, 24) == vector.create(-7, -14, -20));
local val = 1/'8'
assert(vector.create(1, 2, 4) * vector.create(8, 16, 24) == vector.create(8, 32, 96));
assert(vector.create(1, 2, 4) * 8 == vector.create(8, 16, 32));
assert(vector.create(1, 2, 4) * (1 / val) == vector.create(8, 16, 32));
assert(8 * vector.create(8, 16, 24) == vector.create(64, 128, 192));
assert(vector.create(1, 2, 4) * '8' == vector.create(8, 16, 32));
assert('8' * vector.create(8, 16, 24) == vector.create(64, 128, 192));
assert(vector.create(1, 2, 4) * -0.125 == vector.create(-0.125, -0.25, -0.5))
assert(-0.125 * vector.create(1, 2, 4) == vector.create(-0.125, -0.25, -0.5))
assert(vector.create(1, 2, 4) * 100 == vector.create(100, 200, 400))
assert(100 * vector.create(1, 2, 4) == vector.create(100, 200, 400))
if vector_size == 4 then
assert(vector.create(1, 2, 4, 8) / vector.create(8, 16, 24, 32) == vector.create(1/8, 2/16, 4/24, 8/32));
assert(8 / vector.create(8, 16, 24, 32) == vector.create(1, 1/2, 1/3, 1/4));
assert('8' / vector.create(8, 16, 24, 32) == vector.create(1, 1/2, 1/3, 1/4));
else
assert(vector.create(1, 2, 4) / vector.create(8, 16, 24, 1) == vector.create(1/8, 2/16, 4/24));
assert(8 / vector.create(8, 16, 24) == vector.create(1, 1/2, 1/3));
assert('8' / vector.create(8, 16, 24) == vector.create(1, 1/2, 1/3));
end
assert(vector.create(1, 2, 4) / 8 == vector.create(1/8, 1/4, 1/2));
assert(vector.create(1, 2, 4) / (1 / val) == vector.create(1/8, 2/8, 4/8));
assert(vector.create(1, 2, 4) / '8' == vector.create(1/8, 1/4, 1/2));
assert(-vector.create(1, 2, 4) == vector.create(-1, -2, -4));
-- test floor division
assert(vector.create(1, 3, 5) // 2 == vector.create(0, 1, 2))
assert(vector.create(1, 3, 5) // val == vector.create(8, 24, 40))
if vector_size == 4 then
assert(10 // vector.create(1, 2, 3, 4) == vector.create(10, 5, 3, 2))
assert(vector.create(10, 9, 8, 7) // vector.create(1, 2, 3, 4) == vector.create(10, 4, 2, 1))
else
assert(10 // vector.create(1, 2, 3) == vector.create(10, 5, 3))
assert(vector.create(10, 9, 8) // vector.create(1, 2, 3) == vector.create(10, 4, 2))
end
-- test NaN comparison
local nanv = vector.create(0/0, 0/0, 0/0)
assert(nanv ~= nanv);
-- __index
assert(vector.create(1, 2, 2).Magnitude == 3)
assert(vector.create(0, 0, 0)['Dot'](vector.create(1, 2, 4), vector.create(5, 6, 7)) == 45)
assert(vector.create(2, 0, 0).Unit == vector.create(1, 0, 0))
-- __namecall
assert(vector.create(1, 2, 4):Dot(vector.create(5, 6, 7)) == 45)
assert(ecall(function() vector.create(1, 2, 4):Dot() end) == "missing argument #2 (vector expected)")
assert(ecall(function() vector.create(1, 2, 4):Dot("a") end) == "invalid argument #2 (vector expected, got string)")
local function doDot1(a: vector, b)
return a:Dot(b)
end
local function doDot2(a: vector, b)
return (a:Dot(b))
end
local v124 = vector.create(1, 2, 4)
assert(doDot1(v124, vector.create(5, 6, 7)) == 45)
assert(doDot2(v124, vector.create(5, 6, 7)) == 45)
assert(ecall(function() doDot1(v124, "a") end) == "invalid argument #2 (vector expected, got string)")
assert(ecall(function() doDot2(v124, "a") end) == "invalid argument #2 (vector expected, got string)")
assert(select("#", doDot1(v124, vector.create(5, 6, 7))) == 1)
assert(select("#", doDot2(v124, vector.create(5, 6, 7))) == 1)
-- can't use vector with NaN components as table key
assert(pcall(function() local t = {} t[vector.create(0/0, 2, 3)] = 1 end) == false)
assert(pcall(function() local t = {} t[vector.create(1, 0/0, 3)] = 1 end) == false)
assert(pcall(function() local t = {} t[vector.create(1, 2, 0/0)] = 1 end) == false)
assert(pcall(function() local t = {} rawset(t, vector.create(0/0, 2, 3), 1) end) == false)
assert(vector.create(1, 0, 0):Cross(vector.create(0, 1, 0)) == vector.create(0, 0, 1))
assert(vector.create(0, 1, 0):Cross(vector.create(1, 0, 0)) == vector.create(0, 0, -1))
-- make sure we cover both builtin and C impl
assert(vector.create(1, 2, 4) == vector.create("1", "2", "4"))
-- validate component access (both cases)
assert(vector.create(1, 2, 3).x == 1)
assert(vector.create(1, 2, 3).X == 1)
assert(vector.create(1, 2, 3).y == 2)
assert(vector.create(1, 2, 3).Y == 2)
assert(vector.create(1, 2, 3).z == 3)
assert(vector.create(1, 2, 3).Z == 3)
-- additional checks for 4-component vectors
if vector_size == 4 then
assert(vector.create(1, 2, 3, 4).w == 4)
assert(vector.create(1, 2, 3, 4).W == 4)
end
-- negative zero should hash the same as zero
-- note: our earlier test only really checks the low hash bit, so in absence of perfect avalanche it's insufficient
do
local larget = {}
for i = 1, 2^14 do
larget[vector.create(0, 0, i)] = true
end
larget[vector.create(0, 0, 0)] = 42
assert(larget[vector.create(0, 0, 0)] == 42)
assert(larget[vector.create(0, 0, -0)] == 42)
assert(larget[vector.create(0, -0, 0)] == 42)
assert(larget[vector.create(-0, 0, 0)] == 42)
end
local function numvectemporary()
local proptab = {}
proptab.vec3compsum = function(vec: vector)
local num = vec.X + vec.Y
local tmp = vec / num
local num2 = num * 2
return tmp, num2
end
local a, b = proptab.vec3compsum(vector.create(2, 6, 0))
assert(a.X == 0.25)
assert(a.Y == 0.75)
assert(b == 16)
end
numvectemporary()
return 'OK'