mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/608 (#1145)
# Old Solver: - Fix a bug in the old solver where a user could use the keyword `typeof` as the name of a type alias. - Fix stringification of scientific notation to omit a trailing decimal place when not followed by a digit e.g. `1.e+20` -> `1e+20` # New Solver - Continuing work on the New non-strict mode - Introduce `keyof` and `rawkeyof` type function for acquiring the type of all keys in a table or class (https://github.com/luau-lang/rfcs/pull/16) --- 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: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@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>
This commit is contained in:
parent
c0f5538947
commit
f31232d301
@ -85,6 +85,8 @@ struct TarjanNode
|
||||
// https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
|
||||
struct Tarjan
|
||||
{
|
||||
Tarjan();
|
||||
|
||||
// Vertices (types and type packs) are indexed, using pre-order traversal.
|
||||
DenseHashMap<TypeId, int> typeToIndex{nullptr};
|
||||
DenseHashMap<TypePackId, int> packToIndex{nullptr};
|
||||
|
@ -11,10 +11,9 @@
|
||||
#include "Luau/Predicate.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
#include "Luau/VecDeque.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@ -768,6 +767,7 @@ bool isThread(TypeId ty);
|
||||
bool isBuffer(TypeId ty);
|
||||
bool isOptional(TypeId ty);
|
||||
bool isTableIntersection(TypeId ty);
|
||||
bool isTableUnion(TypeId ty);
|
||||
bool isOverloadedFunction(TypeId ty);
|
||||
|
||||
// True when string is a subtype of ty
|
||||
@ -992,7 +992,7 @@ private:
|
||||
// (T* t, size_t currentIndex)
|
||||
using SavedIterInfo = std::pair<const T*, size_t>;
|
||||
|
||||
std::deque<SavedIterInfo> stack;
|
||||
VecDeque<SavedIterInfo> stack;
|
||||
DenseHashSet<const T*> seen{nullptr}; // Only needed to protect the iterator from hanging the thread.
|
||||
|
||||
void advance()
|
||||
|
@ -163,6 +163,8 @@ struct BuiltinTypeFamilies
|
||||
TypeFamily eqFamily;
|
||||
|
||||
TypeFamily refineFamily;
|
||||
TypeFamily keyofFamily;
|
||||
TypeFamily rawkeyofFamily;
|
||||
|
||||
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
|
||||
};
|
||||
|
@ -16,7 +16,6 @@
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties);
|
||||
LUAU_FASTFLAG(LuauClipExtraHasEndProps);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDoEnd, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringLiteralBounds, false);
|
||||
|
||||
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||
@ -1093,11 +1092,8 @@ static AutocompleteEntryMap autocompleteStatement(
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
else if (AstExprFunction* exprFunction = (*it)->as<AstExprFunction>(); exprFunction && !exprFunction->body->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
if (FFlag::LuauAutocompleteDoEnd)
|
||||
{
|
||||
if (AstStatBlock* exprBlock = (*it)->as<AstStatBlock>(); exprBlock && !exprBlock->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
}
|
||||
if (AstStatBlock* exprBlock = (*it)->as<AstStatBlock>(); exprBlock && !exprBlock->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -1114,11 +1110,8 @@ static AutocompleteEntryMap autocompleteStatement(
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
else if (AstExprFunction* exprFunction = (*it)->as<AstExprFunction>(); exprFunction && !exprFunction->DEPRECATED_hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
if (FFlag::LuauAutocompleteDoEnd)
|
||||
{
|
||||
if (AstStatBlock* exprBlock = (*it)->as<AstStatBlock>(); exprBlock && !exprBlock->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
}
|
||||
if (AstStatBlock* exprBlock = (*it)->as<AstStatBlock>(); exprBlock && !exprBlock->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,8 @@
|
||||
* about a function that takes any number of values, but where each value must have some specific type.
|
||||
*/
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAGVARIABLE(LuauSetMetatableOnUnionsOfTables, false);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -963,6 +964,18 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
||||
else if (get<AnyType>(target) || get<ErrorType>(target) || isTableIntersection(target))
|
||||
{
|
||||
}
|
||||
else if (FFlag::LuauSetMetatableOnUnionsOfTables && isTableUnion(target))
|
||||
{
|
||||
const UnionType* ut = get<UnionType>(target);
|
||||
LUAU_ASSERT(ut);
|
||||
|
||||
std::vector<TypeId> resultParts;
|
||||
|
||||
for (TypeId ty : ut)
|
||||
resultParts.push_back(arena.addType(MetatableType{ty, mt}));
|
||||
|
||||
return WithPredicate<TypePackId>{arena.addTypePack({arena.addType(UnionType{std::move(resultParts)})})};
|
||||
}
|
||||
else
|
||||
{
|
||||
typechecker.reportError(TypeError{expr.location, GenericError{"setmetatable should take a table"}});
|
||||
|
@ -159,6 +159,15 @@ private:
|
||||
TypeId target = arena->addType(ty->ty);
|
||||
asMutable(target)->documentationSymbol = ty->documentationSymbol;
|
||||
|
||||
if (auto generic = getMutable<GenericType>(target))
|
||||
generic->scope = nullptr;
|
||||
else if (auto free = getMutable<FreeType>(target))
|
||||
free->scope = nullptr;
|
||||
else if (auto fn = getMutable<FunctionType>(target))
|
||||
fn->scope = nullptr;
|
||||
else if (auto table = getMutable<TableType>(target))
|
||||
table->scope = nullptr;
|
||||
|
||||
(*types)[ty] = target;
|
||||
queue.push_back(target);
|
||||
return target;
|
||||
@ -175,6 +184,11 @@ private:
|
||||
|
||||
TypePackId target = arena->addTypePack(tp->ty);
|
||||
|
||||
if (auto generic = getMutable<GenericTypePack>(target))
|
||||
generic->scope = nullptr;
|
||||
else if (auto free = getMutable<FreeTypePack>(target))
|
||||
free->scope = nullptr;
|
||||
|
||||
(*packs)[tp] = target;
|
||||
queue.push_back(target);
|
||||
return target;
|
||||
@ -563,10 +577,12 @@ struct TypePackCloner
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
|
||||
void operator()(const GenericTypePack& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
|
||||
void operator()(const ErrorTypePack& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
@ -633,7 +649,7 @@ void TypeCloner::operator()(const FreeType& t)
|
||||
{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
FreeType ft{t.scope, clone(t.lowerBound, dest, cloneState), clone(t.upperBound, dest, cloneState)};
|
||||
FreeType ft{nullptr, clone(t.lowerBound, dest, cloneState), clone(t.upperBound, dest, cloneState)};
|
||||
TypeId res = dest.addType(ft);
|
||||
seenTypes[typeId] = res;
|
||||
}
|
||||
|
@ -504,6 +504,11 @@ ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& sco
|
||||
continue;
|
||||
}
|
||||
|
||||
// A type alias might have no name if the code is syntactically
|
||||
// illegal. We mustn't prepopulate anything in this case.
|
||||
if (alias->name == kParseNameError || alias->name == "typeof")
|
||||
continue;
|
||||
|
||||
ScopePtr defnScope = childScope(alias, scope);
|
||||
|
||||
TypeId initialType = arena->addType(BlockedType{});
|
||||
@ -1084,6 +1089,15 @@ static bool occursCheck(TypeId needle, TypeId haystack)
|
||||
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias* alias)
|
||||
{
|
||||
if (alias->name == kParseNameError)
|
||||
return ControlFlow::None;
|
||||
|
||||
if (alias->name == "typeof")
|
||||
{
|
||||
reportError(alias->location, GenericError{"Type aliases cannot be named typeof"});
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
ScopePtr* defnScope = astTypeAliasDefiningScopes.find(alias);
|
||||
|
||||
std::unordered_map<Name, TypeFun>* typeBindings;
|
||||
@ -1516,10 +1530,24 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
||||
LUAU_ASSERT(target);
|
||||
LUAU_ASSERT(mt);
|
||||
|
||||
target = follow(target);
|
||||
|
||||
AstExpr* targetExpr = call->args.data[0];
|
||||
|
||||
MetatableType mtv{target, mt};
|
||||
TypeId resultTy = arena->addType(mtv);
|
||||
TypeId resultTy = nullptr;
|
||||
|
||||
if (isTableUnion(target))
|
||||
{
|
||||
const UnionType* targetUnion = get<UnionType>(target);
|
||||
std::vector<TypeId> newParts;
|
||||
|
||||
for (TypeId ty : targetUnion)
|
||||
newParts.push_back(arena->addType(MetatableType{ty, mt}));
|
||||
|
||||
resultTy = arena->addType(UnionType{std::move(newParts)});
|
||||
}
|
||||
else
|
||||
resultTy = arena->addType(MetatableType{target, mt});
|
||||
|
||||
if (AstExprLocal* targetLocal = targetExpr->as<AstExprLocal>())
|
||||
{
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "Luau/TypeFamily.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier2.h"
|
||||
#include "Luau/VecDeque.h"
|
||||
#include "Luau/VisitType.h"
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
@ -450,10 +451,10 @@ struct TypeAndLocation
|
||||
|
||||
struct FreeTypeSearcher : TypeOnceVisitor
|
||||
{
|
||||
std::deque<TypeAndLocation>* result;
|
||||
VecDeque<TypeAndLocation>* result;
|
||||
Location location;
|
||||
|
||||
FreeTypeSearcher(std::deque<TypeAndLocation>* result, Location location)
|
||||
FreeTypeSearcher(VecDeque<TypeAndLocation>* result, Location location)
|
||||
: result(result)
|
||||
, location(location)
|
||||
{
|
||||
@ -484,7 +485,7 @@ void ConstraintSolver::finalizeModule()
|
||||
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, rootScope, NotNull{&iceReporter}};
|
||||
|
||||
std::deque<TypeAndLocation> queue;
|
||||
VecDeque<TypeAndLocation> queue;
|
||||
for (auto& [name, binding] : rootScope->bindings)
|
||||
queue.push_back({binding.typeId, binding.location});
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -1,8 +1,8 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBufferDefinitions, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBufferTypeck, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCheckedEmbeddedDefinitions, false);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -263,14 +263,238 @@ declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
|
||||
|
||||
)BUILTIN_SRC";
|
||||
|
||||
static const std::string kBuiltinDefinitionLuaSrcChecked = R"BUILTIN_SRC(
|
||||
|
||||
declare bit32: {
|
||||
band: @checked (...number) -> number,
|
||||
bor: @checked (...number) -> number,
|
||||
bxor: @checked (...number) -> number,
|
||||
btest: @checked (number, ...number) -> boolean,
|
||||
rrotate: @checked (x: number, disp: number) -> number,
|
||||
lrotate: @checked (x: number, disp: number) -> number,
|
||||
lshift: @checked (x: number, disp: number) -> number,
|
||||
arshift: @checked (x: number, disp: number) -> number,
|
||||
rshift: @checked (x: number, disp: number) -> number,
|
||||
bnot: @checked (x: number) -> number,
|
||||
extract: @checked (n: number, field: number, width: number?) -> number,
|
||||
replace: @checked (n: number, v: number, field: number, width: number?) -> number,
|
||||
countlz: @checked (n: number) -> number,
|
||||
countrz: @checked (n: number) -> number,
|
||||
byteswap: @checked (n: number) -> number,
|
||||
}
|
||||
|
||||
declare math: {
|
||||
frexp: @checked (n: number) -> (number, number),
|
||||
ldexp: @checked (s: number, e: number) -> number,
|
||||
fmod: @checked (x: number, y: number) -> number,
|
||||
modf: @checked (n: number) -> (number, number),
|
||||
pow: @checked (x: number, y: number) -> number,
|
||||
exp: @checked (n: number) -> number,
|
||||
|
||||
ceil: @checked (n: number) -> number,
|
||||
floor: @checked (n: number) -> number,
|
||||
abs: @checked (n: number) -> number,
|
||||
sqrt: @checked (n: number) -> number,
|
||||
|
||||
log: @checked (n: number, base: number?) -> number,
|
||||
log10: @checked (n: number) -> number,
|
||||
|
||||
rad: @checked (n: number) -> number,
|
||||
deg: @checked (n: number) -> number,
|
||||
|
||||
sin: @checked (n: number) -> number,
|
||||
cos: @checked (n: number) -> number,
|
||||
tan: @checked (n: number) -> number,
|
||||
sinh: @checked (n: number) -> number,
|
||||
cosh: @checked (n: number) -> number,
|
||||
tanh: @checked (n: number) -> number,
|
||||
atan: @checked (n: number) -> number,
|
||||
acos: @checked (n: number) -> number,
|
||||
asin: @checked (n: number) -> number,
|
||||
atan2: @checked (y: number, x: number) -> number,
|
||||
|
||||
min: @checked (number, ...number) -> number,
|
||||
max: @checked (number, ...number) -> number,
|
||||
|
||||
pi: number,
|
||||
huge: number,
|
||||
|
||||
randomseed: @checked (seed: number) -> (),
|
||||
random: @checked (number?, number?) -> number,
|
||||
|
||||
sign: @checked (n: number) -> number,
|
||||
clamp: @checked (n: number, min: number, max: number) -> number,
|
||||
noise: @checked (x: number, y: number?, z: number?) -> number,
|
||||
round: @checked (n: number) -> number,
|
||||
}
|
||||
|
||||
type DateTypeArg = {
|
||||
year: number,
|
||||
month: number,
|
||||
day: number,
|
||||
hour: number?,
|
||||
min: number?,
|
||||
sec: number?,
|
||||
isdst: boolean?,
|
||||
}
|
||||
|
||||
type DateTypeResult = {
|
||||
year: number,
|
||||
month: number,
|
||||
wday: number,
|
||||
yday: number,
|
||||
day: number,
|
||||
hour: number,
|
||||
min: number,
|
||||
sec: number,
|
||||
isdst: boolean,
|
||||
}
|
||||
|
||||
declare os: {
|
||||
time: @checked (time: DateTypeArg?) -> number,
|
||||
date: ((formatString: "*t" | "!*t", time: number?) -> DateTypeResult) & ((formatString: string?, time: number?) -> string),
|
||||
difftime: @checked (t2: DateTypeResult | number, t1: DateTypeResult | number) -> number,
|
||||
clock: () -> number,
|
||||
}
|
||||
|
||||
declare function @checked require(target: any): any
|
||||
|
||||
declare function @checked getfenv(target: any): { [string]: any }
|
||||
|
||||
declare _G: any
|
||||
declare _VERSION: string
|
||||
|
||||
declare function gcinfo(): number
|
||||
|
||||
declare function print<T...>(...: T...)
|
||||
|
||||
declare function type<T>(value: T): string
|
||||
declare function typeof<T>(value: T): string
|
||||
|
||||
-- `assert` has a magic function attached that will give more detailed type information
|
||||
declare function assert<T>(value: T, errorMessage: string?): T
|
||||
declare function error<T>(message: T, level: number?): never
|
||||
|
||||
declare function tostring<T>(value: T): string
|
||||
declare function tonumber<T>(value: T, radix: number?): number?
|
||||
|
||||
declare function rawequal<T1, T2>(a: T1, b: T2): boolean
|
||||
declare function rawget<K, V>(tab: {[K]: V}, k: K): V
|
||||
declare function rawset<K, V>(tab: {[K]: V}, k: K, v: V): {[K]: V}
|
||||
declare function rawlen<K, V>(obj: {[K]: V} | string): number
|
||||
|
||||
declare function setfenv<T..., R...>(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)?
|
||||
|
||||
declare function ipairs<V>(tab: {V}): (({V}, number) -> (number?, V), {V}, number)
|
||||
|
||||
declare function pcall<A..., R...>(f: (A...) -> R..., ...: A...): (boolean, R...)
|
||||
|
||||
-- FIXME: The actual type of `xpcall` is:
|
||||
-- <E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., A...) -> (true, R1...) | (false, R2...)
|
||||
-- Since we can't represent the return value, we use (boolean, R1...).
|
||||
declare function xpcall<E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., ...: A...): (boolean, R1...)
|
||||
|
||||
-- `select` has a magic function attached to provide more detailed type information
|
||||
declare function select<A...>(i: string | number, ...: A...): ...any
|
||||
|
||||
-- FIXME: This type is not entirely correct - `loadstring` returns a function or
|
||||
-- (nil, string).
|
||||
declare function loadstring<A...>(src: string, chunkname: string?): (((A...) -> any)?, string?)
|
||||
|
||||
declare function @checked newproxy(mt: boolean?): any
|
||||
|
||||
declare coroutine: {
|
||||
create: <A..., R...>(f: (A...) -> R...) -> thread,
|
||||
resume: <A..., R...>(co: thread, A...) -> (boolean, R...),
|
||||
running: () -> thread,
|
||||
status: @checked (co: thread) -> "dead" | "running" | "normal" | "suspended",
|
||||
wrap: <A..., R...>(f: (A...) -> R...) -> ((A...) -> R...),
|
||||
yield: <A..., R...>(A...) -> R...,
|
||||
isyieldable: () -> boolean,
|
||||
close: @checked (co: thread) -> (boolean, any)
|
||||
}
|
||||
|
||||
declare table: {
|
||||
concat: <V>(t: {V}, sep: string?, i: number?, j: number?) -> string,
|
||||
insert: (<V>(t: {V}, value: V) -> ()) & (<V>(t: {V}, pos: number, value: V) -> ()),
|
||||
maxn: <V>(t: {V}) -> number,
|
||||
remove: <V>(t: {V}, number?) -> V?,
|
||||
sort: <V>(t: {V}, comp: ((V, V) -> boolean)?) -> (),
|
||||
create: <V>(count: number, value: V?) -> {V},
|
||||
find: <V>(haystack: {V}, needle: V, init: number?) -> number?,
|
||||
|
||||
unpack: <V>(list: {V}, i: number?, j: number?) -> ...V,
|
||||
pack: <V>(...V) -> { n: number, [number]: V },
|
||||
|
||||
getn: <V>(t: {V}) -> number,
|
||||
foreach: <K, V>(t: {[K]: V}, f: (K, V) -> ()) -> (),
|
||||
foreachi: <V>({V}, (number, V) -> ()) -> (),
|
||||
|
||||
move: <V>(src: {V}, a: number, b: number, t: number, dst: {V}?) -> {V},
|
||||
clear: <K, V>(table: {[K]: V}) -> (),
|
||||
|
||||
isfrozen: <K, V>(t: {[K]: V}) -> boolean,
|
||||
}
|
||||
|
||||
declare debug: {
|
||||
info: (<R...>(thread: thread, level: number, options: string) -> R...) & (<R...>(level: number, options: string) -> R...) & (<A..., R1..., R2...>(func: (A...) -> R1..., options: string) -> R2...),
|
||||
traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string),
|
||||
}
|
||||
|
||||
declare utf8: {
|
||||
char: @checked (...number) -> string,
|
||||
charpattern: string,
|
||||
codes: @checked (str: string) -> ((string, number) -> (number, number), string, number),
|
||||
codepoint: @checked (str: string, i: number?, j: number?) -> ...number,
|
||||
len: @checked (s: string, i: number?, j: number?) -> (number?, number?),
|
||||
offset: @checked (s: string, n: number?, i: number?) -> number,
|
||||
}
|
||||
|
||||
-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype.
|
||||
declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
|
||||
|
||||
|
||||
--- Buffer API
|
||||
declare buffer: {
|
||||
create: @checked (size: number) -> buffer,
|
||||
fromstring: @checked (str: string) -> buffer,
|
||||
tostring: @checked (b: buffer) -> string,
|
||||
len: @checked (b: buffer) -> number,
|
||||
copy: @checked (target: buffer, targetOffset: number, source: buffer, sourceOffset: number?, count: number?) -> (),
|
||||
fill: @checked (b: buffer, offset: number, value: number, count: number?) -> (),
|
||||
readi8: @checked (b: buffer, offset: number) -> number,
|
||||
readu8: @checked (b: buffer, offset: number) -> number,
|
||||
readi16: @checked (b: buffer, offset: number) -> number,
|
||||
readu16: @checked (b: buffer, offset: number) -> number,
|
||||
readi32: @checked (b: buffer, offset: number) -> number,
|
||||
readu32: @checked (b: buffer, offset: number) -> number,
|
||||
readf32: @checked (b: buffer, offset: number) -> number,
|
||||
readf64: @checked (b: buffer, offset: number) -> number,
|
||||
writei8: @checked (b: buffer, offset: number, value: number) -> (),
|
||||
writeu8: @checked (b: buffer, offset: number, value: number) -> (),
|
||||
writei16: @checked (b: buffer, offset: number, value: number) -> (),
|
||||
writeu16: @checked (b: buffer, offset: number, value: number) -> (),
|
||||
writei32: @checked (b: buffer, offset: number, value: number) -> (),
|
||||
writeu32: @checked (b: buffer, offset: number, value: number) -> (),
|
||||
writef32: @checked (b: buffer, offset: number, value: number) -> (),
|
||||
writef64: @checked (b: buffer, offset: number, value: number) -> (),
|
||||
readstring: @checked (b: buffer, offset: number, count: number) -> string,
|
||||
writestring: @checked (b: buffer, offset: number, value: string, count: number?) -> (),
|
||||
}
|
||||
|
||||
)BUILTIN_SRC";
|
||||
|
||||
std::string getBuiltinDefinitionSource()
|
||||
{
|
||||
std::string result = kBuiltinDefinitionLuaSrc;
|
||||
|
||||
if (FFlag::LuauBufferTypeck)
|
||||
result = kBuiltinDefinitionBufferSrc + result;
|
||||
else if (FFlag::LuauBufferDefinitions)
|
||||
else
|
||||
result = kBuiltinDefinitionBufferSrc_DEPRECATED + result;
|
||||
// Annotates each non generic function as checked
|
||||
if (FFlag::LuauCheckedEmbeddedDefinitions)
|
||||
result = kBuiltinDefinitionLuaSrcChecked;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -35,8 +35,6 @@ LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
||||
LUAU_FASTFLAGVARIABLE(CorrectEarlyReturnInMarkDirty, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDefinitionFileSetModuleName, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRethrowSingleModuleIce, false)
|
||||
|
||||
namespace Luau
|
||||
@ -165,11 +163,9 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(GlobalTypes& globals, Scop
|
||||
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
|
||||
|
||||
Luau::SourceModule sourceModule;
|
||||
if (FFlag::LuauDefinitionFileSetModuleName)
|
||||
{
|
||||
sourceModule.name = packageName;
|
||||
sourceModule.humanReadableName = packageName;
|
||||
}
|
||||
sourceModule.name = packageName;
|
||||
sourceModule.humanReadableName = packageName;
|
||||
|
||||
Luau::ParseResult parseResult = parseSourceForModule(source, sourceModule, captureComments);
|
||||
if (parseResult.errors.size() > 0)
|
||||
return LoadDefinitionFileResult{false, parseResult, sourceModule, nullptr};
|
||||
@ -1116,16 +1112,8 @@ bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
|
||||
*/
|
||||
void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty)
|
||||
{
|
||||
if (FFlag::CorrectEarlyReturnInMarkDirty)
|
||||
{
|
||||
if (sourceNodes.count(name) == 0)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!moduleResolver.getModule(name) && !moduleResolverForAutocomplete.getModule(name))
|
||||
return;
|
||||
}
|
||||
if (sourceNodes.count(name) == 0)
|
||||
return;
|
||||
|
||||
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
|
||||
for (const auto& module : sourceNodes)
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
#include "Luau/GlobalTypes.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauInitializeStringMetatableInGlobalTypes)
|
||||
LUAU_FASTFLAG(LuauBufferTypeck)
|
||||
|
||||
namespace Luau
|
||||
@ -24,14 +23,11 @@ GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
|
||||
globalScope->addBuiltinTypeBinding("unknown", TypeFun{{}, builtinTypes->unknownType});
|
||||
globalScope->addBuiltinTypeBinding("never", TypeFun{{}, builtinTypes->neverType});
|
||||
|
||||
if (FFlag::LuauInitializeStringMetatableInGlobalTypes)
|
||||
{
|
||||
unfreeze(*builtinTypes->arena);
|
||||
TypeId stringMetatableTy = makeStringMetatable(builtinTypes);
|
||||
asMutable(builtinTypes->stringType)->ty.emplace<PrimitiveType>(PrimitiveType::String, stringMetatableTy);
|
||||
persist(stringMetatableTy);
|
||||
freeze(*builtinTypes->arena);
|
||||
}
|
||||
unfreeze(*builtinTypes->arena);
|
||||
TypeId stringMetatableTy = makeStringMetatable(builtinTypes);
|
||||
asMutable(builtinTypes->stringType)->ty.emplace<PrimitiveType>(PrimitiveType::String, stringMetatableTy);
|
||||
persist(stringMetatableTy);
|
||||
freeze(*builtinTypes->arena);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -10,6 +10,8 @@
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPreallocateTarjanVectors, false);
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -146,6 +148,18 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
|
||||
return resTy;
|
||||
}
|
||||
|
||||
Tarjan::Tarjan()
|
||||
{
|
||||
if (FFlag::LuauPreallocateTarjanVectors)
|
||||
{
|
||||
nodes.reserve(FInt::LuauTarjanPreallocationSize);
|
||||
stack.reserve(FInt::LuauTarjanPreallocationSize);
|
||||
edgesTy.reserve(FInt::LuauTarjanPreallocationSize);
|
||||
edgesTp.reserve(FInt::LuauTarjanPreallocationSize);
|
||||
worklist.reserve(FInt::LuauTarjanPreallocationSize);
|
||||
}
|
||||
}
|
||||
|
||||
void Tarjan::visitChildren(TypeId ty, int index)
|
||||
{
|
||||
LUAU_ASSERT(ty == log->follow(ty));
|
||||
|
@ -545,11 +545,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
// Match head types pairwise
|
||||
|
||||
for (size_t i = 0; i < headSize; ++i)
|
||||
{
|
||||
results.push_back(isCovariantWith(env, subHead[i], superHead[i]).withBothComponent(TypePath::Index{i}));
|
||||
if (!results.back().isSubtype)
|
||||
return results.back();
|
||||
}
|
||||
|
||||
// Handle mismatched head sizes
|
||||
|
||||
@ -599,7 +595,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
unexpected(*subTail);
|
||||
}
|
||||
else
|
||||
return {false};
|
||||
{
|
||||
results.push_back({false});
|
||||
return SubtypingResult::all(results);
|
||||
}
|
||||
}
|
||||
else if (subHead.size() > superHead.size())
|
||||
{
|
||||
@ -664,7 +663,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
bool ok = bindGeneric(env, *subTail, *superTail);
|
||||
results.push_back(SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail));
|
||||
}
|
||||
else if (get2<VariadicTypePack, GenericTypePack>(*subTail, *superTail))
|
||||
else if (auto p = get2<VariadicTypePack, GenericTypePack>(*subTail, *superTail))
|
||||
{
|
||||
if (variance == Variance::Contravariant)
|
||||
{
|
||||
@ -678,9 +677,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
results.push_back(SubtypingResult{false}.withBothComponent(TypePath::PackField::Tail));
|
||||
}
|
||||
}
|
||||
else if (get2<GenericTypePack, VariadicTypePack>(*subTail, *superTail))
|
||||
else if (auto p = get2<GenericTypePack, VariadicTypePack>(*subTail, *superTail))
|
||||
{
|
||||
if (variance == Variance::Contravariant)
|
||||
if (TypeId t = follow(p.second->ty); get<AnyType>(t) || get<UnknownType>(t))
|
||||
{
|
||||
// Extra magic rule:
|
||||
// T... <: ...any
|
||||
// T... <: ...unknown
|
||||
//
|
||||
// See https://github.com/luau-lang/luau/issues/767
|
||||
}
|
||||
else if (variance == Variance::Contravariant)
|
||||
{
|
||||
// (...number) -> number </: <A...>(A...) -> number
|
||||
results.push_back(SubtypingResult{false}.withBothComponent(TypePath::PackField::Tail));
|
||||
@ -747,6 +754,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy)
|
||||
{
|
||||
VarianceFlipper vf{&variance};
|
||||
|
||||
SubtypingResult result = isCovariantWith(env, superTy, subTy);
|
||||
if (result.reasoning.empty())
|
||||
result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Contravariant});
|
||||
@ -778,6 +787,7 @@ template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy)
|
||||
{
|
||||
SubtypingResult result = isCovariantWith(env, subTy, superTy).andAlso(isContravariantWith(env, subTy, superTy));
|
||||
|
||||
if (result.reasoning.empty())
|
||||
result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invariant});
|
||||
else
|
||||
@ -842,11 +852,20 @@ SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, const TryP
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const UnionType* superUnion)
|
||||
{
|
||||
// As per TAPL: T <: A | B iff T <: A || T <: B
|
||||
std::vector<SubtypingResult> subtypings;
|
||||
size_t i = 0;
|
||||
|
||||
for (TypeId ty : superUnion)
|
||||
subtypings.push_back(isCovariantWith(env, subTy, ty).withSuperComponent(TypePath::Index{i++}));
|
||||
return SubtypingResult::any(subtypings);
|
||||
{
|
||||
SubtypingResult next = isCovariantWith(env, subTy, ty);
|
||||
if (next.isSubtype)
|
||||
return SubtypingResult{true};
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Is it possible here to use the context produced by the above
|
||||
* isCovariantWith() calls to produce a richer, more helpful result in the
|
||||
* case that the subtyping relation does not hold?
|
||||
*/
|
||||
return SubtypingResult{false};
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const UnionType* subUnion, TypeId superTy)
|
||||
@ -1173,7 +1192,6 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Func
|
||||
{
|
||||
SubtypingResult result;
|
||||
{
|
||||
VarianceFlipper vf{&variance};
|
||||
result.orElse(isContravariantWith(env, subFunction->argTypes, superFunction->argTypes).withBothComponent(TypePath::PackField::Arguments));
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/VecDeque.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
#include <algorithm>
|
||||
@ -26,7 +27,6 @@ LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
LUAU_FASTFLAGVARIABLE(LuauInitializeStringMetatableInGlobalTypes, false)
|
||||
LUAU_FASTFLAG(LuauBufferTypeck)
|
||||
|
||||
namespace Luau
|
||||
@ -129,7 +129,7 @@ std::vector<TypeId> flattenIntersection(TypeId ty)
|
||||
return {ty};
|
||||
|
||||
std::unordered_set<TypeId> seen;
|
||||
std::deque<TypeId> queue{ty};
|
||||
VecDeque<TypeId> queue{ty};
|
||||
|
||||
std::vector<TypeId> result;
|
||||
|
||||
@ -248,6 +248,15 @@ bool isTableIntersection(TypeId ty)
|
||||
return std::all_of(parts.begin(), parts.end(), getTableType);
|
||||
}
|
||||
|
||||
bool isTableUnion(TypeId ty)
|
||||
{
|
||||
const UnionType* ut = get<UnionType>(follow(ty));
|
||||
if (!ut)
|
||||
return false;
|
||||
|
||||
return std::all_of(begin(ut), end(ut), getTableType);
|
||||
}
|
||||
|
||||
bool isOverloadedFunction(TypeId ty)
|
||||
{
|
||||
if (!get<IntersectionType>(follow(ty)))
|
||||
@ -955,13 +964,6 @@ BuiltinTypes::BuiltinTypes()
|
||||
, uninhabitableTypePack(arena->addTypePack(TypePackVar{TypePack{{neverType}, neverTypePack}, /*persistent*/ true}))
|
||||
, errorTypePack(arena->addTypePack(TypePackVar{Unifiable::Error{}, /*persistent*/ true}))
|
||||
{
|
||||
if (!FFlag::LuauInitializeStringMetatableInGlobalTypes)
|
||||
{
|
||||
TypeId stringMetatable = makeStringMetatable(NotNull{this});
|
||||
asMutable(stringType)->ty = PrimitiveType{PrimitiveType::String, stringMetatable};
|
||||
persist(stringMetatable);
|
||||
}
|
||||
|
||||
freeze(*arena);
|
||||
}
|
||||
|
||||
@ -999,7 +1001,7 @@ TypePackId BuiltinTypes::errorRecoveryTypePack(TypePackId guess) const
|
||||
|
||||
void persist(TypeId ty)
|
||||
{
|
||||
std::deque<TypeId> queue{ty};
|
||||
VecDeque<TypeId> queue{ty};
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
|
@ -1351,111 +1351,131 @@ struct TypeChecker2
|
||||
|
||||
ErrorVec argumentErrors;
|
||||
|
||||
// Reminder: Functions have parameters. You provide arguments.
|
||||
auto paramIter = begin(fn->argTypes);
|
||||
size_t argOffset = 0;
|
||||
TypeId prospectiveFunction = arena->addType(FunctionType{arena->addTypePack(*args), builtinTypes->anyTypePack});
|
||||
SubtypingResult sr = subtyping->isSubtype(fnTy, prospectiveFunction);
|
||||
|
||||
while (paramIter != end(fn->argTypes))
|
||||
if (sr.isSubtype)
|
||||
return {Analysis::Ok, {}};
|
||||
|
||||
if (1 == sr.reasoning.size())
|
||||
{
|
||||
if (argOffset >= args->head.size())
|
||||
break;
|
||||
const SubtypingReasoning& reason = *sr.reasoning.begin();
|
||||
|
||||
TypeId paramTy = *paramIter;
|
||||
TypeId argTy = args->head[argOffset];
|
||||
AstExpr* argLoc = argExprs->at(argOffset >= argExprs->size() ? argExprs->size() - 1 : argOffset);
|
||||
const TypePath::Path justArguments{TypePath::PackField::Arguments};
|
||||
|
||||
if (auto errors = testIsSubtype(argLoc->location, argTy, paramTy))
|
||||
if (reason.subPath == justArguments && reason.superPath == justArguments)
|
||||
{
|
||||
// Since we're stopping right here, we need to decide if this is a nonviable overload or if there is an arity mismatch.
|
||||
// If it's a nonviable overload, then we need to keep going to get all type errors.
|
||||
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
|
||||
if (args->head.size() < minParams)
|
||||
return {ArityMismatch, *errors};
|
||||
else
|
||||
argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end());
|
||||
}
|
||||
// If the subtype test failed only due to an arity mismatch,
|
||||
// it is still possible that this function call is okay.
|
||||
// Subtype testing does not know anything about optional
|
||||
// function arguments.
|
||||
//
|
||||
// This can only happen if the actual function call has a
|
||||
// finite set of arguments which is too short for the
|
||||
// function being called. If all of those unsatisfied
|
||||
// function arguments are options, then this function call
|
||||
// is ok.
|
||||
|
||||
++paramIter;
|
||||
++argOffset;
|
||||
}
|
||||
const size_t firstUnsatisfiedArgument = argExprs->size();
|
||||
const auto [requiredHead, _requiredTail] = flatten(fn->argTypes);
|
||||
|
||||
while (argOffset < args->head.size())
|
||||
{
|
||||
// If we can iterate over the head of arguments, then we have exhausted the head of the parameters.
|
||||
LUAU_ASSERT(paramIter == end(fn->argTypes));
|
||||
// If too many arguments were supplied, this overload
|
||||
// definitely does not match.
|
||||
if (args->head.size() > requiredHead.size())
|
||||
{
|
||||
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
|
||||
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, false}};
|
||||
|
||||
AstExpr* argExpr = argExprs->at(argOffset >= argExprs->size() ? argExprs->size() - 1 : argOffset);
|
||||
return {Analysis::ArityMismatch, {error}};
|
||||
}
|
||||
|
||||
if (!paramIter.tail())
|
||||
{
|
||||
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
|
||||
TypeError error{argExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, false}};
|
||||
return {ArityMismatch, {error}};
|
||||
}
|
||||
else if (auto vtp = get<VariadicTypePack>(follow(paramIter.tail())))
|
||||
{
|
||||
if (auto errors = testIsSubtype(argExpr->location, args->head[argOffset], vtp->ty))
|
||||
argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end());
|
||||
}
|
||||
else if (get<GenericTypePack>(follow(paramIter.tail())))
|
||||
argumentErrors.push_back(TypeError{argExpr->location, TypePackMismatch{fn->argTypes, arena->addTypePack(*args)}});
|
||||
// If any of the unsatisfied arguments are not supertypes of
|
||||
// nil, then this overload does not match.
|
||||
for (size_t i = firstUnsatisfiedArgument; i < requiredHead.size(); ++i)
|
||||
{
|
||||
if (!subtyping->isSubtype(builtinTypes->nilType, requiredHead[i]).isSubtype)
|
||||
{
|
||||
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
|
||||
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, false}};
|
||||
|
||||
++argOffset;
|
||||
}
|
||||
return {Analysis::ArityMismatch, {error}};
|
||||
}
|
||||
}
|
||||
|
||||
while (paramIter != end(fn->argTypes))
|
||||
{
|
||||
// If we can iterate over parameters, then we have exhausted the head of the arguments.
|
||||
LUAU_ASSERT(argOffset == args->head.size());
|
||||
|
||||
// It may have a tail, however, so check that.
|
||||
if (auto vtp = get<VariadicTypePack>(follow(args->tail)))
|
||||
{
|
||||
AstExpr* argExpr = argExprs->at(argExprs->size() - 1);
|
||||
|
||||
if (auto errors = testIsSubtype(argExpr->location, vtp->ty, *paramIter))
|
||||
argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end());
|
||||
}
|
||||
else if (!isOptional(*paramIter))
|
||||
{
|
||||
AstExpr* argExpr = argExprs->empty() ? fnExpr : argExprs->at(argExprs->size() - 1);
|
||||
|
||||
// It is ok to have excess parameters as long as they are all optional.
|
||||
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
|
||||
TypeError error{argExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, false}};
|
||||
return {ArityMismatch, {error}};
|
||||
}
|
||||
|
||||
++paramIter;
|
||||
}
|
||||
|
||||
// We hit the end of the heads for both parameters and arguments, so check their tails.
|
||||
LUAU_ASSERT(paramIter == end(fn->argTypes));
|
||||
LUAU_ASSERT(argOffset == args->head.size());
|
||||
|
||||
const Location argLoc = argExprs->empty() ? Location{} // TODO
|
||||
: argExprs->at(argExprs->size() - 1)->location;
|
||||
|
||||
if (paramIter.tail() && args->tail)
|
||||
{
|
||||
if (auto errors = testIsSubtype(argLoc, *args->tail, *paramIter.tail()))
|
||||
argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end());
|
||||
}
|
||||
else if (paramIter.tail())
|
||||
{
|
||||
const TypePackId paramTail = follow(*paramIter.tail());
|
||||
|
||||
if (get<GenericTypePack>(paramTail))
|
||||
{
|
||||
argumentErrors.push_back(TypeError{argLoc, TypePackMismatch{fn->argTypes, arena->addTypePack(*args)}});
|
||||
}
|
||||
else if (get<VariadicTypePack>(paramTail))
|
||||
{
|
||||
// Nothing. This is ok.
|
||||
return {Analysis::Ok, {}};
|
||||
}
|
||||
}
|
||||
|
||||
return {argumentErrors.empty() ? Ok : OverloadIsNonviable, argumentErrors};
|
||||
ErrorVec errors;
|
||||
|
||||
if (!sr.isErrorSuppressing)
|
||||
{
|
||||
for (const SubtypingReasoning& reason : sr.reasoning)
|
||||
{
|
||||
/* The return type of our prospective function is always
|
||||
* any... so any subtype failures here can only arise from
|
||||
* argument type mismatches.
|
||||
*/
|
||||
|
||||
Location argLocation;
|
||||
|
||||
if (const Luau::TypePath::Index* pathIndexComponent = get_if<Luau::TypePath::Index>(&reason.superPath.components.at(1)))
|
||||
{
|
||||
size_t nthArgument = pathIndexComponent->index;
|
||||
argLocation = argExprs->at(nthArgument)->location;
|
||||
|
||||
std::optional<TypeId> failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes);
|
||||
std::optional<TypeId> failedSuperTy = traverseForType(prospectiveFunction, reason.superPath, builtinTypes);
|
||||
|
||||
if (failedSubTy && failedSuperTy)
|
||||
{
|
||||
// TODO extract location from the SubtypingResult path and argExprs
|
||||
switch (reason.variance)
|
||||
{
|
||||
case SubtypingVariance::Covariant:
|
||||
case SubtypingVariance::Contravariant:
|
||||
errors.emplace_back(argLocation, TypeMismatch{*failedSubTy, *failedSuperTy, TypeMismatch::CovariantContext});
|
||||
break;
|
||||
case SubtypingVariance::Invariant:
|
||||
errors.emplace_back(argLocation, TypeMismatch{*failedSubTy, *failedSuperTy, TypeMismatch::InvariantContext});
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TypePackId> failedSubPack = traverseForPack(fnTy, reason.subPath, builtinTypes);
|
||||
std::optional<TypePackId> failedSuperPack = traverseForPack(prospectiveFunction, reason.superPath, builtinTypes);
|
||||
|
||||
if (failedSubPack && failedSuperPack)
|
||||
{
|
||||
LUAU_ASSERT(!argExprs->empty());
|
||||
argLocation = argExprs->at(argExprs->size() - 1)->location;
|
||||
|
||||
// TODO extract location from the SubtypingResult path and argExprs
|
||||
switch (reason.variance)
|
||||
{
|
||||
case SubtypingVariance::Covariant:
|
||||
errors.emplace_back(argLocation, TypePackMismatch{*failedSubPack, *failedSuperPack});
|
||||
break;
|
||||
case SubtypingVariance::Contravariant:
|
||||
errors.emplace_back(argLocation, TypePackMismatch{*failedSuperPack, *failedSubPack});
|
||||
break;
|
||||
case SubtypingVariance::Invariant:
|
||||
errors.emplace_back(argLocation, TypePackMismatch{*failedSubPack, *failedSuperPack});
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return {Analysis::OverloadIsNonviable, std::move(errors)};
|
||||
}
|
||||
|
||||
size_t indexof(Analysis analysis)
|
||||
@ -1494,7 +1514,6 @@ struct TypeChecker2
|
||||
arityMismatches.emplace_back(ty, std::move(errors));
|
||||
break;
|
||||
case OverloadIsNonviable:
|
||||
LUAU_ASSERT(!errors.empty());
|
||||
nonviableOverloads.emplace_back(ty, std::move(errors));
|
||||
break;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier2.h"
|
||||
#include "Luau/VecDeque.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyGraphReductionMaximumSteps, 1'000'000);
|
||||
@ -23,8 +24,8 @@ namespace Luau
|
||||
|
||||
struct InstanceCollector : TypeOnceVisitor
|
||||
{
|
||||
std::deque<TypeId> tys;
|
||||
std::deque<TypePackId> tps;
|
||||
VecDeque<TypeId> tys;
|
||||
VecDeque<TypePackId> tps;
|
||||
|
||||
bool visit(TypeId ty, const TypeFamilyInstanceType&) override
|
||||
{
|
||||
@ -60,8 +61,8 @@ struct FamilyReducer
|
||||
{
|
||||
TypeFamilyContext ctx;
|
||||
|
||||
std::deque<TypeId> queuedTys;
|
||||
std::deque<TypePackId> queuedTps;
|
||||
VecDeque<TypeId> queuedTys;
|
||||
VecDeque<TypePackId> queuedTps;
|
||||
DenseHashSet<const void*> irreducible{nullptr};
|
||||
FamilyGraphReductionResult result;
|
||||
bool force = false;
|
||||
@ -69,7 +70,7 @@ struct FamilyReducer
|
||||
// Local to the constraint being reduced.
|
||||
Location location;
|
||||
|
||||
FamilyReducer(std::deque<TypeId> queuedTys, std::deque<TypePackId> queuedTps, Location location, TypeFamilyContext ctx, bool force = false)
|
||||
FamilyReducer(VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, Location location, TypeFamilyContext ctx, bool force = false)
|
||||
: ctx(ctx)
|
||||
, queuedTys(std::move(queuedTys))
|
||||
, queuedTps(std::move(queuedTps))
|
||||
@ -258,7 +259,7 @@ struct FamilyReducer
|
||||
};
|
||||
|
||||
static FamilyGraphReductionResult reduceFamiliesInternal(
|
||||
std::deque<TypeId> queuedTys, std::deque<TypePackId> queuedTps, Location location, TypeFamilyContext ctx, bool force)
|
||||
VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, Location location, TypeFamilyContext ctx, bool force)
|
||||
{
|
||||
FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), location, ctx, force};
|
||||
int iterationCount = 0;
|
||||
@ -1051,6 +1052,188 @@ TypeFamilyReductionResult<TypeId> refineFamilyFn(const std::vector<TypeId>& type
|
||||
return {resultTy, false, {}, {}};
|
||||
}
|
||||
|
||||
// computes the keys of `ty` into `result`
|
||||
// `isRaw` parameter indicates whether or not we should follow __index metamethods
|
||||
// returns `false` if `result` should be ignored because the answer is "all strings"
|
||||
bool computeKeysOf(TypeId ty, DenseHashSet<std::string>& result, DenseHashSet<TypeId>& seen, bool isRaw, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
// if the type is the top table type, the answer is just "all strings"
|
||||
if (get<PrimitiveType>(ty))
|
||||
return false;
|
||||
|
||||
// if we've already seen this type, we can do nothing
|
||||
if (seen.contains(ty))
|
||||
return true;
|
||||
seen.insert(ty);
|
||||
|
||||
// if we have a particular table type, we can insert the keys
|
||||
if (auto tableTy = get<TableType>(ty))
|
||||
{
|
||||
for (auto [key, _] : tableTy->props)
|
||||
result.insert(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
// otherwise, we have a metatable to deal with
|
||||
if (auto metatableTy = get<MetatableType>(ty))
|
||||
{
|
||||
bool res = true;
|
||||
|
||||
if (!isRaw)
|
||||
{
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
ErrorVec dummy;
|
||||
|
||||
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, ty, "__index", Location{});
|
||||
if (mmType)
|
||||
res = res && computeKeysOf(*mmType, result, seen, isRaw, ctx);
|
||||
}
|
||||
|
||||
res = res && computeKeysOf(metatableTy->table, result, seen, isRaw, ctx);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// this should not be reachable since the type should be a valid tables part from normalization.
|
||||
LUAU_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> keyofFamilyImpl(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx, bool isRaw)
|
||||
{
|
||||
if (typeParams.size() != 1 || !packParams.empty())
|
||||
{
|
||||
ctx->ice->ice("keyof type family: encountered a type family instance without the required argument structure");
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
TypeId operandTy = follow(typeParams.at(0));
|
||||
|
||||
const NormalizedType* normTy = ctx->normalizer->normalize(operandTy);
|
||||
|
||||
// if the operand failed to normalize, we can't reduce, but know nothing about inhabitance.
|
||||
if (!normTy)
|
||||
return {std::nullopt, false, {}, {}};
|
||||
|
||||
// if we don't have either just tables or just classes, we've got nothing to get keys of (at least until a future version perhaps adds classes as well)
|
||||
if (normTy->hasTables() == normTy->hasClasses())
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
// this is sort of atrocious, but we're trying to reject any type that has not normalized to a table or a union of tables.
|
||||
if (normTy->hasTops() || normTy->hasBooleans() || normTy->hasErrors() || normTy->hasNils() || normTy->hasNumbers() || normTy->hasStrings() ||
|
||||
normTy->hasThreads() || normTy->hasBuffers() || normTy->hasFunctions() || normTy->hasTyvars())
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
// we're going to collect the keys in here
|
||||
DenseHashSet<std::string> keys{{}};
|
||||
|
||||
// computing the keys for classes
|
||||
if (normTy->hasClasses())
|
||||
{
|
||||
LUAU_ASSERT(!normTy->hasTables());
|
||||
|
||||
auto classesIter = normTy->classes.ordering.begin();
|
||||
auto classesIterEnd = normTy->classes.ordering.end();
|
||||
LUAU_ASSERT(classesIter != classesIterEnd); // should be guaranteed by the `hasClasses` check
|
||||
|
||||
auto classTy = get<ClassType>(*classesIter);
|
||||
if (!classTy)
|
||||
{
|
||||
LUAU_ASSERT(false); // this should not be possible according to normalization's spec
|
||||
return {std::nullopt, true, {}, {}};
|
||||
}
|
||||
|
||||
for (auto [key, _] : classTy->props)
|
||||
keys.insert(key);
|
||||
|
||||
// we need to check that if there are multiple classes, they have the same set of keys
|
||||
while (++classesIter != classesIterEnd)
|
||||
{
|
||||
auto classTy = get<ClassType>(*classesIter);
|
||||
if (!classTy)
|
||||
{
|
||||
LUAU_ASSERT(false); // this should not be possible according to normalization's spec
|
||||
return {std::nullopt, true, {}, {}};
|
||||
}
|
||||
|
||||
for (auto [key, _] : classTy->props)
|
||||
{
|
||||
// we will refuse to reduce if the keys are not exactly the same
|
||||
if (!keys.contains(key))
|
||||
return {std::nullopt, true, {}, {}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// computing the keys for tables
|
||||
if (normTy->hasTables())
|
||||
{
|
||||
LUAU_ASSERT(!normTy->hasClasses());
|
||||
|
||||
// seen set for key computation for tables
|
||||
DenseHashSet<TypeId> seen{{}};
|
||||
|
||||
auto tablesIter = normTy->tables.begin();
|
||||
LUAU_ASSERT(tablesIter != normTy->tables.end()); // should be guaranteed by the `hasTables` check earlier
|
||||
|
||||
// collect all the properties from the first table type
|
||||
if (!computeKeysOf(*tablesIter, keys, seen, isRaw, ctx))
|
||||
return {ctx->builtins->stringType, false, {}, {}}; // if it failed, we have the top table type!
|
||||
|
||||
// we need to check that if there are multiple tables, they have the same set of keys
|
||||
while (++tablesIter != normTy->tables.end())
|
||||
{
|
||||
seen.clear(); // we'll reuse the same seen set
|
||||
|
||||
DenseHashSet<std::string> localKeys{{}};
|
||||
|
||||
// the type family is irreducible if there's _also_ the top table type in here
|
||||
if (!computeKeysOf(*tablesIter, localKeys, seen, isRaw, ctx))
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
// the type family is irreducible if the key sets are not equal.
|
||||
if (localKeys != keys)
|
||||
return {std::nullopt, true, {}, {}};
|
||||
}
|
||||
}
|
||||
|
||||
// if the set of keys is empty, `keyof<T>` is `never`
|
||||
if (keys.empty())
|
||||
return {ctx->builtins->neverType, false, {}, {}};
|
||||
|
||||
// everything is validated, we need only construct our big union of singletons now!
|
||||
std::vector<TypeId> singletons;
|
||||
singletons.reserve(keys.size());
|
||||
|
||||
for (std::string key : keys)
|
||||
singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{key}}));
|
||||
|
||||
return {ctx->arena->addType(UnionType{singletons}), false, {}, {}};
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> keyofFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 1 || !packParams.empty())
|
||||
{
|
||||
ctx->ice->ice("keyof type family: encountered a type family instance without the required argument structure");
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
return keyofFamilyImpl(typeParams, packParams, ctx, /* isRaw */ false);
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> rawkeyofFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 1 || !packParams.empty())
|
||||
{
|
||||
ctx->ice->ice("rawkeyof type family: encountered a type family instance without the required argument structure");
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
return keyofFamilyImpl(typeParams, packParams, ctx, /* isRaw */ true);
|
||||
}
|
||||
|
||||
BuiltinTypeFamilies::BuiltinTypeFamilies()
|
||||
: notFamily{"not", notFamilyFn}
|
||||
, lenFamily{"len", lenFamilyFn}
|
||||
@ -1069,6 +1252,8 @@ BuiltinTypeFamilies::BuiltinTypeFamilies()
|
||||
, leFamily{"le", leFamilyFn}
|
||||
, eqFamily{"eq", eqFamilyFn}
|
||||
, refineFamily{"refine", refineFamilyFn}
|
||||
, keyofFamily{"keyof", keyofFamilyFn}
|
||||
, rawkeyofFamily{"rawkeyof", rawkeyofFamilyFn}
|
||||
{
|
||||
}
|
||||
|
||||
@ -1107,6 +1292,9 @@ void BuiltinTypeFamilies::addToScope(NotNull<TypeArena> arena, NotNull<Scope> sc
|
||||
scope->exportedTypeBindings[ltFamily.name] = mkBinaryTypeFamily(<Family);
|
||||
scope->exportedTypeBindings[leFamily.name] = mkBinaryTypeFamily(&leFamily);
|
||||
scope->exportedTypeBindings[eqFamily.name] = mkBinaryTypeFamily(&eqFamily);
|
||||
|
||||
scope->exportedTypeBindings[keyofFamily.name] = mkUnaryTypeFamily(&keyofFamily);
|
||||
scope->exportedTypeBindings[rawkeyofFamily.name] = mkUnaryTypeFamily(&rawkeyofFamily);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -40,6 +40,7 @@ LUAU_FASTFLAGVARIABLE(LuauLoopControlFlowAnalysis, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
|
||||
LUAU_FASTFLAG(LuauBufferTypeck)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauForbidAliasNamedTypeof, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -668,7 +669,7 @@ LUAU_NOINLINE void TypeChecker::checkBlockTypeAliases(const ScopePtr& scope, std
|
||||
{
|
||||
if (const auto& typealias = stat->as<AstStatTypeAlias>())
|
||||
{
|
||||
if (typealias->name == kParseNameError)
|
||||
if (typealias->name == kParseNameError || (FFlag::LuauForbidAliasNamedTypeof && typealias->name == "typeof"))
|
||||
continue;
|
||||
|
||||
auto& bindings = typealias->exported ? scope->exportedTypeBindings : scope->privateTypeBindings;
|
||||
@ -1536,6 +1537,12 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& ty
|
||||
if (name == kParseNameError)
|
||||
return ControlFlow::None;
|
||||
|
||||
if (FFlag::LuauForbidAliasNamedTypeof && name == "typeof")
|
||||
{
|
||||
reportError(typealias.location, GenericError{"Type aliases cannot be named typeof"});
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
std::optional<TypeFun> binding;
|
||||
if (auto it = scope->exportedTypeBindings.find(name); it != scope->exportedTypeBindings.end())
|
||||
binding = it->second;
|
||||
@ -1649,7 +1656,9 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
|
||||
Name name = typealias.name.value;
|
||||
|
||||
// If the alias is missing a name, we can't do anything with it. Ignore it.
|
||||
if (name == kParseNameError)
|
||||
// Also, typeof is not a valid type alias name. We will report an error for
|
||||
// this in check()
|
||||
if (name == kParseNameError || (FFlag::LuauForbidAliasNamedTypeof && name == "typeof"))
|
||||
return;
|
||||
|
||||
std::optional<TypeFun> binding;
|
||||
|
192
CLI/Compile.cpp
192
CLI/Compile.cpp
@ -142,56 +142,6 @@ struct CompileStats
|
||||
|
||||
Luau::CodeGen::LoweringStats lowerStats;
|
||||
|
||||
void serializeToJson(FILE* fp)
|
||||
{
|
||||
// use compact one-line formatting to reduce file length
|
||||
fprintf(fp, "{\
|
||||
\"lines\": %zu, \
|
||||
\"bytecode\": %zu, \
|
||||
\"bytecodeInstructionCount\": %zu, \
|
||||
\"codegen\": %zu, \
|
||||
\"readTime\": %f, \
|
||||
\"miscTime\": %f, \
|
||||
\"parseTime\": %f, \
|
||||
\"compileTime\": %f, \
|
||||
\"codegenTime\": %f, \
|
||||
\"lowerStats\": {\
|
||||
\"totalFunctions\": %u, \
|
||||
\"skippedFunctions\": %u, \
|
||||
\"spillsToSlot\": %d, \
|
||||
\"spillsToRestore\": %d, \
|
||||
\"maxSpillSlotsUsed\": %u, \
|
||||
\"blocksPreOpt\": %u, \
|
||||
\"blocksPostOpt\": %u, \
|
||||
\"maxBlockInstructions\": %u, \
|
||||
\"regAllocErrors\": %d, \
|
||||
\"loweringErrors\": %d\
|
||||
}, \
|
||||
\"blockLinearizationStats\": {\
|
||||
\"constPropInstructionCount\": %u, \
|
||||
\"timeSeconds\": %f\
|
||||
}",
|
||||
lines, bytecode, bytecodeInstructionCount, codegen, readTime, miscTime, parseTime, compileTime, codegenTime, lowerStats.totalFunctions,
|
||||
lowerStats.skippedFunctions, lowerStats.spillsToSlot, lowerStats.spillsToRestore, lowerStats.maxSpillSlotsUsed, lowerStats.blocksPreOpt,
|
||||
lowerStats.blocksPostOpt, lowerStats.maxBlockInstructions, lowerStats.regAllocErrors, lowerStats.loweringErrors,
|
||||
lowerStats.blockLinearizationStats.constPropInstructionCount, lowerStats.blockLinearizationStats.timeSeconds);
|
||||
if (lowerStats.collectFunctionStats)
|
||||
{
|
||||
fprintf(fp, ", \"functions\": [");
|
||||
auto functionCount = lowerStats.functions.size();
|
||||
for (size_t i = 0; i < functionCount; ++i)
|
||||
{
|
||||
const Luau::CodeGen::FunctionStats& fstat = lowerStats.functions[i];
|
||||
fprintf(fp, "{\"name\": \"%s\", \"line\": %d, \"bcodeCount\": %u, \"irCount\": %u, \"asmCount\": %u}", fstat.name.c_str(), fstat.line,
|
||||
fstat.bcodeCount, fstat.irCount, fstat.asmCount);
|
||||
if (i < functionCount - 1)
|
||||
fprintf(fp, ", ");
|
||||
}
|
||||
fprintf(fp, "]");
|
||||
}
|
||||
fprintf(fp, "}");
|
||||
}
|
||||
|
||||
CompileStats& operator+=(const CompileStats& that)
|
||||
{
|
||||
this->lines += that.lines;
|
||||
@ -216,6 +166,121 @@ struct CompileStats
|
||||
}
|
||||
};
|
||||
|
||||
#define WRITE_NAME(INDENT, NAME) fprintf(fp, INDENT "\"" #NAME "\": ")
|
||||
#define WRITE_PAIR(INDENT, NAME, FORMAT) fprintf(fp, INDENT "\"" #NAME "\": " FORMAT, stats.NAME)
|
||||
#define WRITE_PAIR_STRING(INDENT, NAME, FORMAT) fprintf(fp, INDENT "\"" #NAME "\": " FORMAT, stats.NAME.c_str())
|
||||
|
||||
void serializeFunctionStats(FILE* fp, const Luau::CodeGen::FunctionStats& stats)
|
||||
{
|
||||
fprintf(fp, " {\n");
|
||||
WRITE_PAIR_STRING(" ", name, "\"%s\",\n");
|
||||
WRITE_PAIR(" ", line, "%d,\n");
|
||||
WRITE_PAIR(" ", bcodeCount, "%u,\n");
|
||||
WRITE_PAIR(" ", irCount, "%u,\n");
|
||||
WRITE_PAIR(" ", asmCount, "%u,\n");
|
||||
WRITE_PAIR(" ", asmSize, "%u,\n");
|
||||
|
||||
WRITE_NAME(" ", bytecodeSummary);
|
||||
const size_t nestingLimit = stats.bytecodeSummary.size();
|
||||
|
||||
if (nestingLimit == 0)
|
||||
fprintf(fp, "[]");
|
||||
else
|
||||
{
|
||||
fprintf(fp, "[\n");
|
||||
for (size_t i = 0; i < nestingLimit; ++i)
|
||||
{
|
||||
const std::vector<unsigned>& counts = stats.bytecodeSummary[i];
|
||||
fprintf(fp, " [");
|
||||
for (size_t j = 0; j < counts.size(); ++j)
|
||||
{
|
||||
fprintf(fp, "%u", counts[j]);
|
||||
if (j < counts.size() - 1)
|
||||
fprintf(fp, ", ");
|
||||
}
|
||||
fprintf(fp, "]");
|
||||
if (i < stats.bytecodeSummary.size() - 1)
|
||||
fprintf(fp, ",\n");
|
||||
}
|
||||
fprintf(fp, "\n ]");
|
||||
}
|
||||
|
||||
fprintf(fp, "\n }");
|
||||
}
|
||||
|
||||
void serializeBlockLinearizationStats(FILE* fp, const Luau::CodeGen::BlockLinearizationStats& stats)
|
||||
{
|
||||
fprintf(fp, "{\n");
|
||||
|
||||
WRITE_PAIR(" ", constPropInstructionCount, "%u,\n");
|
||||
WRITE_PAIR(" ", timeSeconds, "%f\n");
|
||||
|
||||
fprintf(fp, " }");
|
||||
}
|
||||
|
||||
void serializeLoweringStats(FILE* fp, const Luau::CodeGen::LoweringStats& stats)
|
||||
{
|
||||
fprintf(fp, "{\n");
|
||||
|
||||
WRITE_PAIR(" ", totalFunctions, "%u,\n");
|
||||
WRITE_PAIR(" ", skippedFunctions, "%u,\n");
|
||||
WRITE_PAIR(" ", spillsToSlot, "%d,\n");
|
||||
WRITE_PAIR(" ", spillsToRestore, "%d,\n");
|
||||
WRITE_PAIR(" ", maxSpillSlotsUsed, "%u,\n");
|
||||
WRITE_PAIR(" ", blocksPreOpt, "%u,\n");
|
||||
WRITE_PAIR(" ", blocksPostOpt, "%u,\n");
|
||||
WRITE_PAIR(" ", maxBlockInstructions, "%u,\n");
|
||||
WRITE_PAIR(" ", regAllocErrors, "%d,\n");
|
||||
WRITE_PAIR(" ", loweringErrors, "%d,\n");
|
||||
|
||||
WRITE_NAME(" ", blockLinearizationStats);
|
||||
serializeBlockLinearizationStats(fp, stats.blockLinearizationStats);
|
||||
fprintf(fp, ",\n");
|
||||
|
||||
WRITE_NAME(" ", functions);
|
||||
const size_t functionCount = stats.functions.size();
|
||||
|
||||
if (functionCount == 0)
|
||||
fprintf(fp, "[]");
|
||||
else
|
||||
{
|
||||
fprintf(fp, "[\n");
|
||||
for (size_t i = 0; i < functionCount; ++i)
|
||||
{
|
||||
serializeFunctionStats(fp, stats.functions[i]);
|
||||
if (i < functionCount - 1)
|
||||
fprintf(fp, ",\n");
|
||||
}
|
||||
fprintf(fp, "\n ]");
|
||||
}
|
||||
|
||||
fprintf(fp, "\n }");
|
||||
}
|
||||
|
||||
void serializeCompileStats(FILE* fp, const CompileStats& stats)
|
||||
{
|
||||
fprintf(fp, "{\n");
|
||||
|
||||
WRITE_PAIR(" ", lines, "%zu,\n");
|
||||
WRITE_PAIR(" ", bytecode, "%zu,\n");
|
||||
WRITE_PAIR(" ", bytecodeInstructionCount, "%zu,\n");
|
||||
WRITE_PAIR(" ", codegen, "%zu,\n");
|
||||
WRITE_PAIR(" ", readTime, "%f,\n");
|
||||
WRITE_PAIR(" ", miscTime, "%f,\n");
|
||||
WRITE_PAIR(" ", parseTime, "%f,\n");
|
||||
WRITE_PAIR(" ", compileTime, "%f,\n");
|
||||
WRITE_PAIR(" ", codegenTime, "%f,\n");
|
||||
|
||||
WRITE_NAME(" ", lowerStats);
|
||||
serializeLoweringStats(fp, stats.lowerStats);
|
||||
|
||||
fprintf(fp, "\n }");
|
||||
}
|
||||
|
||||
#undef WRITE_NAME
|
||||
#undef WRITE_PAIR
|
||||
#undef WRITE_PAIR_STRING
|
||||
|
||||
static double recordDeltaTime(double& timer)
|
||||
{
|
||||
double now = Luau::TimeTrace::getClock();
|
||||
@ -347,8 +412,9 @@ static void displayHelp(const char* argv0)
|
||||
printf(" -g<n>: compile with debug level n (default 1, n should be between 0 and 2).\n");
|
||||
printf(" --target=<target>: compile code for specific architecture (a64, x64, a64_nf, x64_ms).\n");
|
||||
printf(" --timetrace: record compiler time tracing information into trace.json\n");
|
||||
printf(" --record-stats=<granularity>: granularity of compilation stats (total, file, function).\n");
|
||||
printf(" --bytecode-summary: Compute bytecode operation distribution.\n");
|
||||
printf(" --stats-file=<filename>: file in which compilation stats will be recored (default 'stats.json').\n");
|
||||
printf(" --record-stats=<granularity>: granularity of compilation stats recorded in stats.json (total, file, function).\n");
|
||||
printf(" --vector-lib=<name>: name of the library providing vector type operations.\n");
|
||||
printf(" --vector-ctor=<name>: name of the function constructing a vector value.\n");
|
||||
printf(" --vector-type=<name>: name of the vector type.\n");
|
||||
@ -394,6 +460,7 @@ int main(int argc, char** argv)
|
||||
Luau::CodeGen::AssemblyOptions::Target assemblyTarget = Luau::CodeGen::AssemblyOptions::Host;
|
||||
RecordStats recordStats = RecordStats::None;
|
||||
std::string statsFile("stats.json");
|
||||
bool bytecodeSummary = false;
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
@ -456,10 +523,14 @@ int main(int argc, char** argv)
|
||||
recordStats = RecordStats::Function;
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Error: unknown 'granularity' for '--record-stats'\n");
|
||||
fprintf(stderr, "Error: unknown 'granularity' for '--record-stats'.\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (strncmp(argv[i], "--bytecode-summary", 18) == 0)
|
||||
{
|
||||
bytecodeSummary = true;
|
||||
}
|
||||
else if (strncmp(argv[i], "--stats-file=", 13) == 0)
|
||||
{
|
||||
statsFile = argv[i] + 13;
|
||||
@ -498,6 +569,12 @@ int main(int argc, char** argv)
|
||||
}
|
||||
}
|
||||
|
||||
if (bytecodeSummary && (recordStats != RecordStats::Function))
|
||||
{
|
||||
fprintf(stderr, "'Error: Required '--record-stats=function' for '--bytecode-summary'.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if !defined(LUAU_ENABLE_TIME_TRACE)
|
||||
if (FFlag::DebugLuauTimeTracing)
|
||||
{
|
||||
@ -521,11 +598,12 @@ int main(int argc, char** argv)
|
||||
fileStats.reserve(fileCount);
|
||||
|
||||
int failed = 0;
|
||||
|
||||
unsigned functionStats = (recordStats == RecordStats::Function ? Luau::CodeGen::FunctionStats_Enable : 0) |
|
||||
(bytecodeSummary ? Luau::CodeGen::FunctionStats_BytecodeSummary : 0);
|
||||
for (const std::string& path : files)
|
||||
{
|
||||
CompileStats fileStat = {};
|
||||
fileStat.lowerStats.collectFunctionStats = (recordStats == RecordStats::Function);
|
||||
fileStat.lowerStats.functionStatsFlags = functionStats;
|
||||
failed += !compileFile(path.c_str(), compileFormat, assemblyTarget, fileStat);
|
||||
stats += fileStat;
|
||||
if (recordStats == RecordStats::File || recordStats == RecordStats::Function)
|
||||
@ -561,7 +639,7 @@ int main(int argc, char** argv)
|
||||
|
||||
if (recordStats == RecordStats::Total)
|
||||
{
|
||||
stats.serializeToJson(fp);
|
||||
serializeCompileStats(fp, stats);
|
||||
}
|
||||
else if (recordStats == RecordStats::File || recordStats == RecordStats::Function)
|
||||
{
|
||||
@ -569,8 +647,8 @@ int main(int argc, char** argv)
|
||||
for (size_t i = 0; i < fileCount; ++i)
|
||||
{
|
||||
std::string escaped(escapeFilename(files[i]));
|
||||
fprintf(fp, "\"%s\": ", escaped.c_str());
|
||||
fileStats[i].serializeToJson(fp);
|
||||
fprintf(fp, " \"%s\": ", escaped.c_str());
|
||||
serializeCompileStats(fp, fileStats[i]);
|
||||
fprintf(fp, i == (fileCount - 1) ? "\n" : ",\n");
|
||||
}
|
||||
fprintf(fp, "}");
|
||||
|
@ -180,6 +180,8 @@ public:
|
||||
|
||||
uint32_t getCodeSize() const;
|
||||
|
||||
unsigned getInstructionCount() const;
|
||||
|
||||
// Resulting data and code that need to be copied over one after the other
|
||||
// The *end* of 'data' has to be aligned to 16 bytes, this will also align 'code'
|
||||
std::vector<uint8_t> data;
|
||||
|
@ -187,6 +187,8 @@ public:
|
||||
|
||||
uint32_t getCodeSize() const;
|
||||
|
||||
unsigned getInstructionCount() const;
|
||||
|
||||
// Resulting data and code that need to be copied over one after the other
|
||||
// The *end* of 'data' has to be aligned to 16 bytes, this will also align 'code'
|
||||
std::vector<uint8_t> data;
|
||||
@ -266,6 +268,8 @@ private:
|
||||
|
||||
uint8_t* codePos = nullptr;
|
||||
uint8_t* codeEnd = nullptr;
|
||||
|
||||
unsigned instructionCount = 0;
|
||||
};
|
||||
|
||||
} // namespace X64
|
||||
|
@ -102,6 +102,14 @@ struct BlockLinearizationStats
|
||||
}
|
||||
};
|
||||
|
||||
enum FunctionStatsFlags
|
||||
{
|
||||
// Enable stats collection per function
|
||||
FunctionStats_Enable = 1 << 0,
|
||||
// Compute function bytecode summary
|
||||
FunctionStats_BytecodeSummary = 1 << 1,
|
||||
};
|
||||
|
||||
struct FunctionStats
|
||||
{
|
||||
std::string name;
|
||||
@ -109,6 +117,8 @@ struct FunctionStats
|
||||
unsigned bcodeCount = 0;
|
||||
unsigned irCount = 0;
|
||||
unsigned asmCount = 0;
|
||||
unsigned asmSize = 0;
|
||||
std::vector<std::vector<unsigned>> bytecodeSummary;
|
||||
};
|
||||
|
||||
struct LoweringStats
|
||||
@ -127,7 +137,7 @@ struct LoweringStats
|
||||
|
||||
BlockLinearizationStats blockLinearizationStats;
|
||||
|
||||
bool collectFunctionStats = false;
|
||||
unsigned functionStatsFlags = 0;
|
||||
std::vector<FunctionStats> functions;
|
||||
|
||||
LoweringStats operator+(const LoweringStats& other) const
|
||||
@ -150,7 +160,7 @@ struct LoweringStats
|
||||
this->regAllocErrors += that.regAllocErrors;
|
||||
this->loweringErrors += that.loweringErrors;
|
||||
this->blockLinearizationStats += that.blockLinearizationStats;
|
||||
if (this->collectFunctionStats)
|
||||
if (this->functionStatsFlags & FunctionStats_Enable)
|
||||
this->functions.insert(this->functions.end(), that.functions.begin(), that.functions.end());
|
||||
return *this;
|
||||
}
|
||||
|
@ -13,8 +13,6 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauKeepVmapLinear2)
|
||||
|
||||
struct Proto;
|
||||
|
||||
namespace Luau
|
||||
@ -89,6 +87,11 @@ enum class IrCmd : uint8_t
|
||||
// B: tag
|
||||
STORE_TAG,
|
||||
|
||||
// Store an integer into the extra field of the TValue
|
||||
// A: Rn
|
||||
// B: int
|
||||
STORE_EXTRA,
|
||||
|
||||
// Store a pointer (*) into TValue
|
||||
// A: Rn
|
||||
// B: pointer
|
||||
@ -964,7 +967,6 @@ struct IrFunction
|
||||
// For each instruction, an operand that can be used to recompute the value
|
||||
std::vector<IrOp> valueRestoreOps;
|
||||
std::vector<uint32_t> validRestoreOpBlocks;
|
||||
uint32_t validRestoreOpBlockIdx = 0;
|
||||
|
||||
Proto* proto = nullptr;
|
||||
bool variadic = false;
|
||||
@ -1108,37 +1110,21 @@ struct IrFunction
|
||||
if (instIdx >= valueRestoreOps.size())
|
||||
return {};
|
||||
|
||||
if (FFlag::LuauKeepVmapLinear2)
|
||||
// When spilled, values can only reference restore operands in the current block chain
|
||||
if (limitToCurrentBlock)
|
||||
{
|
||||
// When spilled, values can only reference restore operands in the current block chain
|
||||
if (limitToCurrentBlock)
|
||||
for (uint32_t blockIdx : validRestoreOpBlocks)
|
||||
{
|
||||
for (uint32_t blockIdx : validRestoreOpBlocks)
|
||||
{
|
||||
const IrBlock& block = blocks[blockIdx];
|
||||
const IrBlock& block = blocks[blockIdx];
|
||||
|
||||
if (instIdx >= block.start && instIdx <= block.finish)
|
||||
return valueRestoreOps[instIdx];
|
||||
}
|
||||
|
||||
return {};
|
||||
if (instIdx >= block.start && instIdx <= block.finish)
|
||||
return valueRestoreOps[instIdx];
|
||||
}
|
||||
|
||||
return valueRestoreOps[instIdx];
|
||||
return {};
|
||||
}
|
||||
else
|
||||
{
|
||||
const IrBlock& block = blocks[validRestoreOpBlockIdx];
|
||||
|
||||
// When spilled, values can only reference restore operands in the current block
|
||||
if (limitToCurrentBlock)
|
||||
{
|
||||
if (instIdx < block.start || instIdx > block.finish)
|
||||
return {};
|
||||
}
|
||||
|
||||
return valueRestoreOps[instIdx];
|
||||
}
|
||||
return valueRestoreOps[instIdx];
|
||||
}
|
||||
|
||||
IrOp findRestoreOp(const IrInst& inst, bool limitToCurrentBlock) const
|
||||
|
@ -23,6 +23,7 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i
|
||||
visitor.maybeUse(inst.a); // Argument can also be a VmConst
|
||||
break;
|
||||
case IrCmd::STORE_TAG:
|
||||
case IrCmd::STORE_EXTRA:
|
||||
case IrCmd::STORE_POINTER:
|
||||
case IrCmd::STORE_DOUBLE:
|
||||
case IrCmd::STORE_INT:
|
||||
|
@ -776,6 +776,11 @@ uint32_t AssemblyBuilderA64::getCodeSize() const
|
||||
return uint32_t(codePos - code.data());
|
||||
}
|
||||
|
||||
unsigned AssemblyBuilderA64::getInstructionCount() const
|
||||
{
|
||||
return unsigned(getCodeSize()) / 4;
|
||||
}
|
||||
|
||||
bool AssemblyBuilderA64::isMaskSupported(uint32_t mask)
|
||||
{
|
||||
int lz = countlz(mask);
|
||||
|
@ -6,8 +6,6 @@
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauCodeGenFixByteLower)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
@ -1052,6 +1050,11 @@ uint32_t AssemblyBuilderX64::getCodeSize() const
|
||||
return uint32_t(codePos - code.data());
|
||||
}
|
||||
|
||||
unsigned AssemblyBuilderX64::getInstructionCount() const
|
||||
{
|
||||
return instructionCount;
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::placeBinary(const char* name, OperandX64 lhs, OperandX64 rhs, uint8_t codeimm8, uint8_t codeimm, uint8_t codeimmImm8,
|
||||
uint8_t code8rev, uint8_t coderev, uint8_t code8, uint8_t code, uint8_t opreg)
|
||||
{
|
||||
@ -1439,18 +1442,8 @@ void AssemblyBuilderX64::placeImm8(int32_t imm)
|
||||
{
|
||||
int8_t imm8 = int8_t(imm);
|
||||
|
||||
if (FFlag::LuauCodeGenFixByteLower)
|
||||
{
|
||||
LUAU_ASSERT(imm8 == imm);
|
||||
place(imm8);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (imm8 == imm)
|
||||
place(imm8);
|
||||
else
|
||||
LUAU_ASSERT(!"Invalid immediate value");
|
||||
}
|
||||
LUAU_ASSERT(imm8 == imm);
|
||||
place(imm8);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::placeImm16(int16_t imm)
|
||||
@ -1503,6 +1496,8 @@ void AssemblyBuilderX64::commit()
|
||||
{
|
||||
LUAU_ASSERT(codePos <= codeEnd);
|
||||
|
||||
++instructionCount;
|
||||
|
||||
if (unsigned(codeEnd - codePos) < kMaxInstructionLength)
|
||||
extend();
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BytecodeSummary.h"
|
||||
#include "Luau/BytecodeUtils.h"
|
||||
#include "CodeGenLower.h"
|
||||
|
||||
#include "lua.h"
|
||||
@ -36,11 +37,12 @@ FunctionBytecodeSummary FunctionBytecodeSummary::fromProto(Proto* proto, unsigne
|
||||
|
||||
FunctionBytecodeSummary summary(source, name, line, nestingLimit);
|
||||
|
||||
for (int i = 0; i < proto->sizecode; ++i)
|
||||
for (int i = 0; i < proto->sizecode;)
|
||||
{
|
||||
Instruction insn = proto->code[i];
|
||||
uint8_t op = LUAU_INSN_OP(insn);
|
||||
summary.incCount(0, op);
|
||||
i += Luau::getOpLength(LuauOpcode(op));
|
||||
}
|
||||
|
||||
return summary;
|
||||
@ -61,7 +63,8 @@ std::vector<FunctionBytecodeSummary> summarizeBytecode(lua_State* L, int idx, un
|
||||
|
||||
for (Proto* proto : protos)
|
||||
{
|
||||
summaries.push_back(FunctionBytecodeSummary::fromProto(proto, nestingLimit));
|
||||
if (proto)
|
||||
summaries.push_back(FunctionBytecodeSummary::fromProto(proto, nestingLimit));
|
||||
}
|
||||
|
||||
return summaries;
|
||||
|
@ -1,6 +1,7 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/CodeGen.h"
|
||||
#include "Luau/BytecodeUtils.h"
|
||||
#include "Luau/BytecodeSummary.h"
|
||||
|
||||
#include "CodeGenLower.h"
|
||||
|
||||
@ -93,7 +94,8 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
|
||||
{
|
||||
IrBuilder ir;
|
||||
ir.buildFunctionIr(p);
|
||||
unsigned asmCount = build.getCodeSize();
|
||||
unsigned asmSize = build.getCodeSize();
|
||||
unsigned asmCount = build.getInstructionCount();
|
||||
|
||||
if (options.includeAssembly || options.includeIr)
|
||||
logFunctionHeader(build, p);
|
||||
@ -103,6 +105,7 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
|
||||
if (build.logText)
|
||||
build.logAppend("; skipping (can't lower)\n");
|
||||
|
||||
asmSize = 0;
|
||||
asmCount = 0;
|
||||
|
||||
if (stats)
|
||||
@ -110,16 +113,25 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
|
||||
}
|
||||
else
|
||||
{
|
||||
asmCount = build.getCodeSize() - asmCount;
|
||||
asmSize = build.getCodeSize() - asmSize;
|
||||
asmCount = build.getInstructionCount() - asmCount;
|
||||
}
|
||||
|
||||
if (stats && stats->collectFunctionStats)
|
||||
if (stats && (stats->functionStatsFlags & FunctionStats_Enable))
|
||||
{
|
||||
const char* name = p->debugname ? getstr(p->debugname) : "";
|
||||
int line = p->linedefined;
|
||||
unsigned bcodeCount = getInstructionCount(p->code, p->sizecode);
|
||||
unsigned irCount = unsigned(ir.function.instructions.size());
|
||||
stats->functions.push_back({name, line, bcodeCount, irCount, asmCount});
|
||||
FunctionStats functionStat;
|
||||
functionStat.name = p->debugname ? getstr(p->debugname) : "";
|
||||
functionStat.line = p->linedefined;
|
||||
functionStat.bcodeCount = getInstructionCount(p->code, p->sizecode);
|
||||
functionStat.irCount = unsigned(ir.function.instructions.size());
|
||||
functionStat.asmSize = asmSize;
|
||||
functionStat.asmCount = asmCount;
|
||||
if (stats->functionStatsFlags & FunctionStats_BytecodeSummary)
|
||||
{
|
||||
FunctionBytecodeSummary summary(FunctionBytecodeSummary::fromProto(p, 0));
|
||||
functionStat.bytecodeSummary.push_back(summary.getCounts(0));
|
||||
}
|
||||
stats->functions.push_back(std::move(functionStat));
|
||||
}
|
||||
|
||||
if (build.logText)
|
||||
|
@ -26,7 +26,6 @@ LUAU_FASTFLAG(DebugCodegenSkipNumbering)
|
||||
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
|
||||
LUAU_FASTINT(CodegenHeuristicsBlockLimit)
|
||||
LUAU_FASTINT(CodegenHeuristicsBlockInstructionLimit)
|
||||
LUAU_FASTFLAG(LuauKeepVmapLinear2)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -113,16 +112,8 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
||||
toStringDetailed(ctx, block, blockIndex, /* includeUseInfo */ true);
|
||||
}
|
||||
|
||||
if (FFlag::LuauKeepVmapLinear2)
|
||||
{
|
||||
// Values can only reference restore operands in the current block chain
|
||||
function.validRestoreOpBlocks.push_back(blockIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Values can only reference restore operands in the current block
|
||||
function.validRestoreOpBlockIdx = blockIndex;
|
||||
}
|
||||
// Values can only reference restore operands in the current block chain
|
||||
function.validRestoreOpBlocks.push_back(blockIndex);
|
||||
|
||||
build.setLabel(block.label);
|
||||
|
||||
@ -209,7 +200,7 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
||||
if (options.includeIr)
|
||||
build.logAppend("#\n");
|
||||
|
||||
if (FFlag::LuauKeepVmapLinear2 && block.expectedNextBlock == ~0u)
|
||||
if (block.expectedNextBlock == ~0u)
|
||||
function.validRestoreOpBlocks.clear();
|
||||
}
|
||||
|
||||
|
@ -112,6 +112,11 @@ inline OperandX64 luauRegTag(int ri)
|
||||
return dword[rBase + ri * sizeof(TValue) + offsetof(TValue, tt)];
|
||||
}
|
||||
|
||||
inline OperandX64 luauRegExtra(int ri)
|
||||
{
|
||||
return dword[rBase + ri * sizeof(TValue) + offsetof(TValue, extra)];
|
||||
}
|
||||
|
||||
inline OperandX64 luauRegValueInt(int ri)
|
||||
{
|
||||
return dword[rBase + ri * sizeof(TValue) + offsetof(TValue, value)];
|
||||
|
@ -400,8 +400,9 @@ void emitInstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRep
|
||||
build.cmp(dword[elemPtr + offsetof(TValue, tt)], LUA_TNIL);
|
||||
build.jcc(ConditionX64::Equal, skipArrayNil);
|
||||
|
||||
// setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(index + 1)));
|
||||
// setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(index + 1)), LU_TAG_ITERATOR);
|
||||
build.mov(luauRegValue(ra + 2), index);
|
||||
// Extra should already be set to LU_TAG_ITERATOR
|
||||
// Tag should already be set to lightuserdata
|
||||
|
||||
// setnvalue(ra + 3, double(index + 1));
|
||||
|
@ -13,8 +13,6 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenBytecodeInfer, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
@ -123,8 +121,7 @@ void IrBuilder::buildFunctionIr(Proto* proto)
|
||||
rebuildBytecodeBasicBlocks(proto);
|
||||
|
||||
// Infer register tags in bytecode
|
||||
if (FFlag::LuauCodegenBytecodeInfer)
|
||||
analyzeBytecodeTypes(function);
|
||||
analyzeBytecodeTypes(function);
|
||||
|
||||
function.bcMapping.resize(proto->sizecode, {~0u, ~0u});
|
||||
|
||||
@ -231,8 +228,7 @@ void IrBuilder::rebuildBytecodeBasicBlocks(Proto* proto)
|
||||
}
|
||||
}
|
||||
|
||||
if (FFlag::LuauCodegenBytecodeInfer)
|
||||
buildBytecodeBlocks(function, jumpTargets);
|
||||
buildBytecodeBlocks(function, jumpTargets);
|
||||
}
|
||||
|
||||
void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
|
||||
|
@ -103,6 +103,8 @@ const char* getCmdName(IrCmd cmd)
|
||||
return "GET_CLOSURE_UPVAL_ADDR";
|
||||
case IrCmd::STORE_TAG:
|
||||
return "STORE_TAG";
|
||||
case IrCmd::STORE_EXTRA:
|
||||
return "STORE_EXTRA";
|
||||
case IrCmd::STORE_POINTER:
|
||||
return "STORE_POINTER";
|
||||
case IrCmd::STORE_DOUBLE:
|
||||
|
@ -416,6 +416,21 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IrCmd::STORE_EXTRA:
|
||||
{
|
||||
AddressA64 addr = tempAddr(inst.a, offsetof(TValue, extra));
|
||||
if (intOp(inst.b) == 0)
|
||||
{
|
||||
build.str(wzr, addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
RegisterA64 temp = regs.allocTemp(KindA64::w);
|
||||
build.mov(temp, intOp(inst.b));
|
||||
build.str(temp, addr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IrCmd::STORE_DOUBLE:
|
||||
{
|
||||
AddressA64 addr = tempAddr(inst.a, offsetof(TValue, value));
|
||||
|
@ -15,8 +15,6 @@
|
||||
#include "lstate.h"
|
||||
#include "lgc.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauCodeGenFixByteLower)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
@ -212,7 +210,9 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
|
||||
build.mov(luauRegTag(vmRegOp(inst.a)), tagOp(inst.b));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"Unsupported instruction form");
|
||||
}
|
||||
break;
|
||||
case IrCmd::STORE_POINTER:
|
||||
{
|
||||
@ -233,6 +233,19 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IrCmd::STORE_EXTRA:
|
||||
if (inst.b.kind == IrOpKind::Constant)
|
||||
{
|
||||
if (inst.a.kind == IrOpKind::Inst)
|
||||
build.mov(dword[regOp(inst.a) + offsetof(TValue, extra)], intOp(inst.b));
|
||||
else
|
||||
build.mov(luauRegExtra(vmRegOp(inst.a)), intOp(inst.b));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"Unsupported instruction form");
|
||||
}
|
||||
break;
|
||||
case IrCmd::STORE_DOUBLE:
|
||||
{
|
||||
OperandX64 valueLhs = inst.a.kind == IrOpKind::Inst ? qword[regOp(inst.a) + offsetof(TValue, value)] : luauRegValue(vmRegOp(inst.a));
|
||||
@ -1803,18 +1816,9 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
|
||||
|
||||
case IrCmd::BUFFER_WRITEI8:
|
||||
{
|
||||
if (FFlag::LuauCodeGenFixByteLower)
|
||||
{
|
||||
OperandX64 value = inst.c.kind == IrOpKind::Inst ? byteReg(regOp(inst.c)) : OperandX64(int8_t(intOp(inst.c)));
|
||||
OperandX64 value = inst.c.kind == IrOpKind::Inst ? byteReg(regOp(inst.c)) : OperandX64(int8_t(intOp(inst.c)));
|
||||
|
||||
build.mov(byte[bufferAddrOp(inst.a, inst.b)], value);
|
||||
}
|
||||
else
|
||||
{
|
||||
OperandX64 value = inst.c.kind == IrOpKind::Inst ? byteReg(regOp(inst.c)) : OperandX64(intOp(inst.c));
|
||||
|
||||
build.mov(byte[bufferAddrOp(inst.a, inst.b)], value);
|
||||
}
|
||||
build.mov(byte[bufferAddrOp(inst.a, inst.b)], value);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1832,18 +1836,9 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
|
||||
|
||||
case IrCmd::BUFFER_WRITEI16:
|
||||
{
|
||||
if (FFlag::LuauCodeGenFixByteLower)
|
||||
{
|
||||
OperandX64 value = inst.c.kind == IrOpKind::Inst ? wordReg(regOp(inst.c)) : OperandX64(int16_t(intOp(inst.c)));
|
||||
OperandX64 value = inst.c.kind == IrOpKind::Inst ? wordReg(regOp(inst.c)) : OperandX64(int16_t(intOp(inst.c)));
|
||||
|
||||
build.mov(word[bufferAddrOp(inst.a, inst.b)], value);
|
||||
}
|
||||
else
|
||||
{
|
||||
OperandX64 value = inst.c.kind == IrOpKind::Inst ? wordReg(regOp(inst.c)) : OperandX64(intOp(inst.c));
|
||||
|
||||
build.mov(word[bufferAddrOp(inst.a, inst.b)], value);
|
||||
}
|
||||
build.mov(word[bufferAddrOp(inst.a, inst.b)], value);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,6 @@
|
||||
|
||||
#include <math.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBufferTranslateIr, false)
|
||||
|
||||
// TODO: when nresults is less than our actual result count, we can skip computing/writing unused results
|
||||
|
||||
static const int kMinMaxUnrolledParams = 5;
|
||||
@ -749,9 +747,6 @@ static void translateBufferArgsAndCheckBounds(IrBuilder& build, int nparams, int
|
||||
static BuiltinImplResult translateBuiltinBufferRead(
|
||||
IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos, IrCmd readCmd, int size, IrCmd convCmd)
|
||||
{
|
||||
if (!FFlag::LuauBufferTranslateIr)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
if (nparams < 2 || nresults > 1)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
@ -768,9 +763,6 @@ static BuiltinImplResult translateBuiltinBufferRead(
|
||||
static BuiltinImplResult translateBuiltinBufferWrite(
|
||||
IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, int pcpos, IrCmd writeCmd, int size, IrCmd convCmd)
|
||||
{
|
||||
if (!FFlag::LuauBufferTranslateIr)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
if (nparams < 3 || nresults > 0)
|
||||
return {BuiltinImplType::None, -1};
|
||||
|
||||
|
@ -12,8 +12,7 @@
|
||||
#include "lstate.h"
|
||||
#include "ltm.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFullLoopLuserdata, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLoopInterruptFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenLuData, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -754,23 +753,11 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
LUAU_ASSERT(!build.numericLoopStack.empty());
|
||||
IrBuilder::LoopInfo loopInfo = build.numericLoopStack.back();
|
||||
|
||||
if (FFlag::LuauLoopInterruptFix)
|
||||
{
|
||||
// normally, the interrupt is placed at the beginning of the loop body by FORNPREP translation
|
||||
// however, there are rare cases where FORNLOOP might not jump directly to the first loop instruction
|
||||
// we detect this by checking the starting instruction of the loop body from loop information stack
|
||||
if (repeatJumpTarget != loopInfo.startpc)
|
||||
build.inst(IrCmd::INTERRUPT, build.constUint(pcpos));
|
||||
}
|
||||
else
|
||||
{
|
||||
// normally, the interrupt is placed at the beginning of the loop body by FORNPREP translation
|
||||
// however, there are rare contrived cases where FORNLOOP ends up jumping to itself without an interrupt placed
|
||||
// we detect this by checking if loopRepeat has any instructions (it should normally start with INTERRUPT) and emit a failsafe INTERRUPT if
|
||||
// not
|
||||
if (build.function.blockOp(loopRepeat).start == build.function.instructions.size())
|
||||
build.inst(IrCmd::INTERRUPT, build.constUint(pcpos));
|
||||
}
|
||||
// normally, the interrupt is placed at the beginning of the loop body by FORNPREP translation
|
||||
// however, there are rare cases where FORNLOOP might not jump directly to the first loop instruction
|
||||
// we detect this by checking the starting instruction of the loop body from loop information stack
|
||||
if (repeatJumpTarget != loopInfo.startpc)
|
||||
build.inst(IrCmd::INTERRUPT, build.constUint(pcpos));
|
||||
|
||||
IrOp stepK = loopInfo.step;
|
||||
|
||||
@ -817,8 +804,12 @@ void translateInstForGPrepNext(IrBuilder& build, const Instruction* pc, int pcpo
|
||||
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNIL));
|
||||
|
||||
// setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0)));
|
||||
build.inst(FFlag::LuauFullLoopLuserdata ? IrCmd::STORE_POINTER : IrCmd::STORE_INT, build.vmReg(ra + 2), build.constInt(0));
|
||||
// setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0)), LU_TAG_ITERATOR);
|
||||
build.inst(IrCmd::STORE_POINTER, build.vmReg(ra + 2), build.constInt(0));
|
||||
|
||||
if (FFlag::LuauCodegenLuData)
|
||||
build.inst(IrCmd::STORE_EXTRA, build.vmReg(ra + 2), build.constInt(LU_TAG_ITERATOR));
|
||||
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra + 2), build.constTag(LUA_TLIGHTUSERDATA));
|
||||
|
||||
build.inst(IrCmd::JUMP, target);
|
||||
@ -849,8 +840,12 @@ void translateInstForGPrepInext(IrBuilder& build, const Instruction* pc, int pcp
|
||||
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNIL));
|
||||
|
||||
// setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0)));
|
||||
build.inst(FFlag::LuauFullLoopLuserdata ? IrCmd::STORE_POINTER : IrCmd::STORE_INT, build.vmReg(ra + 2), build.constInt(0));
|
||||
// setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0)), LU_TAG_ITERATOR);
|
||||
build.inst(IrCmd::STORE_POINTER, build.vmReg(ra + 2), build.constInt(0));
|
||||
|
||||
if (FFlag::LuauCodegenLuData)
|
||||
build.inst(IrCmd::STORE_EXTRA, build.vmReg(ra + 2), build.constInt(LU_TAG_ITERATOR));
|
||||
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra + 2), build.constTag(LUA_TLIGHTUSERDATA));
|
||||
|
||||
build.inst(IrCmd::JUMP, target);
|
||||
|
@ -40,6 +40,7 @@ IrValueKind getCmdValueKind(IrCmd cmd)
|
||||
case IrCmd::GET_CLOSURE_UPVAL_ADDR:
|
||||
return IrValueKind::Pointer;
|
||||
case IrCmd::STORE_TAG:
|
||||
case IrCmd::STORE_EXTRA:
|
||||
case IrCmd::STORE_POINTER:
|
||||
case IrCmd::STORE_DOUBLE:
|
||||
case IrCmd::STORE_INT:
|
||||
|
@ -28,6 +28,10 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst)
|
||||
// Tag update is a bit tricky, restore operations of values are not affected
|
||||
invalidateRestoreOp(inst.a, /*skipValueInvalidation*/ true);
|
||||
break;
|
||||
case IrCmd::STORE_EXTRA:
|
||||
// While extra field update doesn't invalidate some of the values, it can invalidate a vector type field
|
||||
invalidateRestoreOp(inst.a, /*skipValueInvalidation*/ false);
|
||||
break;
|
||||
case IrCmd::STORE_POINTER:
|
||||
case IrCmd::STORE_DOUBLE:
|
||||
case IrCmd::STORE_INT:
|
||||
|
@ -17,8 +17,6 @@
|
||||
LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3)
|
||||
LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodeGenFixByteLower, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKeepVmapLinear2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReuseBufferChecks, false)
|
||||
|
||||
namespace Luau
|
||||
@ -627,6 +625,8 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
state.valueMap[state.versionedVmRegLoad(activeLoadCmd, source)] = activeLoadValue;
|
||||
}
|
||||
break;
|
||||
case IrCmd::STORE_EXTRA:
|
||||
break;
|
||||
case IrCmd::STORE_POINTER:
|
||||
if (inst.a.kind == IrOpKind::VmReg)
|
||||
{
|
||||
@ -1374,16 +1374,6 @@ static void constPropInBlock(IrBuilder& build, IrBlock& block, ConstPropState& s
|
||||
|
||||
constPropInInst(state, build, function, block, inst, index);
|
||||
}
|
||||
|
||||
if (!FFlag::LuauKeepVmapLinear2)
|
||||
{
|
||||
// Value numbering and load/store propagation is not performed between blocks
|
||||
state.invalidateValuePropagation();
|
||||
|
||||
// Same for table and buffer data propagation
|
||||
state.invalidateHeapTableData();
|
||||
state.invalidateHeapBufferData();
|
||||
}
|
||||
}
|
||||
|
||||
static void constPropInBlockChain(IrBuilder& build, std::vector<uint8_t>& visited, IrBlock* block, ConstPropState& state)
|
||||
@ -1403,15 +1393,12 @@ static void constPropInBlockChain(IrBuilder& build, std::vector<uint8_t>& visite
|
||||
|
||||
constPropInBlock(build, *block, state);
|
||||
|
||||
if (FFlag::LuauKeepVmapLinear2)
|
||||
{
|
||||
// Value numbering and load/store propagation is not performed between blocks
|
||||
state.invalidateValuePropagation();
|
||||
// Value numbering and load/store propagation is not performed between blocks
|
||||
state.invalidateValuePropagation();
|
||||
|
||||
// Same for table and buffer data propagation
|
||||
state.invalidateHeapTableData();
|
||||
state.invalidateHeapBufferData();
|
||||
}
|
||||
// Same for table and buffer data propagation
|
||||
state.invalidateHeapTableData();
|
||||
state.invalidateHeapBufferData();
|
||||
|
||||
// Blocks in a chain are guaranteed to follow each other
|
||||
// We force that by giving all blocks the same sorting key, but consecutive chain keys
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <stddef.h>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
442
Common/include/Luau/VecDeque.h
Normal file
442
Common/include/Luau/VecDeque.h
Normal file
@ -0,0 +1,442 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Common.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
// `VecDeque` is a general double-ended implementation designed as a drop-in replacement for the
|
||||
// standard library `std::deque`. It's backed by a growable ring buffer, rather than using the
|
||||
// segmented queue design of `std::deque` which can degrade into a linked list in the worst case.
|
||||
// The motivation for `VecDeque` as a replacement is to maintain the asymptotic complexity of
|
||||
// `std::deque` while reducing overall allocations and promoting better usage of the cache. Its API
|
||||
// is intended to be compatible with `std::deque` and `std::vector` as appropriate, and as such
|
||||
// provides corresponding method definitions and supports the use of custom allocators.
|
||||
//
|
||||
// `VecDeque` offers pushing and popping from both ends with an amortized O(1) complexity. It also
|
||||
// supports `std::vector`-style random-access in O(1). The implementation of buffer resizing uses
|
||||
// a growth factor of 1.5x to enable better memory reuse after resizing, and reduce overall memory
|
||||
// fragmentation when using the queue.
|
||||
//
|
||||
// Since `VecDeque` is a ring buffer, its elements are not necessarily contiguous in memory. To
|
||||
// describe this, we refer to the two portions of the buffer as the `head` and the `tail`. The
|
||||
// `head` is the initial portion of the queue that is on the range `[head, capacity)` and the tail
|
||||
// is the (optionally) remaining portion on the range `[0, head + size - capacity)` whenever the
|
||||
// `head + size` exceeds the capacity of the buffer.
|
||||
//
|
||||
// `VecDeque` does not currently support iteration since its primary focus is on providing
|
||||
// double-ended queue functionality specifically, but it can be reasonably expanded to provide
|
||||
// an iterator if we have a use-case for one in the future.
|
||||
template<typename T, class Allocator = std::allocator<T>>
|
||||
class VecDeque : Allocator
|
||||
{
|
||||
private:
|
||||
static_assert(std::is_nothrow_move_constructible_v<T>);
|
||||
static_assert(std::is_nothrow_move_assignable_v<T>);
|
||||
|
||||
T* buffer = nullptr; // the existing allocation we have backing this queue
|
||||
size_t buffer_capacity = 0; // the size of our allocation
|
||||
|
||||
size_t head = 0; // the index of the head of the queue
|
||||
size_t queue_size = 0; // the size of the queue
|
||||
|
||||
void destroyElements() noexcept
|
||||
{
|
||||
size_t head_size =
|
||||
std::min(queue_size, capacity() - head); // how many elements are in the head portion (i.e. from the head to the end of the buffer)
|
||||
size_t tail_size = queue_size - head_size; // how many elements are in the tail portion (i.e. any portion that wrapped to the front)
|
||||
|
||||
// we have to destroy every element in the head portion
|
||||
for (size_t index = head; index < head + head_size; index++)
|
||||
buffer[index].~T();
|
||||
|
||||
// and any in the tail portion, if one exists
|
||||
for (size_t index = 0; index < tail_size; index++)
|
||||
buffer[index].~T();
|
||||
}
|
||||
|
||||
bool is_full()
|
||||
{
|
||||
return queue_size == capacity();
|
||||
}
|
||||
|
||||
void grow()
|
||||
{
|
||||
size_t old_capacity = capacity();
|
||||
|
||||
// we use a growth factor of 1.5x (plus a constant) here in order to enable the
|
||||
// previous memory to be reused after a certain number of calls to grow.
|
||||
// see: https://github.com/facebook/folly/blob/main/folly/docs/FBVector.md#memory-handling
|
||||
size_t new_capacity = (old_capacity > 0) ? old_capacity * 3 / 2 + 1 : 4;
|
||||
|
||||
// check that it's a legal allocation
|
||||
if (new_capacity > max_size())
|
||||
throw std::bad_array_new_length();
|
||||
|
||||
// allocate a new backing buffer
|
||||
T* new_buffer = this->allocate(new_capacity);
|
||||
|
||||
// we should not be growing if the capacity is not the current size
|
||||
LUAU_ASSERT(old_capacity == queue_size);
|
||||
|
||||
size_t head_size =
|
||||
std::min(queue_size, old_capacity - head); // how many elements are in the head portion (i.e. from the head to the end of the buffer)
|
||||
size_t tail_size = queue_size - head_size; // how many elements are in the tail portion (i.e. any portion that wrapped to the front)
|
||||
|
||||
// move the head into the new buffer
|
||||
std::uninitialized_move(buffer + head, buffer + head + head_size, new_buffer);
|
||||
|
||||
// move the tail into the new buffer immediately after
|
||||
if (head_size < queue_size)
|
||||
std::uninitialized_move(buffer, buffer + tail_size, new_buffer + head_size);
|
||||
|
||||
// destroy the old elements
|
||||
destroyElements();
|
||||
// deallocate the old buffer
|
||||
this->deallocate(buffer, old_capacity);
|
||||
|
||||
// set up the queue to be backed by the new buffer
|
||||
buffer = new_buffer;
|
||||
buffer_capacity = new_capacity;
|
||||
head = 0;
|
||||
}
|
||||
|
||||
size_t logicalToPhysical(size_t pos)
|
||||
{
|
||||
return (head + pos) % capacity();
|
||||
}
|
||||
|
||||
public:
|
||||
VecDeque() = default;
|
||||
|
||||
explicit VecDeque(const Allocator& alloc) noexcept
|
||||
: Allocator{alloc}
|
||||
{
|
||||
}
|
||||
|
||||
VecDeque(const VecDeque& other)
|
||||
: buffer(this->allocate(other.buffer_capacity))
|
||||
, buffer_capacity(other.buffer_capacity)
|
||||
, head(other.head)
|
||||
, queue_size(other.queue_size)
|
||||
{
|
||||
// copy the contents of the other buffer to this one
|
||||
std::uninitialized_copy(other.buffer, other.buffer + other.buffer_capacity, buffer);
|
||||
}
|
||||
|
||||
VecDeque(const VecDeque& other, const Allocator& alloc)
|
||||
: Allocator{alloc}
|
||||
, buffer(this->allocate(other.buffer_capacity))
|
||||
, buffer_capacity(other.buffer_capacity)
|
||||
, head(other.head)
|
||||
, queue_size(other.queue_size)
|
||||
{
|
||||
// copy the contents of the other buffer to this one
|
||||
std::uninitialized_copy(other.buffer, other.buffer + other.buffer_capacity, buffer);
|
||||
}
|
||||
|
||||
VecDeque(VecDeque&& other) noexcept
|
||||
: buffer(std::exchange(other.buffer, nullptr))
|
||||
, buffer_capacity(std::exchange(other.buffer_capacity, 0))
|
||||
, head(std::exchange(other.head, 0))
|
||||
, queue_size(std::exchange(other.queue_size, 0))
|
||||
{
|
||||
}
|
||||
|
||||
VecDeque(VecDeque&& other, const Allocator& alloc) noexcept
|
||||
: Allocator{alloc}
|
||||
, buffer(std::exchange(other.buffer, nullptr))
|
||||
, buffer_capacity(std::exchange(other.buffer_capacity, 0))
|
||||
, head(std::exchange(other.head, 0))
|
||||
, queue_size(std::exchange(other.queue_size, 0))
|
||||
{
|
||||
}
|
||||
|
||||
VecDeque(std::initializer_list<T> init, const Allocator& alloc = Allocator())
|
||||
: Allocator{alloc}
|
||||
{
|
||||
buffer = this->allocate(init.size());
|
||||
buffer_capacity = init.size();
|
||||
queue_size = init.size();
|
||||
|
||||
std::uninitialized_copy(init.begin(), init.end(), buffer);
|
||||
}
|
||||
|
||||
~VecDeque() noexcept
|
||||
{
|
||||
// destroy any elements that exist
|
||||
destroyElements();
|
||||
// free the allocated buffer
|
||||
this->deallocate(buffer, buffer_capacity);
|
||||
}
|
||||
|
||||
VecDeque& operator=(const VecDeque& other)
|
||||
{
|
||||
if (this == &other)
|
||||
return *this;
|
||||
|
||||
// destroy all of the existing elements
|
||||
destroyElements();
|
||||
|
||||
if (buffer_capacity < other.size())
|
||||
{
|
||||
// free the current buffer
|
||||
this->deallocate(buffer, buffer_capacity);
|
||||
|
||||
buffer = this->allocate(other.buffer_capacity);
|
||||
buffer_capacity = other.buffer_capacity;
|
||||
}
|
||||
|
||||
size_t head_size = other.capacity() - other.head; // how many elements are in the head portion (i.e. from the head to the end of the buffer)
|
||||
size_t tail_size = other.size() - head_size; // how many elements are in the tail portion (i.e. any portion that wrapped to the front)
|
||||
|
||||
// copy the contents of the other buffer's head into place
|
||||
std::uninitialized_copy(other.buffer + other.head, other.buffer + head + head_size, buffer);
|
||||
|
||||
// copy the contents of the other buffer's tail into place immediately after
|
||||
std::uninitialized_copy(other.buffer, other.buffer + tail_size, buffer + head_size);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
VecDeque& operator=(VecDeque&& other)
|
||||
{
|
||||
if (this == &other)
|
||||
return *this;
|
||||
|
||||
// destroy all of the existing elements
|
||||
destroyElements();
|
||||
// free the current buffer
|
||||
this->deallocate(buffer, buffer_capacity);
|
||||
|
||||
buffer = std::exchange(other.buffer, nullptr);
|
||||
buffer_capacity = std::exchange(other.buffer_capacity, 0);
|
||||
head = std::exchange(other.head, 0);
|
||||
queue_size = std::exchange(other.queue_size, 0);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Allocator get_allocator() const noexcept
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
// element access
|
||||
|
||||
T& at(size_t pos)
|
||||
{
|
||||
if (pos >= queue_size)
|
||||
throw std::out_of_range("VecDeque");
|
||||
|
||||
return buffer[logicalToPhysical(pos)];
|
||||
}
|
||||
|
||||
const T& at(size_t pos) const
|
||||
{
|
||||
if (pos >= queue_size)
|
||||
throw std::out_of_range("VecDeque");
|
||||
|
||||
return buffer[logicalToPhysical(pos)];
|
||||
}
|
||||
|
||||
[[nodiscard]] T& operator[](size_t pos) noexcept
|
||||
{
|
||||
LUAU_ASSERT(pos < queue_size);
|
||||
|
||||
return buffer[logicalToPhysical(pos)];
|
||||
}
|
||||
|
||||
[[nodiscard]] const T& operator[](size_t pos) const noexcept
|
||||
{
|
||||
LUAU_ASSERT(pos < queue_size);
|
||||
|
||||
return buffer[logicalToPhysical(pos)];
|
||||
}
|
||||
|
||||
T& front() {
|
||||
LUAU_ASSERT(!empty());
|
||||
|
||||
return buffer[head];
|
||||
}
|
||||
|
||||
const T& front() const {
|
||||
LUAU_ASSERT(!empty());
|
||||
|
||||
return buffer[head];
|
||||
}
|
||||
|
||||
T& back() {
|
||||
LUAU_ASSERT(!empty());
|
||||
|
||||
size_t back = logicalToPhysical(queue_size - 1);
|
||||
return buffer[back];
|
||||
}
|
||||
|
||||
const T& back() const {
|
||||
LUAU_ASSERT(!empty());
|
||||
|
||||
size_t back = logicalToPhysical(queue_size - 1);
|
||||
return buffer[back];
|
||||
}
|
||||
|
||||
// capacity
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return queue_size == 0;
|
||||
}
|
||||
|
||||
size_t size() const noexcept
|
||||
{
|
||||
return queue_size;
|
||||
}
|
||||
|
||||
size_t max_size() const noexcept
|
||||
{
|
||||
return std::numeric_limits<size_t>::max() / sizeof(T);
|
||||
}
|
||||
|
||||
void reserve(size_t new_capacity)
|
||||
{
|
||||
// error if this allocation would be illegal
|
||||
if (new_capacity > max_size())
|
||||
throw std::length_error("too large");
|
||||
|
||||
size_t old_capacity = capacity();
|
||||
|
||||
// do nothing if we're requesting a capacity that would not cause growth
|
||||
if (new_capacity <= old_capacity)
|
||||
return;
|
||||
|
||||
size_t head_size =
|
||||
std::min(queue_size, old_capacity - head); // how many elements are in the head portion (i.e. from the head to the end of the buffer)
|
||||
size_t tail_size = queue_size - head_size; // how many elements are in the tail portion (i.e. any portion that wrapped to the front)
|
||||
|
||||
// allocate a new backing buffer
|
||||
T* new_buffer = this->allocate(new_capacity);
|
||||
|
||||
// move the head into the new buffer
|
||||
std::uninitialized_move(buffer + head, buffer + head + head_size, new_buffer);
|
||||
|
||||
// move the tail into the new buffer immediately after, if we have one
|
||||
if (head_size < queue_size)
|
||||
std::uninitialized_move(buffer, buffer + tail_size, new_buffer + head_size);
|
||||
|
||||
// move the tail into the new buffer immediately after
|
||||
std::uninitialized_move(buffer, buffer + tail_size, new_buffer + head_size);
|
||||
|
||||
// destroy all the existing elements before freeing the old buffer
|
||||
destroyElements();
|
||||
// deallocate the old buffer
|
||||
this->deallocate(buffer, old_capacity);
|
||||
|
||||
// set up the queue to be backed by the new buffer
|
||||
buffer = new_buffer;
|
||||
buffer_capacity = new_capacity;
|
||||
head = 0;
|
||||
}
|
||||
|
||||
size_t capacity() const noexcept
|
||||
{
|
||||
return buffer_capacity;
|
||||
}
|
||||
|
||||
void shrink_to_fit() {
|
||||
size_t old_capacity = capacity();
|
||||
size_t new_capacity = queue_size;
|
||||
|
||||
if (old_capacity == new_capacity)
|
||||
return;
|
||||
|
||||
size_t head_size =
|
||||
std::min(queue_size, old_capacity - head); // how many elements are in the head portion (i.e. from the head to the end of the buffer)
|
||||
size_t tail_size = queue_size - head_size; // how many elements are in the tail portion (i.e. any portion that wrapped to the front)
|
||||
|
||||
// allocate a new backing buffer
|
||||
T* new_buffer = this->allocate(new_capacity);
|
||||
|
||||
// move the head into the new buffer
|
||||
std::uninitialized_move(buffer + head, buffer + head + head_size, new_buffer);
|
||||
|
||||
// move the tail into the new buffer immediately after, if we have one
|
||||
if (head_size < queue_size)
|
||||
std::uninitialized_move(buffer, buffer + tail_size, new_buffer + head_size);
|
||||
|
||||
// destroy all the existing elements before freeing the old buffer
|
||||
destroyElements();
|
||||
// deallocate the old buffer
|
||||
this->deallocate(buffer, old_capacity);
|
||||
|
||||
// set up the queue to be backed by the new buffer
|
||||
buffer = new_buffer;
|
||||
buffer_capacity = new_capacity;
|
||||
head = 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_contiguous() const noexcept
|
||||
{
|
||||
// this is an overflow-safe alternative to writing `head + size <= capacity`.
|
||||
return head <= capacity() - queue_size;
|
||||
}
|
||||
|
||||
// modifiers
|
||||
|
||||
void clear() noexcept
|
||||
{
|
||||
destroyElements();
|
||||
|
||||
head = 0;
|
||||
queue_size = 0;
|
||||
}
|
||||
|
||||
void push_back(const T& value)
|
||||
{
|
||||
if (is_full())
|
||||
grow();
|
||||
|
||||
size_t next_back = logicalToPhysical(queue_size);
|
||||
new (buffer + next_back)T(value);
|
||||
queue_size++;
|
||||
}
|
||||
|
||||
void pop_back()
|
||||
{
|
||||
LUAU_ASSERT(!empty());
|
||||
|
||||
queue_size--;
|
||||
size_t next_back = logicalToPhysical(queue_size);
|
||||
buffer[next_back].~T();
|
||||
}
|
||||
|
||||
void push_front(const T& value)
|
||||
{
|
||||
if (is_full())
|
||||
grow();
|
||||
|
||||
head = (head == 0) ? capacity() - 1 : head - 1;
|
||||
new (buffer + head)T(value);
|
||||
queue_size++;
|
||||
}
|
||||
|
||||
void pop_front()
|
||||
{
|
||||
LUAU_ASSERT(!empty());
|
||||
|
||||
buffer[head].~T();
|
||||
head++;
|
||||
queue_size--;
|
||||
|
||||
if (head == capacity())
|
||||
head = 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
@ -4,10 +4,6 @@
|
||||
#include "Luau/Bytecode.h"
|
||||
#include "Luau/Compiler.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBit32ByteswapBuiltin, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBufferBuiltins, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace Compile
|
||||
@ -170,7 +166,7 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op
|
||||
return LBF_BIT32_COUNTLZ;
|
||||
if (builtin.method == "countrz")
|
||||
return LBF_BIT32_COUNTRZ;
|
||||
if (FFlag::LuauBit32ByteswapBuiltin && builtin.method == "byteswap")
|
||||
if (builtin.method == "byteswap")
|
||||
return LBF_BIT32_BYTESWAP;
|
||||
}
|
||||
|
||||
@ -194,7 +190,7 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op
|
||||
return LBF_TABLE_UNPACK;
|
||||
}
|
||||
|
||||
if (FFlag::LuauBufferBuiltins && builtin.object == "buffer")
|
||||
if (builtin.object == "buffer")
|
||||
{
|
||||
if (builtin.method == "readi8")
|
||||
return LBF_BUFFER_READI8;
|
||||
|
@ -28,6 +28,7 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileRevK, false)
|
||||
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -995,7 +996,11 @@ struct Compiler
|
||||
bytecode.emitAD(LOP_NEWCLOSURE, target, pid);
|
||||
|
||||
for (const Capture& c : captures)
|
||||
{
|
||||
|
||||
bytecode.emitABC(LOP_CAPTURE, uint8_t(c.type), c.data, 0);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
LuauOpcode getUnaryOp(AstExprUnary::Op op)
|
||||
|
@ -3,8 +3,6 @@
|
||||
|
||||
#include "Luau/BytecodeBuilder.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileBufferAnnotation, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -29,7 +27,7 @@ static LuauBytecodeType getPrimitiveType(AstName name)
|
||||
return LBC_TYPE_STRING;
|
||||
else if (name == "thread")
|
||||
return LBC_TYPE_THREAD;
|
||||
else if (FFlag::LuauCompileBufferAnnotation && name == "buffer")
|
||||
else if (name == "buffer")
|
||||
return LBC_TYPE_BUFFER;
|
||||
else if (name == "any" || name == "unknown")
|
||||
return LBC_TYPE_ANY;
|
||||
|
@ -7,6 +7,7 @@ if(NOT ${CMAKE_VERSION} VERSION_LESS "3.19")
|
||||
Common/include/Luau/BytecodeUtils.h
|
||||
Common/include/Luau/DenseHash.h
|
||||
Common/include/Luau/ExperimentalFlags.h
|
||||
Common/include/Luau/VecDeque.h
|
||||
)
|
||||
endif()
|
||||
|
||||
@ -467,6 +468,7 @@ if(TARGET Luau.UnitTest)
|
||||
tests/TypeVar.test.cpp
|
||||
tests/Unifier2.test.cpp
|
||||
tests/Variant.test.cpp
|
||||
tests/VecDeque.test.cpp
|
||||
tests/VisitType.test.cpp
|
||||
tests/main.cpp)
|
||||
endif()
|
||||
|
@ -5,8 +5,6 @@
|
||||
#include "lcommon.h"
|
||||
#include "lnumutils.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBit32Byteswap, false)
|
||||
|
||||
#define ALLONES ~0u
|
||||
#define NBITS int(8 * sizeof(unsigned))
|
||||
|
||||
@ -214,9 +212,6 @@ static int b_countrz(lua_State* L)
|
||||
|
||||
static int b_swap(lua_State* L)
|
||||
{
|
||||
if (!FFlag::LuauBit32Byteswap)
|
||||
luaL_error(L, "bit32.byteswap isn't enabled");
|
||||
|
||||
b_uint n = luaL_checkunsigned(L, 1);
|
||||
n = (n << 24) | ((n << 8) & 0xff0000) | ((n >> 8) & 0xff00) | (n >> 24);
|
||||
|
||||
|
@ -10,8 +10,6 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBufferBetterMsg, false)
|
||||
|
||||
// while C API returns 'size_t' for binary compatibility in case of future extensions,
|
||||
// in the current implementation, length and offset are limited to 31 bits
|
||||
// because offset is limited to an integer, a single 64bit comparison can be used and will not overflow
|
||||
@ -38,15 +36,7 @@ static int buffer_create(lua_State* L)
|
||||
{
|
||||
int size = luaL_checkinteger(L, 1);
|
||||
|
||||
if (FFlag::LuauBufferBetterMsg)
|
||||
{
|
||||
luaL_argcheck(L, size >= 0, 1, "size");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (size < 0)
|
||||
luaL_error(L, "invalid size");
|
||||
}
|
||||
luaL_argcheck(L, size >= 0, 1, "size");
|
||||
|
||||
lua_newbuffer(L, size);
|
||||
return 1;
|
||||
@ -174,15 +164,7 @@ static int buffer_readstring(lua_State* L)
|
||||
int offset = luaL_checkinteger(L, 2);
|
||||
int size = luaL_checkinteger(L, 3);
|
||||
|
||||
if (FFlag::LuauBufferBetterMsg)
|
||||
{
|
||||
luaL_argcheck(L, size >= 0, 3, "size");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (size < 0)
|
||||
luaL_error(L, "invalid size");
|
||||
}
|
||||
luaL_argcheck(L, size >= 0, 3, "size");
|
||||
|
||||
if (isoutofbounds(offset, len, unsigned(size)))
|
||||
luaL_error(L, "buffer access out of bounds");
|
||||
@ -200,15 +182,7 @@ static int buffer_writestring(lua_State* L)
|
||||
const char* val = luaL_checklstring(L, 3, &size);
|
||||
int count = luaL_optinteger(L, 4, int(size));
|
||||
|
||||
if (FFlag::LuauBufferBetterMsg)
|
||||
{
|
||||
luaL_argcheck(L, count >= 0, 4, "count");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (count < 0)
|
||||
luaL_error(L, "invalid count");
|
||||
}
|
||||
luaL_argcheck(L, count >= 0, 4, "count");
|
||||
|
||||
if (size_t(count) > size)
|
||||
luaL_error(L, "string length overflow");
|
||||
|
@ -11,6 +11,8 @@
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauSciNumberSkipTrailDot, false)
|
||||
|
||||
// This work is based on:
|
||||
// Raffaello Giulietti. The Schubfach way to render doubles. 2021
|
||||
// https://drive.google.com/file/d/1IEeATSVnEE6TkrHlCYNY2GjaraBjOT4f/edit
|
||||
@ -361,6 +363,9 @@ char* luai_num2str(char* buf, double n)
|
||||
|
||||
char* exp = trimzero(buf + declen + 1);
|
||||
|
||||
if (FFlag::LuauSciNumberSkipTrailDot && exp[-1] == '.')
|
||||
exp--;
|
||||
|
||||
return printexp(exp, dot - 1);
|
||||
}
|
||||
}
|
||||
|
@ -242,8 +242,11 @@ typedef struct TString
|
||||
// 1 byte padding
|
||||
|
||||
int16_t atom;
|
||||
|
||||
|
||||
// 2 byte padding
|
||||
|
||||
|
||||
TString* next; // next string in the hash table bucket
|
||||
|
||||
unsigned int hash;
|
||||
@ -252,6 +255,8 @@ typedef struct TString
|
||||
char data[1]; // string data is allocated right after the header
|
||||
} TString;
|
||||
|
||||
|
||||
|
||||
#define getstr(ts) (ts)->data
|
||||
#define svalue(o) getstr(tsvalue(o))
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "ldo.h"
|
||||
#include "ldebug.h"
|
||||
|
||||
|
||||
/*
|
||||
** Main thread combines a thread state and the global state
|
||||
*/
|
||||
@ -180,6 +181,7 @@ lua_State* lua_newstate(lua_Alloc f, void* ud)
|
||||
g->uvhead.u.open.next = &g->uvhead;
|
||||
g->GCthreshold = 0; // mark it as unfinished state
|
||||
g->registryfree = 0;
|
||||
|
||||
g->errorjmp = NULL;
|
||||
g->rngstate = 0;
|
||||
g->ptrenckey[0] = 1;
|
||||
|
@ -201,8 +201,10 @@ typedef struct global_State
|
||||
TValue pseudotemp; // storage for temporary values used in pseudo2addr
|
||||
|
||||
TValue registry; // registry table, used by lua_ref and LUA_REGISTRYINDEX
|
||||
|
||||
int registryfree; // next free slot in registry
|
||||
|
||||
|
||||
struct lua_jmpbuf* errorjmp; // jump buffer data for longjmp-style error handling
|
||||
|
||||
uint64_t rngstate; // PCG random number generator state
|
||||
|
@ -7,6 +7,8 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
|
||||
unsigned int luaS_hash(const char* str, size_t len)
|
||||
{
|
||||
// Note that this hashing algorithm is replicated in BytecodeBuilder.cpp, BytecodeBuilder::getStringHash
|
||||
@ -76,6 +78,7 @@ static TString* newlstr(lua_State* L, const char* str, size_t l, unsigned int h)
|
||||
TString* ts = luaM_newgco(L, TString, sizestring(l), L->activememcat);
|
||||
luaC_init(L, ts, LUA_TSTRING);
|
||||
ts->atom = ATOM_UNDEF;
|
||||
|
||||
ts->hash = h;
|
||||
ts->len = unsigned(l);
|
||||
|
||||
@ -102,6 +105,7 @@ TString* luaS_bufstart(lua_State* L, size_t size)
|
||||
TString* ts = luaM_newgco(L, TString, sizestring(size), L->activememcat);
|
||||
luaC_init(L, ts, LUA_TSTRING);
|
||||
ts->atom = ATOM_UNDEF;
|
||||
|
||||
ts->hash = 0; // computed in luaS_buffinish
|
||||
ts->len = unsigned(size);
|
||||
|
||||
@ -189,5 +193,6 @@ void luaS_free(lua_State* L, TString* ts, lua_Page* page)
|
||||
else
|
||||
LUAU_ASSERT(ts->next == NULL); // orphaned string buffer
|
||||
|
||||
|
||||
luaM_freegco(L, ts, sizestring(ts->len), ts->memcat, page);
|
||||
}
|
||||
|
@ -8,8 +8,6 @@
|
||||
|
||||
#define iscont(p) ((*(p)&0xC0) == 0x80)
|
||||
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauStricterUtf8, false)
|
||||
|
||||
// from strlib
|
||||
// translate a relative string position: negative means back from end
|
||||
static int u_posrelat(int pos, size_t len)
|
||||
@ -47,7 +45,7 @@ static const char* utf8_decode(const char* o, int* val)
|
||||
res |= ((c & 0x7F) << (count * 5)); // add first byte
|
||||
if (count > 3 || res > MAXUNICODE || res <= limits[count])
|
||||
return NULL; // invalid byte sequence
|
||||
if (DFFlag::LuauStricterUtf8 && unsigned(res - 0xD800) < 0x800)
|
||||
if (unsigned(res - 0xD800) < 0x800)
|
||||
return NULL; // surrogate
|
||||
s += count; // skip continuation bytes read
|
||||
}
|
||||
|
@ -778,6 +778,7 @@ reentry:
|
||||
break;
|
||||
|
||||
case LCT_REF:
|
||||
|
||||
setupvalue(L, &ncl->l.uprefs[ui], luaF_findupval(L, VM_REG(LUAU_INSN_B(uinsn))));
|
||||
break;
|
||||
|
||||
@ -1112,7 +1113,9 @@ reentry:
|
||||
VM_NEXT();
|
||||
|
||||
case LUA_TLIGHTUSERDATA:
|
||||
pc += (pvalue(ra) == pvalue(rb) && (!FFlag::LuauTaggedLuData || lightuserdatatag(ra) == lightuserdatatag(rb))) ? LUAU_INSN_D(insn) : 1;
|
||||
pc += (pvalue(ra) == pvalue(rb) && (!FFlag::LuauTaggedLuData || lightuserdatatag(ra) == lightuserdatatag(rb)))
|
||||
? LUAU_INSN_D(insn)
|
||||
: 1;
|
||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
||||
VM_NEXT();
|
||||
|
||||
@ -1227,7 +1230,9 @@ reentry:
|
||||
VM_NEXT();
|
||||
|
||||
case LUA_TLIGHTUSERDATA:
|
||||
pc += (pvalue(ra) != pvalue(rb) || (FFlag::LuauTaggedLuData && lightuserdatatag(ra) != lightuserdatatag(rb))) ? LUAU_INSN_D(insn) : 1;
|
||||
pc += (pvalue(ra) != pvalue(rb) || (FFlag::LuauTaggedLuData && lightuserdatatag(ra) != lightuserdatatag(rb)))
|
||||
? LUAU_INSN_D(insn)
|
||||
: 1;
|
||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
||||
VM_NEXT();
|
||||
|
||||
|
@ -16,7 +16,6 @@
|
||||
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
|
||||
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
||||
LUAU_FASTFLAG(LuauAutocompleteStringLiteralBounds);
|
||||
LUAU_FASTFLAG(LuauAutocompleteDoEnd);
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
@ -980,8 +979,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_end_with_lambda")
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_end_of_do_block")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauAutocompleteDoEnd, true};
|
||||
|
||||
check("do @1");
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
@ -3107,7 +3104,10 @@ TEST_CASE_FIXTURE(ACFixture, "string_singleton_as_table_key")
|
||||
// https://github.com/Roblox/luau/issues/858
|
||||
TEST_CASE_FIXTURE(ACFixture, "string_singleton_in_if_statement")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauAutocompleteStringLiteralBounds, true};
|
||||
ScopedFastFlag sff[]{
|
||||
{FFlag::LuauAutocompleteStringLiteralBounds, true},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, true},
|
||||
};
|
||||
|
||||
check(R"(
|
||||
--!strict
|
||||
@ -3131,7 +3131,7 @@ TEST_CASE_FIXTURE(ACFixture, "string_singleton_in_if_statement")
|
||||
ac = autocomplete('2');
|
||||
|
||||
CHECK(ac.entryMap.count("left"));
|
||||
CHECK(ac.entryMap.count("right"));
|
||||
CHECK(!ac.entryMap.count("right"));
|
||||
|
||||
ac = autocomplete('3');
|
||||
|
||||
@ -3161,7 +3161,7 @@ TEST_CASE_FIXTURE(ACFixture, "string_singleton_in_if_statement")
|
||||
ac = autocomplete('8');
|
||||
|
||||
CHECK(ac.entryMap.count("left"));
|
||||
CHECK(ac.entryMap.count("right"));
|
||||
CHECK(!ac.entryMap.count("right"));
|
||||
|
||||
ac = autocomplete('9');
|
||||
|
||||
|
@ -26,14 +26,8 @@ extern bool verbose;
|
||||
extern bool codegen;
|
||||
extern int optimizationLevel;
|
||||
|
||||
LUAU_FASTFLAG(LuauBit32Byteswap);
|
||||
LUAU_FASTFLAG(LuauBufferBetterMsg);
|
||||
LUAU_FASTFLAG(LuauBufferDefinitions);
|
||||
LUAU_FASTFLAG(LuauCodeGenFixByteLower);
|
||||
LUAU_FASTFLAG(LuauCompileBufferAnnotation);
|
||||
LUAU_FASTFLAG(LuauLoopInterruptFix);
|
||||
LUAU_FASTFLAG(LuauTaggedLuData);
|
||||
LUAU_DYNAMIC_FASTFLAG(LuauStricterUtf8);
|
||||
LUAU_FASTFLAG(LuauSciNumberSkipTrailDot);
|
||||
LUAU_FASTINT(CodegenHeuristicsInstructionLimit);
|
||||
|
||||
static lua_CompileOptions defaultOptions()
|
||||
@ -324,9 +318,6 @@ TEST_CASE("Basic")
|
||||
|
||||
TEST_CASE("Buffers")
|
||||
{
|
||||
ScopedFastFlag luauBufferBetterMsg{FFlag::LuauBufferBetterMsg, true};
|
||||
ScopedFastFlag luauCodeGenFixByteLower{FFlag::LuauCodeGenFixByteLower, true};
|
||||
|
||||
runConformance("buffers.lua");
|
||||
}
|
||||
|
||||
@ -441,13 +432,11 @@ TEST_CASE("GC")
|
||||
|
||||
TEST_CASE("Bitwise")
|
||||
{
|
||||
ScopedFastFlag sffs{FFlag::LuauBit32Byteswap, true};
|
||||
runConformance("bitwise.lua");
|
||||
}
|
||||
|
||||
TEST_CASE("UTF8")
|
||||
{
|
||||
ScopedFastFlag sff(DFFlag::LuauStricterUtf8, true);
|
||||
runConformance("utf8.lua");
|
||||
}
|
||||
|
||||
@ -592,8 +581,6 @@ static void populateRTTI(lua_State* L, Luau::TypeId type)
|
||||
|
||||
TEST_CASE("Types")
|
||||
{
|
||||
ScopedFastFlag luauBufferDefinitions{FFlag::LuauBufferDefinitions, true};
|
||||
|
||||
runConformance("types.lua", [](lua_State* L) {
|
||||
Luau::NullModuleResolver moduleResolver;
|
||||
Luau::NullFileResolver fileResolver;
|
||||
@ -1439,6 +1426,8 @@ TEST_CASE("Coverage")
|
||||
|
||||
TEST_CASE("StringConversion")
|
||||
{
|
||||
ScopedFastFlag luauSciNumberSkipTrailDot{FFlag::LuauSciNumberSkipTrailDot, true};
|
||||
|
||||
runConformance("strconv.lua");
|
||||
}
|
||||
|
||||
@ -1540,8 +1529,6 @@ TEST_CASE("GCDump")
|
||||
|
||||
TEST_CASE("Interrupt")
|
||||
{
|
||||
ScopedFastFlag luauLoopInterruptFix{FFlag::LuauLoopInterruptFix, true};
|
||||
|
||||
lua_CompileOptions copts = defaultOptions();
|
||||
copts.optimizationLevel = 1; // disable loop unrolling to get fixed expected hit results
|
||||
|
||||
@ -1719,10 +1706,36 @@ TEST_CASE("LightuserdataApi")
|
||||
CHECK(!lua_getlightuserdataname(L, 0));
|
||||
CHECK(strcmp(lua_getlightuserdataname(L, 1), "id") == 0);
|
||||
CHECK(strcmp(luaL_typename(L, -1), "id") == 0);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_pushlightuserdatatagged(L, value, 0);
|
||||
lua_pushlightuserdatatagged(L, value, 1);
|
||||
CHECK(lua_rawequal(L, -1, -2) == 0);
|
||||
lua_pop(L, 2);
|
||||
|
||||
// Check lightuserdata table key uniqueness
|
||||
lua_newtable(L);
|
||||
|
||||
lua_pushlightuserdatatagged(L, value, 2);
|
||||
lua_pushinteger(L, 20);
|
||||
lua_settable(L, -3);
|
||||
lua_pushlightuserdatatagged(L, value, 3);
|
||||
lua_pushinteger(L, 30);
|
||||
lua_settable(L, -3);
|
||||
|
||||
lua_pushlightuserdatatagged(L, value, 2);
|
||||
lua_gettable(L, -2);
|
||||
lua_pushinteger(L, 20);
|
||||
CHECK(lua_rawequal(L, -1, -2) == 1);
|
||||
lua_pop(L, 2);
|
||||
|
||||
lua_pushlightuserdatatagged(L, value, 3);
|
||||
lua_gettable(L, -2);
|
||||
lua_pushinteger(L, 30);
|
||||
CHECK(lua_rawequal(L, -1, -2) == 1);
|
||||
lua_pop(L, 2);
|
||||
|
||||
lua_pop(L, 1);
|
||||
|
||||
globalState.reset();
|
||||
}
|
||||
@ -1962,8 +1975,6 @@ TEST_CASE("NativeTypeAnnotations")
|
||||
if (!codegen || !luau_codegen_supported())
|
||||
return;
|
||||
|
||||
ScopedFastFlag luauCompileBufferAnnotation{FFlag::LuauCompileBufferAnnotation, true};
|
||||
|
||||
lua_CompileOptions copts = defaultOptions();
|
||||
copts.vectorCtor = "vector";
|
||||
copts.vectorType = "vector";
|
||||
@ -2132,14 +2143,16 @@ end
|
||||
CHECK_EQ(summaries[0].getName(), "inner");
|
||||
CHECK_EQ(summaries[0].getLine(), 6);
|
||||
CHECK_EQ(summaries[0].getCounts(0),
|
||||
std::vector<unsigned>({1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
|
||||
std::vector<unsigned>({0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
|
||||
|
||||
CHECK_EQ(summaries[1].getName(), "first");
|
||||
CHECK_EQ(summaries[1].getLine(), 2);
|
||||
CHECK_EQ(summaries[1].getCounts(0),
|
||||
std::vector<unsigned>({1, 0, 1, 0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
std::vector<unsigned>({0, 0, 1, 0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
|
||||
|
||||
|
||||
CHECK_EQ(summaries[2].getName(), "second");
|
||||
CHECK_EQ(summaries[2].getLine(), 15);
|
||||
CHECK_EQ(summaries[2].getCounts(0),
|
||||
|
@ -14,7 +14,6 @@ using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena);
|
||||
LUAU_FASTFLAG(CorrectEarlyReturnInMarkDirty);
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -1254,8 +1253,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "parse_only")
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "markdirty_early_return")
|
||||
{
|
||||
ScopedFastFlag fflag(FFlag::CorrectEarlyReturnInMarkDirty, true);
|
||||
|
||||
constexpr char moduleName[] = "game/Gui/Modules/A";
|
||||
fileResolver.source[moduleName] = R"(
|
||||
return 1
|
||||
|
@ -1048,6 +1048,24 @@ TEST_CASE_FIXTURE(SubtypeFixture, "~~number <: number")
|
||||
CHECK_IS_SUBTYPE(negate(negate(builtinTypes->numberType)), builtinTypes->numberType);
|
||||
}
|
||||
|
||||
// See https://github.com/luau-lang/luau/issues/767
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "(...any) -> () <: <T>(T...) -> ()")
|
||||
{
|
||||
TypeId anysToNothing = arena.addType(FunctionType{builtinTypes->anyTypePack, builtinTypes->emptyTypePack});
|
||||
TypeId genericTToAnys = arena.addType(FunctionType{genericAs, builtinTypes->emptyTypePack});
|
||||
|
||||
CHECK_MESSAGE(subtyping.isSubtype(anysToNothing, genericTToAnys).isSubtype, "(...any) -> () <: <T>(T...) -> ()");
|
||||
}
|
||||
|
||||
// See https://github.com/luau-lang/luau/issues/767
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "(...unknown) -> () <: <T>(T...) -> ()")
|
||||
{
|
||||
TypeId anysToNothing = arena.addType(FunctionType{arena.addTypePack(VariadicTypePack{builtinTypes->unknownType}), builtinTypes->emptyTypePack});
|
||||
TypeId genericTToAnys = arena.addType(FunctionType{genericAs, builtinTypes->emptyTypePack});
|
||||
|
||||
CHECK_MESSAGE(subtyping.isSubtype(anysToNothing, genericTToAnys).isSubtype, "(...unknown) -> () <: <T>(T...) -> ()");
|
||||
}
|
||||
|
||||
/*
|
||||
* Within the scope to which a generic belongs, that generic ought to be treated
|
||||
* as its bounds.
|
||||
@ -1191,6 +1209,20 @@ TEST_CASE_FIXTURE(SubtypeFixture, "fn_arguments")
|
||||
}});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "arity_mismatch")
|
||||
{
|
||||
TypeId subTy = fn({builtinTypes->numberType}, {});
|
||||
TypeId superTy = fn({}, {});
|
||||
|
||||
SubtypingResult result = isSubtype(subTy, superTy);
|
||||
CHECK(!result.isSubtype);
|
||||
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
||||
/* subPath */ TypePath::PathBuilder().args().build(),
|
||||
/* superPath */ TypePath::PathBuilder().args().build(),
|
||||
/* variance */ SubtypingVariance::Contravariant,
|
||||
}});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "fn_arguments_tail")
|
||||
{
|
||||
TypeId subTy = fn({}, VariadicTypePack{builtinTypes->numberType}, {});
|
||||
|
@ -345,7 +345,7 @@ TEST_CASE("always_emit_a_space_after_local_keyword")
|
||||
TEST_CASE_FIXTURE(Fixture, "types_should_not_be_considered_cyclic_if_they_are_not_recursive")
|
||||
{
|
||||
std::string code = R"(
|
||||
local common: {foo:string}
|
||||
local common: {foo:string} = {foo = 'foo'}
|
||||
|
||||
local t = {}
|
||||
t.x = common
|
||||
@ -353,7 +353,7 @@ TEST_CASE_FIXTURE(Fixture, "types_should_not_be_considered_cyclic_if_they_are_no
|
||||
)";
|
||||
|
||||
std::string expected = R"(
|
||||
local common: {foo:string}
|
||||
local common: {foo:string} = {foo = 'foo'}
|
||||
|
||||
local t:{x:{foo:string},y:{foo:string}}={}
|
||||
t.x = common
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/Type.h"
|
||||
|
||||
#include "ClassFixture.h"
|
||||
#include "Fixture.h"
|
||||
|
||||
#include "doctest.h"
|
||||
@ -232,6 +233,31 @@ TEST_CASE_FIXTURE(Fixture, "internal_families_raise_errors")
|
||||
"signature; this construct cannot be type-checked at this time");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_families_can_be_shadowed")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type add<T> = string -- shadow add
|
||||
|
||||
-- this should be ok
|
||||
function hi(f: add<unknown>)
|
||||
return string.format("hi %s", f)
|
||||
end
|
||||
|
||||
-- this should still work totally fine (and use the real type family)
|
||||
function plus(a, b)
|
||||
return a + b
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK(toString(requireType("hi")) == "(string) -> string");
|
||||
CHECK(toString(requireType("plus")) == "<a, b>(a, b) -> add<a, b>");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_families_inhabited_with_normalization")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
@ -250,4 +276,287 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_families_inhabited_with_normalization")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_works")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type MyObject = { x: number, y: number, z: number }
|
||||
type KeysOfMyObject = keyof<MyObject>
|
||||
|
||||
local function ok(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
||||
local function err(idx: KeysOfMyObject): "x" | "y" return idx end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
||||
REQUIRE(tpm);
|
||||
CHECK_EQ("\"x\" | \"y\"", toString(tpm->wantedTp));
|
||||
CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_works_with_metatables")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local metatable = { __index = {w = 1} }
|
||||
local obj = setmetatable({x = 1, y = 2, z = 3}, metatable)
|
||||
type MyObject = typeof(obj)
|
||||
type KeysOfMyObject = keyof<MyObject>
|
||||
|
||||
local function ok(idx: KeysOfMyObject): "w" | "x" | "y" | "z" return idx end
|
||||
local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
||||
REQUIRE(tpm);
|
||||
CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->wantedTp));
|
||||
CHECK_EQ("\"w\" | \"x\" | \"y\" | \"z\"", toString(tpm->givenTp));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_errors_if_it_has_nontable_part")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type MyObject = { x: number, y: number, z: number }
|
||||
type KeysOfMyObject = keyof<MyObject | boolean>
|
||||
|
||||
local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
||||
)");
|
||||
|
||||
// FIXME: we should actually only report the type family being uninhabited error at its first use, I think?
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
CHECK(toString(result.errors[0]) == "Type family instance keyof<MyObject | boolean> is uninhabited");
|
||||
CHECK(toString(result.errors[1]) == "Type family instance keyof<MyObject | boolean> is uninhabited");
|
||||
CHECK(toString(result.errors[2]) == "Type family instance keyof<MyObject | boolean> is uninhabited");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_errors_if_union_of_differing_tables")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type MyObject = { x: number, y: number, z: number }
|
||||
type MyOtherObject = { w: number, y: number, z: number }
|
||||
type KeysOfMyObject = keyof<MyObject | MyOtherObject>
|
||||
|
||||
local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
||||
)");
|
||||
|
||||
// FIXME: we should actually only report the type family being uninhabited error at its first use, I think?
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
CHECK(toString(result.errors[0]) == "Type family instance keyof<MyObject | MyOtherObject> is uninhabited");
|
||||
CHECK(toString(result.errors[1]) == "Type family instance keyof<MyObject | MyOtherObject> is uninhabited");
|
||||
CHECK(toString(result.errors[2]) == "Type family instance keyof<MyObject | MyOtherObject> is uninhabited");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_never_for_empty_table")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type KeyofEmpty = keyof<{}>
|
||||
|
||||
local foo = ((nil :: any) :: KeyofEmpty)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireType("foo")) == "never");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_works")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type MyObject = { x: number, y: number, z: number }
|
||||
type KeysOfMyObject = rawkeyof<MyObject>
|
||||
|
||||
local function ok(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
||||
local function err(idx: KeysOfMyObject): "x" | "y" return idx end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
||||
REQUIRE(tpm);
|
||||
CHECK_EQ("\"x\" | \"y\"", toString(tpm->wantedTp));
|
||||
CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_ignores_metatables")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local metatable = { __index = {w = 1} }
|
||||
local obj = setmetatable({x = 1, y = 2, z = 3}, metatable)
|
||||
type MyObject = typeof(obj)
|
||||
type KeysOfMyObject = rawkeyof<MyObject>
|
||||
|
||||
local function ok(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
||||
local function err(idx: KeysOfMyObject): "x" | "y" return idx end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
||||
REQUIRE(tpm);
|
||||
CHECK_EQ("\"x\" | \"y\"", toString(tpm->wantedTp));
|
||||
CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_errors_if_it_has_nontable_part")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type MyObject = { x: number, y: number, z: number }
|
||||
type KeysOfMyObject = rawkeyof<MyObject | boolean>
|
||||
|
||||
local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
||||
)");
|
||||
|
||||
// FIXME: we should actually only report the type family being uninhabited error at its first use, I think?
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
CHECK(toString(result.errors[0]) == "Type family instance rawkeyof<MyObject | boolean> is uninhabited");
|
||||
CHECK(toString(result.errors[1]) == "Type family instance rawkeyof<MyObject | boolean> is uninhabited");
|
||||
CHECK(toString(result.errors[2]) == "Type family instance rawkeyof<MyObject | boolean> is uninhabited");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_errors_if_union_of_differing_tables")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type MyObject = { x: number, y: number, z: number }
|
||||
type MyOtherObject = { w: number, y: number, z: number }
|
||||
type KeysOfMyObject = rawkeyof<MyObject | MyOtherObject>
|
||||
|
||||
local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
||||
)");
|
||||
|
||||
// FIXME: we should actually only report the type family being uninhabited error at its first use, I think?
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
CHECK(toString(result.errors[0]) == "Type family instance rawkeyof<MyObject | MyOtherObject> is uninhabited");
|
||||
CHECK(toString(result.errors[1]) == "Type family instance rawkeyof<MyObject | MyOtherObject> is uninhabited");
|
||||
CHECK(toString(result.errors[2]) == "Type family instance rawkeyof<MyObject | MyOtherObject> is uninhabited");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_never_for_empty_table")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type RawkeyofEmpty = rawkeyof<{}>
|
||||
|
||||
local foo = ((nil :: any) :: RawkeyofEmpty)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireType("foo")) == "never");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_works_on_classes")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type KeysOfMyObject = keyof<BaseClass>
|
||||
|
||||
local function ok(idx: KeysOfMyObject): "BaseMethod" | "BaseField" return idx end
|
||||
local function err(idx: KeysOfMyObject): "BaseMethod" return idx end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
||||
REQUIRE(tpm);
|
||||
CHECK_EQ("\"BaseMethod\"", toString(tpm->wantedTp));
|
||||
CHECK_EQ("\"BaseField\" | \"BaseMethod\"", toString(tpm->givenTp));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_errors_if_it_has_nonclass_part")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type KeysOfMyObject = keyof<BaseClass | boolean>
|
||||
|
||||
local function err(idx: KeysOfMyObject): "BaseMethod" | "BaseField" return idx end
|
||||
)");
|
||||
|
||||
// FIXME: we should actually only report the type family being uninhabited error at its first use, I think?
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
CHECK(toString(result.errors[0]) == "Type family instance keyof<BaseClass | boolean> is uninhabited");
|
||||
CHECK(toString(result.errors[1]) == "Type family instance keyof<BaseClass | boolean> is uninhabited");
|
||||
CHECK(toString(result.errors[2]) == "Type family instance keyof<BaseClass | boolean> is uninhabited");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_errors_if_union_of_differing_classes")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type KeysOfMyObject = keyof<BaseClass | Vector2>
|
||||
|
||||
local function err(idx: KeysOfMyObject): "BaseMethod" | "BaseField" return idx end
|
||||
)");
|
||||
|
||||
// FIXME: we should actually only report the type family being uninhabited error at its first use, I think?
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
CHECK(toString(result.errors[0]) == "Type family instance keyof<BaseClass | Vector2> is uninhabited");
|
||||
CHECK(toString(result.errors[1]) == "Type family instance keyof<BaseClass | Vector2> is uninhabited");
|
||||
CHECK(toString(result.errors[2]) == "Type family instance keyof<BaseClass | Vector2> is uninhabited");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_rfc_example")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local animals = {
|
||||
cat = { speak = function() print "meow" end },
|
||||
dog = { speak = function() print "woof woof" end },
|
||||
monkey = { speak = function() print "oo oo" end },
|
||||
fox = { speak = function() print "gekk gekk" end }
|
||||
}
|
||||
|
||||
type AnimalType = keyof<typeof(animals)>
|
||||
|
||||
function speakByType(animal: AnimalType)
|
||||
animals[animal].speak()
|
||||
end
|
||||
|
||||
speakByType("dog") -- ok
|
||||
speakByType("cactus") -- errors
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
||||
REQUIRE(tpm);
|
||||
CHECK_EQ("\"cat\" | \"dog\" | \"monkey\" | \"fox\"", toString(tpm->wantedTp));
|
||||
CHECK_EQ("\"cactus\"", toString(tpm->givenTp));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -9,6 +9,7 @@ using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(DebugLuauSharedSelf);
|
||||
LUAU_FASTFLAG(LuauForbidAliasNamedTypeof);
|
||||
|
||||
TEST_SUITE_BEGIN("TypeAliases");
|
||||
|
||||
@ -159,14 +160,16 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_types_of_named_table_fields_do_not_expand_whe
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
type Node = { Parent: Node?; }
|
||||
local node: Node;
|
||||
node.Parent = 1
|
||||
|
||||
function f(node: Node)
|
||||
node.Parent = 1
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
REQUIRE_MESSAGE(tm, result.errors[0]);
|
||||
CHECK_EQ("Node?", toString(tm->wantedType));
|
||||
CHECK_EQ(builtinTypes->numberType, tm->givenType);
|
||||
}
|
||||
@ -361,15 +364,19 @@ TEST_CASE_FIXTURE(Fixture, "corecursive_types_generic")
|
||||
const std::string code = R"(
|
||||
type A<T> = {v:T, b:B<T>}
|
||||
type B<T> = {v:T, a:A<T>}
|
||||
local aa:A<number>
|
||||
local bb = aa
|
||||
|
||||
function f(a: A<number>)
|
||||
return a
|
||||
end
|
||||
)";
|
||||
|
||||
const std::string expected = R"(
|
||||
type A<T> = {v:T, b:B<T>}
|
||||
type B<T> = {v:T, a:A<T>}
|
||||
local aa:A<number>
|
||||
local bb:A<number>=aa
|
||||
|
||||
function f(a: A<number>): A<number>
|
||||
return a
|
||||
end
|
||||
)";
|
||||
|
||||
CHECK_EQ(expected, decorateWithTypes(code));
|
||||
@ -383,14 +390,12 @@ TEST_CASE_FIXTURE(Fixture, "corecursive_function_types")
|
||||
CheckResult result = check(R"(
|
||||
type A = () -> (number, B)
|
||||
type B = () -> (string, A)
|
||||
local a: A
|
||||
local b: B
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("t1 where t1 = () -> (number, () -> (string, t1))", toString(requireType("a")));
|
||||
CHECK_EQ("t1 where t1 = () -> (string, () -> (number, t1))", toString(requireType("b")));
|
||||
CHECK_EQ("t1 where t1 = () -> (number, () -> (string, t1))", toString(requireTypeAlias("A")));
|
||||
CHECK_EQ("t1 where t1 = () -> (string, () -> (number, t1))", toString(requireTypeAlias("B")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_param_remap")
|
||||
@ -1057,4 +1062,17 @@ TEST_CASE_FIXTURE(Fixture, "table_types_record_the_property_locations")
|
||||
CHECK_EQ(propIt->second.typeLocation, Location({2, 12}, {2, 18}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "typeof_is_not_a_valid_alias_name")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauForbidAliasNamedTypeof, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type typeof = number
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK("Type aliases cannot be named typeof" == toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -10,6 +10,7 @@ using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
|
||||
LUAU_FASTFLAG(LuauSetMetatableOnUnionsOfTables);
|
||||
|
||||
TEST_SUITE_BEGIN("BuiltinTests");
|
||||
|
||||
@ -368,6 +369,29 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_unpacks_arg_types_correctly")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_on_union_of_tables")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSetMetatableOnUnionsOfTables, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type A = {tag: "A", x: number}
|
||||
type B = {tag: "B", y: string}
|
||||
|
||||
type T = A | B
|
||||
|
||||
type X = typeof(
|
||||
setmetatable({} :: T, {})
|
||||
)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("{ @metatable {| |}, A } | { @metatable {| |}, B }" == toString(requireTypeAlias("X")));
|
||||
else
|
||||
CHECK("{ @metatable { }, A } | { @metatable { }, B }" == toString(requireTypeAlias("X")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_correctly_infers_type_of_array_2_args_overload")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -796,16 +820,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_use_correct_argument3")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "debug_traceback_is_crazy")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local co: thread = ...
|
||||
-- debug.traceback takes thread?, message?, level? - yes, all optional!
|
||||
debug.traceback()
|
||||
debug.traceback(nil, 1)
|
||||
debug.traceback("msg")
|
||||
debug.traceback("msg", 1)
|
||||
debug.traceback(co)
|
||||
debug.traceback(co, "msg")
|
||||
debug.traceback(co, "msg", 1)
|
||||
)");
|
||||
function f(co: thread)
|
||||
-- debug.traceback takes thread?, message?, level? - yes, all optional!
|
||||
debug.traceback()
|
||||
debug.traceback(nil, 1)
|
||||
debug.traceback("msg")
|
||||
debug.traceback("msg", 1)
|
||||
debug.traceback(co)
|
||||
debug.traceback(co, "msg")
|
||||
debug.traceback(co, "msg", 1)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
@ -813,13 +838,13 @@ debug.traceback(co, "msg", 1)
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "debug_info_is_crazy")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local co: thread, f: ()->() = ...
|
||||
|
||||
-- debug.info takes thread?, level, options or function, options
|
||||
debug.info(1, "n")
|
||||
debug.info(co, 1, "n")
|
||||
debug.info(f, "n")
|
||||
)");
|
||||
function f(co: thread, f: () -> ())
|
||||
-- debug.info takes thread?, level, options or function, options
|
||||
debug.info(1, "n")
|
||||
debug.info(co, 1, "n")
|
||||
debug.info(f, "n")
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
@ -7,8 +7,6 @@
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauDefinitionFileSetModuleName)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
TEST_SUITE_BEGIN("DefinitionTests");
|
||||
@ -453,8 +451,6 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "definition_file_has_source_module_name_set")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauDefinitionFileSetModuleName, true};
|
||||
|
||||
LoadDefinitionFileResult result = loadDefinition(R"(
|
||||
declare class Foo
|
||||
end
|
||||
|
@ -1286,7 +1286,7 @@ TEST_CASE_FIXTURE(Fixture, "variadic_any_is_compatible_with_a_generic_TypePack")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
// https://github.com/Roblox/luau/issues/767
|
||||
// https://github.com/luau-lang/luau/issues/767
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "variadic_any_is_compatible_with_a_generic_TypePack_2")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -17,14 +17,15 @@ TEST_CASE_FIXTURE(Fixture, "select_correct_union_fn")
|
||||
CheckResult result = check(R"(
|
||||
type A = (number) -> (string)
|
||||
type B = (string) -> (number)
|
||||
local f:A & B
|
||||
local b = f(10) -- b is a string
|
||||
local c = f("a") -- c is a number
|
||||
|
||||
local function foo(f: A & B)
|
||||
return f(10), f("a")
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(requireType("b"), builtinTypes->stringType);
|
||||
CHECK_EQ(requireType("c"), builtinTypes->numberType);
|
||||
|
||||
CHECK_EQ("(((number) -> string) & ((string) -> number)) -> (string, number)", toString(requireType("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_combines")
|
||||
@ -32,6 +33,7 @@ TEST_CASE_FIXTURE(Fixture, "table_combines")
|
||||
CheckResult result = check(R"(
|
||||
type A={a:number}
|
||||
type B={b:string}
|
||||
|
||||
local c:A & B = {a=10, b="s"}
|
||||
)");
|
||||
|
||||
@ -43,6 +45,7 @@ TEST_CASE_FIXTURE(Fixture, "table_combines_missing")
|
||||
CheckResult result = check(R"(
|
||||
type A={a:number}
|
||||
type B={b:string}
|
||||
|
||||
local c:A & B = {a=10}
|
||||
)");
|
||||
|
||||
@ -63,8 +66,10 @@ TEST_CASE_FIXTURE(Fixture, "table_extra_ok")
|
||||
CheckResult result = check(R"(
|
||||
type A={a:number}
|
||||
type B={b:string}
|
||||
local c:A & B
|
||||
local d:A = c
|
||||
|
||||
local function f(t: A & B): A
|
||||
return t
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -76,9 +81,10 @@ TEST_CASE_FIXTURE(Fixture, "fx_intersection_as_argument")
|
||||
type A = (number) -> (string)
|
||||
type B = (string) -> (number)
|
||||
type C = (A) -> (number)
|
||||
local f:A & B
|
||||
local g:C
|
||||
local b = g(f)
|
||||
|
||||
local function foo(f: A & B, g: C)
|
||||
return g(f)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -90,9 +96,10 @@ TEST_CASE_FIXTURE(Fixture, "fx_union_as_argument_fails")
|
||||
type A = (number) -> (string)
|
||||
type B = (string) -> (number)
|
||||
type C = (A) -> (number)
|
||||
local f:A | B
|
||||
local g:C
|
||||
local b = g(f)
|
||||
|
||||
local function foo(f: A | B, g: C)
|
||||
return g(f)
|
||||
end
|
||||
)");
|
||||
|
||||
REQUIRE(!result.errors.empty());
|
||||
@ -102,10 +109,11 @@ TEST_CASE_FIXTURE(Fixture, "argument_is_intersection")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type A = (number | boolean) -> number
|
||||
local f: A
|
||||
|
||||
f(5)
|
||||
f(true)
|
||||
local function foo(f: A)
|
||||
f(5)
|
||||
f(true)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -114,21 +122,17 @@ TEST_CASE_FIXTURE(Fixture, "argument_is_intersection")
|
||||
TEST_CASE_FIXTURE(Fixture, "should_still_pick_an_overload_whose_arguments_are_unions")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type A = (number | boolean) -> number
|
||||
type B = (string | nil) -> string
|
||||
local f: A & B
|
||||
type A = (number) -> string
|
||||
type B = (string) -> number
|
||||
|
||||
local a1, a2 = f(1), f(true)
|
||||
local b1, b2 = f("foo"), f(nil)
|
||||
local function foo(f: A & B)
|
||||
return f(1), f("five")
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*requireType("a1"), *builtinTypes->numberType);
|
||||
CHECK_EQ(*requireType("a2"), *builtinTypes->numberType);
|
||||
|
||||
CHECK_EQ(*requireType("b1"), *builtinTypes->stringType);
|
||||
CHECK_EQ(*requireType("b2"), *builtinTypes->stringType);
|
||||
CHECK_EQ("(((number) -> string) & ((string) -> number)) -> (string, number)", toString(requireType("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "propagates_name")
|
||||
@ -137,16 +141,18 @@ TEST_CASE_FIXTURE(Fixture, "propagates_name")
|
||||
type A={a:number}
|
||||
type B={b:string}
|
||||
|
||||
local c:A&B
|
||||
local b = c
|
||||
local function f(t: A & B)
|
||||
return t
|
||||
end
|
||||
)";
|
||||
|
||||
const std::string expected = R"(
|
||||
type A={a:number}
|
||||
type B={b:string}
|
||||
|
||||
local c:A&B
|
||||
local b:A&B=c
|
||||
local function f(t: A & B): A&B
|
||||
return t
|
||||
end
|
||||
)";
|
||||
|
||||
CHECK_EQ(expected, decorateWithTypes(code));
|
||||
@ -157,16 +163,18 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guarante
|
||||
CheckResult result = check(R"(
|
||||
type A = {x: {y: number}}
|
||||
type B = {x: {y: number}}
|
||||
local t: A & B
|
||||
|
||||
local r = t.x
|
||||
local function f(t: A & B)
|
||||
return t.x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("{ y: number }" == toString(requireType("r")));
|
||||
CHECK("(A & B) -> { y: number }" == toString(requireType("f")));
|
||||
else
|
||||
CHECK("{| y: number |} & {| y: number |}" == toString(requireType("r")));
|
||||
CHECK("(A & B) -> {| y: number |} & {| y: number |}" == toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_depth")
|
||||
@ -174,21 +182,18 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_dep
|
||||
CheckResult result = check(R"(
|
||||
type A = {x: {y: {z: {thing: string}}}}
|
||||
type B = {x: {y: {z: {thing: string}}}}
|
||||
local t: A & B
|
||||
|
||||
local r = t.x.y.z.thing
|
||||
local function f(t: A & B)
|
||||
return t.x.y.z.thing
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("string", toString(requireType("r")));
|
||||
}
|
||||
CHECK_EQ("(A & B) -> string", toString(requireType("f")));
|
||||
else
|
||||
{
|
||||
CHECK_EQ("string & string", toString(requireType("r")));
|
||||
}
|
||||
CHECK_EQ("(A & B) -> string & string", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_mixed_types")
|
||||
@ -196,16 +201,18 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_mixed_types")
|
||||
CheckResult result = check(R"(
|
||||
type A = {x: number}
|
||||
type B = {x: string}
|
||||
local t: A & B
|
||||
|
||||
local r = t.x
|
||||
local function f(t: A & B)
|
||||
return t.x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("never", toString(requireType("r")));
|
||||
CHECK_EQ("(A & B) -> never", toString(requireType("f")));
|
||||
else
|
||||
CHECK_EQ("number & string", toString(requireType("r")));
|
||||
CHECK_EQ("(A & B) -> number & string", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_part_missing_the_property")
|
||||
@ -213,13 +220,14 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_part_missing_
|
||||
CheckResult result = check(R"(
|
||||
type A = {x: number}
|
||||
type B = {}
|
||||
local t: A & B
|
||||
|
||||
local r = t.x
|
||||
local function f(t: A & B)
|
||||
return t.x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("number", toString(requireType("r")));
|
||||
CHECK_EQ("(A & B) -> number", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_property_of_type_any")
|
||||
@ -227,13 +235,14 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_property_of_t
|
||||
CheckResult result = check(R"(
|
||||
type A = {y: number}
|
||||
type B = {x: any}
|
||||
local t: A & B
|
||||
|
||||
local r = t.x
|
||||
local function f(t: A & B)
|
||||
return t.x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(*builtinTypes->anyType, *requireType("r"));
|
||||
CHECK_EQ("(A & B) -> any", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_all_parts_missing_the_property")
|
||||
@ -260,8 +269,9 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write")
|
||||
type X = { x: number }
|
||||
type XY = X & { y: number }
|
||||
|
||||
local a : XY = { x = 1, y = 2 }
|
||||
a.x = 10
|
||||
function f(t: XY)
|
||||
t.x = 10
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -270,8 +280,9 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write")
|
||||
type X = {}
|
||||
type XY = X & { x: number, y: number }
|
||||
|
||||
local a : XY = { x = 1, y = 2 }
|
||||
a.x = 10
|
||||
function f(t: XY)
|
||||
t.x = 10
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -281,8 +292,9 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write")
|
||||
type Y = { y: number }
|
||||
type XY = X & Y
|
||||
|
||||
local a : XY = { x = 1, y = 2 }
|
||||
a.x = 10
|
||||
function f(t: XY)
|
||||
t.x = 10
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -290,10 +302,11 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write")
|
||||
result = check(R"(
|
||||
type A = { x: {y: number} }
|
||||
type B = { x: {y: number} }
|
||||
local t : A & B = { x = { y = 1 } }
|
||||
|
||||
t.x = { y = 4 }
|
||||
t.x.y = 40
|
||||
function f(t: A & B)
|
||||
t.x = { y = 4 }
|
||||
t.x.y = 40
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -306,8 +319,9 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed")
|
||||
type Y = { y: number }
|
||||
type XY = X & Y
|
||||
|
||||
local a : XY = { x = 1, y = 2 }
|
||||
a.z = 10
|
||||
function f(t: XY)
|
||||
t.z = 10
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -323,26 +337,33 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
|
||||
|
||||
type XY = X & Y
|
||||
|
||||
local xy : XY = {
|
||||
x = function(a: number) return -a end,
|
||||
y = function(a: string) return a .. "b" end
|
||||
}
|
||||
function xy.z(a:number) return a * 10 end
|
||||
function xy:y(a:number) return a * 10 end
|
||||
function xy:w(a:number) return a * 10 end
|
||||
function f(t: XY)
|
||||
function t.z(a:number) return a * 10 end
|
||||
function t:y(a:number) return a * 10 end
|
||||
function t:w(a:number) return a * 10 end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
const std::string expected = R"(Type
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot add property 'z' to table 'X & Y'");
|
||||
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'w' to table 'X & Y'");
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
const std::string expected = R"(Type
|
||||
'(string, number) -> string'
|
||||
could not be converted into
|
||||
'(string) -> string'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'");
|
||||
CHECK_EQ(toString(result.errors[2]), "Type 'number' could not be converted into 'string'");
|
||||
CHECK_EQ(toString(result.errors[3]), "Cannot add property 'w' to table 'X & Y'");
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'");
|
||||
CHECK_EQ(toString(result.errors[2]), "Type 'number' could not be converted into 'string'");
|
||||
CHECK_EQ(toString(result.errors[3]), "Cannot add property 'w' to table 'X & Y'");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect")
|
||||
@ -377,8 +398,9 @@ caused by:
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_intersection_setmetatable")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t: {} & {}
|
||||
setmetatable(t, {})
|
||||
function f(t: {} & {})
|
||||
setmetatable(t, {})
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -409,8 +431,10 @@ type X = { x: number }
|
||||
type Y = { y: number }
|
||||
type Z = { z: number }
|
||||
type XYZ = X & Y & Z
|
||||
local a: XYZ
|
||||
local b: number = a
|
||||
|
||||
function f(a: XYZ): number
|
||||
return a
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -448,9 +472,10 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_flattenintersection")
|
||||
TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : (boolean & false)
|
||||
local y : false = x -- OK
|
||||
local z : true = x -- Not OK
|
||||
function f(x: boolean & false)
|
||||
local y : false = x -- OK
|
||||
local z : true = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -460,9 +485,10 @@ TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false")
|
||||
TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : false & (boolean & false)
|
||||
local y : false = x -- OK
|
||||
local z : true = x -- Not OK
|
||||
function f(x: false & (boolean & false))
|
||||
local y : false = x -- OK
|
||||
local z : true = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -474,9 +500,10 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
|
||||
TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : ((number?) -> number?) & ((string?) -> string?)
|
||||
local y : (nil) -> nil = x -- OK
|
||||
local z : (number) -> number = x -- Not OK
|
||||
function foo(x: ((number?) -> number?) & ((string?) -> string?))
|
||||
local y : (nil) -> nil = x -- OK
|
||||
local z : (number) -> number = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -489,11 +516,11 @@ could not be converted into
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
|
||||
{
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : ((number) -> number) & ((string) -> string)
|
||||
local y : ((number | string) -> (number | string)) = x -- OK
|
||||
local z : ((number | boolean) -> (number | boolean)) = x -- Not OK
|
||||
function f(x: ((number) -> number) & ((string) -> string))
|
||||
local y : ((number | string) -> (number | string)) = x -- OK
|
||||
local z : ((number | boolean) -> (number | boolean)) = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -507,9 +534,10 @@ could not be converted into
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : { p : number?, q : string? } & { p : number?, q : number?, r : number? }
|
||||
local y : { p : number?, q : nil, r : number? } = x -- OK
|
||||
local z : { p : nil } = x -- Not OK
|
||||
function f(x: { p : number?, q : string? } & { p : number?, q : number?, r : number? })
|
||||
local y : { p : number?, q : nil, r : number? } = x -- OK
|
||||
local z : { p : nil } = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -529,9 +557,10 @@ could not be converted into
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : { p : number?, q : any } & { p : unknown, q : string? } = { p = 123, q = "foo" }
|
||||
local y : { p : number?, q : string? } = x -- OK
|
||||
local z : { p : string?, q : number? } = x -- Not OK
|
||||
function f(x : { p : number?, q : any } & { p : unknown, q : string? })
|
||||
local y : { p : number?, q : string? } = x -- OK
|
||||
local z : { p : string?, q : number? } = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
@ -570,9 +599,10 @@ could not be converted into
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : { p : number?, q : never } & { p : never, q : string? } -- OK
|
||||
local y : { p : never, q : never } = x -- OK
|
||||
local z : never = x -- OK
|
||||
function f(x : { p : number?, q : never } & { p : never, q : string? })
|
||||
local y : { p : never, q : never } = x -- OK
|
||||
local z : never = x -- OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -581,9 +611,10 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties")
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : ((number?) -> ({ p : number } & { q : number })) & ((string?) -> ({ p : number } & { r : number }))
|
||||
local y : (nil) -> { p : number, q : number, r : number} = x -- OK
|
||||
local z : (number?) -> { p : number, q : number, r : number} = x -- Not OK
|
||||
function f(x : ((number?) -> ({ p : number } & { q : number })) & ((string?) -> ({ p : number } & { r : number })))
|
||||
local y : (nil) -> { p : number, q : number, r : number} = x -- OK
|
||||
local z : (number?) -> { p : number, q : number, r : number} = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -603,11 +634,12 @@ could not be converted into
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a>()
|
||||
local x : ((number?) -> (a | number)) & ((string?) -> (a | string))
|
||||
local y : (nil) -> a = x -- OK
|
||||
local z : (number?) -> a = x -- Not OK
|
||||
end
|
||||
function f<a>()
|
||||
function g(x : ((number?) -> (a | number)) & ((string?) -> (a | string)))
|
||||
local y : (nil) -> a = x -- OK
|
||||
local z : (number?) -> a = x -- Not OK
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -621,11 +653,12 @@ could not be converted into
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a,b,c>()
|
||||
local x : ((a?) -> (a | b)) & ((c?) -> (b | c))
|
||||
local y : (nil) -> ((a & c) | b) = x -- OK
|
||||
local z : (a?) -> ((a & c) | b) = x -- Not OK
|
||||
end
|
||||
function f<a,b,c>()
|
||||
function g(x : ((a?) -> (a | b)) & ((c?) -> (b | c)))
|
||||
local y : (nil) -> ((a & c) | b) = x -- OK
|
||||
local z : (a?) -> ((a & c) | b) = x -- Not OK
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -639,11 +672,12 @@ could not be converted into
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))
|
||||
local y : ((nil, a...) -> (nil, b...)) = x -- OK
|
||||
local z : ((nil, b...) -> (nil, a...)) = x -- Not OK
|
||||
end
|
||||
function f<a...,b...>()
|
||||
function g(x : ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...)))
|
||||
local y : ((nil, a...) -> (nil, b...)) = x -- OK
|
||||
local z : ((nil, b...) -> (nil, a...)) = x -- Not OK
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -657,11 +691,12 @@ could not be converted into
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((number) -> number) & ((nil) -> unknown)
|
||||
local y : (number?) -> unknown = x -- OK
|
||||
local z : (number?) -> number? = x -- Not OK
|
||||
end
|
||||
function f<a...,b...>()
|
||||
function g(x : ((number) -> number) & ((nil) -> unknown))
|
||||
local y : (number?) -> unknown = x -- OK
|
||||
local z : (number?) -> number? = x -- Not OK
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -675,11 +710,12 @@ could not be converted into
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((number) -> number?) & ((unknown) -> string?)
|
||||
local y : (number) -> nil = x -- OK
|
||||
local z : (number?) -> nil = x -- Not OK
|
||||
end
|
||||
function f<a...,b...>()
|
||||
function g(x : ((number) -> number?) & ((unknown) -> string?))
|
||||
local y : (number) -> nil = x -- OK
|
||||
local z : (number?) -> nil = x -- Not OK
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -693,11 +729,12 @@ could not be converted into
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((number) -> number) & ((nil) -> never)
|
||||
local y : (number?) -> number = x -- OK
|
||||
local z : (number?) -> never = x -- Not OK
|
||||
end
|
||||
function f<a...,b...>()
|
||||
function g(x : ((number) -> number) & ((nil) -> never))
|
||||
local y : (number?) -> number = x -- OK
|
||||
local z : (number?) -> never = x -- Not OK
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -711,11 +748,12 @@ could not be converted into
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((number) -> number?) & ((never) -> string?)
|
||||
local y : (never) -> nil = x -- OK
|
||||
local z : (number?) -> nil = x -- Not OK
|
||||
end
|
||||
function f<a...,b...>()
|
||||
function g(x : ((number) -> number?) & ((never) -> string?))
|
||||
local y : (never) -> nil = x -- OK
|
||||
local z : (number?) -> nil = x -- Not OK
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -729,9 +767,10 @@ could not be converted into
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x : ((string?) -> (string | number)) & ((number?) -> ...number)
|
||||
local y : ((nil) -> (number, number?)) = x -- OK
|
||||
local z : ((string | number) -> (number, number?)) = x -- Not OK
|
||||
function f(x : ((string?) -> (string | number)) & ((number?) -> ...number))
|
||||
local y : ((nil) -> (number, number?)) = x -- OK
|
||||
local z : ((string | number) -> (number, number?)) = x -- Not OK
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -745,11 +784,12 @@ could not be converted into
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : (() -> a...) & (() -> b...)
|
||||
local y : (() -> b...) & (() -> a...) = x -- OK
|
||||
local z : () -> () = x -- Not OK
|
||||
end
|
||||
function f<a...,b...>()
|
||||
function g(x : (() -> a...) & (() -> b...))
|
||||
local y : (() -> b...) & (() -> a...) = x -- OK
|
||||
local z : () -> () = x -- Not OK
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -760,11 +800,12 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1")
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a...,b...>()
|
||||
local x : ((a...) -> ()) & ((b...) -> ())
|
||||
local y : ((b...) -> ()) & ((a...) -> ()) = x -- OK
|
||||
local z : () -> () = x -- Not OK
|
||||
end
|
||||
function f<a...,b...>()
|
||||
function g(x : ((a...) -> ()) & ((b...) -> ()))
|
||||
local y : ((b...) -> ()) & ((a...) -> ()) = x -- OK
|
||||
local z : () -> () = x -- Not OK
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -775,11 +816,12 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2")
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a...>()
|
||||
local x : (() -> a...) & (() -> (number?,a...))
|
||||
local y : (() -> (number?,a...)) & (() -> a...) = x -- OK
|
||||
local z : () -> (number) = x -- Not OK
|
||||
end
|
||||
function f<a...>()
|
||||
function g(x : (() -> a...) & (() -> (number?,a...)))
|
||||
local y : (() -> (number?,a...)) & (() -> a...) = x -- OK
|
||||
local z : () -> (number) = x -- Not OK
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -793,11 +835,12 @@ could not be converted into
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f<a...>()
|
||||
local x : ((a...) -> ()) & ((number,a...) -> number)
|
||||
local y : ((number,a...) -> number) & ((a...) -> ()) = x -- OK
|
||||
local z : (number?) -> () = x -- Not OK
|
||||
end
|
||||
function f<a...>()
|
||||
function g(x : ((a...) -> ()) & ((number,a...) -> number))
|
||||
local y : ((number,a...) -> number) & ((a...) -> ()) = x -- OK
|
||||
local z : (number?) -> () = x -- Not OK
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -810,61 +853,89 @@ could not be converted into
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a : string? = nil
|
||||
local b : number? = nil
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f(a: string?, b: string?)
|
||||
local x = setmetatable({}, { p = 5, q = a })
|
||||
local y = setmetatable({}, { q = b, r = "hi" })
|
||||
local z = setmetatable({}, { p = 5, q = nil, r = "hi" })
|
||||
|
||||
local x = setmetatable({}, { p = 5, q = a });
|
||||
local y = setmetatable({}, { q = b, r = "hi" });
|
||||
local z = setmetatable({}, { p = 5, q = nil, r = "hi" });
|
||||
type X = typeof(x)
|
||||
type Y = typeof(y)
|
||||
type Z = typeof(z)
|
||||
|
||||
type X = typeof(x)
|
||||
type Y = typeof(y)
|
||||
type Z = typeof(z)
|
||||
function g(xy: X&Y, yx: Y&X): (Z, Z)
|
||||
return xy, yx
|
||||
end
|
||||
|
||||
local xy : X&Y = z;
|
||||
local yx : Y&X = z;
|
||||
z = xy;
|
||||
z = yx;
|
||||
)");
|
||||
g(z, z)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a : string? = nil
|
||||
local b : number? = nil
|
||||
|
||||
local x = setmetatable({}, { p = 5, q = a });
|
||||
local y = setmetatable({}, { q = b, r = "hi" });
|
||||
local z = setmetatable({}, { p = 5, q = nil, r = "hi" });
|
||||
|
||||
type X = typeof(x)
|
||||
type Y = typeof(y)
|
||||
type Z = typeof(z)
|
||||
|
||||
local xy : X&Y = z;
|
||||
local yx : Y&X = z;
|
||||
z = xy;
|
||||
z = yx;
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_subtypes")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x = setmetatable({ a = 5 }, { p = 5 });
|
||||
local y = setmetatable({ b = "hi" }, { p = 5, q = "hi" });
|
||||
local z = setmetatable({ a = 5, b = "hi" }, { p = 5, q = "hi" });
|
||||
local x = setmetatable({ a = 5 }, { p = 5 })
|
||||
local y = setmetatable({ b = "hi" }, { p = 5, q = "hi" })
|
||||
local z = setmetatable({ a = 5, b = "hi" }, { p = 5, q = "hi" })
|
||||
|
||||
type X = typeof(x)
|
||||
type Y = typeof(y)
|
||||
type Z = typeof(z)
|
||||
|
||||
local xy : X&Y = z;
|
||||
local yx : Y&X = z;
|
||||
z = xy;
|
||||
z = yx;
|
||||
function f(xy: X&Y, yx: Y&X): (Z, Z)
|
||||
return xy, yx
|
||||
end
|
||||
|
||||
f(z, z)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables_with_properties")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x = setmetatable({ a = 5 }, { p = 5 });
|
||||
local y = setmetatable({ b = "hi" }, { q = "hi" });
|
||||
local z = setmetatable({ a = 5, b = "hi" }, { p = 5, q = "hi" });
|
||||
local x = setmetatable({ a = 5 }, { p = 5 })
|
||||
local y = setmetatable({ b = "hi" }, { q = "hi" })
|
||||
local z = setmetatable({ a = 5, b = "hi" }, { p = 5, q = "hi" })
|
||||
|
||||
type X = typeof(x)
|
||||
type Y = typeof(y)
|
||||
type Z = typeof(z)
|
||||
|
||||
local xy : X&Y = z;
|
||||
z = xy;
|
||||
function f(xy: X&Y): Z
|
||||
return xy
|
||||
end
|
||||
|
||||
f(z)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -872,22 +943,44 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables_with_properties")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_with_table")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x = setmetatable({ a = 5 }, { p = 5 });
|
||||
local z = setmetatable({ a = 5, b = "hi" }, { p = 5 });
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x = setmetatable({ a = 5 }, { p = 5 })
|
||||
local z = setmetatable({ a = 5, b = "hi" }, { p = 5 })
|
||||
|
||||
type X = typeof(x)
|
||||
type Y = { b : string }
|
||||
type Z = typeof(z)
|
||||
type X = typeof(x)
|
||||
type Y = { b : string }
|
||||
type Z = typeof(z)
|
||||
|
||||
-- TODO: once we have shape types, we should be able to initialize these with z
|
||||
local xy : X&Y;
|
||||
local yx : Y&X;
|
||||
z = xy;
|
||||
z = yx;
|
||||
)");
|
||||
function f(xy: X&Y, yx: Y&X): (Z, Z)
|
||||
return xy, yx
|
||||
end
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
f(z, z)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x = setmetatable({ a = 5 }, { p = 5 });
|
||||
local z = setmetatable({ a = 5, b = "hi" }, { p = 5 });
|
||||
|
||||
type X = typeof(x)
|
||||
type Y = { b : string }
|
||||
type Z = typeof(z)
|
||||
|
||||
-- TODO: once we have shape types, we should be able to initialize these with z
|
||||
local xy : X&Y;
|
||||
local yx : Y&X;
|
||||
z = xy;
|
||||
z = yx;
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "CLI-44817")
|
||||
@ -900,11 +993,11 @@ TEST_CASE_FIXTURE(Fixture, "CLI-44817")
|
||||
type XY = {x: number, y: number}
|
||||
type XYZ = {x:number, y: number, z: number}
|
||||
|
||||
local xy: XY = {x = 0, y = 0}
|
||||
local xyz: XYZ = {x = 0, y = 0, z = 0}
|
||||
function f(xy: XY, xyz: XYZ): (X&Y, X&Y&Z)
|
||||
return xy, xyz
|
||||
end
|
||||
|
||||
local xNy: X&Y = xy
|
||||
local xNyNz: X&Y&Z = xyz
|
||||
local xNy, xNyNz = f({x = 0, y = 0}, {x = 0, y = 0, z = 0})
|
||||
|
||||
local t1: XY = xNy -- Type 'X & Y' could not be converted into 'XY'
|
||||
local t2: XY = xNyNz -- Type 'X & Y & Z' could not be converted into 'XY'
|
||||
@ -955,8 +1048,9 @@ type Foo = {
|
||||
Bar: string,
|
||||
} & { Baz: number }
|
||||
|
||||
local x: Foo = { Bar = "1", Baz = 2 }
|
||||
local y = x.Bar
|
||||
function f(x: Foo)
|
||||
return x.Bar
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -969,8 +1063,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_property_table_intersection_2")
|
||||
Bar: string,
|
||||
} & { Baz: number }
|
||||
|
||||
local x: Foo = { Bar = "1", Baz = 2 }
|
||||
local y = x["Bar"]
|
||||
function f(x: Foo)
|
||||
return x["Bar"]
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -990,14 +1085,13 @@ TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_degenerate_intersections")
|
||||
}
|
||||
|
||||
type C = A & B
|
||||
local obj: C = {
|
||||
x = 3,
|
||||
}
|
||||
|
||||
local x: number = obj.x or 3
|
||||
function f(obj: C): number
|
||||
return obj.x or 3
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_more_realistic_intersections")
|
||||
@ -1016,14 +1110,13 @@ TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_more_realistic_intersections")
|
||||
}
|
||||
|
||||
type C = A & B
|
||||
local obj: C = {
|
||||
x = 3,
|
||||
}
|
||||
|
||||
local x: number = obj.x or 3
|
||||
function f(obj: C): number
|
||||
return obj.x or 3
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -365,9 +365,9 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
// FIXME: This could be improved by expanding the contents of `a`
|
||||
const std::string expectedError =
|
||||
"Type 'a' could not be converted into 'Err<number> | Ok<string>'; type a (a) is not a subtype of Err<number> | Ok<string>[1] (Err<number>)\n"
|
||||
"\ttype a[\"success\"] (false) is not exactly Err<number> | Ok<string>[0][\"success\"] (true)";
|
||||
"Type 'a' could not be converted into 'Err<number> | Ok<string>'";
|
||||
|
||||
CHECK(toString(result.errors[0]) == expectedError);
|
||||
}
|
||||
|
@ -250,7 +250,14 @@ TEST_CASE_FIXTURE(Fixture, "tc_member_function_2")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "call_method")
|
||||
{
|
||||
CheckResult result = check("local T = {} T.x = 0 function T:method() return self.x end local a = T:method()");
|
||||
CheckResult result = check(R"(
|
||||
local T = {}
|
||||
T.x = 0
|
||||
function T:method()
|
||||
return self.x
|
||||
end
|
||||
local a = T:method()
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(*builtinTypes->numberType, *requireType("a"));
|
||||
@ -258,7 +265,17 @@ TEST_CASE_FIXTURE(Fixture, "call_method")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "call_method_with_explicit_self_argument")
|
||||
{
|
||||
CheckResult result = check("local T = {} T.x = 0 function T:method() return self.x end local a = T.method(T)");
|
||||
CheckResult result = check(R"(
|
||||
local T = {}
|
||||
T.x = 0
|
||||
|
||||
function T:method()
|
||||
return self.x
|
||||
end
|
||||
|
||||
local a = T.method(T)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
@ -843,10 +860,9 @@ TEST_CASE_FIXTURE(Fixture, "array_factory_function")
|
||||
TEST_CASE_FIXTURE(Fixture, "sealed_table_indexers_must_unify")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local A = { 5, 7, 8 }
|
||||
local B = { "one", "two", "three" }
|
||||
|
||||
B = A
|
||||
function f(a: {number}): {string}
|
||||
return a
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -857,10 +873,9 @@ TEST_CASE_FIXTURE(Fixture, "sealed_table_indexers_must_unify")
|
||||
TEST_CASE_FIXTURE(Fixture, "indexer_on_sealed_table_must_unify_with_free_table")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local A = { 1, 2, 3 }
|
||||
function F(t)
|
||||
function F(t): {number}
|
||||
t[4] = "hi"
|
||||
A = t
|
||||
return t
|
||||
end
|
||||
)");
|
||||
|
||||
@ -870,8 +885,11 @@ TEST_CASE_FIXTURE(Fixture, "indexer_on_sealed_table_must_unify_with_free_table")
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_type_when_indexing_from_a_table_indexer")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t: { [number]: string }
|
||||
local s = t[1]
|
||||
function f(t: {string})
|
||||
return t[1]
|
||||
end
|
||||
|
||||
local s = f({})
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -879,10 +897,15 @@ TEST_CASE_FIXTURE(Fixture, "infer_type_when_indexing_from_a_table_indexer")
|
||||
CHECK_EQ(*builtinTypes->stringType, *requireType("s"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "indexing_from_a_table_should_prefer_properties_when_possible")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "indexing_from_a_table_should_prefer_properties_when_possible")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t: { a: string, [string]: number }
|
||||
function f(): { a: string, [string]: number }
|
||||
error("e")
|
||||
end
|
||||
|
||||
local t = f()
|
||||
|
||||
local a1 = t.a
|
||||
local a2 = t["a"]
|
||||
|
||||
@ -1143,7 +1166,7 @@ TEST_CASE_FIXTURE(Fixture, "user_defined_table_types_are_named")
|
||||
CheckResult result = check(R"(
|
||||
type Vector3 = {x: number, y: number}
|
||||
|
||||
local v: Vector3
|
||||
local v: Vector3 = {x = 5, y = 7}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -1185,8 +1208,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "result_is_always_any_if_lhs_is_any")
|
||||
TEST_CASE_FIXTURE(Fixture, "result_is_bool_for_equality_operators_if_lhs_is_any")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a: any
|
||||
local b: number
|
||||
function f(): (any, number)
|
||||
return 5, 7
|
||||
end
|
||||
|
||||
local a: any, b: number = f()
|
||||
|
||||
local c = a < b
|
||||
)");
|
||||
@ -1290,13 +1316,14 @@ TEST_CASE_FIXTURE(Fixture, "pass_incompatible_union_to_a_generic_table_without_c
|
||||
CheckResult result = check(R"(
|
||||
-- must be in this specific order, and with (roughly) those exact properties!
|
||||
type A = {x: number, [any]: any} | {}
|
||||
local a: A
|
||||
|
||||
function f(t)
|
||||
t.y = 1
|
||||
end
|
||||
|
||||
f(a)
|
||||
function g(a: A)
|
||||
f(a)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -1313,7 +1340,9 @@ TEST_CASE_FIXTURE(Fixture, "passing_compatible_unions_to_a_generic_table_without
|
||||
t.y = 1
|
||||
end
|
||||
|
||||
f({y = 5} :: A)
|
||||
function g(a: A)
|
||||
f(a)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -1532,10 +1561,9 @@ TEST_CASE_FIXTURE(Fixture, "right_table_missing_key")
|
||||
TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local lt: { [string]: string, a: string }
|
||||
local rt: {}
|
||||
|
||||
lt = rt
|
||||
function f(t: {}): { [string]: string, a: string }
|
||||
return t
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
@ -1647,22 +1675,31 @@ TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer4")
|
||||
TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multiple_errors")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local vec3 = {x = 1, y = 2, z = 3}
|
||||
local vec1 = {x = 1}
|
||||
|
||||
vec3 = vec1
|
||||
function f(vec1: {x: number}): {x: number, y: number, z: number}
|
||||
return vec1
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
MissingProperties* mp = get<MissingProperties>(result.errors[0]);
|
||||
REQUIRE(mp);
|
||||
CHECK_EQ(mp->context, MissingProperties::Missing);
|
||||
REQUIRE_EQ(2, mp->properties.size());
|
||||
CHECK_EQ(mp->properties[0], "y");
|
||||
CHECK_EQ(mp->properties[1], "z");
|
||||
CHECK_EQ("vec3", toString(mp->superType));
|
||||
CHECK_EQ("vec1", toString(mp->subType));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }'"
|
||||
" at [0], { x: number } is not a subtype of { x: number, y: number, z: number }",
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
MissingProperties* mp = get<MissingProperties>(result.errors[0]);
|
||||
REQUIRE_MESSAGE(mp, result.errors[0]);
|
||||
CHECK_EQ(mp->context, MissingProperties::Missing);
|
||||
REQUIRE_EQ(2, mp->properties.size());
|
||||
CHECK_EQ(mp->properties[0], "y");
|
||||
CHECK_EQ(mp->properties[1], "z");
|
||||
|
||||
CHECK_EQ("{| x: number, y: number, z: number |}", toString(mp->superType));
|
||||
CHECK_EQ("{| x: number |}", toString(mp->subType));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multiple_errors2")
|
||||
@ -1687,8 +1724,8 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_extra_props_dont_report_multipl
|
||||
function mkvec3() return {x = 1, y = 2, z = 3} end
|
||||
function mkvec1() return {x = 1} end
|
||||
|
||||
local vec3 = {mkvec3()}
|
||||
local vec1 = {mkvec1()}
|
||||
local vec3: {{x: number, y: number, z: number}} = {mkvec3()}
|
||||
local vec1: {{x: number}} = {mkvec1()}
|
||||
|
||||
vec1 = vec3
|
||||
)");
|
||||
@ -1697,8 +1734,17 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_extra_props_dont_report_multipl
|
||||
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK_EQ("vec1", toString(tm->wantedType));
|
||||
CHECK_EQ("vec3", toString(tm->givenType));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("vec1", toString(tm->wantedType));
|
||||
CHECK_EQ("vec3", toString(tm->givenType));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("{{| x: number |}}", toString(tm->wantedType));
|
||||
CHECK_EQ("{{| x: number, y: number, z: number |}}", toString(tm->givenType));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_extra_props_is_ok")
|
||||
|
@ -226,6 +226,9 @@ TEST_CASE_FIXTURE(Fixture, "variadic_packs")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
CHECK(Location{Position{3, 21}, Position{3, 26}} == result.errors[0].location);
|
||||
CHECK(Location{Position{4, 29}, Position{4, 30}} == result.errors[1].location);
|
||||
|
||||
CHECK_EQ(
|
||||
result.errors[0], (TypeError{Location(Position{3, 21}, Position{3, 26}), TypeMismatch{builtinTypes->numberType, builtinTypes->stringType}}));
|
||||
|
||||
@ -1015,7 +1018,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks2")
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
CHECK("Unknown type 't0'" == toString(result.errors[0]));
|
||||
CHECK(get<FunctionExitsWithoutReturning>(result.errors[1]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments")
|
||||
|
596
tests/VecDeque.test.cpp
Normal file
596
tests/VecDeque.test.cpp
Normal file
@ -0,0 +1,596 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/VecDeque.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
TEST_SUITE_BEGIN("VecDequeTests");
|
||||
|
||||
TEST_CASE("forward_queue_test_no_initial_capacity")
|
||||
{
|
||||
// initial capacity is not set, so this should grow to be 11
|
||||
Luau::VecDeque<int> queue{};
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
queue.push_back(i);
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
CHECK_EQ(queue.capacity(), 11);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
CHECK_EQ(queue.front(), j);
|
||||
CHECK_EQ(queue.back(), 9);
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
queue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("forward_queue_test")
|
||||
{
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<int> queue;
|
||||
queue.reserve(5);
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
queue.push_back(i);
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
CHECK_EQ(queue.capacity(), 13);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
CHECK_EQ(queue.front(), j);
|
||||
CHECK_EQ(queue.back(), 9);
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
queue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("forward_queue_test_initializer_list")
|
||||
{
|
||||
Luau::VecDeque<int> queue{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
CHECK_EQ(queue.capacity(), 10);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
CHECK_EQ(queue.front(), j);
|
||||
CHECK_EQ(queue.back(), 9);
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
queue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("reverse_queue_test")
|
||||
{
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<int> queue;
|
||||
queue.reserve(5);
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
queue.push_front(i);
|
||||
// q: 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
CHECK_EQ(queue.capacity(), 13);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
CHECK_EQ(queue.front(), 9);
|
||||
CHECK_EQ(queue.back(), j);
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
queue.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("random_access_queue_test")
|
||||
{
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<int> queue;
|
||||
queue.reserve(5);
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
queue.push_back(i);
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
CHECK_EQ(queue.at(j), j);
|
||||
CHECK_EQ(queue[j], j);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("clear_works_on_queue")
|
||||
{
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<int> queue;
|
||||
queue.reserve(5);
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
queue.push_back(i);
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue[j], j);
|
||||
|
||||
queue.clear();
|
||||
CHECK(queue.empty());
|
||||
CHECK(queue.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("pop_front_at_end")
|
||||
{
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<int> queue;
|
||||
queue.reserve(5);
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
// setting up the internal buffer to be: 1234567890 by the end (i.e. 0 at the end of the buffer)
|
||||
queue.push_front(0);
|
||||
|
||||
for (int i = 1; i < 10; i++)
|
||||
queue.push_back(i);
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
CHECK_EQ(queue.front(), j);
|
||||
CHECK_EQ(queue.back(), 9);
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
queue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("pop_back_at_front")
|
||||
{
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<int> queue;
|
||||
queue.reserve(5);
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
// setting up the internal buffer to be: 9012345678 by the end (i.e. 9 at the front of the buffer)
|
||||
queue.push_back(0);
|
||||
|
||||
for (int i = 1; i < 10; i++)
|
||||
queue.push_front(i);
|
||||
// q: 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
CHECK_EQ(queue.front(), 9);
|
||||
CHECK_EQ(queue.back(), j);
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
queue.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("queue_is_contiguous")
|
||||
{
|
||||
// initial capacity is not set, so this should grow to be 11
|
||||
Luau::VecDeque<int> queue{};
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
queue.push_back(i);
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
CHECK_EQ(queue.capacity(), 11);
|
||||
CHECK(queue.is_contiguous());
|
||||
}
|
||||
|
||||
TEST_CASE("queue_is_not_contiguous")
|
||||
{
|
||||
// initial capacity is not set, so this should grow to be 11
|
||||
Luau::VecDeque<int> queue{};
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 5; i < 10; i++)
|
||||
queue.push_back(i);
|
||||
for (int i = 4; i >= 0; i--)
|
||||
queue.push_front(i);
|
||||
// buffer: 56789......01234
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
CHECK_EQ(queue.capacity(), 11);
|
||||
CHECK(!queue.is_contiguous());
|
||||
|
||||
// checking that it is indeed sequential integers from 0 to 9
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue[j], j);
|
||||
}
|
||||
|
||||
TEST_CASE("shrink_to_fit_works")
|
||||
{
|
||||
// initial capacity is not set, so this should grow to be 11
|
||||
Luau::VecDeque<int> queue{};
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 5; i < 10; i++)
|
||||
queue.push_back(i);
|
||||
for (int i = 4; i >= 0; i--)
|
||||
queue.push_front(i);
|
||||
// buffer: 56789......01234
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
REQUIRE_EQ(queue.capacity(), 11);
|
||||
CHECK(!queue.is_contiguous());
|
||||
|
||||
// checking that it is indeed sequential integers from 0 to 9
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue[j], j);
|
||||
|
||||
queue.shrink_to_fit();
|
||||
// shrink to fit always makes a contiguous buffer
|
||||
CHECK(queue.is_contiguous());
|
||||
// the capacity should be exactly the size now
|
||||
CHECK_EQ(queue.capacity(), queue.size());
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
|
||||
// checking that it is still sequential integers from 0 to 9
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue[j], j);
|
||||
}
|
||||
|
||||
// To avoid hitting SSO issues, we need sufficiently long strings.
|
||||
// This list of test strings consists of quotes from Ursula K. Le Guin.
|
||||
const static std::string testStrings[] = {
|
||||
"Love doesn't just sit there, like a stone, it has to be made, like bread; remade all the time, made new.",
|
||||
"People who deny the existence of dragons are often eaten by dragons. From within.",
|
||||
"It is good to have an end to journey toward; but it is the journey that matters, in the end.",
|
||||
"We're each of us alone, to be sure. What can you do but hold your hand out in the dark?",
|
||||
"When you light a candle, you also cast a shadow.",
|
||||
"You cannot buy the revolution. You cannot make the revolution. You can only be the revolution. It is in your spirit, or it is nowhere.",
|
||||
"To learn which questions are unanswerable, and not to answer them: this skill is most needful in times of stress and darkness.",
|
||||
"What sane person could live in this world and not be crazy?",
|
||||
"The only thing that makes life possible is permanent, intolerable uncertainty: not knowing what comes next.",
|
||||
"My imagination makes me human and makes me a fool; it gives me all the world and exiles me from it."
|
||||
};
|
||||
|
||||
TEST_CASE("string_queue_test_no_initial_capacity")
|
||||
{
|
||||
// initial capacity is not set, so this should grow to be 11
|
||||
Luau::VecDeque<std::string> queue;
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
queue.push_back(testStrings[i]);
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
CHECK_EQ(queue.capacity(), 11);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
CHECK_EQ(queue.front(), testStrings[j]);
|
||||
CHECK_EQ(queue.back(), testStrings[9]);
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
queue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("string_queue_test")
|
||||
{
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<std::string> queue;
|
||||
queue.reserve(5);
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
queue.push_back(testStrings[i]);
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
CHECK_EQ(queue.capacity(), 13);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
CHECK_EQ(queue.front(), testStrings[j]);
|
||||
CHECK_EQ(queue.back(), testStrings[9]);
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
queue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("string_queue_test_initializer_list")
|
||||
{
|
||||
Luau::VecDeque<std::string> queue{
|
||||
testStrings[0],
|
||||
testStrings[1],
|
||||
testStrings[2],
|
||||
testStrings[3],
|
||||
testStrings[4],
|
||||
testStrings[5],
|
||||
testStrings[6],
|
||||
testStrings[7],
|
||||
testStrings[8],
|
||||
testStrings[9],
|
||||
};
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
CHECK_EQ(queue.capacity(), 10);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
CHECK_EQ(queue.front(), testStrings[j]);
|
||||
CHECK_EQ(queue.back(), testStrings[9]);
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
queue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("reverse_string_queue_test")
|
||||
{
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<std::string> queue;
|
||||
queue.reserve(5);
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
queue.push_front(testStrings[i]);
|
||||
// q: 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
CHECK_EQ(queue.capacity(), 13);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
CHECK_EQ(queue.front(), testStrings[9]);
|
||||
CHECK_EQ(queue.back(), testStrings[j]);
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
queue.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("random_access_string_queue_test")
|
||||
{
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<std::string> queue;
|
||||
queue.reserve(5);
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
queue.push_back(testStrings[i]);
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
CHECK_EQ(queue.at(j), testStrings[j]);
|
||||
CHECK_EQ(queue[j], testStrings[j]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("clear_works_on_string_queue")
|
||||
{
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<std::string> queue;
|
||||
queue.reserve(5);
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
queue.push_back(testStrings[i]);
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue[j], testStrings[j]);
|
||||
|
||||
queue.clear();
|
||||
CHECK(queue.empty());
|
||||
CHECK(queue.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("pop_front_string_at_end")
|
||||
{
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<std::string> queue;
|
||||
queue.reserve(5);
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
// setting up the internal buffer to be: 1234567890 by the end (i.e. 0 at the end of the buffer)
|
||||
queue.push_front(testStrings[0]);
|
||||
|
||||
for (int i = 1; i < 10; i++)
|
||||
queue.push_back(testStrings[i]);
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
CHECK_EQ(queue.front(), testStrings[j]);
|
||||
CHECK_EQ(queue.back(), testStrings[9]);
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
queue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("pop_back_string_at_front")
|
||||
{
|
||||
// initial capacity set to 5 so that a grow is necessary
|
||||
Luau::VecDeque<std::string> queue;
|
||||
queue.reserve(5);
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
// setting up the internal buffer to be: 9012345678 by the end (i.e. 9 at the front of the buffer)
|
||||
queue.push_back(testStrings[0]);
|
||||
|
||||
for (int i = 1; i < 10; i++)
|
||||
queue.push_front(testStrings[i]);
|
||||
// q: 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
CHECK_EQ(queue.front(), testStrings[9]);
|
||||
CHECK_EQ(queue.back(), testStrings[j]);
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
queue.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("string_queue_is_contiguous")
|
||||
{
|
||||
// initial capacity is not set, so this should grow to be 11
|
||||
Luau::VecDeque<std::string> queue{};
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
queue.push_back(testStrings[i]);
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
CHECK_EQ(queue.capacity(), 11);
|
||||
CHECK(queue.is_contiguous());
|
||||
}
|
||||
|
||||
TEST_CASE("string_queue_is_not_contiguous")
|
||||
{
|
||||
// initial capacity is not set, so this should grow to be 11
|
||||
Luau::VecDeque<std::string> queue{};
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 5; i < 10; i++)
|
||||
queue.push_back(testStrings[i]);
|
||||
for (int i = 4; i >= 0; i--)
|
||||
queue.push_front(testStrings[i]);
|
||||
// buffer: 56789......01234
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
CHECK_EQ(queue.capacity(), 11);
|
||||
CHECK(!queue.is_contiguous());
|
||||
|
||||
// checking that it is indeed sequential integers from 0 to 9
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue[j], testStrings[j]);
|
||||
}
|
||||
|
||||
TEST_CASE("shrink_to_fit_works_with_strings")
|
||||
{
|
||||
// initial capacity is not set, so this should grow to be 11
|
||||
Luau::VecDeque<std::string> queue{};
|
||||
|
||||
REQUIRE(queue.empty());
|
||||
|
||||
for (int i = 5; i < 10; i++)
|
||||
queue.push_back(testStrings[i]);
|
||||
for (int i = 4; i >= 0; i--)
|
||||
queue.push_front(testStrings[i]);
|
||||
// buffer: 56789......01234
|
||||
// q: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
REQUIRE(queue.size() == 10);
|
||||
|
||||
REQUIRE_EQ(queue.capacity(), 11);
|
||||
CHECK(!queue.is_contiguous());
|
||||
|
||||
// checking that it is indeed sequential integers from 0 to 9
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue[j], testStrings[j]);
|
||||
|
||||
queue.shrink_to_fit();
|
||||
// shrink to fit always makes a contiguous buffer
|
||||
CHECK(queue.is_contiguous());
|
||||
// the capacity should be exactly the size now
|
||||
CHECK_EQ(queue.capacity(), queue.size());
|
||||
|
||||
REQUIRE(!queue.empty());
|
||||
|
||||
// checking that it is still sequential integers from 0 to 9
|
||||
for (int j = 0; j < 10; j++)
|
||||
CHECK_EQ(queue[j], testStrings[j]);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
@ -23,6 +23,10 @@ assert(tostring(0.1) == "0.1")
|
||||
assert(tostring(-0.17) == "-0.17")
|
||||
assert(tostring(math.pi) == "3.141592653589793")
|
||||
|
||||
-- scientific
|
||||
assert(tostring(1e+30) == "1e+30")
|
||||
assert(tostring(-1e+24) == "-1e+24")
|
||||
|
||||
-- fuzzing corpus
|
||||
-- Note: If the assert below fires it may indicate floating point denormalized values
|
||||
-- are not handled as expected.
|
||||
@ -32,7 +36,7 @@ assert(tostring(4.4154895841930002e-305) == "4.415489584193e-305")
|
||||
assert(tostring(1125968630513728) == "1125968630513728")
|
||||
assert(tostring(3.3951932655938423e-313) == "3.3951932656e-313")
|
||||
assert(tostring(1.625) == "1.625")
|
||||
assert(tostring(4.9406564584124654e-324) == "5.e-324")
|
||||
assert(tostring(4.9406564584124654e-324) == "5e-324")
|
||||
assert(tostring(2.0049288280105384) == "2.0049288280105384")
|
||||
assert(tostring(3.0517578125e-05) == "0.000030517578125")
|
||||
assert(tostring(1.383544921875) == "1.383544921875")
|
||||
|
185
tools/codesizeprediction.py
Normal file
185
tools/codesizeprediction.py
Normal file
@ -0,0 +1,185 @@
|
||||
#!/usr/bin/python3
|
||||
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
# NOTE: This script is experimental. This script uses a linear regression to construct a model for predicting native
|
||||
# code size from bytecode. Some initial work has been done to analyze a large corpus of Luau scripts, and while for
|
||||
# most functions the model predicts the native code size quite well (+/-25%), there are many cases where the predicted
|
||||
# size is off by as much as 13x. Notably, the predicted size is generally better for smaller functions and worse for
|
||||
# larger functions. Therefore, in its current form this analysis is probably not suitable for use as a basis for
|
||||
# compilation heuristics. A nonlinear model may produce better results. The script here exists as a foundation for
|
||||
# further exploration.
|
||||
|
||||
|
||||
import json
|
||||
import glob
|
||||
from pathlib import Path
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from sklearn.linear_model import LinearRegression
|
||||
import matplotlib.pyplot as plt
|
||||
import argparse
|
||||
|
||||
|
||||
def readStats(statsFileGlob):
|
||||
'''Reads files matching the supplied glob.
|
||||
Files should be generated by the Compile.cpp CLI'''
|
||||
|
||||
statsFiles = glob.glob(statsFileGlob, recursive=True)
|
||||
|
||||
print("Reading %s files." % len(statsFiles))
|
||||
|
||||
df_dict = {
|
||||
"statsFile": [],
|
||||
"script": [],
|
||||
"name": [],
|
||||
"line": [],
|
||||
"bcodeCount": [],
|
||||
"irCount": [],
|
||||
"asmCount": [],
|
||||
"bytecodeSummary": []
|
||||
}
|
||||
|
||||
for statsFile in statsFiles:
|
||||
stats = json.loads(Path(statsFile).read_text())
|
||||
for script, filestats in stats.items():
|
||||
for funstats in filestats["lowerStats"]["functions"]:
|
||||
df_dict["statsFile"].append(statsFile)
|
||||
df_dict["script"].append(script)
|
||||
df_dict["name"].append(funstats["name"])
|
||||
df_dict["line"].append(funstats["line"])
|
||||
df_dict["bcodeCount"].append(funstats["bcodeCount"])
|
||||
df_dict["irCount"].append(funstats["irCount"])
|
||||
df_dict["asmCount"].append(funstats["asmCount"])
|
||||
df_dict["bytecodeSummary"].append(
|
||||
tuple(funstats["bytecodeSummary"][0]))
|
||||
|
||||
return pd.DataFrame.from_dict(df_dict)
|
||||
|
||||
|
||||
def addFunctionCount(df):
|
||||
df2 = df.drop_duplicates(subset=['asmCount', 'bytecodeSummary'], ignore_index=True).groupby(
|
||||
['bytecodeSummary']).size().reset_index(name='functionCount')
|
||||
return df.merge(df2, on='bytecodeSummary', how='left')
|
||||
|
||||
# def deduplicateDf(df):
|
||||
# return df.drop_duplicates(subset=['bcodeCount', 'asmCount', 'bytecodeSummary'], ignore_index=True)
|
||||
|
||||
|
||||
def randomizeDf(df):
|
||||
return df.sample(frac=1)
|
||||
|
||||
|
||||
def splitSeq(seq):
|
||||
n = len(seq) // 2
|
||||
return (seq[:n], seq[n:])
|
||||
|
||||
|
||||
def trainAsmSizePredictor(df):
|
||||
XTrain, XValidate = splitSeq(
|
||||
np.array([list(seq) for seq in df.bytecodeSummary]))
|
||||
YTrain, YValidate = splitSeq(np.array(df.asmCount))
|
||||
|
||||
reg = LinearRegression(
|
||||
positive=True, fit_intercept=False).fit(XTrain, YTrain)
|
||||
YPredict1 = reg.predict(XTrain)
|
||||
YPredict2 = reg.predict(XValidate)
|
||||
|
||||
trainRmse = np.sqrt(np.mean((np.array(YPredict1) - np.array(YTrain))**2))
|
||||
predictRmse = np.sqrt(
|
||||
np.mean((np.array(YPredict2) - np.array(YValidate))**2))
|
||||
|
||||
print(f"Score: {reg.score(XTrain, YTrain)}")
|
||||
print(f"Training RMSE: {trainRmse}")
|
||||
print(f"Prediction RMSE: {predictRmse}")
|
||||
print(f"Model Intercept: {reg.intercept_}")
|
||||
print(f"Model Coefficients:\n{reg.coef_}")
|
||||
|
||||
df.loc[:, 'asmCountPredicted'] = np.concatenate(
|
||||
(YPredict1, YPredict2)).round().astype(int)
|
||||
df['usedForTraining'] = np.concatenate(
|
||||
(np.repeat(True, YPredict1.size), np.repeat(False, YPredict2.size)))
|
||||
df['diff'] = df['asmCountPredicted'] - df['asmCount']
|
||||
df['diffPerc'] = (100 * df['diff']) / df['asmCount']
|
||||
df.loc[(df["diffPerc"] == np.inf), 'diffPerc'] = 0.0
|
||||
df['diffPerc'] = df['diffPerc'].round()
|
||||
|
||||
return (reg, df)
|
||||
|
||||
|
||||
def saveModel(reg, file):
|
||||
f = open(file, "w")
|
||||
f.write(f"Intercept: {reg.intercept_}\n")
|
||||
f.write(f"Coefficients: \n{reg.coef_}\n")
|
||||
f.close()
|
||||
|
||||
|
||||
def bcodeVsAsmPlot(df, plotFile=None, minBcodeCount=None, maxBcodeCount=None):
|
||||
if minBcodeCount is None:
|
||||
minBcodeCount = df.bcodeCount.min()
|
||||
if maxBcodeCount is None:
|
||||
maxBcodeCount = df.bcodeCount.max()
|
||||
|
||||
subDf = df[(df.bcodeCount <= maxBcodeCount) &
|
||||
(df.bcodeCount >= minBcodeCount)]
|
||||
|
||||
plt.scatter(subDf.bcodeCount, subDf.asmCount)
|
||||
plt.title("ASM variation by Bytecode")
|
||||
plt.xlabel("Bytecode Instruction Count")
|
||||
plt.ylabel("ASM Instruction Count")
|
||||
|
||||
if plotFile is not None:
|
||||
plt.savefig(plotFile)
|
||||
|
||||
return plt
|
||||
|
||||
|
||||
def predictionErrorPlot(df, plotFile=None, minPerc=None, maxPerc=None, bins=200):
|
||||
if minPerc is None:
|
||||
minPerc = df['diffPerc'].min()
|
||||
if maxPerc is None:
|
||||
maxPerc = df['diffPerc'].max()
|
||||
|
||||
plotDf = df[(df["usedForTraining"] == False) & (
|
||||
df["diffPerc"] >= minPerc) & (df["diffPerc"] <= maxPerc)]
|
||||
|
||||
plt.hist(plotDf["diffPerc"], bins=bins)
|
||||
plt.title("Prediction Error Distribution")
|
||||
plt.xlabel("Prediction Error %")
|
||||
plt.ylabel("Function Count")
|
||||
|
||||
if plotFile is not None:
|
||||
plt.savefig(plotFile)
|
||||
|
||||
return plt
|
||||
|
||||
|
||||
def parseArgs():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='codesizeprediction.py',
|
||||
description='Constructs a linear regression model to predict native instruction count from bytecode opcode distribution')
|
||||
parser.add_argument("fileglob",
|
||||
help="glob pattern for stats files to be used for training")
|
||||
parser.add_argument("modelfile",
|
||||
help="text file to save model details")
|
||||
parser.add_argument("--nativesizefig",
|
||||
help="path for saving the plot showing the variation of native code size with bytecode")
|
||||
parser.add_argument("--predictionerrorfig",
|
||||
help="path for saving the plot showing the distribution of prediction error")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parseArgs()
|
||||
|
||||
df0 = readStats(args.fileglob)
|
||||
df1 = addFunctionCount(df0)
|
||||
df2 = randomizeDf(df1)
|
||||
|
||||
plt = bcodeVsAsmPlot(df2, args.nativesizefig, 0, 100)
|
||||
plt.show()
|
||||
|
||||
(reg, df4) = trainAsmSizePredictor(df2)
|
||||
saveModel(reg, args.modelfile)
|
||||
|
||||
plt = predictionErrorPlot(df4, args.predictionerrorfig, -200, 200)
|
||||
plt.show()
|
@ -4,11 +4,11 @@ AstQuery::getDocumentationSymbolAtPosition.table_overloaded_function_prop
|
||||
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
|
||||
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
|
||||
AutocompleteTest.autocomplete_interpolated_string_as_singleton
|
||||
AutocompleteTest.autocomplete_response_perf1
|
||||
AutocompleteTest.autocomplete_string_singleton_equality
|
||||
AutocompleteTest.autocomplete_string_singleton_escape
|
||||
AutocompleteTest.autocomplete_string_singletons
|
||||
AutocompleteTest.do_wrong_compatible_nonself_calls
|
||||
AutocompleteTest.string_singleton_in_if_statement
|
||||
AutocompleteTest.suggest_external_module_type
|
||||
AutocompleteTest.type_correct_expected_argument_type_pack_suggestion
|
||||
AutocompleteTest.type_correct_expected_argument_type_suggestion
|
||||
@ -23,7 +23,6 @@ AutocompleteTest.type_correct_suggestion_for_overloads
|
||||
AutocompleteTest.type_correct_suggestion_in_argument
|
||||
BuiltinTests.aliased_string_format
|
||||
BuiltinTests.assert_removes_falsy_types
|
||||
BuiltinTests.assert_removes_falsy_types2
|
||||
BuiltinTests.assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type
|
||||
BuiltinTests.assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy
|
||||
BuiltinTests.bad_select_should_not_crash
|
||||
@ -41,11 +40,8 @@ BuiltinTests.ipairs_iterator_should_infer_types_and_type_check
|
||||
BuiltinTests.next_iterator_should_infer_types_and_type_check
|
||||
BuiltinTests.os_time_takes_optional_date_table
|
||||
BuiltinTests.pairs_iterator_should_infer_types_and_type_check
|
||||
BuiltinTests.see_thru_select
|
||||
BuiltinTests.see_thru_select_count
|
||||
BuiltinTests.select_slightly_out_of_range
|
||||
BuiltinTests.select_way_out_of_range
|
||||
BuiltinTests.select_with_decimal_argument_is_rounded_down
|
||||
BuiltinTests.select_with_variadic_typepack_tail_and_string_head
|
||||
BuiltinTests.set_metatable_needs_arguments
|
||||
BuiltinTests.setmetatable_should_not_mutate_persisted_types
|
||||
@ -62,14 +58,8 @@ BuiltinTests.table_dot_remove_optionally_returns_generic
|
||||
BuiltinTests.table_freeze_is_generic
|
||||
BuiltinTests.table_insert_correctly_infers_type_of_array_2_args_overload
|
||||
BuiltinTests.table_insert_correctly_infers_type_of_array_3_args_overload
|
||||
BuiltinTests.table_pack
|
||||
BuiltinTests.table_pack_reduce
|
||||
BuiltinTests.table_pack_variadic
|
||||
BuiltinTests.tonumber_returns_optional_number_type
|
||||
BuiltinTests.tonumber_returns_optional_number_type2
|
||||
BuiltinTests.trivial_select
|
||||
BuiltinTests.xpcall
|
||||
ControlFlowAnalysis.do_assert_x
|
||||
ControlFlowAnalysis.for_record_do_if_not_x_break
|
||||
ControlFlowAnalysis.for_record_do_if_not_x_continue
|
||||
ControlFlowAnalysis.if_not_x_break
|
||||
@ -90,8 +80,6 @@ ControlFlowAnalysis.if_not_x_continue_if_not_y_continue
|
||||
ControlFlowAnalysis.if_not_x_continue_if_not_y_throw
|
||||
ControlFlowAnalysis.if_not_x_return_elif_not_y_break
|
||||
ControlFlowAnalysis.if_not_x_return_elif_not_y_fallthrough_elif_not_z_break
|
||||
ControlFlowAnalysis.if_not_x_then_assert_false
|
||||
ControlFlowAnalysis.if_not_x_then_error
|
||||
ControlFlowAnalysis.prototyping_and_visiting_alias_has_the_same_scope_breaking
|
||||
ControlFlowAnalysis.prototyping_and_visiting_alias_has_the_same_scope_continuing
|
||||
ControlFlowAnalysis.tagged_unions
|
||||
@ -112,7 +100,6 @@ Differ.equal_table_kind_B
|
||||
Differ.equal_table_kind_C
|
||||
Differ.equal_table_kind_D
|
||||
Differ.equal_table_measuring_tapes
|
||||
Differ.equal_table_two_cyclic_tables_are_not_different
|
||||
Differ.equal_table_two_shifted_circles_are_not_different
|
||||
Differ.generictp_normal
|
||||
Differ.generictp_normal_2
|
||||
@ -122,7 +109,6 @@ Differ.metatable_metamissing_left
|
||||
Differ.metatable_metamissing_right
|
||||
Differ.metatable_metanormal
|
||||
Differ.negation
|
||||
Differ.right_cyclic_table_left_table_missing_property
|
||||
Differ.right_cyclic_table_left_table_property_wrong
|
||||
Differ.table_left_circle_right_measuring_tape
|
||||
FrontendTest.accumulate_cached_errors_in_consistent_order
|
||||
@ -159,6 +145,7 @@ GenericsTests.generic_type_pack_unification1
|
||||
GenericsTests.generic_type_pack_unification2
|
||||
GenericsTests.generic_type_pack_unification3
|
||||
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
|
||||
GenericsTests.hof_subtype_instantiation_regression
|
||||
GenericsTests.infer_generic_function
|
||||
GenericsTests.infer_generic_function_function_argument
|
||||
GenericsTests.infer_generic_function_function_argument_2
|
||||
@ -179,17 +166,11 @@ GenericsTests.rank_N_types_via_typeof
|
||||
GenericsTests.self_recursive_instantiated_param
|
||||
GenericsTests.type_parameters_can_be_polytypes
|
||||
GenericsTests.typefuns_sharing_types
|
||||
IntersectionTypes.argument_is_intersection
|
||||
IntersectionTypes.error_detailed_intersection_all
|
||||
IntersectionTypes.error_detailed_intersection_part
|
||||
IntersectionTypes.fx_intersection_as_argument
|
||||
IntersectionTypes.index_on_an_intersection_type_with_mixed_types
|
||||
IntersectionTypes.index_on_an_intersection_type_with_one_part_missing_the_property
|
||||
IntersectionTypes.index_on_an_intersection_type_with_one_property_of_type_any
|
||||
IntersectionTypes.index_on_an_intersection_type_with_property_guaranteed_to_exist
|
||||
IntersectionTypes.index_on_an_intersection_type_works_at_arbitrary_depth
|
||||
IntersectionTypes.intersect_bool_and_false
|
||||
IntersectionTypes.intersect_false_and_bool_and_false
|
||||
IntersectionTypes.intersect_metatables
|
||||
IntersectionTypes.intersect_saturate_overloaded_functions
|
||||
IntersectionTypes.intersection_of_tables
|
||||
IntersectionTypes.intersection_of_tables_with_never_properties
|
||||
@ -208,16 +189,10 @@ IntersectionTypes.overloadeded_functions_with_weird_typepacks_1
|
||||
IntersectionTypes.overloadeded_functions_with_weird_typepacks_2
|
||||
IntersectionTypes.overloadeded_functions_with_weird_typepacks_3
|
||||
IntersectionTypes.overloadeded_functions_with_weird_typepacks_4
|
||||
IntersectionTypes.propagates_name
|
||||
IntersectionTypes.select_correct_union_fn
|
||||
IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions
|
||||
IntersectionTypes.table_extra_ok
|
||||
IntersectionTypes.table_intersection_setmetatable
|
||||
IntersectionTypes.table_intersection_write_sealed
|
||||
IntersectionTypes.table_intersection_write_sealed_indirect
|
||||
IntersectionTypes.table_write_sealed_indirect
|
||||
IntersectionTypes.union_saturate_overloaded_functions
|
||||
isSubtype.any_is_unknown_union_error
|
||||
Linter.DeprecatedApiFenv
|
||||
Linter.FormatStringTyped
|
||||
Linter.TableOperationsIndexer
|
||||
@ -236,7 +211,6 @@ Normalize.higher_order_function_with_annotation
|
||||
Normalize.negations_of_tables
|
||||
Normalize.specific_functions_cannot_be_negated
|
||||
ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal
|
||||
ProvisionalTests.choose_the_right_overload_for_pcall
|
||||
ProvisionalTests.discriminate_from_x_not_equal_to_nil
|
||||
ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
|
||||
ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean
|
||||
@ -245,15 +219,12 @@ ProvisionalTests.floating_generics_should_not_be_allowed
|
||||
ProvisionalTests.free_is_not_bound_to_any
|
||||
ProvisionalTests.free_options_can_be_unified_together
|
||||
ProvisionalTests.free_options_cannot_be_unified_together
|
||||
ProvisionalTests.function_returns_many_things_but_first_of_it_is_forgotten
|
||||
ProvisionalTests.generic_type_leak_to_module_interface
|
||||
ProvisionalTests.generic_type_leak_to_module_interface_variadic
|
||||
ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns
|
||||
ProvisionalTests.it_should_be_agnostic_of_actual_size
|
||||
ProvisionalTests.luau-polyfill.Array.filter
|
||||
ProvisionalTests.luau_roact_useState_minimization
|
||||
ProvisionalTests.optional_class_instances_are_invariant
|
||||
ProvisionalTests.pcall_returns_at_least_two_value_but_function_returns_nothing
|
||||
ProvisionalTests.setmetatable_constrains_free_type_into_free_table
|
||||
ProvisionalTests.specialization_binds_with_prototypes_too_early
|
||||
ProvisionalTests.table_insert_with_a_singleton_argument
|
||||
@ -262,15 +233,12 @@ ProvisionalTests.typeguard_inference_incomplete
|
||||
ProvisionalTests.while_body_are_also_refined
|
||||
ProvisionalTests.xpcall_returns_what_f_returns
|
||||
RefinementTest.assert_a_to_be_truthy_then_assert_a_to_be_number
|
||||
RefinementTest.assert_non_binary_expressions_actually_resolve_constraints
|
||||
RefinementTest.correctly_lookup_a_shadowed_local_that_which_was_previously_refined
|
||||
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined
|
||||
RefinementTest.dataflow_analysis_can_tell_refinements_when_its_appropriate_to_refine_into_nil_or_never
|
||||
RefinementTest.discriminate_from_isa_of_x
|
||||
RefinementTest.discriminate_from_truthiness_of_x
|
||||
RefinementTest.discriminate_tag
|
||||
RefinementTest.discriminate_tag_with_implicit_else
|
||||
RefinementTest.either_number_or_string
|
||||
RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union
|
||||
RefinementTest.fail_to_refine_a_property_of_subscript_expression
|
||||
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
|
||||
@ -280,7 +248,6 @@ RefinementTest.index_on_a_refined_property
|
||||
RefinementTest.isa_type_refinement_must_be_known_ahead_of_time
|
||||
RefinementTest.luau_polyfill_isindexkey_refine_conjunction
|
||||
RefinementTest.luau_polyfill_isindexkey_refine_conjunction_variant
|
||||
RefinementTest.merge_should_be_fully_agnostic_of_hashmap_ordering
|
||||
RefinementTest.narrow_property_of_a_bounded_variable
|
||||
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
||||
RefinementTest.not_t_or_some_prop_of_t
|
||||
@ -292,12 +259,9 @@ RefinementTest.refinements_should_preserve_error_suppression
|
||||
RefinementTest.string_not_equal_to_string_or_nil
|
||||
RefinementTest.truthy_constraint_on_properties
|
||||
RefinementTest.type_annotations_arent_relevant_when_doing_dataflow_analysis
|
||||
RefinementTest.type_comparison_ifelse_expression
|
||||
RefinementTest.type_guard_narrowed_into_nothingness
|
||||
RefinementTest.type_narrow_to_vector
|
||||
RefinementTest.typeguard_cast_free_table_to_vector
|
||||
RefinementTest.typeguard_in_assert_position
|
||||
RefinementTest.typeguard_in_if_condition_position
|
||||
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
||||
RefinementTest.x_is_not_instance_or_else_not_part
|
||||
TableTests.a_free_shape_can_turn_into_a_scalar_directly
|
||||
@ -306,8 +270,6 @@ TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
|
||||
TableTests.accidentally_checked_prop_in_opposite_branch
|
||||
TableTests.any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict_mode
|
||||
TableTests.array_factory_function
|
||||
TableTests.call_method
|
||||
TableTests.call_method_with_explicit_self_argument
|
||||
TableTests.casting_tables_with_props_into_table_with_indexer2
|
||||
TableTests.casting_tables_with_props_into_table_with_indexer3
|
||||
TableTests.casting_tables_with_props_into_table_with_indexer4
|
||||
@ -339,12 +301,9 @@ TableTests.explicitly_typed_table_with_indexer
|
||||
TableTests.generalize_table_argument
|
||||
TableTests.generic_table_instantiation_potential_regression
|
||||
TableTests.indexer_mismatch
|
||||
TableTests.indexer_on_sealed_table_must_unify_with_free_table
|
||||
TableTests.indexers_get_quantified_too
|
||||
TableTests.indexing_from_a_table_should_prefer_properties_when_possible
|
||||
TableTests.inequality_operators_imply_exactly_matching_types
|
||||
TableTests.infer_indexer_from_its_variable_type_and_unifiable
|
||||
TableTests.infer_type_when_indexing_from_a_table_indexer
|
||||
TableTests.inferred_return_type_of_free_table
|
||||
TableTests.instantiate_table_cloning_3
|
||||
TableTests.invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound
|
||||
@ -367,6 +326,7 @@ TableTests.oop_polymorphic
|
||||
TableTests.open_table_unification_2
|
||||
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table
|
||||
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2
|
||||
TableTests.pass_incompatible_union_to_a_generic_table_without_crashing
|
||||
TableTests.passing_compatible_unions_to_a_generic_table_without_crashing
|
||||
TableTests.persistent_sealed_table_is_immutable
|
||||
TableTests.prop_access_on_key_whose_types_mismatches
|
||||
@ -376,7 +336,6 @@ TableTests.quantify_metatables_of_metatables_of_table
|
||||
TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table
|
||||
TableTests.recursive_metatable_type_call
|
||||
TableTests.result_is_always_any_if_lhs_is_any
|
||||
TableTests.result_is_bool_for_equality_operators_if_lhs_is_any
|
||||
TableTests.right_table_missing_key2
|
||||
TableTests.scalar_is_a_subtype_of_a_compatible_polymorphic_shape_type
|
||||
TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type
|
||||
@ -386,10 +345,8 @@ TableTests.shared_selfs
|
||||
TableTests.shared_selfs_from_free_param
|
||||
TableTests.shared_selfs_through_metatables
|
||||
TableTests.table_call_metamethod_basic
|
||||
TableTests.table_call_metamethod_generic
|
||||
TableTests.table_call_metamethod_must_be_callable
|
||||
TableTests.table_function_check_use_after_free
|
||||
TableTests.table_param_width_subtyping_1
|
||||
TableTests.table_param_width_subtyping_2
|
||||
TableTests.table_param_width_subtyping_3
|
||||
TableTests.table_simple_call
|
||||
@ -404,7 +361,6 @@ TableTests.unification_of_unions_in_a_self_referential_type
|
||||
TableTests.unifying_tables_shouldnt_uaf1
|
||||
TableTests.used_colon_instead_of_dot
|
||||
TableTests.used_dot_instead_of_colon
|
||||
TableTests.used_dot_instead_of_colon_but_correctly
|
||||
TableTests.when_augmenting_an_unsealed_table_with_an_indexer_apply_the_correct_scope_to_the_indexer_type
|
||||
TableTests.wrong_assign_does_hit_indexer
|
||||
ToDot.function
|
||||
@ -419,15 +375,12 @@ ToString.toStringDetailed2
|
||||
ToString.toStringErrorPack
|
||||
ToString.toStringNamedFunction_generic_pack
|
||||
ToString.toStringNamedFunction_map
|
||||
TranspilerTests.types_should_not_be_considered_cyclic_if_they_are_not_recursive
|
||||
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
|
||||
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
|
||||
TryUnifyTests.typepack_unification_should_trim_free_tails
|
||||
TryUnifyTests.uninhabited_table_sub_anything
|
||||
TryUnifyTests.uninhabited_table_sub_never
|
||||
TryUnifyTests.variadics_should_use_reversed_properly
|
||||
TypeAliases.corecursive_types_generic
|
||||
TypeAliases.cyclic_types_of_named_table_fields_do_not_expand_when_stringified
|
||||
TypeAliases.dont_lose_track_of_PendingExpansionTypes_after_substitution
|
||||
TypeAliases.generic_param_remap
|
||||
TypeAliases.mismatched_generic_type_param
|
||||
@ -450,6 +403,7 @@ TypeFamilyTests.family_as_fn_arg
|
||||
TypeFamilyTests.family_as_fn_ret
|
||||
TypeFamilyTests.function_internal_families
|
||||
TypeFamilyTests.internal_families_raise_errors
|
||||
TypeFamilyTests.keyof_rfc_example
|
||||
TypeFamilyTests.table_internal_families
|
||||
TypeFamilyTests.type_families_inhabited_with_normalization
|
||||
TypeFamilyTests.unsolvable_family
|
||||
@ -477,6 +431,7 @@ TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parame
|
||||
TypeInfer.statements_are_topologically_sorted
|
||||
TypeInfer.stringify_nested_unions_with_optionals
|
||||
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
||||
TypeInfer.tc_if_else_expressions_expected_type_3
|
||||
TypeInfer.type_infer_recursion_limit_no_ice
|
||||
TypeInfer.type_infer_recursion_limit_normalizer
|
||||
TypeInfer.unify_nearly_identical_recursive_types
|
||||
@ -545,7 +500,6 @@ TypeInferFunctions.infer_return_value_type
|
||||
TypeInferFunctions.infer_that_function_does_not_return_a_table
|
||||
TypeInferFunctions.inferred_higher_order_functions_are_quantified_at_the_right_time3
|
||||
TypeInferFunctions.instantiated_type_packs_must_have_a_non_null_scope
|
||||
TypeInferFunctions.it_is_ok_to_oversaturate_a_higher_order_function_argument
|
||||
TypeInferFunctions.list_all_overloads_if_no_overload_takes_given_argument_count
|
||||
TypeInferFunctions.list_only_alternative_overloads_that_match_argument_count
|
||||
TypeInferFunctions.luau_subtyping_is_np_hard
|
||||
@ -569,7 +523,6 @@ TypeInferFunctions.too_many_arguments_error_location
|
||||
TypeInferFunctions.too_many_return_values_in_parentheses
|
||||
TypeInferFunctions.too_many_return_values_no_function
|
||||
TypeInferFunctions.toposort_doesnt_break_mutual_recursion
|
||||
TypeInferFunctions.vararg_function_is_quantified
|
||||
TypeInferLoops.cli_68448_iterators_need_not_accept_nil
|
||||
TypeInferLoops.dcr_iteration_explore_raycast_minimization
|
||||
TypeInferLoops.dcr_iteration_fragmented_keys
|
||||
@ -628,12 +581,10 @@ TypeInferOperators.concat_op_on_string_lhs_and_free_rhs
|
||||
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
|
||||
TypeInferOperators.equality_operations_succeed_if_any_union_branch_succeeds
|
||||
TypeInferOperators.error_on_invalid_operand_types_to_relational_operators2
|
||||
TypeInferOperators.luau-polyfill.String.slice
|
||||
TypeInferOperators.luau_polyfill_is_array
|
||||
TypeInferOperators.mm_comparisons_must_return_a_boolean
|
||||
TypeInferOperators.normalize_strings_comparison
|
||||
TypeInferOperators.operator_eq_verifies_types_do_intersect
|
||||
TypeInferOperators.primitive_arith_no_metatable
|
||||
TypeInferOperators.reducing_and
|
||||
TypeInferOperators.refine_and_or
|
||||
TypeInferOperators.reworked_and
|
||||
@ -654,6 +605,8 @@ TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_neve
|
||||
TypeInferUnknownNever.length_of_never
|
||||
TypeInferUnknownNever.math_operators_and_never
|
||||
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable
|
||||
TypePackTests.detect_cyclic_typepacks2
|
||||
TypePackTests.fuzz_typepack_iter_follow_2
|
||||
TypePackTests.pack_tail_unification_check
|
||||
TypePackTests.type_alias_backwards_compatible
|
||||
TypePackTests.type_alias_default_type_errors
|
||||
@ -669,10 +622,8 @@ TypeSingletons.function_call_with_singletons_mismatch
|
||||
TypeSingletons.return_type_of_f_is_not_widened
|
||||
TypeSingletons.table_properties_type_error_escapes
|
||||
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
|
||||
TypeStatesTest.invalidate_type_refinements_upon_assignments
|
||||
UnionTypes.error_detailed_optional
|
||||
UnionTypes.error_detailed_union_all
|
||||
UnionTypes.error_takes_optional_arguments
|
||||
UnionTypes.generic_function_with_optional_arg
|
||||
UnionTypes.index_on_a_union_type_with_missing_property
|
||||
UnionTypes.less_greedy_unification_with_union_types
|
||||
|
@ -4,7 +4,7 @@
|
||||
<Type Name="::lua_TValue">
|
||||
<DisplayString Condition="tt == lua_Type::LUA_TNIL">nil</DisplayString>
|
||||
<DisplayString Condition="tt == lua_Type::LUA_TBOOLEAN">{(bool)value.b}</DisplayString>
|
||||
<DisplayString Condition="tt == lua_Type::LUA_TLIGHTUSERDATA">lightuserdata {value.p}</DisplayString>
|
||||
<DisplayString Condition="tt == lua_Type::LUA_TLIGHTUSERDATA">lightuserdata {(uintptr_t)value.p,h} tag: {extra[0]}</DisplayString>
|
||||
<DisplayString Condition="tt == lua_Type::LUA_TNUMBER">number = {value.n}</DisplayString>
|
||||
<DisplayString Condition="tt == lua_Type::LUA_TVECTOR">vector = {value.v[0]}, {value.v[1]}, {*(float*)&extra}</DisplayString>
|
||||
<DisplayString Condition="tt == lua_Type::LUA_TSTRING">{value.gc->ts}</DisplayString>
|
||||
@ -28,6 +28,7 @@
|
||||
<Item Name="[buffer]" Condition="tt == lua_Type::LUA_TBUFFER">value.gc->buf</Item>
|
||||
<Item Name="[proto]" Condition="tt == lua_Type::LUA_TPROTO">value.gc->p</Item>
|
||||
<Item Name="[upvalue]" Condition="tt == lua_Type::LUA_TUPVAL">value.gc->uv</Item>
|
||||
<Item Name="[tag]" Condition="tt == lua_Type::LUA_TLIGHTUSERDATA">extra[0]</Item>
|
||||
<Synthetic Name="[gc]" Condition="tt >= lua_Type::LUA_TSTRING">
|
||||
<DisplayString Condition="value.gc->gch.marked & 8">fixed ({(int)value.gc->gch.marked})</DisplayString>
|
||||
<DisplayString Condition="value.gc->gch.marked & 4">black ({(int)value.gc->gch.marked})</DisplayString>
|
||||
@ -41,7 +42,7 @@
|
||||
<Type Name="::TKey">
|
||||
<DisplayString Condition="tt == lua_Type::LUA_TNIL">nil</DisplayString>
|
||||
<DisplayString Condition="tt == lua_Type::LUA_TBOOLEAN">{(bool)value.b}</DisplayString>
|
||||
<DisplayString Condition="tt == lua_Type::LUA_TLIGHTUSERDATA">lightuserdata {value.p}</DisplayString>
|
||||
<DisplayString Condition="tt == lua_Type::LUA_TLIGHTUSERDATA">lightuserdata {(uintptr_t)value.p,h} tag: {extra[0]}</DisplayString>
|
||||
<DisplayString Condition="tt == lua_Type::LUA_TNUMBER">number = {value.n}</DisplayString>
|
||||
<DisplayString Condition="tt == lua_Type::LUA_TVECTOR">vector = {value.v[0]}, {value.v[1]}, {*(float*)&extra}</DisplayString>
|
||||
<DisplayString Condition="tt == lua_Type::LUA_TSTRING">{value.gc->ts}</DisplayString>
|
||||
@ -66,6 +67,7 @@
|
||||
<Item Name="[proto]" Condition="tt == lua_Type::LUA_TPROTO">value.gc->p</Item>
|
||||
<Item Name="[upvalue]" Condition="tt == lua_Type::LUA_TUPVAL">value.gc->uv</Item>
|
||||
|
||||
<Item Name="[tag]" Condition="tt == lua_Type::LUA_TLIGHTUSERDATA">extra[0]</Item>
|
||||
<Item Name="[next]">next</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
@ -60,29 +60,35 @@ def getDuration(nodes, nid):
|
||||
node = nodes[nid - 1]
|
||||
total = node['TotalDuration']
|
||||
|
||||
for cid in node['NodeIds']:
|
||||
total -= nodes[cid - 1]['TotalDuration']
|
||||
if 'NodeIds' in node:
|
||||
for cid in node['NodeIds']:
|
||||
total -= nodes[cid - 1]['TotalDuration']
|
||||
|
||||
return total
|
||||
|
||||
def getFunctionKey(fn):
|
||||
return fn['Source'] + "," + fn['Name'] + "," + str(fn['Line'])
|
||||
source = fn['Source'] if 'Source' in fn else ''
|
||||
name = fn['Name'] if 'Name' in fn else ''
|
||||
line = str(fn['Line']) if 'Line' in fn else '-1'
|
||||
|
||||
return source + "," + name + "," + line
|
||||
|
||||
def recursivelyBuildNodeTree(nodes, functions, parent, fid, nid):
|
||||
ninfo = nodes[nid - 1]
|
||||
finfo = functions[fid - 1]
|
||||
|
||||
child = parent.child(getFunctionKey(finfo))
|
||||
child.source = finfo['Source']
|
||||
child.function = finfo['Name']
|
||||
child.line = int(finfo['Line']) if finfo['Line'] > 0 else 0
|
||||
child.source = finfo['Source'] if 'Source' in finfo else ''
|
||||
child.function = finfo['Name'] if 'Name' in finfo else ''
|
||||
child.line = int(finfo['Line']) if 'Line' in finfo and finfo['Line'] > 0 else 0
|
||||
|
||||
child.ticks = getDuration(nodes, nid)
|
||||
|
||||
assert(len(ninfo['FunctionIds']) == len(ninfo['NodeIds']))
|
||||
if 'FunctionIds' in ninfo:
|
||||
assert(len(ninfo['FunctionIds']) == len(ninfo['NodeIds']))
|
||||
|
||||
for i in range(0, len(ninfo['FunctionIds'])):
|
||||
recursivelyBuildNodeTree(nodes, functions, child, ninfo['FunctionIds'][i], ninfo['NodeIds'][i])
|
||||
for i in range(0, len(ninfo['FunctionIds'])):
|
||||
recursivelyBuildNodeTree(nodes, functions, child, ninfo['FunctionIds'][i], ninfo['NodeIds'][i])
|
||||
|
||||
return
|
||||
|
||||
@ -104,10 +110,11 @@ def nodeFromJSONV2(dump):
|
||||
child.function = name
|
||||
child.ticks = getDuration(nodes, nid)
|
||||
|
||||
assert(len(node['FunctionIds']) == len(node['NodeIds']))
|
||||
if 'FunctionIds' in node:
|
||||
assert(len(node['FunctionIds']) == len(node['NodeIds']))
|
||||
|
||||
for i in range(0, len(node['FunctionIds'])):
|
||||
recursivelyBuildNodeTree(nodes, functions, child, node['FunctionIds'][i], node['NodeIds'][i])
|
||||
for i in range(0, len(node['FunctionIds'])):
|
||||
recursivelyBuildNodeTree(nodes, functions, child, node['FunctionIds'][i], node['NodeIds'][i])
|
||||
|
||||
return root
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user