luau/tests/TypeInfer.oop.test.cpp

555 lines
13 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/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(LuauSolverV2);
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")
{
// CLI-116571 method calls are missing arity checking?
DOES_NOT_PASS_NEW_SOLVER_GUARD();
2022-03-18 08:46:04 +08:00
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")
{
// CLI-116571 method calls are missing arity checking?
DOES_NOT_PASS_NEW_SOLVER_GUARD();
2022-03-18 08:46:04 +08:00
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();
if (FFlag::LuauSolverV2)
CHECK_GE(80, module->internalTypes.types.size());
else
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::LuauSolverV2)
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))
{
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);
}
Sync to upstream/release/616 (#1184) # What's Changed * Add a compiler hint to improve Luau memory allocation inlining ### New Type Solver * Added a system for recommending explicit type annotations to users in cases where we've inferred complex generic types with type families. * Marked string library functions as `@checked` for use in new non-strict mode. * Fixed a bug with new non-strict mode where we would incorrectly report arity mismatches when missing optional arguments. * Implement an occurs check for unifications that would produce self-recursive types. * Fix bug where overload resolution would fail when applied to non-overloaded functions. * Fix bug that caused the subtyping to report an error whenever a generic was instantiated in an invariant context. * Fix crash caused by `SetPropConstraint` not blocking properly. ### Native Code Generation * Implement optimization to eliminate dead stores * Optimize vector ops for X64 when the source is computed (thanks, @zeux!) * Use more efficient lowering for UNM_* (thanks, @zeux!) --- ### 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: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@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-03-09 08:47:53 +08:00
TEST_CASE_FIXTURE(Fixture, "method_should_not_create_cyclic_type")
{
ScopedFastFlag sff(FFlag::LuauSolverV2, true);
Sync to upstream/release/616 (#1184) # What's Changed * Add a compiler hint to improve Luau memory allocation inlining ### New Type Solver * Added a system for recommending explicit type annotations to users in cases where we've inferred complex generic types with type families. * Marked string library functions as `@checked` for use in new non-strict mode. * Fixed a bug with new non-strict mode where we would incorrectly report arity mismatches when missing optional arguments. * Implement an occurs check for unifications that would produce self-recursive types. * Fix bug where overload resolution would fail when applied to non-overloaded functions. * Fix bug that caused the subtyping to report an error whenever a generic was instantiated in an invariant context. * Fix crash caused by `SetPropConstraint` not blocking properly. ### Native Code Generation * Implement optimization to eliminate dead stores * Optimize vector ops for X64 when the source is computed (thanks, @zeux!) * Use more efficient lowering for UNM_* (thanks, @zeux!) --- ### 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: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@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-03-09 08:47:53 +08:00
CheckResult result = check(R"(
local Component = {}
function Component:__resolveUpdate(incomingState)
local oldState = self.state
incomingState = oldState
self.state = incomingState
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_metatable")
{
fileResolver.source["game/A"] = R"(
--!strict
local cls = {}
cls.__index = cls
function cls:abc() return 4 end
return cls
)";
fileResolver.source["game/B"] = R"(
--!strict
local cls = require(game.A)
local tbl = {}
setmetatable(tbl, cls)
)";
CheckResult result = frontend.check("game/B");
LUAU_REQUIRE_NO_ERRORS(result);
ModulePtr b = frontend.moduleResolver.getModule("game/B");
REQUIRE(b);
std::optional<Binding> clsBinding = b->getModuleScope()->linearSearchForBinding("tbl");
REQUIRE(clsBinding);
TypeId clsType = clsBinding->typeId;
CHECK("{ @metatable cls, tbl }" == toString(clsType));
}
2022-03-18 08:46:04 +08:00
TEST_SUITE_END();