luau/tests/TypeInfer.oop.test.cpp

506 lines
12 KiB
C++
Raw Normal View History

2022-03-18 08:46:04 +08:00
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/AstQuery.h"
#include "Luau/BuiltinDefinitions.h"
#include "Luau/Scope.h"
#include "Luau/TypeInfer.h"
#include "Luau/Type.h"
#include "Luau/VisitType.h"
2022-03-18 08:46:04 +08:00
#include "Fixture.h"
#include "ScopedFlags.h"
2022-03-18 08:46:04 +08:00
#include "doctest.h"
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
Sync to upstream/release/605 (#1118) - Implemented [Require by String with Relative Paths](https://github.com/luau-lang/rfcs/blob/master/docs/new-require-by-string-semantics.md) RFC - Implemented [Require by String with Aliases](https://github.com/luau-lang/rfcs/blob/master/docs/require-by-string-aliases.md) RFC with support for `paths` and `alias` arrays in .luarc - Added SUBRK and DIVRK bytecode instructions to speed up constant-number and constant/number operations - Added `--vector-lib`, `--vector-ctor` and `--vector-type` options to luau-compile to support code with vectors New Solver - Correctness fixes to subtyping - Improvements to dataflow analysis Native Code Generation - Added bytecode analysis pass to predict type tags used in operations - Fixed rare cases of numerical loops being generated without an interrupt instruction - Restored optimization data propagation into the linear block - Duplicate buffer length checks are optimized away Miscellaneous - Small performance improvements to new non-strict mode - Introduced more scripts for fuzzing Luau and processing the results, including fuzzer build support for CMake Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2023-12-02 15:46:57 +08:00
LUAU_FASTFLAG(LuauStacklessTypeClone3);
2022-03-18 08:46:04 +08:00
TEST_SUITE_BEGIN("TypeInferOOP");
TEST_CASE_FIXTURE(Fixture, "dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon")
{
CheckResult result = check(R"(
local someTable = {}
someTable.Function1 = function(Arg1)
end
someTable.Function1() -- Argument count mismatch
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
REQUIRE(get<CountMismatch>(result.errors[0]));
2022-03-18 08:46:04 +08:00
}
TEST_CASE_FIXTURE(Fixture, "dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2")
{
CheckResult result = check(R"(
local someTable = {}
someTable.Function2 = function(Arg1, Arg2)
end
someTable.Function2() -- Argument count mismatch
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
REQUIRE(get<CountMismatch>(result.errors[0]));
2022-03-18 08:46:04 +08:00
}
TEST_CASE_FIXTURE(Fixture, "dont_suggest_using_colon_rather_than_dot_if_another_overload_works")
{
CheckResult result = check(R"(
type T = {method: ((T, number) -> number) & ((number) -> number)}
local T: T
T.method(4)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "method_depends_on_table")
{
CheckResult result = check(R"(
-- This catches a bug where x:m didn't count as a use of x
-- so toposort would happily reorder a definition of
-- function x:m before the definition of x.
function g() f() end
local x = {}
function x:m() end
function f() x:m() end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "methods_are_topologically_sorted")
{
CheckResult result = check(R"(
local T = {}
function T:foo()
return T:bar(999), T:bar("hi")
end
function T:bar(i)
return i
end
local a, b = T:foo()
)");
LUAU_REQUIRE_NO_ERRORS(result);
dumpErrors(result);
CHECK_EQ(PrimitiveType::Number, getPrimitiveType(requireType("a")));
CHECK_EQ(PrimitiveType::String, getPrimitiveType(requireType("b")));
2022-03-18 08:46:04 +08:00
}
TEST_CASE_FIXTURE(Fixture, "quantify_methods_defined_using_dot_syntax_and_explicit_self_parameter")
{
check(R"(
local T = {}
function T.method(self)
self:method()
end
function T.method2(self)
self:method()
end
T:method2()
)");
}
TEST_CASE_FIXTURE(Fixture, "inferring_hundreds_of_self_calls_should_not_suffocate_memory")
{
CheckResult result = check(R"(
("foo")
:lower()
:lower()
:lower()
:lower()
:lower()
:lower()
:lower()
:lower()
:lower()
:lower()
:lower()
:lower()
:lower()
:lower()
:lower()
:lower()
:lower()
:lower()
)");
ModulePtr module = getMainModule();
CHECK_GE(50, module->internalTypes.types.size());
2022-03-18 08:46:04 +08:00
}
2022-05-14 03:36:37 +08:00
TEST_CASE_FIXTURE(BuiltinsFixture, "object_constructor_can_refer_to_method_of_self")
2022-03-18 08:46:04 +08:00
{
// CLI-30902
CheckResult result = check(R"(
--!strict
type Foo = {
fooConn: () -> () | nil
}
local Foo = {}
Foo.__index = Foo
function Foo.new()
local self: Foo = {
fooConn = nil,
}
setmetatable(self, Foo)
self.fooConn = function()
self:method() -- Key 'method' not found in table self
end
return self
end
function Foo:method()
print("foo")
end
local foo = Foo.new()
-- TODO This is the best our current refinement support can offer :(
local bar = foo.fooConn
if bar then bar() end
-- foo.fooConn()
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "CheckMethodsOfSealed")
{
CheckResult result = check(R"(
local x: {prop: number} = {prop=9999}
function x:y(z: number)
local s: string = z
end
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
}
TEST_CASE_FIXTURE(Fixture, "nonstrict_self_mismatch_tail")
{
CheckResult result = check(R"(
2022-04-15 07:57:43 +08:00
--!nonstrict
local f = {}
function f:foo(a: number, b: number) end
2022-03-18 08:46:04 +08:00
2022-04-15 07:57:43 +08:00
function bar(...)
f.foo(f, 1, ...)
end
2022-03-18 08:46:04 +08:00
2022-04-15 07:57:43 +08:00
bar(2)
)");
2022-03-18 08:46:04 +08:00
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "inferred_methods_of_free_tables_have_the_same_level_as_the_enclosing_table")
{
check(R"(
function Base64FileReader(data)
local reader = {}
Sync to upstream/release/610 (#1154) # What's changed? * Check interrupt handler inside the pattern match engine to eliminate potential for programs to hang during string library function execution. * Allow iteration over table properties to pass the old type solver. ### Native Code Generation * Use in-place memory operands for math library operations on x64. * Replace opaque bools with separate enum classes in IrDump to improve code maintainability. * Translate operations on inferred vectors to IR. * Enable support for debugging native-compiled functions in Roblox Studio. ### New Type Solver * Rework type inference for boolean and string literals to introduce bounded free types (bounded below by the singleton type, and above by the primitive type) and reworked primitive type constraint to decide which is the appropriate type for the literal. * Introduce `FunctionCheckConstraint` to handle bidirectional typechecking for function calls, pushing the expected parameter types from the function onto the arguments. * Introduce `union` and `intersect` type families to compute deferred simplified unions and intersections to be employed by the constraint generation logic in the new solver. * Implement support for expanding the domain of local types in `Unifier2`. * Rework type inference for iteration variables bound by for in loops to use local types. * Change constraint blocking logic to use a set to prevent accidental re-blocking. * Add logic to detect missing return statements in functions. ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2024-01-27 11:20:56 +08:00
local index: number = 0
2022-03-18 08:46:04 +08:00
function reader:PeekByte()
return data:byte(index)
end
function reader:Byte()
return data:byte(index - 1)
end
return reader
end
Base64FileReader()
function ReadMidiEvents(data)
local reader = Base64FileReader(data)
while reader:HasMore() do
(reader:Byte() % 128)
end
end
)");
}
2022-05-14 03:36:37 +08:00
TEST_CASE_FIXTURE(BuiltinsFixture, "table_oop")
2022-03-18 08:46:04 +08:00
{
CheckResult result = check(R"(
--!strict
local Class = {}
Class.__index = Class
type Class = typeof(setmetatable({} :: { x: number }, Class))
function Class.new(x: number): Class
return setmetatable({x = x}, Class)
end
function Class.getx(self: Class)
return self.x
end
function test()
local c = Class.new(42)
local n = c:getx()
local nn = c.x
print(string.format("%d %d", n, nn))
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-04 04:21:14 +08:00
TEST_CASE_FIXTURE(BuiltinsFixture, "set_prop_of_intersection_containing_metatable")
{
CheckResult result = check(R"(
export type Set<T> = typeof(setmetatable(
{} :: {
add: (self: Set<T>, T) -> Set<T>,
},
{}
))
local Set = {} :: Set<any> & {}
function Set:add(t)
return self
end
)");
}
// DCR once had a bug in the following code where it would erroneously bind the 'self' table to itself.
TEST_CASE_FIXTURE(Fixture, "dont_bind_free_tables_to_themselves")
{
CheckResult result = check(R"(
local T = {}
local b: any
function T:m()
local a = b[i]
if a then
self:n()
if self:p(a) then
self:n()
end
end
end
)");
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-18 03:20:37 +08:00
// We should probably flag an error on this. See CLI-68672
TEST_CASE_FIXTURE(BuiltinsFixture, "flag_when_index_metamethod_returns_0_values")
{
CheckResult result = check(R"(
local T = {}
function T.__index()
end
local a = setmetatable({}, T)
local p = a.prop
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK("nil" == toString(requireType("p")));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "augmenting_an_unsealed_table_with_a_metatable")
{
CheckResult result = check(R"(
local A = {number = 8}
local B = setmetatable({}, A)
function B:method()
return "hello!!"
end
)");
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("{ @metatable { number: number }, { method: (unknown) -> string } }" == toString(requireType("B"), {true}));
else
CHECK("{ @metatable { number: number }, { method: <a>(a) -> string } }" == toString(requireType("B"), {true}));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "react_style_oo")
{
CheckResult result = check(R"(
local Prototype = {}
local ClassMetatable = {
__index = Prototype
}
local BaseClass = (setmetatable({}, ClassMetatable))
function BaseClass:extend(name)
local class = {
name=name
}
class.__index = class
function class.ctor(props)
return setmetatable({props=props}, class)
end
return setmetatable(class, getmetatable(self))
end
local C = BaseClass:extend('C')
local i = C.ctor({hello='world'})
local iName = i.name
local cName = C.name
local hello = i.props.hello
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK("string" == toString(requireType("iName")));
CHECK("string" == toString(requireType("cName")));
CHECK("string" == toString(requireType("hello")));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "cycle_between_object_constructor_and_alias")
{
CheckResult result = check(R"(
local T = {}
T.__index = T
function T.new(): T
return setmetatable({}, T)
end
export type T = typeof(T.new())
return T
)");
LUAU_REQUIRE_NO_ERRORS(result);
auto module = getMainModule();
REQUIRE(module->exportedTypeBindings.count("T"));
TypeId aliasType = module->exportedTypeBindings["T"].type;
CHECK_MESSAGE(get<MetatableType>(follow(aliasType)), "Expected metatable type but got: " << toString(aliasType));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "promise_type_error_too_complex" * doctest::timeout(2))
{
// TODO: LTI changes to function call resolution have rendered this test impossibly slow
// shared self should fix it, but there may be other mitigations possible as well
REQUIRE(!FFlag::DebugLuauDeferredConstraintResolution);
Sync to upstream/release/605 (#1118) - Implemented [Require by String with Relative Paths](https://github.com/luau-lang/rfcs/blob/master/docs/new-require-by-string-semantics.md) RFC - Implemented [Require by String with Aliases](https://github.com/luau-lang/rfcs/blob/master/docs/require-by-string-aliases.md) RFC with support for `paths` and `alias` arrays in .luarc - Added SUBRK and DIVRK bytecode instructions to speed up constant-number and constant/number operations - Added `--vector-lib`, `--vector-ctor` and `--vector-type` options to luau-compile to support code with vectors New Solver - Correctness fixes to subtyping - Improvements to dataflow analysis Native Code Generation - Added bytecode analysis pass to predict type tags used in operations - Fixed rare cases of numerical loops being generated without an interrupt instruction - Restored optimization data propagation into the linear block - Duplicate buffer length checks are optimized away Miscellaneous - Small performance improvements to new non-strict mode - Introduced more scripts for fuzzing Luau and processing the results, including fuzzer build support for CMake Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2023-12-02 15:46:57 +08:00
ScopedFastFlag sff{FFlag::LuauStacklessTypeClone3, true};
frontend.options.retainFullTypeGraphs = false;
// Used `luau-reduce` tool to extract a minimal reproduction.
// Credit: https://github.com/evaera/roblox-lua-promise/blob/v4.0.0/lib/init.lua
CheckResult result = check(R"(
--!strict
local Promise = {}
Promise.prototype = {}
Promise.__index = Promise.prototype
function Promise._new(traceback, callback, parent)
if parent ~= nil and not Promise.is(parent)then
end
local self = {
_parent = parent,
}
parent._consumers[self] = true
setmetatable(self, Promise)
self:_reject()
return self
end
function Promise.resolve(...)
return Promise._new(debug.traceback(nil, 2), function(resolve)
end)
end
function Promise.reject(...)
return Promise._new(debug.traceback(nil, 2), function(_, reject)
end)
end
function Promise._try(traceback, callback, ...)
return Promise._new(traceback, function(resolve)
end)
end
function Promise.try(callback, ...)
return Promise._try(debug.traceback(nil, 2), callback, ...)
end
function Promise._all(traceback, promises, amount)
if #promises == 0 or amount == 0 then
return Promise.resolve({})
end
return Promise._new(traceback, function(resolve, reject, onCancel)
end)
end
function Promise.all(promises)
return Promise._all(debug.traceback(nil, 2), promises)
end
function Promise.allSettled(promises)
return Promise.resolve({})
end
function Promise.race(promises)
return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel)
end)
end
function Promise.each(list, predicate)
return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel)
local predicatePromise = Promise.resolve(predicate(value, index))
local success, result = predicatePromise:await()
end)
end
function Promise.is(object)
end
function Promise.prototype:_reject(...)
self:_finalize()
end
)");
LUAU_REQUIRE_ERRORS(result);
}
2022-03-18 08:46:04 +08:00
TEST_SUITE_END();