diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index ceb9cab4..bb358abb 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -420,6 +420,11 @@ public: void throwUserCancelError() const; ToStringOptions opts; + + void fillInDiscriminantTypes( + NotNull constraint, + const std::vector>& discriminantTypes + ); }; void dump(NotNull rootScope, struct ToStringOptions& opts); diff --git a/Analysis/include/Luau/TypeFunctionRuntime.h b/Analysis/include/Luau/TypeFunctionRuntime.h index 356d34a5..d715ccd3 100644 --- a/Analysis/include/Luau/TypeFunctionRuntime.h +++ b/Analysis/include/Luau/TypeFunctionRuntime.h @@ -119,7 +119,14 @@ struct TypeFunctionVariadicTypePack TypeFunctionTypeId type; }; -using TypeFunctionTypePackVariant = Variant; +struct TypeFunctionGenericTypePack +{ + bool isNamed = false; + + std::string name; +}; + +using TypeFunctionTypePackVariant = Variant; struct TypeFunctionTypePackVar { @@ -135,6 +142,9 @@ struct TypeFunctionTypePackVar struct TypeFunctionFunctionType { + std::vector generics; + std::vector genericPacks; + TypeFunctionTypePackId argTypes; TypeFunctionTypePackId retTypes; }; @@ -210,6 +220,14 @@ struct TypeFunctionClassType std::string name; }; +struct TypeFunctionGenericType +{ + bool isNamed = false; + bool isPack = false; + + std::string name; +}; + using TypeFunctionTypeVariant = Luau::Variant< TypeFunctionPrimitiveType, TypeFunctionAnyType, @@ -221,7 +239,8 @@ using TypeFunctionTypeVariant = Luau::Variant< TypeFunctionNegationType, TypeFunctionFunctionType, TypeFunctionTableType, - TypeFunctionClassType>; + TypeFunctionClassType, + TypeFunctionGenericType>; struct TypeFunctionType { diff --git a/Analysis/src/AstQuery.cpp b/Analysis/src/AstQuery.cpp index 93dabeae..96c4ea10 100644 --- a/Analysis/src/AstQuery.cpp +++ b/Analysis/src/AstQuery.cpp @@ -13,8 +13,6 @@ LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAGVARIABLE(LuauDocumentationAtPosition) - namespace Luau { @@ -518,7 +516,6 @@ static std::optional getMetatableDocumentation( const AstName& index ) { - LUAU_ASSERT(FFlag::LuauDocumentationAtPosition); auto indexIt = mtable->props.find("__index"); if (indexIt == mtable->props.end()) return std::nullopt; @@ -575,26 +572,7 @@ std::optional getDocumentationSymbolAtPosition(const Source } else if (const ClassType* ctv = get(parentTy)) { - if (FFlag::LuauDocumentationAtPosition) - { - while (ctv) - { - if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end()) - { - if (FFlag::LuauSolverV2) - { - if (auto ty = propIt->second.readTy) - return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol); - } - else - return checkOverloadedDocumentationSymbol( - module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol - ); - } - ctv = ctv->parent ? Luau::get(*ctv->parent) : nullptr; - } - } - else + while (ctv) { if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end()) { @@ -608,17 +586,15 @@ std::optional getDocumentationSymbolAtPosition(const Source module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol ); } + ctv = ctv->parent ? Luau::get(*ctv->parent) : nullptr; } } - else if (FFlag::LuauDocumentationAtPosition) + else if (const PrimitiveType* ptv = get(parentTy); ptv && ptv->metatable) { - if (const PrimitiveType* ptv = get(parentTy); ptv && ptv->metatable) + if (auto mtable = get(*ptv->metatable)) { - if (auto mtable = get(*ptv->metatable)) - { - if (std::optional docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index)) - return docSymbol; - } + if (std::optional docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index)) + return docSymbol; } } } diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 2db6d567..fba3c964 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -31,6 +31,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauTypestateBuiltins2) LUAU_FASTFLAGVARIABLE(LuauStringFormatArityFix) +LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression) LUAU_FASTFLAG(AutocompleteRequirePathSuggestions2) LUAU_FASTFLAG(LuauVectorDefinitionsExtra) @@ -631,10 +632,29 @@ static void dcrMagicFunctionTypeCheckFormat(MagicFunctionTypeCheckContext contex Location location = context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location; // use subtyping instead here SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope); + if (!result.isSubtype) { - Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result); - context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); + if (FFlag::LuauStringFormatErrorSuppression) + { + switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy)) + { + case ErrorSuppression::Suppress: + break; + case ErrorSuppression::NormalizationFailed: + break; + case ErrorSuppression::DoNotSuppress: + Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result); + + if (!reasonings.suppressed) + context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); + } + } + else + { + Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result); + context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); + } } } } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 1be02a71..e6e54916 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -37,6 +37,7 @@ LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations) LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer) LUAU_FASTFLAG(LuauUserTypeFunNoExtraConstraint) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) +LUAU_FASTFLAGVARIABLE(LuauAlwaysFillInFunctionCallDiscriminantTypes) namespace Luau { @@ -1153,6 +1154,42 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul return true; } +void ConstraintSolver::fillInDiscriminantTypes( + NotNull constraint, + const std::vector>& discriminantTypes +) +{ + for (std::optional ty : discriminantTypes) + { + if (!ty) + continue; + + // If the discriminant type has been transmuted, we need to unblock them. + if (!isBlocked(*ty)) + { + unblock(*ty, constraint->location); + continue; + } + + if (FFlag::LuauRemoveNotAnyHack) + { + // We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored. + emplaceType(asMutable(follow(*ty)), builtinTypes->noRefineType); + } + else + { + // We use `any` here because the discriminant type may be pointed at by both branches, + // where the discriminant type is not negated, and the other where it is negated, i.e. + // `unknown ~ unknown` and `~unknown ~ never`, so `T & unknown ~ T` and `T & ~unknown ~ never` + // v.s. + // `any ~ any` and `~any ~ any`, so `T & any ~ T` and `T & ~any ~ T` + // + // In practice, users cannot negate `any`, so this is an implementation detail we can always change. + emplaceType(asMutable(follow(*ty)), builtinTypes->anyType); + } + } +} + bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull constraint) { TypeId fn = follow(c.fn); @@ -1168,6 +1205,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull(asMutable(c.result), builtinTypes->anyTypePack); unblock(c.result, constraint->location); + if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) + fillInDiscriminantTypes(constraint, c.discriminantTypes); return true; } @@ -1175,12 +1214,16 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull(fn)) { bind(constraint, c.result, builtinTypes->errorRecoveryTypePack()); + if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) + fillInDiscriminantTypes(constraint, c.discriminantTypes); return true; } if (get(fn)) { bind(constraint, c.result, builtinTypes->neverTypePack); + if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) + fillInDiscriminantTypes(constraint, c.discriminantTypes); return true; } @@ -1261,36 +1304,45 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull(constraint, c.result, constraint->scope); } - for (std::optional ty : c.discriminantTypes) + if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) { - if (!ty) - continue; + fillInDiscriminantTypes(constraint, c.discriminantTypes); + } + else + { + // NOTE: This is the body of the `fillInDiscriminantTypes` helper. + for (std::optional ty : c.discriminantTypes) + { + if (!ty) + continue; - // If the discriminant type has been transmuted, we need to unblock them. - if (!isBlocked(*ty)) - { - unblock(*ty, constraint->location); - continue; - } + // If the discriminant type has been transmuted, we need to unblock them. + if (!isBlocked(*ty)) + { + unblock(*ty, constraint->location); + continue; + } - if (FFlag::LuauRemoveNotAnyHack) - { - // We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored. - emplaceType(asMutable(follow(*ty)), builtinTypes->noRefineType); - } - else - { - // We use `any` here because the discriminant type may be pointed at by both branches, - // where the discriminant type is not negated, and the other where it is negated, i.e. - // `unknown ~ unknown` and `~unknown ~ never`, so `T & unknown ~ T` and `T & ~unknown ~ never` - // v.s. - // `any ~ any` and `~any ~ any`, so `T & any ~ T` and `T & ~any ~ T` - // - // In practice, users cannot negate `any`, so this is an implementation detail we can always change. - emplaceType(asMutable(follow(*ty)), builtinTypes->anyType); + if (FFlag::LuauRemoveNotAnyHack) + { + // We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored. + emplaceType(asMutable(follow(*ty)), builtinTypes->noRefineType); + } + else + { + // We use `any` here because the discriminant type may be pointed at by both branches, + // where the discriminant type is not negated, and the other where it is negated, i.e. + // `unknown ~ unknown` and `~unknown ~ never`, so `T & unknown ~ T` and `T & ~unknown ~ never` + // v.s. + // `any ~ any` and `~any ~ any`, so `T & any ~ T` and `T & ~any ~ T` + // + // In practice, users cannot negate `any`, so this is an implementation detail we can always change. + emplaceType(asMutable(follow(*ty)), builtinTypes->anyType); + } } } + OverloadResolver resolver{ builtinTypes, NotNull{arena}, diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index e794588f..937c414c 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -2,14 +2,14 @@ #include "Luau/BuiltinDefinitions.h" LUAU_FASTFLAGVARIABLE(LuauVectorDefinitionsExtra) -LUAU_FASTFLAG(LuauBufferBitMethods) LUAU_FASTFLAG(LuauVector2Constructor) +LUAU_FASTFLAG(LuauBufferBitMethods2) +LUAU_FASTFLAGVARIABLE(LuauMathMapDefinition) namespace Luau { -// TODO: there has to be a better way, like splitting up per library -static const std::string kBuiltinDefinitionLuaSrcChecked = R"BUILTIN_SRC( +static const std::string kBuiltinDefinitionLuaSrcChecked_DEPRECATED = R"BUILTIN_SRC( declare bit32: { band: @checked (...number) -> number, @@ -202,6 +202,228 @@ declare function unpack(tab: {V}, i: number?, j: number?): ...V )BUILTIN_SRC"; +static const std::string kBuiltinDefinitionBaseSrc = R"BUILTIN_SRC( + +@checked declare function require(target: any): any + +@checked declare function getfenv(target: any): { [string]: any } + +declare _G: any +declare _VERSION: string + +declare function gcinfo(): number + +declare function print(...: T...) + +declare function type(value: T): string +declare function typeof(value: T): string + +-- `assert` has a magic function attached that will give more detailed type information +declare function assert(value: T, errorMessage: string?): T +declare function error(message: T, level: number?): never + +declare function tostring(value: T): string +declare function tonumber(value: T, radix: number?): number? + +declare function rawequal(a: T1, b: T2): boolean +declare function rawget(tab: {[K]: V}, k: K): V +declare function rawset(tab: {[K]: V}, k: K, v: V): {[K]: V} +declare function rawlen(obj: {[K]: V} | string): number + +declare function setfenv(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)? + +declare function ipairs(tab: {V}): (({V}, number) -> (number?, V), {V}, number) + +declare function pcall(f: (A...) -> R..., ...: A...): (boolean, R...) + +-- FIXME: The actual type of `xpcall` is: +-- (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(f: (A...) -> R1..., err: (E) -> R2..., ...: A...): (boolean, R1...) + +-- `select` has a magic function attached to provide more detailed type information +declare function select(i: string | number, ...: A...): ...any + +-- FIXME: This type is not entirely correct - `loadstring` returns a function or +-- (nil, string). +declare function loadstring(src: string, chunkname: string?): (((A...) -> any)?, string?) + +@checked declare function newproxy(mt: boolean?): any + +-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype. +declare function unpack(tab: {V}, i: number?, j: number?): ...V + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionBit32Src = 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, +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionMathSrc = R"BUILTIN_SRC( + +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, + map: @checked (x: number, inmin: number, inmax: number, outmin: number, outmax: number) -> number, + lerp: @checked (a: number, b: number, t: number) -> number, +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionOsSrc = R"BUILTIN_SRC( + +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: (time: DateTypeArg?) -> number, + date: ((formatString: "*t" | "!*t", time: number?) -> DateTypeResult) & ((formatString: string?, time: number?) -> string), + difftime: (t2: DateTypeResult | number, t1: DateTypeResult | number) -> number, + clock: () -> number, +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionCoroutineSrc = R"BUILTIN_SRC( + +declare coroutine: { + create: (f: (A...) -> R...) -> thread, + resume: (co: thread, A...) -> (boolean, R...), + running: () -> thread, + status: @checked (co: thread) -> "dead" | "running" | "normal" | "suspended", + wrap: (f: (A...) -> R...) -> ((A...) -> R...), + yield: (A...) -> R..., + isyieldable: () -> boolean, + close: @checked (co: thread) -> (boolean, any) +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionTableSrc = R"BUILTIN_SRC( + +declare table: { + concat: (t: {V}, sep: string?, i: number?, j: number?) -> string, + insert: ((t: {V}, value: V) -> ()) & ((t: {V}, pos: number, value: V) -> ()), + maxn: (t: {V}) -> number, + remove: (t: {V}, number?) -> V?, + sort: (t: {V}, comp: ((V, V) -> boolean)?) -> (), + create: (count: number, value: V?) -> {V}, + find: (haystack: {V}, needle: V, init: number?) -> number?, + + unpack: (list: {V}, i: number?, j: number?) -> ...V, + pack: (...V) -> { n: number, [number]: V }, + + getn: (t: {V}) -> number, + foreach: (t: {[K]: V}, f: (K, V) -> ()) -> (), + foreachi: ({V}, (number, V) -> ()) -> (), + + move: (src: {V}, a: number, b: number, t: number, dst: {V}?) -> {V}, + clear: (table: {[K]: V}) -> (), + + isfrozen: (t: {[K]: V}) -> boolean, +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionDebugSrc = R"BUILTIN_SRC( + +declare debug: { + info: ((thread: thread, level: number, options: string) -> R...) & ((level: number, options: string) -> R...) & ((func: (A...) -> R1..., options: string) -> R2...), + traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string), +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC( + +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, +} + +)BUILTIN_SRC"; + static const std::string kBuiltinDefinitionBufferSrc_DEPRECATED = R"BUILTIN_SRC( --- Buffer API declare buffer: { @@ -380,9 +602,20 @@ declare vector: { std::string getBuiltinDefinitionSource() { - std::string result = kBuiltinDefinitionLuaSrcChecked; + std::string result = FFlag::LuauMathMapDefinition ? kBuiltinDefinitionBaseSrc : kBuiltinDefinitionLuaSrcChecked_DEPRECATED; - result += FFlag::LuauBufferBitMethods ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED; + if (FFlag::LuauMathMapDefinition) + { + result += kBuiltinDefinitionBit32Src; + result += kBuiltinDefinitionMathSrc; + result += kBuiltinDefinitionOsSrc; + result += kBuiltinDefinitionCoroutineSrc; + result += kBuiltinDefinitionTableSrc; + result += kBuiltinDefinitionDebugSrc; + result += kBuiltinDefinitionUtf8Src; + } + + result += FFlag::LuauBufferBitMethods2 ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED; if (FFlag::LuauVectorDefinitionsExtra) { diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index bb0309fc..3766224b 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -18,6 +18,8 @@ LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixInner) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunPrintToError) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixNoReadWrite) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunThreadBuffer) +LUAU_FASTFLAGVARIABLE(LuauUserTypeFunGenerics) +LUAU_FASTFLAGVARIABLE(LuauUserTypeFunCloneTail) namespace Luau { @@ -161,6 +163,8 @@ static std::string getTag(lua_State* L, TypeFunctionTypeId ty) return "function"; else if (get(ty)) return "class"; + else if (FFlag::LuauUserTypeFunGenerics && get(ty)) + return "generic"; LUAU_UNREACHABLE(); luaL_error(L, "VM encountered unexpected type variant when determining tag"); @@ -267,6 +271,20 @@ static int createSingleton(lua_State* L) luaL_error(L, "types.singleton: can't create singleton from `%s` type", lua_typename(L, 1)); } +// Luau: `types.generic(name: string, ispack: boolean?) -> type +// Create a generic type with the specified type. If an optinal boolean is set to true, result is a generic pack +static int createGeneric(lua_State* L) +{ + const char* name = luaL_checkstring(L, 1); + bool isPack = luaL_optboolean(L, 2, false); + + if (strlen(name) == 0) + luaL_error(L, "types.generic: generic name cannot be empty"); + + allocTypeUserData(L, TypeFunctionGenericType{/* isNamed */ true, isPack, name}); + return 1; +} + // Luau: `self:value() -> type` // Returns the value of a singleton static int getSingletonValue(lua_State* L) @@ -780,9 +798,159 @@ static int setTableMetatable(lua_State* L) return 0; } -// Luau: `types.newfunction(parameters: {head: {type}?, tail: type?}, returns: {head: {type}?, tail: type?}) -> type` -// Returns the type instance representing a function -static int createFunction(lua_State* L) +static std::tuple, std::vector> getGenerics(lua_State* L, int idx, const char* fname) +{ + std::vector types; + std::vector packs; + + if (lua_istable(L, idx)) + { + lua_pushvalue(L, idx); + + for (int i = 1; i <= lua_objlen(L, -1); i++) + { + lua_pushinteger(L, i); + lua_gettable(L, -2); + + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + break; + } + + TypeFunctionTypeId ty = getTypeUserData(L, -1); + + if (auto gty = get(ty)) + { + if (gty->isPack) + { + packs.push_back(allocateTypeFunctionTypePack(L, TypeFunctionGenericTypePack{gty->isNamed, gty->name})); + } + else + { + if (!packs.empty()) + luaL_error(L, "%s: generic type cannot follow a generic pack", fname); + + types.push_back(ty); + } + } + else + { + luaL_error(L, "%s: table member was not a generic type", fname); + } + + lua_pop(L, 1); + } + + lua_pop(L, 1); + } + else if (!lua_isnoneornil(L, idx)) + { + luaL_typeerrorL(L, idx, "table"); + } + + return {types, packs}; +} + +static TypeFunctionTypePackId getTypePack(lua_State* L, int headIdx, int tailIdx) +{ + TypeFunctionTypePackId result = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{}); + + std::vector head; + + if (lua_istable(L, headIdx)) + { + lua_pushvalue(L, headIdx); + + for (int i = 1; i <= lua_objlen(L, -1); i++) + { + lua_pushinteger(L, i); + lua_gettable(L, -2); + + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + break; + } + + head.push_back(getTypeUserData(L, -1)); + lua_pop(L, 1); + } + + lua_pop(L, 1); + } + + std::optional tail; + + if (auto type = optionalTypeUserData(L, tailIdx)) + { + if (auto gty = get(*type); gty && gty->isPack) + tail = allocateTypeFunctionTypePack(L, TypeFunctionGenericTypePack{gty->isNamed, gty->name}); + else + tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); + } + + if (head.size() == 0 && tail.has_value()) + result = *tail; + else + result = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail}); + + return result; +} + +static void pushTypePack(lua_State* L, TypeFunctionTypePackId tp) +{ + if (auto tftp = get(tp)) + { + lua_createtable(L, 0, 2); + + if (!tftp->head.empty()) + { + lua_createtable(L, int(tftp->head.size()), 0); + int pos = 1; + + for (auto el : tftp->head) + { + allocTypeUserData(L, el->type); + lua_rawseti(L, -2, pos++); + } + + lua_setfield(L, -2, "head"); + } + + if (tftp->tail.has_value()) + { + if (auto tfvp = get(*tftp->tail)) + allocTypeUserData(L, tfvp->type->type); + else if (auto tfgp = get(*tftp->tail)) + allocTypeUserData(L, TypeFunctionGenericType{tfgp->isNamed, true, tfgp->name}); + else + luaL_error(L, "unsupported type pack type"); + + lua_setfield(L, -2, "tail"); + } + } + else if (auto tfvp = get(tp)) + { + lua_createtable(L, 0, 1); + + allocTypeUserData(L, tfvp->type->type); + lua_setfield(L, -2, "tail"); + } + else if (auto tfgp = get(tp)) + { + lua_createtable(L, 0, 1); + + allocTypeUserData(L, TypeFunctionGenericType{tfgp->isNamed, true, tfgp->name}); + lua_setfield(L, -2, "tail"); + } + else + { + luaL_error(L, "unsupported type pack type"); + } +} + +static int createFunction_DEPRECATED(lua_State* L) { int argumentCount = lua_gettop(L); if (argumentCount > 2) @@ -870,7 +1038,62 @@ static int createFunction(lua_State* L) else if (!lua_isnoneornil(L, 2)) luaL_typeerrorL(L, 2, "table"); - allocTypeUserData(L, TypeFunctionFunctionType{argTypes, retTypes}); + allocTypeUserData(L, TypeFunctionFunctionType{{}, {}, argTypes, retTypes}); + + return 1; +} + +// Luau: `types.newfunction(parameters: {head: {type}?, tail: type?}, returns: {head: {type}?, tail: type?}, generics: {type}?) -> type` +// Returns the type instance representing a function +static int createFunction(lua_State* L) +{ + int argumentCount = lua_gettop(L); + if (argumentCount > 3) + luaL_error(L, "types.newfunction: expected 0-3 arguments, but got %d", argumentCount); + + TypeFunctionTypePackId argTypes = nullptr; + + if (lua_istable(L, 1)) + { + lua_getfield(L, 1, "head"); + lua_getfield(L, 1, "tail"); + + argTypes = getTypePack(L, -2, -1); + + lua_pop(L, 2); + } + else if (!lua_isnoneornil(L, 1)) + { + luaL_typeerrorL(L, 1, "table"); + } + else + { + argTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{}); + } + + TypeFunctionTypePackId retTypes = nullptr; + + if (lua_istable(L, 2)) + { + lua_getfield(L, 2, "head"); + lua_getfield(L, 2, "tail"); + + retTypes = getTypePack(L, -2, -1); + + lua_pop(L, 2); + } + else if (!lua_isnoneornil(L, 2)) + { + luaL_typeerrorL(L, 2, "table"); + } + else + { + retTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{}); + } + + auto [genericTypes, genericPacks] = getGenerics(L, 3, "types.newfunction"); + + allocTypeUserData(L, TypeFunctionFunctionType{std::move(genericTypes), std::move(genericPacks), argTypes, retTypes}); return 1; } @@ -888,38 +1111,45 @@ static int setFunctionParameters(lua_State* L) if (!tfft) luaL_error(L, "type.setparameters: expected self to be a function, but got %s instead", getTag(L, self).c_str()); - std::vector head{}; - if (lua_istable(L, 2)) + if (FFlag::LuauUserTypeFunGenerics) { - int argSize = lua_objlen(L, 2); - for (int i = 1; i <= argSize; i++) - { - lua_pushinteger(L, i); - lua_gettable(L, 2); - - if (lua_isnil(L, -1)) - { - lua_pop(L, 1); - break; - } - - TypeFunctionTypeId ty = getTypeUserData(L, -1); - head.push_back(ty); - - lua_pop(L, 1); // Remove `ty` from stack - } + tfft->argTypes = getTypePack(L, 2, 3); } - else if (!lua_isnoneornil(L, 2)) - luaL_typeerrorL(L, 2, "table"); + else + { + std::vector head{}; + if (lua_istable(L, 2)) + { + int argSize = lua_objlen(L, 2); + for (int i = 1; i <= argSize; i++) + { + lua_pushinteger(L, i); + lua_gettable(L, 2); - std::optional tail; - if (auto type = optionalTypeUserData(L, 3)) - tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + break; + } - if (head.size() == 0 && tail.has_value()) // Make argTypes a variadic type pack - tfft->argTypes = *tail; - else // Make argTypes a type pack - tfft->argTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail}); + TypeFunctionTypeId ty = getTypeUserData(L, -1); + head.push_back(ty); + + lua_pop(L, 1); // Remove `ty` from stack + } + } + else if (!lua_isnoneornil(L, 2)) + luaL_typeerrorL(L, 2, "table"); + + std::optional tail; + if (auto type = optionalTypeUserData(L, 3)) + tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); + + if (head.size() == 0 && tail.has_value()) // Make argTypes a variadic type pack + tfft->argTypes = *tail; + else // Make argTypes a type pack + tfft->argTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail}); + } return 0; } @@ -937,52 +1167,60 @@ static int getFunctionParameters(lua_State* L) if (!tfft) luaL_error(L, "type.parameters: expected self to be a function, but got %s instead", getTag(L, self).c_str()); - if (auto tftp = get(tfft->argTypes)) + if (FFlag::LuauUserTypeFunGenerics) { - int size = 0; - if (tftp->head.size() > 0) - size++; - if (tftp->tail.has_value()) - size++; - - lua_createtable(L, 0, size); - - int argSize = (int)tftp->head.size(); - if (argSize > 0) + pushTypePack(L, tfft->argTypes); + } + else + { + if (auto tftp = get(tfft->argTypes)) { - lua_createtable(L, argSize, 0); - for (int i = 0; i < argSize; i++) + int size = 0; + if (tftp->head.size() > 0) + size++; + if (tftp->tail.has_value()) + size++; + + lua_createtable(L, 0, size); + + int argSize = (int)tftp->head.size(); + if (argSize > 0) { - allocTypeUserData(L, tftp->head[i]->type); - lua_rawseti(L, -2, i + 1); // Luau is 1-indexed while C++ is 0-indexed + lua_createtable(L, argSize, 0); + for (int i = 0; i < argSize; i++) + { + allocTypeUserData(L, tftp->head[i]->type); + lua_rawseti(L, -2, i + 1); // Luau is 1-indexed while C++ is 0-indexed + } + lua_setfield(L, -2, "head"); } - lua_setfield(L, -2, "head"); + + if (tftp->tail.has_value()) + { + auto tfvp = get(*tftp->tail); + if (!tfvp) + LUAU_ASSERT(!"We should only be supporting variadic packs as TypeFunctionTypePack.tail at the moment"); + + allocTypeUserData(L, tfvp->type->type); + lua_setfield(L, -2, "tail"); + } + + return 1; } - if (tftp->tail.has_value()) + if (auto tfvp = get(tfft->argTypes)) { - auto tfvp = get(*tftp->tail); - if (!tfvp) - LUAU_ASSERT(!"We should only be supporting variadic packs as TypeFunctionTypePack.tail at the moment"); + lua_createtable(L, 0, 1); allocTypeUserData(L, tfvp->type->type); lua_setfield(L, -2, "tail"); + + return 1; } - return 1; + lua_createtable(L, 0, 0); } - if (auto tfvp = get(tfft->argTypes)) - { - lua_createtable(L, 0, 1); - - allocTypeUserData(L, tfvp->type->type); - lua_setfield(L, -2, "tail"); - - return 1; - } - - lua_createtable(L, 0, 0); return 1; } @@ -999,38 +1237,45 @@ static int setFunctionReturns(lua_State* L) if (!tfft) luaL_error(L, "type.setreturns: expected self to be a function, but got %s instead", getTag(L, self).c_str()); - std::vector head{}; - if (lua_istable(L, 2)) + if (FFlag::LuauUserTypeFunGenerics) { - int argSize = lua_objlen(L, 2); - for (int i = 1; i <= argSize; i++) - { - lua_pushinteger(L, i); - lua_gettable(L, 2); - - if (lua_isnil(L, -1)) - { - lua_pop(L, 1); - break; - } - - TypeFunctionTypeId ty = getTypeUserData(L, -1); - head.push_back(ty); - - lua_pop(L, 1); // Remove `ty` from stack - } + tfft->retTypes = getTypePack(L, 2, 3); } - else if (!lua_isnoneornil(L, 2)) - luaL_typeerrorL(L, 2, "table"); + else + { + std::vector head{}; + if (lua_istable(L, 2)) + { + int argSize = lua_objlen(L, 2); + for (int i = 1; i <= argSize; i++) + { + lua_pushinteger(L, i); + lua_gettable(L, 2); - std::optional tail; - if (auto type = optionalTypeUserData(L, 3)) - tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + break; + } - if (head.size() == 0 && tail.has_value()) // Make retTypes a variadic type pack - tfft->retTypes = *tail; - else // Make retTypes a type pack - tfft->retTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail}); + TypeFunctionTypeId ty = getTypeUserData(L, -1); + head.push_back(ty); + + lua_pop(L, 1); // Remove `ty` from stack + } + } + else if (!lua_isnoneornil(L, 2)) + luaL_typeerrorL(L, 2, "table"); + + std::optional tail; + if (auto type = optionalTypeUserData(L, 3)) + tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); + + if (head.size() == 0 && tail.has_value()) // Make retTypes a variadic type pack + tfft->retTypes = *tail; + else // Make retTypes a type pack + tfft->retTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail}); + } return 0; } @@ -1048,52 +1293,109 @@ static int getFunctionReturns(lua_State* L) if (!tfft) luaL_error(L, "type.returns: expected self to be a function, but got %s instead", getTag(L, self).c_str()); - if (auto tftp = get(tfft->retTypes)) + if (FFlag::LuauUserTypeFunGenerics) { - int size = 0; - if (tftp->head.size() > 0) - size++; - if (tftp->tail.has_value()) - size++; - - lua_createtable(L, 0, size); - - int argSize = (int)tftp->head.size(); - if (argSize > 0) + pushTypePack(L, tfft->retTypes); + } + else + { + if (auto tftp = get(tfft->retTypes)) { - lua_createtable(L, argSize, 0); - for (int i = 0; i < argSize; i++) + int size = 0; + if (tftp->head.size() > 0) + size++; + if (tftp->tail.has_value()) + size++; + + lua_createtable(L, 0, size); + + int argSize = (int)tftp->head.size(); + if (argSize > 0) { - allocTypeUserData(L, tftp->head[i]->type); - lua_rawseti(L, -2, i + 1); // Luau is 1-indexed while C++ is 0-indexed + lua_createtable(L, argSize, 0); + for (int i = 0; i < argSize; i++) + { + allocTypeUserData(L, tftp->head[i]->type); + lua_rawseti(L, -2, i + 1); // Luau is 1-indexed while C++ is 0-indexed + } + lua_setfield(L, -2, "head"); } - lua_setfield(L, -2, "head"); + + if (tftp->tail.has_value()) + { + auto tfvp = get(*tftp->tail); + if (!tfvp) + LUAU_ASSERT(!"We should only be supporting variadic packs as TypeFunctionTypePack.tail at the moment"); + + allocTypeUserData(L, tfvp->type->type); + lua_setfield(L, -2, "tail"); + } + + return 1; } - if (tftp->tail.has_value()) + if (auto tfvp = get(tfft->retTypes)) { - auto tfvp = get(*tftp->tail); - if (!tfvp) - LUAU_ASSERT(!"We should only be supporting variadic packs as TypeFunctionTypePack.tail at the moment"); + lua_createtable(L, 0, 1); allocTypeUserData(L, tfvp->type->type); lua_setfield(L, -2, "tail"); + + return 1; } - return 1; + lua_createtable(L, 0, 0); } - if (auto tfvp = get(tfft->retTypes)) + return 1; +} + +// Luau: `self:setgenerics(generics: {type}?)` +static int setFunctionGenerics(lua_State* L) +{ + TypeFunctionTypeId self = getTypeUserData(L, 1); + auto tfft = getMutable(self); + if (!tfft) + luaL_error(L, "type.setgenerics: expected self to be a function, but got %s instead", getTag(L, self).c_str()); + + int argumentCount = lua_gettop(L); + if (argumentCount > 3) + luaL_error(L, "type.setgenerics: expected 3 arguments, but got %d", argumentCount); + + auto [genericTypes, genericPacks] = getGenerics(L, 2, "types.setgenerics"); + + tfft->generics = std::move(genericTypes); + tfft->genericPacks = std::move(genericPacks); + + return 0; +} + +// Luau: `self:generics() -> {type}` +static int getFunctionGenerics(lua_State* L) +{ + TypeFunctionTypeId self = getTypeUserData(L, 1); + auto tfft = get(self); + if (!tfft) + luaL_error(L, "type.generics: expected self to be a function, but got %s instead", getTag(L, self).c_str()); + + lua_createtable(L, int(tfft->generics.size()) + int(tfft->genericPacks.size()), 0); + + int pos = 1; + + for (const auto& el : tfft->generics) { - lua_createtable(L, 0, 1); - - allocTypeUserData(L, tfvp->type->type); - lua_setfield(L, -2, "tail"); - - return 1; + allocTypeUserData(L, el->type); + lua_rawseti(L, -2, pos++); + } + + for (const auto& el : tfft->genericPacks) + { + auto gty = get(el); + LUAU_ASSERT(gty); + allocTypeUserData(L, TypeFunctionGenericType{gty->isNamed, true, gty->name}); + lua_rawseti(L, -2, pos++); } - lua_createtable(L, 0, 0); return 1; } @@ -1119,6 +1421,36 @@ static int getClassParent(lua_State* L) return 1; } +// Luau: `self:name() -> string?` +// Returns the name of the generic or 'nil' if the generic is unnamed +static int getGenericName(lua_State* L) +{ + TypeFunctionTypeId self = getTypeUserData(L, 1); + auto tfgt = get(self); + if (!tfgt) + luaL_error(L, "type.name: expected self to be a generic, but got %s instead", getTag(L, self).c_str()); + + if (tfgt->isNamed) + lua_pushstring(L, tfgt->name.c_str()); + else + lua_pushnil(L); + + return 1; +} + +// Luau: `self:ispack() -> boolean` +// Returns true if the generic is a pack +static int getGenericIsPack(lua_State* L) +{ + TypeFunctionTypeId self = getTypeUserData(L, 1); + auto tfgt = get(self); + if (!tfgt) + luaL_error(L, "type.ispack: expected self to be a generic, but got %s instead", getTag(L, self).c_str()); + + lua_pushboolean(L, tfgt->isPack); + return 1; +} + // Luau: `self:properties() -> {[type]: { read: type?, write: type? }}` // Returns the properties of a table or class type static int getProps(lua_State* L) @@ -1388,7 +1720,7 @@ static int checkTag(lua_State* L) TypeFunctionTypeId deepClone(NotNull runtime, TypeFunctionTypeId ty); // Forward declaration -// Luau: `types.copy(arg: string) -> type` +// Luau: `types.copy(arg: type) -> type` // Returns a deep copy of the argument static int deepCopy(lua_State* L) { @@ -1438,8 +1770,9 @@ void registerTypesLibrary(lua_State* L) {"unionof", createUnion}, {"intersectionof", createIntersection}, {"newtable", createTable}, - {"newfunction", createFunction}, + {"newfunction", FFlag::LuauUserTypeFunGenerics ? createFunction : createFunction_DEPRECATED}, {"copy", deepCopy}, + {FFlag::LuauUserTypeFunGenerics ? "generic" : nullptr, FFlag::LuauUserTypeFunGenerics ? createGeneric : nullptr}, {nullptr, nullptr} }; @@ -1504,6 +1837,8 @@ void registerTypeUserData(lua_State* L) {"parameters", getFunctionParameters}, {"setreturns", setFunctionReturns}, {"returns", getFunctionReturns}, + {"setgenerics", setFunctionGenerics}, + {"generics", getFunctionGenerics}, // Union and Intersection type methods {"components", getComponents}, @@ -1511,6 +1846,14 @@ void registerTypeUserData(lua_State* L) // Class type methods {"parent", getClassParent}, + // Function type methods (cont.) + {FFlag::LuauUserTypeFunGenerics ? "setgenerics" : nullptr, FFlag::LuauUserTypeFunGenerics ? setFunctionGenerics : nullptr}, + {FFlag::LuauUserTypeFunGenerics ? "generics" : nullptr, FFlag::LuauUserTypeFunGenerics ? getFunctionGenerics : nullptr}, + + // Generic type methods + {FFlag::LuauUserTypeFunGenerics ? "name" : nullptr, FFlag::LuauUserTypeFunGenerics ? getGenericName : nullptr}, + {FFlag::LuauUserTypeFunGenerics ? "ispack" : nullptr, FFlag::LuauUserTypeFunGenerics ? getGenericIsPack : nullptr}, + {nullptr, nullptr} }; @@ -1776,6 +2119,27 @@ bool areEqual(SeenSet& seen, const TypeFunctionFunctionType& lhs, const TypeFunc if (seenSetContains(seen, &lhs, &rhs)) return true; + if (FFlag::LuauUserTypeFunGenerics) + { + if (lhs.generics.size() != rhs.generics.size()) + return false; + + for (auto l = lhs.generics.begin(), r = rhs.generics.begin(); l != lhs.generics.end() && r != rhs.generics.end(); ++l, ++r) + { + if (!areEqual(seen, **l, **r)) + return false; + } + + if (lhs.genericPacks.size() != rhs.genericPacks.size()) + return false; + + for (auto l = lhs.genericPacks.begin(), r = rhs.genericPacks.begin(); l != lhs.genericPacks.end() && r != rhs.genericPacks.end(); ++l, ++r) + { + if (!areEqual(seen, **l, **r)) + return false; + } + } + if (bool(lhs.argTypes) != bool(rhs.argTypes)) return false; @@ -1876,6 +2240,16 @@ bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType return areEqual(seen, *lf, *rf); } + if (FFlag::LuauUserTypeFunGenerics) + { + { + const TypeFunctionGenericType* lg = get(&lhs); + const TypeFunctionGenericType* rg = get(&rhs); + if (lg && rg) + return lg->isNamed == rg->isNamed && lg->isPack == rg->isPack && lg->name == rg->name; + } + } + return false; } @@ -1922,6 +2296,16 @@ bool areEqual(SeenSet& seen, const TypeFunctionTypePackVar& lhs, const TypeFunct return areEqual(seen, *lv, *rv); } + if (FFlag::LuauUserTypeFunGenerics) + { + { + const TypeFunctionGenericTypePack* lg = get(&lhs); + const TypeFunctionGenericTypePack* rg = get(&rhs); + if (lg && rg) + return lg->isNamed == rg->isNamed && lg->name == rg->name; + } + } + return false; } @@ -2146,10 +2530,14 @@ private: else if (auto f = get(ty)) { TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{}); - target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{emptyTypePack, emptyTypePack}); + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack}); } else if (auto c = get(ty)) target = ty; // Don't copy a class since they are immutable + else if (auto g = get(ty); FFlag::LuauUserTypeFunGenerics && g) + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionGenericType{g->isNamed, g->isPack, g->name}); + else + LUAU_ASSERT(!"Unknown type"); types[ty] = target; queue.emplace_back(ty, target); @@ -2167,6 +2555,10 @@ private: target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{{}}); else if (auto vPack = get(tp)) target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionVariadicTypePack{}); + else if (auto gPack = get(tp); gPack && FFlag::LuauUserTypeFunGenerics) + target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionGenericTypePack{gPack->isNamed, gPack->name}); + else + LUAU_ASSERT(!"Unknown type"); packs[tp] = target; queue.emplace_back(tp, target); @@ -2197,6 +2589,9 @@ private: cloneChildren(f1, f2); else if (auto [c1, c2] = std::tuple{getMutable(ty), getMutable(tfti)}; c1 && c2) cloneChildren(c1, c2); + else if (auto [g1, g2] = std::tuple{getMutable(ty), getMutable(tfti)}; + FFlag::LuauUserTypeFunGenerics && g1 && g2) + cloneChildren(g1, g2); else LUAU_ASSERT(!"Unknown pair?"); // First and argument should always represent the same types } @@ -2208,6 +2603,9 @@ private: else if (auto [vPack1, vPack2] = std::tuple{getMutable(tp), getMutable(tftp)}; vPack1 && vPack2) cloneChildren(vPack1, vPack2); + else if (auto [gPack1, gPack2] = std::tuple{getMutable(tp), getMutable(tftp)}; + FFlag::LuauUserTypeFunGenerics && gPack1 && gPack2) + cloneChildren(gPack1, gPack2); else LUAU_ASSERT(!"Unknown pair?"); // First and argument should always represent the same types } @@ -2288,6 +2686,17 @@ private: void cloneChildren(TypeFunctionFunctionType* f1, TypeFunctionFunctionType* f2) { + if (FFlag::LuauUserTypeFunGenerics) + { + f2->generics.reserve(f1->generics.size()); + for (auto ty : f1->generics) + f2->generics.push_back(shallowClone(ty)); + + f2->genericPacks.reserve(f1->genericPacks.size()); + for (auto tp : f1->genericPacks) + f2->genericPacks.push_back(shallowClone(tp)); + } + f2->argTypes = shallowClone(f1->argTypes); f2->retTypes = shallowClone(f1->retTypes); } @@ -2297,16 +2706,32 @@ private: // noop. } + void cloneChildren(TypeFunctionGenericType* g1, TypeFunctionGenericType* g2) + { + // noop. + } + void cloneChildren(TypeFunctionTypePack* t1, TypeFunctionTypePack* t2) { for (TypeFunctionTypeId& ty : t1->head) t2->head.push_back(shallowClone(ty)); + + if (FFlag::LuauUserTypeFunCloneTail) + { + if (t1->tail) + t2->tail = shallowClone(*t1->tail); + } } void cloneChildren(TypeFunctionVariadicTypePack* v1, TypeFunctionVariadicTypePack* v2) { v2->type = shallowClone(v1->type); } + + void cloneChildren(TypeFunctionGenericTypePack* g1, TypeFunctionGenericTypePack* g2) + { + // noop. + } }; TypeFunctionTypeId deepClone(NotNull runtime, TypeFunctionTypeId ty) diff --git a/Analysis/src/TypeFunctionRuntimeBuilder.cpp b/Analysis/src/TypeFunctionRuntimeBuilder.cpp index a89784b9..4aa9344f 100644 --- a/Analysis/src/TypeFunctionRuntimeBuilder.cpp +++ b/Analysis/src/TypeFunctionRuntimeBuilder.cpp @@ -21,6 +21,7 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000); LUAU_FASTFLAG(LuauUserTypeFunThreadBuffer) +LUAU_FASTFLAG(LuauUserTypeFunGenerics) namespace Luau { @@ -221,13 +222,22 @@ private: else if (auto f = get(ty)) { TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{}); - target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{emptyTypePack, emptyTypePack}); + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack}); } else if (auto c = get(ty)) { state->classesSerialized[c->name] = ty; target = typeFunctionRuntime->typeArena.allocate(TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, c->name}); } + else if (auto g = get(ty); FFlag::LuauUserTypeFunGenerics && g) + { + Name name = g->name; + + if (!g->explicitName) + name = format("g%d", g->index); + + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionGenericType{g->explicitName, false, name}); + } else { std::string error = format("Argument of type %s is not currently serializable by type functions", toString(ty).c_str()); @@ -252,6 +262,15 @@ private: target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{{}}); else if (auto vPack = get(tp)) target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionVariadicTypePack{}); + else if (auto gPack = get(tp); FFlag::LuauUserTypeFunGenerics && gPack) + { + Name name = gPack->name; + + if (!gPack->explicitName) + name = format("g%d", gPack->index); + + target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionGenericTypePack{gPack->explicitName, name}); + } else { std::string error = format("Argument of type pack %s is not currently serializable by type functions", toString(tp).c_str()); @@ -289,6 +308,9 @@ private: serializeChildren(f1, f2); else if (auto [c1, c2] = std::tuple{get(ty), getMutable(tfti)}; c1 && c2) serializeChildren(c1, c2); + else if (auto [g1, g2] = std::tuple{get(ty), getMutable(tfti)}; + FFlag::LuauUserTypeFunGenerics && g1 && g2) + serializeChildren(g1, g2); else { // Either this or ty and tfti do not represent the same type std::string error = format("Argument of type %s is not currently serializable by type functions", toString(ty).c_str()); @@ -302,6 +324,9 @@ private: serializeChildren(tPack1, tPack2); else if (auto [vPack1, vPack2] = std::tuple{get(tp), getMutable(tftp)}; vPack1 && vPack2) serializeChildren(vPack1, vPack2); + else if (auto [gPack1, gPack2] = std::tuple{get(tp), getMutable(tftp)}; + FFlag::LuauUserTypeFunGenerics && gPack1 && gPack2) + serializeChildren(gPack1, gPack2); else { // Either this or ty and tfti do not represent the same type std::string error = format("Argument of type pack %s is not currently serializable by type functions", toString(tp).c_str()); @@ -391,6 +416,17 @@ private: void serializeChildren(const FunctionType* f1, TypeFunctionFunctionType* f2) { + if (FFlag::LuauUserTypeFunGenerics) + { + f2->generics.reserve(f1->generics.size()); + for (auto ty : f1->generics) + f2->generics.push_back(shallowSerialize(ty)); + + f2->genericPacks.reserve(f1->genericPacks.size()); + for (auto tp : f1->genericPacks) + f2->genericPacks.push_back(shallowSerialize(tp)); + } + f2->argTypes = shallowSerialize(f1->argTypes); f2->retTypes = shallowSerialize(f1->retTypes); } @@ -420,6 +456,11 @@ private: c2->parent = shallowSerialize(*c1->parent); } + void serializeChildren(const GenericType* g1, TypeFunctionGenericType* g2) + { + // noop. + } + void serializeChildren(const TypePack* t1, TypeFunctionTypePack* t2) { for (const TypeId& ty : t1->head) @@ -433,6 +474,25 @@ private: { v2->type = shallowSerialize(v1->ty); } + + void serializeChildren(const GenericTypePack* v1, TypeFunctionGenericTypePack* v2) + { + // noop. + } +}; + +template +struct SerializedGeneric +{ + bool isNamed = false; + std::string name; + T type = nullptr; +}; + +struct SerializedFunctionScope +{ + size_t oldQueueSize = 0; + TypeFunctionFunctionType* function = nullptr; }; // Complete inverse of TypeFunctionSerializer @@ -453,6 +513,15 @@ class TypeFunctionDeserializer // second must be PrimitiveType; else there should be an error std::vector> queue; + // Generic types and packs currently in scope + // Generics are resolved by name even if runtime generic type pointers are different + // Multiple names mapping to the same generic can be in scope for nested generic functions + std::vector> genericTypes; + std::vector> genericPacks; + + // To track when generics go out of scope, we have a list of queue positions at which a specific function has introduced generics + std::vector functionScopes; + SeenTypes types; // Mapping of TypeFunctionTypeIds that have been shallow deserialized to TypeIds SeenTypePacks packs; // Mapping of TypeFunctionTypePackIds that have been shallow deserialized to TypePackIds @@ -520,6 +589,16 @@ private: queue.pop_back(); deserializeChildren(tfti, ty); + + if (FFlag::LuauUserTypeFunGenerics) + { + // If we have completed working on all children of a function, remove the generic parameters from scope + if (!functionScopes.empty() && queue.size() == functionScopes.back().oldQueueSize && state->errors.empty()) + { + closeFunctionScope(functionScopes.back().function); + functionScopes.pop_back(); + } + } } } @@ -552,6 +631,21 @@ private: } } + void closeFunctionScope(TypeFunctionFunctionType* f) + { + if (!f->generics.empty()) + { + LUAU_ASSERT(genericTypes.size() >= f->generics.size()); + genericTypes.erase(genericTypes.begin() + int(genericTypes.size() - f->generics.size()), genericTypes.end()); + } + + if (!f->genericPacks.empty()) + { + LUAU_ASSERT(genericPacks.size() >= f->genericPacks.size()); + genericPacks.erase(genericPacks.begin() + int(genericPacks.size() - f->genericPacks.size()), genericPacks.end()); + } + } + TypeId shallowDeserialize(TypeFunctionTypeId ty) { if (auto it = find(ty)) @@ -631,6 +725,33 @@ private: else state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious class type is being deserialized"); } + else if (auto g = get(ty); FFlag::LuauUserTypeFunGenerics && g) + { + if (g->isPack) + { + state->errors.push_back(format("Generic type pack '%s...' cannot be placed in a type position", g->name.c_str())); + return nullptr; + } + else + { + auto it = std::find_if( + genericTypes.rbegin(), + genericTypes.rend(), + [&](const SerializedGeneric& el) + { + return g->isNamed == el.isNamed && g->name == el.name; + } + ); + + if (it == genericTypes.rend()) + { + state->errors.push_back(format("Generic type '%s' is not in a scope of the active generic function", g->name.c_str())); + return nullptr; + } + + target = it->type; + } + } else state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized"); @@ -647,11 +768,36 @@ private: // Create a shallow deserialization TypePackId target = {}; if (auto tPack = get(tp)) + { target = state->ctx->arena->addTypePack(TypePack{}); + } else if (auto vPack = get(tp)) + { target = state->ctx->arena->addTypePack(VariadicTypePack{}); + } + else if (auto gPack = get(tp); FFlag::LuauUserTypeFunGenerics && gPack) + { + auto it = std::find_if( + genericPacks.rbegin(), + genericPacks.rend(), + [&](const SerializedGeneric& el) + { + return gPack->isNamed == el.isNamed && gPack->name == el.name; + } + ); + + if (it == genericPacks.rend()) + { + state->errors.push_back(format("Generic type pack '%s...' is not in a scope of the active generic function", gPack->name.c_str())); + return nullptr; + } + + target = it->type; + } else + { state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized"); + } packs[tp] = target; queue.emplace_back(tp, target); @@ -686,6 +832,9 @@ private: deserializeChildren(f2, f1); else if (auto [c1, c2] = std::tuple{getMutable(ty), getMutable(tfti)}; c1 && c2) deserializeChildren(c2, c1); + else if (auto [g1, g2] = std::tuple{getMutable(ty), getMutable(tfti)}; + FFlag::LuauUserTypeFunGenerics && g1 && g2) + deserializeChildren(g2, g1); else state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized"); } @@ -697,6 +846,9 @@ private: else if (auto [vPack1, vPack2] = std::tuple{getMutable(tp), getMutable(tftp)}; vPack1 && vPack2) deserializeChildren(vPack2, vPack1); + else if (auto [gPack1, gPack2] = std::tuple{getMutable(tp), getMutable(tftp)}; + FFlag::LuauUserTypeFunGenerics && gPack1 && gPack2) + deserializeChildren(gPack2, gPack1); else state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized"); } @@ -780,6 +932,64 @@ private: void deserializeChildren(TypeFunctionFunctionType* f2, FunctionType* f1) { + if (FFlag::LuauUserTypeFunGenerics) + { + functionScopes.push_back({queue.size(), f2}); + + std::set> genericNames; + + // Introduce generic function parameters into scope + for (auto ty : f2->generics) + { + auto gty = get(ty); + LUAU_ASSERT(gty && !gty->isPack); + + std::pair nameKey = std::make_pair(gty->isNamed, gty->name); + + // Duplicates are not allowed + if (genericNames.find(nameKey) != genericNames.end()) + { + state->errors.push_back(format("Duplicate type parameter '%s'", gty->name.c_str())); + return; + } + + genericNames.insert(nameKey); + + TypeId mapping = state->ctx->arena->addTV(Type(gty->isNamed ? GenericType{state->ctx->scope.get(), gty->name} : GenericType{})); + genericTypes.push_back({gty->isNamed, gty->name, mapping}); + } + + for (auto tp : f2->genericPacks) + { + auto gtp = get(tp); + LUAU_ASSERT(gtp); + + std::pair nameKey = std::make_pair(gtp->isNamed, gtp->name); + + // Duplicates are not allowed + if (genericNames.find(nameKey) != genericNames.end()) + { + state->errors.push_back(format("Duplicate type parameter '%s'", gtp->name.c_str())); + return; + } + + genericNames.insert(nameKey); + + TypePackId mapping = + state->ctx->arena->addTypePack(TypePackVar(gtp->isNamed ? GenericTypePack{state->ctx->scope.get(), gtp->name} : GenericTypePack{}) + ); + genericPacks.push_back({gtp->isNamed, gtp->name, mapping}); + } + + f1->generics.reserve(f2->generics.size()); + for (auto ty : f2->generics) + f1->generics.push_back(shallowDeserialize(ty)); + + f1->genericPacks.reserve(f2->genericPacks.size()); + for (auto tp : f2->genericPacks) + f1->genericPacks.push_back(shallowDeserialize(tp)); + } + if (f2->argTypes) f1->argTypes = shallowDeserialize(f2->argTypes); @@ -792,6 +1002,11 @@ private: // noop. } + void deserializeChildren(TypeFunctionGenericType* g2, GenericType* g1) + { + // noop. + } + void deserializeChildren(TypeFunctionTypePack* t2, TypePack* t1) { for (TypeFunctionTypeId& ty : t2->head) @@ -805,6 +1020,11 @@ private: { v1->ty = shallowDeserialize(v2->type); } + + void deserializeChildren(TypeFunctionGenericTypePack* v2, GenericTypePack* v1) + { + // noop. + } }; TypeFunctionTypeId serialize(TypeId ty, TypeFunctionRuntimeBuilderState* state) diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index 475d19da..ce98f58e 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -116,7 +116,7 @@ private: AstStat* parseFor(); // funcname ::= Name {`.' Name} [`:' Name] - AstExpr* parseFunctionName(Location start, bool& hasself, AstName& debugname); + AstExpr* parseFunctionName(Location start_DEPRECATED, bool& hasself, AstName& debugname); // function funcname funcbody LUAU_FORCEINLINE AstStat* parseFunctionStat(const AstArray& attributes = {nullptr, 0}); diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index fcb74694..f618bc06 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauAllowFragmentParsing) LUAU_FASTFLAGVARIABLE(LuauAllowComplexTypesInGenericParams) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForTableTypes) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForClassNames) +LUAU_FASTFLAGVARIABLE(LuauFixFunctionNameStartPosition) namespace Luau { @@ -638,7 +639,7 @@ AstStat* Parser::parseFor() } // funcname ::= Name {`.' Name} [`:' Name] -AstExpr* Parser::parseFunctionName(Location start, bool& hasself, AstName& debugname) +AstExpr* Parser::parseFunctionName(Location start_DEPRECATED, bool& hasself, AstName& debugname) { if (lexer.current().type == Lexeme::Name) debugname = AstName(lexer.current().name); @@ -658,7 +659,9 @@ AstExpr* Parser::parseFunctionName(Location start, bool& hasself, AstName& debug // while we could concatenate the name chain, for now let's just write the short name debugname = name.name; - expr = allocator.alloc(Location(start, name.location), expr, name.name, name.location, opPosition, '.'); + expr = allocator.alloc( + Location(FFlag::LuauFixFunctionNameStartPosition ? expr->location : start_DEPRECATED, name.location), expr, name.name, name.location, opPosition, '.' + ); // note: while the parser isn't recursive here, we're generating recursive structures of unbounded depth incrementRecursionCounter("function name"); @@ -677,7 +680,9 @@ AstExpr* Parser::parseFunctionName(Location start, bool& hasself, AstName& debug // while we could concatenate the name chain, for now let's just write the short name debugname = name.name; - expr = allocator.alloc(Location(start, name.location), expr, name.name, name.location, opPosition, ':'); + expr = allocator.alloc( + Location(FFlag::LuauFixFunctionNameStartPosition ? expr->location : start_DEPRECATED, name.location), expr, name.name, name.location, opPosition, ':' + ); hasself = true; } diff --git a/CodeGen/include/Luau/CodeAllocator.h b/CodeGen/include/Luau/CodeAllocator.h index dcc1de85..db1774d8 100644 --- a/CodeGen/include/Luau/CodeAllocator.h +++ b/CodeGen/include/Luau/CodeAllocator.h @@ -1,7 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once -#include "Luau/CodeGen.h" +#include "Luau/CodeGenOptions.h" #include diff --git a/CodeGen/include/Luau/CodeGen.h b/CodeGen/include/Luau/CodeGen.h index 0cf9d9a5..2e689fe2 100644 --- a/CodeGen/include/Luau/CodeGen.h +++ b/CodeGen/include/Luau/CodeGen.h @@ -1,7 +1,10 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once -#include +#include "Luau/CodeGenCommon.h" +#include "Luau/CodeGenOptions.h" +#include "Luau/LoweringStats.h" + #include #include #include @@ -12,25 +15,11 @@ struct lua_State; -#if defined(__x86_64__) || defined(_M_X64) -#define CODEGEN_TARGET_X64 -#elif defined(__aarch64__) || defined(_M_ARM64) -#define CODEGEN_TARGET_A64 -#endif - namespace Luau { namespace CodeGen { -enum CodeGenFlags -{ - // Only run native codegen for modules that have been marked with --!native - CodeGen_OnlyNativeModules = 1 << 0, - // Run native codegen for functions that the compiler considers not profitable - CodeGen_ColdFunctions = 1 << 1, -}; - // These enum values can be reported through telemetry. // To ensure consistency, changes should be additive. enum class CodeGenCompilationResult @@ -72,106 +61,6 @@ struct CompilationResult } }; -struct IrBuilder; -struct IrOp; - -using HostVectorOperationBytecodeType = uint8_t (*)(const char* member, size_t memberLength); -using HostVectorAccessHandler = bool (*)(IrBuilder& builder, const char* member, size_t memberLength, int resultReg, int sourceReg, int pcpos); -using HostVectorNamecallHandler = - bool (*)(IrBuilder& builder, const char* member, size_t memberLength, int argResReg, int sourceReg, int params, int results, int pcpos); - -enum class HostMetamethod -{ - Add, - Sub, - Mul, - Div, - Idiv, - Mod, - Pow, - Minus, - Equal, - LessThan, - LessEqual, - Length, - Concat, -}; - -using HostUserdataOperationBytecodeType = uint8_t (*)(uint8_t type, const char* member, size_t memberLength); -using HostUserdataMetamethodBytecodeType = uint8_t (*)(uint8_t lhsTy, uint8_t rhsTy, HostMetamethod method); -using HostUserdataAccessHandler = - bool (*)(IrBuilder& builder, uint8_t type, const char* member, size_t memberLength, int resultReg, int sourceReg, int pcpos); -using HostUserdataMetamethodHandler = - bool (*)(IrBuilder& builder, uint8_t lhsTy, uint8_t rhsTy, int resultReg, IrOp lhs, IrOp rhs, HostMetamethod method, int pcpos); -using HostUserdataNamecallHandler = bool (*)( - IrBuilder& builder, - uint8_t type, - const char* member, - size_t memberLength, - int argResReg, - int sourceReg, - int params, - int results, - int pcpos -); - -struct HostIrHooks -{ - // Suggest result type of a vector field access - HostVectorOperationBytecodeType vectorAccessBytecodeType = nullptr; - - // Suggest result type of a vector function namecall - HostVectorOperationBytecodeType vectorNamecallBytecodeType = nullptr; - - // Handle vector value field access - // 'sourceReg' is guaranteed to be a vector - // Guards should take a VM exit to 'pcpos' - HostVectorAccessHandler vectorAccess = nullptr; - - // Handle namecall performed on a vector value - // 'sourceReg' (self argument) is guaranteed to be a vector - // All other arguments can be of any type - // Guards should take a VM exit to 'pcpos' - HostVectorNamecallHandler vectorNamecall = nullptr; - - // Suggest result type of a userdata field access - HostUserdataOperationBytecodeType userdataAccessBytecodeType = nullptr; - - // Suggest result type of a metamethod call - HostUserdataMetamethodBytecodeType userdataMetamethodBytecodeType = nullptr; - - // Suggest result type of a userdata namecall - HostUserdataOperationBytecodeType userdataNamecallBytecodeType = nullptr; - - // Handle userdata value field access - // 'sourceReg' is guaranteed to be a userdata, but tag has to be checked - // Write to 'resultReg' might invalidate 'sourceReg' - // Guards should take a VM exit to 'pcpos' - HostUserdataAccessHandler userdataAccess = nullptr; - - // Handle metamethod operation on a userdata value - // 'lhs' and 'rhs' operands can be VM registers of constants - // Operand types have to be checked and userdata operand tags have to be checked - // Write to 'resultReg' might invalidate source operands - // Guards should take a VM exit to 'pcpos' - HostUserdataMetamethodHandler userdataMetamethod = nullptr; - - // Handle namecall performed on a userdata value - // 'sourceReg' (self argument) is guaranteed to be a userdata, but tag has to be checked - // All other arguments can be of any type - // Guards should take a VM exit to 'pcpos' - HostUserdataNamecallHandler userdataNamecall = nullptr; -}; - -struct CompilationOptions -{ - unsigned int flags = 0; - HostIrHooks hooks; - - // null-terminated array of userdata types names that might have custom lowering - const char* const* userdataTypes = nullptr; -}; - struct CompilationStats { size_t bytecodeSizeBytes = 0; @@ -184,8 +73,6 @@ struct CompilationStats uint32_t functionsBound = 0; }; -using AllocationCallback = void(void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize); - bool isSupported(); class SharedCodeGenContext; @@ -249,153 +136,6 @@ CompilationResult compile(const ModuleId& moduleId, lua_State* L, int idx, unsig CompilationResult compile(lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats = nullptr); CompilationResult compile(const ModuleId& moduleId, lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats = nullptr); -using AnnotatorFn = void (*)(void* context, std::string& result, int fid, int instpos); - -// Output "#" before IR blocks and instructions -enum class IncludeIrPrefix -{ - No, - Yes -}; - -// Output user count and last use information of blocks and instructions -enum class IncludeUseInfo -{ - No, - Yes -}; - -// Output CFG informations like block predecessors, successors and etc -enum class IncludeCfgInfo -{ - No, - Yes -}; - -// Output VM register live in/out information for blocks -enum class IncludeRegFlowInfo -{ - No, - Yes -}; - -struct AssemblyOptions -{ - enum Target - { - Host, - A64, - A64_NoFeatures, - X64_Windows, - X64_SystemV, - }; - - Target target = Host; - - CompilationOptions compilationOptions; - - bool outputBinary = false; - - bool includeAssembly = false; - bool includeIr = false; - bool includeOutlinedCode = false; - bool includeIrTypes = false; - - IncludeIrPrefix includeIrPrefix = IncludeIrPrefix::Yes; - IncludeUseInfo includeUseInfo = IncludeUseInfo::Yes; - IncludeCfgInfo includeCfgInfo = IncludeCfgInfo::Yes; - IncludeRegFlowInfo includeRegFlowInfo = IncludeRegFlowInfo::Yes; - - // Optional annotator function can be provided to describe each instruction, it takes function id and sequential instruction id - AnnotatorFn annotator = nullptr; - void* annotatorContext = nullptr; -}; - -struct BlockLinearizationStats -{ - unsigned int constPropInstructionCount = 0; - double timeSeconds = 0.0; - - BlockLinearizationStats& operator+=(const BlockLinearizationStats& that) - { - this->constPropInstructionCount += that.constPropInstructionCount; - this->timeSeconds += that.timeSeconds; - - return *this; - } - - BlockLinearizationStats operator+(const BlockLinearizationStats& other) const - { - BlockLinearizationStats result(*this); - result += other; - return result; - } -}; - -enum FunctionStatsFlags -{ - // Enable stats collection per function - FunctionStats_Enable = 1 << 0, - // Compute function bytecode summary - FunctionStats_BytecodeSummary = 1 << 1, -}; - -struct FunctionStats -{ - std::string name; - int line = -1; - unsigned bcodeCount = 0; - unsigned irCount = 0; - unsigned asmCount = 0; - unsigned asmSize = 0; - std::vector> bytecodeSummary; -}; - -struct LoweringStats -{ - unsigned totalFunctions = 0; - unsigned skippedFunctions = 0; - int spillsToSlot = 0; - int spillsToRestore = 0; - unsigned maxSpillSlotsUsed = 0; - unsigned blocksPreOpt = 0; - unsigned blocksPostOpt = 0; - unsigned maxBlockInstructions = 0; - - int regAllocErrors = 0; - int loweringErrors = 0; - - BlockLinearizationStats blockLinearizationStats; - - unsigned functionStatsFlags = 0; - std::vector functions; - - LoweringStats operator+(const LoweringStats& other) const - { - LoweringStats result(*this); - result += other; - return result; - } - - LoweringStats& operator+=(const LoweringStats& that) - { - this->totalFunctions += that.totalFunctions; - this->skippedFunctions += that.skippedFunctions; - this->spillsToSlot += that.spillsToSlot; - this->spillsToRestore += that.spillsToRestore; - this->maxSpillSlotsUsed = std::max(this->maxSpillSlotsUsed, that.maxSpillSlotsUsed); - this->blocksPreOpt += that.blocksPreOpt; - this->blocksPostOpt += that.blocksPostOpt; - this->maxBlockInstructions = std::max(this->maxBlockInstructions, that.maxBlockInstructions); - this->regAllocErrors += that.regAllocErrors; - this->loweringErrors += that.loweringErrors; - this->blockLinearizationStats += that.blockLinearizationStats; - if (this->functionStatsFlags & FunctionStats_Enable) - this->functions.insert(this->functions.end(), that.functions.begin(), that.functions.end()); - return *this; - } -}; - // Generates assembly for target function and all inner functions std::string getAssembly(lua_State* L, int idx, AssemblyOptions options = {}, LoweringStats* stats = nullptr); diff --git a/CodeGen/include/Luau/CodeGenCommon.h b/CodeGen/include/Luau/CodeGenCommon.h index 84090423..a9d1761c 100644 --- a/CodeGen/include/Luau/CodeGenCommon.h +++ b/CodeGen/include/Luau/CodeGenCommon.h @@ -10,3 +10,9 @@ #else #define CODEGEN_ASSERT(expr) (void)sizeof(!!(expr)) #endif + +#if defined(__x86_64__) || defined(_M_X64) +#define CODEGEN_TARGET_X64 +#elif defined(__aarch64__) || defined(_M_ARM64) +#define CODEGEN_TARGET_A64 +#endif diff --git a/CodeGen/include/Luau/CodeGenOptions.h b/CodeGen/include/Luau/CodeGenOptions.h new file mode 100644 index 00000000..de95efa6 --- /dev/null +++ b/CodeGen/include/Luau/CodeGenOptions.h @@ -0,0 +1,188 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include + +#include +#include + +namespace Luau +{ +namespace CodeGen +{ + +enum CodeGenFlags +{ + // Only run native codegen for modules that have been marked with --!native + CodeGen_OnlyNativeModules = 1 << 0, + // Run native codegen for functions that the compiler considers not profitable + CodeGen_ColdFunctions = 1 << 1, +}; + +using AllocationCallback = void(void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize); + +struct IrBuilder; +struct IrOp; + +using HostVectorOperationBytecodeType = uint8_t (*)(const char* member, size_t memberLength); +using HostVectorAccessHandler = bool (*)(IrBuilder& builder, const char* member, size_t memberLength, int resultReg, int sourceReg, int pcpos); +using HostVectorNamecallHandler = + bool (*)(IrBuilder& builder, const char* member, size_t memberLength, int argResReg, int sourceReg, int params, int results, int pcpos); + +enum class HostMetamethod +{ + Add, + Sub, + Mul, + Div, + Idiv, + Mod, + Pow, + Minus, + Equal, + LessThan, + LessEqual, + Length, + Concat, +}; + +using HostUserdataOperationBytecodeType = uint8_t (*)(uint8_t type, const char* member, size_t memberLength); +using HostUserdataMetamethodBytecodeType = uint8_t (*)(uint8_t lhsTy, uint8_t rhsTy, HostMetamethod method); +using HostUserdataAccessHandler = + bool (*)(IrBuilder& builder, uint8_t type, const char* member, size_t memberLength, int resultReg, int sourceReg, int pcpos); +using HostUserdataMetamethodHandler = + bool (*)(IrBuilder& builder, uint8_t lhsTy, uint8_t rhsTy, int resultReg, IrOp lhs, IrOp rhs, HostMetamethod method, int pcpos); +using HostUserdataNamecallHandler = bool (*)( + IrBuilder& builder, + uint8_t type, + const char* member, + size_t memberLength, + int argResReg, + int sourceReg, + int params, + int results, + int pcpos +); + +struct HostIrHooks +{ + // Suggest result type of a vector field access + HostVectorOperationBytecodeType vectorAccessBytecodeType = nullptr; + + // Suggest result type of a vector function namecall + HostVectorOperationBytecodeType vectorNamecallBytecodeType = nullptr; + + // Handle vector value field access + // 'sourceReg' is guaranteed to be a vector + // Guards should take a VM exit to 'pcpos' + HostVectorAccessHandler vectorAccess = nullptr; + + // Handle namecall performed on a vector value + // 'sourceReg' (self argument) is guaranteed to be a vector + // All other arguments can be of any type + // Guards should take a VM exit to 'pcpos' + HostVectorNamecallHandler vectorNamecall = nullptr; + + // Suggest result type of a userdata field access + HostUserdataOperationBytecodeType userdataAccessBytecodeType = nullptr; + + // Suggest result type of a metamethod call + HostUserdataMetamethodBytecodeType userdataMetamethodBytecodeType = nullptr; + + // Suggest result type of a userdata namecall + HostUserdataOperationBytecodeType userdataNamecallBytecodeType = nullptr; + + // Handle userdata value field access + // 'sourceReg' is guaranteed to be a userdata, but tag has to be checked + // Write to 'resultReg' might invalidate 'sourceReg' + // Guards should take a VM exit to 'pcpos' + HostUserdataAccessHandler userdataAccess = nullptr; + + // Handle metamethod operation on a userdata value + // 'lhs' and 'rhs' operands can be VM registers of constants + // Operand types have to be checked and userdata operand tags have to be checked + // Write to 'resultReg' might invalidate source operands + // Guards should take a VM exit to 'pcpos' + HostUserdataMetamethodHandler userdataMetamethod = nullptr; + + // Handle namecall performed on a userdata value + // 'sourceReg' (self argument) is guaranteed to be a userdata, but tag has to be checked + // All other arguments can be of any type + // Guards should take a VM exit to 'pcpos' + HostUserdataNamecallHandler userdataNamecall = nullptr; +}; + +struct CompilationOptions +{ + unsigned int flags = 0; + HostIrHooks hooks; + + // null-terminated array of userdata types names that might have custom lowering + const char* const* userdataTypes = nullptr; +}; + + +using AnnotatorFn = void (*)(void* context, std::string& result, int fid, int instpos); + +// Output "#" before IR blocks and instructions +enum class IncludeIrPrefix +{ + No, + Yes +}; + +// Output user count and last use information of blocks and instructions +enum class IncludeUseInfo +{ + No, + Yes +}; + +// Output CFG informations like block predecessors, successors and etc +enum class IncludeCfgInfo +{ + No, + Yes +}; + +// Output VM register live in/out information for blocks +enum class IncludeRegFlowInfo +{ + No, + Yes +}; + +struct AssemblyOptions +{ + enum Target + { + Host, + A64, + A64_NoFeatures, + X64_Windows, + X64_SystemV, + }; + + Target target = Host; + + CompilationOptions compilationOptions; + + bool outputBinary = false; + + bool includeAssembly = false; + bool includeIr = false; + bool includeOutlinedCode = false; + bool includeIrTypes = false; + + IncludeIrPrefix includeIrPrefix = IncludeIrPrefix::Yes; + IncludeUseInfo includeUseInfo = IncludeUseInfo::Yes; + IncludeCfgInfo includeCfgInfo = IncludeCfgInfo::Yes; + IncludeRegFlowInfo includeRegFlowInfo = IncludeRegFlowInfo::Yes; + + // Optional annotator function can be provided to describe each instruction, it takes function id and sequential instruction id + AnnotatorFn annotator = nullptr; + void* annotatorContext = nullptr; +}; + +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 44f2495b..38519f95 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -20,6 +20,8 @@ namespace Luau namespace CodeGen { +struct LoweringStats; + // IR extensions to LuauBuiltinFunction enum (these only exist inside IR, and start from 256 to avoid collisions) enum { @@ -67,18 +69,18 @@ enum class IrCmd : uint8_t LOAD_ENV, // Get pointer (TValue) to table array at index - // A: pointer (Table) + // A: pointer (LuaTable) // B: int GET_ARR_ADDR, // Get pointer (LuaNode) to table node element at the active cached slot index - // A: pointer (Table) + // A: pointer (LuaTable) // B: unsigned int (pcpos) // C: Kn GET_SLOT_NODE_ADDR, // Get pointer (LuaNode) to table node element at the main position of the specified key hash - // A: pointer (Table) + // A: pointer (LuaTable) // B: unsigned int (hash) GET_HASH_NODE_ADDR, @@ -273,7 +275,7 @@ enum class IrCmd : uint8_t JUMP_SLOT_MATCH, // Get table length - // A: pointer (Table) + // A: pointer (LuaTable) TABLE_LEN, // Get string length @@ -286,11 +288,11 @@ enum class IrCmd : uint8_t NEW_TABLE, // Duplicate a table - // A: pointer (Table) + // A: pointer (LuaTable) DUP_TABLE, // Insert an integer key into a table and return the pointer to inserted value (TValue) - // A: pointer (Table) + // A: pointer (LuaTable) // B: int (key) TABLE_SETNUM, @@ -430,13 +432,13 @@ enum class IrCmd : uint8_t CHECK_TRUTHY, // Guard against readonly table - // A: pointer (Table) + // A: pointer (LuaTable) // B: block/vmexit/undef // When undef is specified instead of a block, execution is aborted on check failure CHECK_READONLY, // Guard against table having a metatable - // A: pointer (Table) + // A: pointer (LuaTable) // B: block/vmexit/undef // When undef is specified instead of a block, execution is aborted on check failure CHECK_NO_METATABLE, @@ -447,7 +449,7 @@ enum class IrCmd : uint8_t CHECK_SAFE_ENV, // Guard against index overflowing the table array size - // A: pointer (Table) + // A: pointer (LuaTable) // B: int (index) // C: block/vmexit/undef // When undef is specified instead of a block, execution is aborted on check failure @@ -503,11 +505,11 @@ enum class IrCmd : uint8_t BARRIER_OBJ, // Handle GC write barrier (backwards) for a write into a table - // A: pointer (Table) + // A: pointer (LuaTable) BARRIER_TABLE_BACK, // Handle GC write barrier (forward) for a write into a table - // A: pointer (Table) + // A: pointer (LuaTable) // B: Rn (TValue that was written to the object) // C: tag/undef (tag of the value that was written) BARRIER_TABLE_FORWARD, @@ -1049,6 +1051,8 @@ struct IrFunction CfgInfo cfg; + LoweringStats* stats = nullptr; + IrBlock& blockOp(IrOp op) { CODEGEN_ASSERT(op.kind == IrOpKind::Block); diff --git a/CodeGen/include/Luau/IrDump.h b/CodeGen/include/Luau/IrDump.h index c0737ae8..27a9feb4 100644 --- a/CodeGen/include/Luau/IrDump.h +++ b/CodeGen/include/Luau/IrDump.h @@ -2,7 +2,7 @@ #pragma once #include "Luau/IrData.h" -#include "Luau/CodeGen.h" +#include "Luau/CodeGenOptions.h" #include #include diff --git a/CodeGen/include/Luau/LoweringStats.h b/CodeGen/include/Luau/LoweringStats.h new file mode 100644 index 00000000..532a5270 --- /dev/null +++ b/CodeGen/include/Luau/LoweringStats.h @@ -0,0 +1,103 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include +#include +#include + +namespace Luau +{ +namespace CodeGen +{ + +struct BlockLinearizationStats +{ + unsigned int constPropInstructionCount = 0; + double timeSeconds = 0.0; + + BlockLinearizationStats& operator+=(const BlockLinearizationStats& that) + { + this->constPropInstructionCount += that.constPropInstructionCount; + this->timeSeconds += that.timeSeconds; + + return *this; + } + + BlockLinearizationStats operator+(const BlockLinearizationStats& other) const + { + BlockLinearizationStats result(*this); + result += other; + return result; + } +}; + +enum FunctionStatsFlags +{ + // Enable stats collection per function + FunctionStats_Enable = 1 << 0, + // Compute function bytecode summary + FunctionStats_BytecodeSummary = 1 << 1, +}; + +struct FunctionStats +{ + std::string name; + int line = -1; + unsigned bcodeCount = 0; + unsigned irCount = 0; + unsigned asmCount = 0; + unsigned asmSize = 0; + std::vector> bytecodeSummary; +}; + +struct LoweringStats +{ + unsigned totalFunctions = 0; + unsigned skippedFunctions = 0; + int spillsToSlot = 0; + int spillsToRestore = 0; + unsigned maxSpillSlotsUsed = 0; + unsigned blocksPreOpt = 0; + unsigned blocksPostOpt = 0; + unsigned maxBlockInstructions = 0; + + int regAllocErrors = 0; + int loweringErrors = 0; + + BlockLinearizationStats blockLinearizationStats; + + unsigned functionStatsFlags = 0; + std::vector functions; + + LoweringStats operator+(const LoweringStats& other) const + { + LoweringStats result(*this); + result += other; + return result; + } + + LoweringStats& operator+=(const LoweringStats& that) + { + this->totalFunctions += that.totalFunctions; + this->skippedFunctions += that.skippedFunctions; + this->spillsToSlot += that.spillsToSlot; + this->spillsToRestore += that.spillsToRestore; + this->maxSpillSlotsUsed = std::max(this->maxSpillSlotsUsed, that.maxSpillSlotsUsed); + this->blocksPreOpt += that.blocksPreOpt; + this->blocksPostOpt += that.blocksPostOpt; + this->maxBlockInstructions = std::max(this->maxBlockInstructions, that.maxBlockInstructions); + + this->regAllocErrors += that.regAllocErrors; + this->loweringErrors += that.loweringErrors; + + this->blockLinearizationStats += that.blockLinearizationStats; + + if (this->functionStatsFlags & FunctionStats_Enable) + this->functions.insert(this->functions.end(), that.functions.begin(), that.functions.end()); + + return *this; + } +}; + +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/src/BytecodeAnalysis.cpp b/CodeGen/src/BytecodeAnalysis.cpp index 134a49f2..b859b111 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -2,7 +2,7 @@ #include "Luau/BytecodeAnalysis.h" #include "Luau/BytecodeUtils.h" -#include "Luau/CodeGen.h" +#include "Luau/CodeGenOptions.h" #include "Luau/IrData.h" #include "Luau/IrUtils.h" diff --git a/CodeGen/src/CodeBlockUnwind.cpp b/CodeGen/src/CodeBlockUnwind.cpp index cb2d693a..137c554c 100644 --- a/CodeGen/src/CodeBlockUnwind.cpp +++ b/CodeGen/src/CodeBlockUnwind.cpp @@ -2,6 +2,7 @@ #include "Luau/CodeBlockUnwind.h" #include "Luau/CodeAllocator.h" +#include "Luau/CodeGenCommon.h" #include "Luau/UnwindBuilder.h" #include diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index f22b2379..a518165f 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -3,7 +3,7 @@ #include "CodeGenLower.h" -#include "Luau/Common.h" +#include "Luau/CodeGenCommon.h" #include "Luau/CodeAllocator.h" #include "Luau/CodeBlockUnwind.h" #include "Luau/IrBuilder.h" @@ -44,6 +44,7 @@ LUAU_FASTFLAGVARIABLE(DebugCodegenNoOpt) LUAU_FASTFLAGVARIABLE(DebugCodegenOptSize) LUAU_FASTFLAGVARIABLE(DebugCodegenSkipNumbering) +LUAU_FASTFLAGVARIABLE(CodegenWiderLoweringStats) // Per-module IR instruction count limit LUAU_FASTINTVARIABLE(CodegenHeuristicsInstructionLimit, 1'048'576) // 1 M diff --git a/CodeGen/src/CodeGenAssembly.cpp b/CodeGen/src/CodeGenAssembly.cpp index bffce517..6bbdc473 100644 --- a/CodeGen/src/CodeGenAssembly.cpp +++ b/CodeGen/src/CodeGenAssembly.cpp @@ -1,5 +1,4 @@ // 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/BytecodeAnalysis.h" #include "Luau/BytecodeUtils.h" #include "Luau/BytecodeSummary.h" diff --git a/CodeGen/src/CodeGenContext.cpp b/CodeGen/src/CodeGenContext.cpp index 262d4a42..82dfa17e 100644 --- a/CodeGen/src/CodeGenContext.cpp +++ b/CodeGen/src/CodeGenContext.cpp @@ -5,6 +5,7 @@ #include "CodeGenLower.h" #include "CodeGenX64.h" +#include "Luau/CodeGenCommon.h" #include "Luau/CodeBlockUnwind.h" #include "Luau/UnwindBuilder.h" #include "Luau/UnwindBuilderDwarf2.h" diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index f41211cd..406fe5c9 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -7,6 +7,7 @@ #include "Luau/IrBuilder.h" #include "Luau/IrDump.h" #include "Luau/IrUtils.h" +#include "Luau/LoweringStats.h" #include "Luau/OptimizeConstProp.h" #include "Luau/OptimizeDeadStore.h" #include "Luau/OptimizeFinalX64.h" @@ -24,6 +25,7 @@ LUAU_FASTFLAG(DebugCodegenNoOpt) LUAU_FASTFLAG(DebugCodegenOptSize) LUAU_FASTFLAG(DebugCodegenSkipNumbering) +LUAU_FASTFLAG(CodegenWiderLoweringStats) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTINT(CodegenHeuristicsBlockLimit) LUAU_FASTINT(CodegenHeuristicsBlockInstructionLimit) @@ -298,6 +300,9 @@ inline bool lowerFunction( CodeGenCompilationResult& codeGenCompilationResult ) { + if (FFlag::CodegenWiderLoweringStats) + ir.function.stats = stats; + killUnusedBlocks(ir.function); unsigned preOptBlockCount = 0; diff --git a/CodeGen/src/CodeGenUtils.cpp b/CodeGen/src/CodeGenUtils.cpp index 7244e6cc..26451eea 100644 --- a/CodeGen/src/CodeGenUtils.cpp +++ b/CodeGen/src/CodeGenUtils.cpp @@ -63,7 +63,7 @@ namespace Luau namespace CodeGen { -bool forgLoopTableIter(lua_State* L, Table* h, int index, TValue* ra) +bool forgLoopTableIter(lua_State* L, LuaTable* h, int index, TValue* ra) { int sizearray = h->sizearray; @@ -106,7 +106,7 @@ bool forgLoopTableIter(lua_State* L, Table* h, int index, TValue* ra) return false; } -bool forgLoopNodeIter(lua_State* L, Table* h, int index, TValue* ra) +bool forgLoopNodeIter(lua_State* L, LuaTable* h, int index, TValue* ra) { int sizearray = h->sizearray; int sizenode = 1 << h->lsizenode; @@ -233,7 +233,7 @@ Udata* newUserdata(lua_State* L, size_t s, int tag) { Udata* u = luaU_newudata(L, s, tag); - if (Table* h = L->global->udatamt[tag]) + if (LuaTable* h = L->global->udatamt[tag]) { // currently, we always allocate unmarked objects, so forward barrier can be skipped LUAU_ASSERT(!isblack(obj2gco(u))); @@ -345,7 +345,7 @@ const Instruction* executeGETGLOBAL(lua_State* L, const Instruction* pc, StkId b LUAU_ASSERT(ttisstring(kv)); // fast-path should already have been checked, so we skip checking for it here - Table* h = cl->env; + LuaTable* h = cl->env; int slot = LUAU_INSN_C(insn) & h->nodemask8; // slow-path, may invoke Lua calls via __index metamethod @@ -368,7 +368,7 @@ const Instruction* executeSETGLOBAL(lua_State* L, const Instruction* pc, StkId b LUAU_ASSERT(ttisstring(kv)); // fast-path should already have been checked, so we skip checking for it here - Table* h = cl->env; + LuaTable* h = cl->env; int slot = LUAU_INSN_C(insn) & h->nodemask8; // slow-path, may invoke Lua calls via __newindex metamethod @@ -394,7 +394,7 @@ const Instruction* executeGETTABLEKS(lua_State* L, const Instruction* pc, StkId // fast-path: built-in table if (ttistable(rb)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); // we ignore the fast path that checks for the cached slot since IrTranslation already checks for it. @@ -506,7 +506,7 @@ const Instruction* executeSETTABLEKS(lua_State* L, const Instruction* pc, StkId // fast-path: built-in table if (ttistable(rb)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); // we ignore the fast path that checks for the cached slot since IrTranslation already checks for it. @@ -591,7 +591,7 @@ const Instruction* executeNAMECALL(lua_State* L, const Instruction* pc, StkId ba } else { - Table* mt = ttisuserdata(rb) ? uvalue(rb)->metatable : L->global->mt[ttype(rb)]; + LuaTable* mt = ttisuserdata(rb) ? uvalue(rb)->metatable : L->global->mt[ttype(rb)]; const TValue* tmi = 0; // fast-path: metatable with __namecall @@ -605,7 +605,7 @@ const Instruction* executeNAMECALL(lua_State* L, const Instruction* pc, StkId ba } else if ((tmi = fasttm(L, mt, TM_INDEX)) && ttistable(tmi)) { - Table* h = hvalue(tmi); + LuaTable* h = hvalue(tmi); int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -662,7 +662,7 @@ const Instruction* executeSETLIST(lua_State* L, const Instruction* pc, StkId bas L->top = L->ci->top; } - Table* h = hvalue(ra); + LuaTable* h = hvalue(ra); // TODO: we really don't need this anymore if (!ttistable(ra)) @@ -697,7 +697,7 @@ const Instruction* executeFORGPREP(lua_State* L, const Instruction* pc, StkId ba } else { - Table* mt = ttistable(ra) ? hvalue(ra)->metatable : ttisuserdata(ra) ? uvalue(ra)->metatable : cast_to(Table*, NULL); + LuaTable* mt = ttistable(ra) ? hvalue(ra)->metatable : ttisuserdata(ra) ? uvalue(ra)->metatable : cast_to(LuaTable*, NULL); if (const TValue* fn = fasttm(L, mt, TM_ITER)) { diff --git a/CodeGen/src/CodeGenUtils.h b/CodeGen/src/CodeGenUtils.h index 15d4c95d..1003a6f3 100644 --- a/CodeGen/src/CodeGenUtils.h +++ b/CodeGen/src/CodeGenUtils.h @@ -8,8 +8,8 @@ namespace Luau namespace CodeGen { -bool forgLoopTableIter(lua_State* L, Table* h, int index, TValue* ra); -bool forgLoopNodeIter(lua_State* L, Table* h, int index, TValue* ra); +bool forgLoopTableIter(lua_State* L, LuaTable* h, int index, TValue* ra); +bool forgLoopNodeIter(lua_State* L, LuaTable* h, int index, TValue* ra); bool forgLoopNonTableFallback(lua_State* L, int insnA, int aux); void forgPrepXnextFallback(lua_State* L, TValue* ra, int pc); diff --git a/CodeGen/src/EmitCommonX64.cpp b/CodeGen/src/EmitCommonX64.cpp index 79562b88..36b5130e 100644 --- a/CodeGen/src/EmitCommonX64.cpp +++ b/CodeGen/src/EmitCommonX64.cpp @@ -120,12 +120,12 @@ void getTableNodeAtCachedSlot(AssemblyBuilderX64& build, RegisterX64 tmp, Regist CODEGEN_ASSERT(tmp != node); CODEGEN_ASSERT(table != node); - build.mov(node, qword[table + offsetof(Table, node)]); + build.mov(node, qword[table + offsetof(LuaTable, node)]); // compute cached slot build.mov(tmp, sCode); build.movzx(dwordReg(tmp), byte[tmp + pcpos * sizeof(Instruction) + kOffsetOfInstructionC]); - build.and_(byteReg(tmp), byte[table + offsetof(Table, nodemask8)]); + build.and_(byteReg(tmp), byte[table + offsetof(LuaTable, nodemask8)]); // LuaNode* n = &h->node[slot]; build.shl(dwordReg(tmp), kLuaNodeSizeLog2); @@ -282,7 +282,7 @@ void callBarrierTableFast(IrRegAllocX64& regs, AssemblyBuilderX64& build, Regist IrCallWrapperX64 callWrap(regs, build); callWrap.addArgument(SizeX64::qword, rState); callWrap.addArgument(SizeX64::qword, table, tableOp); - callWrap.addArgument(SizeX64::qword, addr[table + offsetof(Table, gclist)]); + callWrap.addArgument(SizeX64::qword, addr[table + offsetof(LuaTable, gclist)]); callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaC_barrierback)]); } diff --git a/CodeGen/src/EmitInstructionX64.cpp b/CodeGen/src/EmitInstructionX64.cpp index ae3d1308..207f7f56 100644 --- a/CodeGen/src/EmitInstructionX64.cpp +++ b/CodeGen/src/EmitInstructionX64.cpp @@ -292,7 +292,7 @@ void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int Label skipResize; // Resize if h->sizearray < last - build.cmp(dword[table + offsetof(Table, sizearray)], last); + build.cmp(dword[table + offsetof(LuaTable, sizearray)], last); build.jcc(ConditionX64::NotBelow, skipResize); // Argument setup reordered to avoid conflicts @@ -309,7 +309,7 @@ void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int RegisterX64 arrayDst = rdx; RegisterX64 offset = rcx; - build.mov(arrayDst, qword[table + offsetof(Table, array)]); + build.mov(arrayDst, qword[table + offsetof(LuaTable, array)]); const int kUnrollSetListLimit = 4; @@ -380,7 +380,7 @@ void emitInstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRep // &array[index] build.mov(dwordReg(elemPtr), dwordReg(index)); build.shl(dwordReg(elemPtr), kTValueSizeLog2); - build.add(elemPtr, qword[table + offsetof(Table, array)]); + build.add(elemPtr, qword[table + offsetof(LuaTable, array)]); // Clear extra variables since we might have more than two for (int i = 2; i < aux; ++i) @@ -391,7 +391,7 @@ void emitInstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRep // First we advance index through the array portion // while (unsigned(index) < unsigned(sizearray)) Label arrayLoop = build.setLabel(); - build.cmp(dwordReg(index), dword[table + offsetof(Table, sizearray)]); + build.cmp(dwordReg(index), dword[table + offsetof(LuaTable, sizearray)]); build.jcc(ConditionX64::NotBelow, skipArray); // If element is nil, we increment the index; if it's not, we still need 'index + 1' inside diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index d29755f1..2ec72445 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -4,6 +4,7 @@ #include "Luau/DenseHash.h" #include "Luau/IrData.h" #include "Luau/IrUtils.h" +#include "Luau/LoweringStats.h" #include "EmitCommonA64.h" #include "NativeState.h" @@ -330,7 +331,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) case IrCmd::GET_ARR_ADDR: { inst.regA64 = regs.allocReuse(KindA64::x, index, {inst.a}); - build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, array))); + build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(LuaTable, array))); if (inst.b.kind == IrOpKind::Inst) { @@ -376,11 +377,11 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // C field can be shifted as long as it's at the most significant byte of the instruction word CODEGEN_ASSERT(kOffsetOfInstructionC == 3); - build.ldrb(temp2, mem(regOp(inst.a), offsetof(Table, nodemask8))); + build.ldrb(temp2, mem(regOp(inst.a), offsetof(LuaTable, nodemask8))); build.and_(temp2, temp2, temp1w, -24); // note: this may clobber inst.a, so it's important that we don't use it after this - build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, node))); + build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(LuaTable, node))); build.add(inst.regA64, inst.regA64, temp2x, kLuaNodeSizeLog2); // "zero extend" temp2 to get a larger shift (top 32 bits are zero) break; } @@ -393,13 +394,13 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // hash & ((1 << lsizenode) - 1) == hash & ~(-1 << lsizenode) build.mov(temp1, -1); - build.ldrb(temp2, mem(regOp(inst.a), offsetof(Table, lsizenode))); + build.ldrb(temp2, mem(regOp(inst.a), offsetof(LuaTable, lsizenode))); build.lsl(temp1, temp1, temp2); build.mov(temp2, uintOp(inst.b)); build.bic(temp2, temp2, temp1); // note: this may clobber inst.a, so it's important that we don't use it after this - build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, node))); + build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(LuaTable, node))); build.add(inst.regA64, inst.regA64, temp2x, kLuaNodeSizeLog2); // "zero extend" temp2 to get a larger shift (top 32 bits are zero) break; } @@ -1075,10 +1076,10 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp1 = regs.allocTemp(KindA64::x); RegisterA64 temp2 = regs.allocTemp(KindA64::w); - build.ldr(temp1, mem(regOp(inst.a), offsetof(Table, metatable))); + build.ldr(temp1, mem(regOp(inst.a), offsetof(LuaTable, metatable))); build.cbz(temp1, labelOp(inst.c)); // no metatable - build.ldrb(temp2, mem(temp1, offsetof(Table, tmcache))); + build.ldrb(temp2, mem(temp1, offsetof(LuaTable, tmcache))); build.tst(temp2, 1 << intOp(inst.b)); // can't use tbz/tbnz because their jump offsets are too short build.b(ConditionA64::NotEqual, labelOp(inst.c)); // Equal = Zero after tst; tmcache caches *absence* of metamethods @@ -1515,7 +1516,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { Label fresh; // used when guard aborts execution or jumps to a VM exit RegisterA64 temp = regs.allocTemp(KindA64::w); - build.ldrb(temp, mem(regOp(inst.a), offsetof(Table, readonly))); + build.ldrb(temp, mem(regOp(inst.a), offsetof(LuaTable, readonly))); build.cbnz(temp, getTargetLabel(inst.b, fresh)); finalizeTargetLabel(inst.b, fresh); break; @@ -1524,7 +1525,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { Label fresh; // used when guard aborts execution or jumps to a VM exit RegisterA64 temp = regs.allocTemp(KindA64::x); - build.ldr(temp, mem(regOp(inst.a), offsetof(Table, metatable))); + build.ldr(temp, mem(regOp(inst.a), offsetof(LuaTable, metatable))); build.cbnz(temp, getTargetLabel(inst.b, fresh)); finalizeTargetLabel(inst.b, fresh); break; @@ -1535,7 +1536,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp = regs.allocTemp(KindA64::x); RegisterA64 tempw = castReg(KindA64::w, temp); build.ldr(temp, mem(rClosure, offsetof(Closure, env))); - build.ldrb(tempw, mem(temp, offsetof(Table, safeenv))); + build.ldrb(tempw, mem(temp, offsetof(LuaTable, safeenv))); build.cbz(tempw, getTargetLabel(inst.a, fresh)); finalizeTargetLabel(inst.a, fresh); break; @@ -1546,7 +1547,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) Label& fail = getTargetLabel(inst.c, fresh); RegisterA64 temp = regs.allocTemp(KindA64::w); - build.ldr(temp, mem(regOp(inst.a), offsetof(Table, sizearray))); + build.ldr(temp, mem(regOp(inst.a), offsetof(LuaTable, sizearray))); if (inst.b.kind == IrOpKind::Inst) { @@ -1773,7 +1774,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) size_t spills = regs.spill(build, index, {reg}); build.mov(x1, reg); build.mov(x0, rState); - build.add(x2, x1, uint16_t(offsetof(Table, gclist))); + build.add(x2, x1, uint16_t(offsetof(LuaTable, gclist))); build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaC_barrierback))); build.blr(x3); diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index c1a84c8e..e0ece9da 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -4,6 +4,7 @@ #include "Luau/DenseHash.h" #include "Luau/IrData.h" #include "Luau/IrUtils.h" +#include "Luau/LoweringStats.h" #include "Luau/IrCallWrapperX64.h" @@ -159,13 +160,13 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.mov(dwordReg(inst.regX64), regOp(inst.b)); build.shl(dwordReg(inst.regX64), kTValueSizeLog2); - build.add(inst.regX64, qword[regOp(inst.a) + offsetof(Table, array)]); + build.add(inst.regX64, qword[regOp(inst.a) + offsetof(LuaTable, array)]); } else if (inst.b.kind == IrOpKind::Constant) { inst.regX64 = regs.allocRegOrReuse(SizeX64::qword, index, {inst.a}); - build.mov(inst.regX64, qword[regOp(inst.a) + offsetof(Table, array)]); + build.mov(inst.regX64, qword[regOp(inst.a) + offsetof(LuaTable, array)]); if (intOp(inst.b) != 0) build.lea(inst.regX64, addr[inst.regX64 + intOp(inst.b) * sizeof(TValue)]); @@ -193,9 +194,9 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) ScopedRegX64 tmp{regs, SizeX64::qword}; - build.mov(inst.regX64, qword[regOp(inst.a) + offsetof(Table, node)]); + build.mov(inst.regX64, qword[regOp(inst.a) + offsetof(LuaTable, node)]); build.mov(dwordReg(tmp.reg), 1); - build.mov(byteReg(shiftTmp.reg), byte[regOp(inst.a) + offsetof(Table, lsizenode)]); + build.mov(byteReg(shiftTmp.reg), byte[regOp(inst.a) + offsetof(LuaTable, lsizenode)]); build.shl(dwordReg(tmp.reg), byteReg(shiftTmp.reg)); build.dec(dwordReg(tmp.reg)); build.and_(dwordReg(tmp.reg), uintOp(inst.b)); @@ -954,13 +955,13 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { ScopedRegX64 tmp{regs, SizeX64::qword}; - build.mov(tmp.reg, qword[regOp(inst.a) + offsetof(Table, metatable)]); + build.mov(tmp.reg, qword[regOp(inst.a) + offsetof(LuaTable, metatable)]); regs.freeLastUseReg(function.instOp(inst.a), index); // Release before the call if it's the last use build.test(tmp.reg, tmp.reg); build.jcc(ConditionX64::Zero, labelOp(inst.c)); // No metatable - build.test(byte[tmp.reg + offsetof(Table, tmcache)], 1 << intOp(inst.b)); + build.test(byte[tmp.reg + offsetof(LuaTable, tmcache)], 1 << intOp(inst.b)); build.jcc(ConditionX64::NotZero, labelOp(inst.c)); // No tag method ScopedRegX64 tmp2{regs, SizeX64::qword}; @@ -1320,11 +1321,11 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) break; } case IrCmd::CHECK_READONLY: - build.cmp(byte[regOp(inst.a) + offsetof(Table, readonly)], 0); + build.cmp(byte[regOp(inst.a) + offsetof(LuaTable, readonly)], 0); jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.b, next); break; case IrCmd::CHECK_NO_METATABLE: - build.cmp(qword[regOp(inst.a) + offsetof(Table, metatable)], 0); + build.cmp(qword[regOp(inst.a) + offsetof(LuaTable, metatable)], 0); jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.b, next); break; case IrCmd::CHECK_SAFE_ENV: @@ -1333,16 +1334,16 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.mov(tmp.reg, sClosure); build.mov(tmp.reg, qword[tmp.reg + offsetof(Closure, env)]); - build.cmp(byte[tmp.reg + offsetof(Table, safeenv)], 0); + build.cmp(byte[tmp.reg + offsetof(LuaTable, safeenv)], 0); jumpOrAbortOnUndef(ConditionX64::Equal, inst.a, next); break; } case IrCmd::CHECK_ARRAY_SIZE: if (inst.b.kind == IrOpKind::Inst) - build.cmp(dword[regOp(inst.a) + offsetof(Table, sizearray)], regOp(inst.b)); + build.cmp(dword[regOp(inst.a) + offsetof(LuaTable, sizearray)], regOp(inst.b)); else if (inst.b.kind == IrOpKind::Constant) - build.cmp(dword[regOp(inst.a) + offsetof(Table, sizearray)], intOp(inst.b)); + build.cmp(dword[regOp(inst.a) + offsetof(LuaTable, sizearray)], intOp(inst.b)); else CODEGEN_ASSERT(!"Unsupported instruction form"); diff --git a/CodeGen/src/IrRegAllocA64.cpp b/CodeGen/src/IrRegAllocA64.cpp index bd2147a7..15a306c9 100644 --- a/CodeGen/src/IrRegAllocA64.cpp +++ b/CodeGen/src/IrRegAllocA64.cpp @@ -2,8 +2,8 @@ #include "IrRegAllocA64.h" #include "Luau/AssemblyBuilderA64.h" -#include "Luau/CodeGen.h" #include "Luau/IrUtils.h" +#include "Luau/LoweringStats.h" #include "BitUtils.h" #include "EmitCommonA64.h" diff --git a/CodeGen/src/IrRegAllocX64.cpp b/CodeGen/src/IrRegAllocX64.cpp index d647484b..64625868 100644 --- a/CodeGen/src/IrRegAllocX64.cpp +++ b/CodeGen/src/IrRegAllocX64.cpp @@ -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/IrRegAllocX64.h" -#include "Luau/CodeGen.h" #include "Luau/IrUtils.h" +#include "Luau/LoweringStats.h" #include "EmitCommonX64.h" diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 62829766..d15d57e2 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -3,7 +3,7 @@ #include "Luau/Bytecode.h" #include "Luau/BytecodeUtils.h" -#include "Luau/CodeGen.h" +#include "Luau/CodeGenOptions.h" #include "Luau/IrBuilder.h" #include "Luau/IrUtils.h" diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index 74bbc6d7..00f274e7 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -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/IrUtils.h" +#include "Luau/CodeGenOptions.h" #include "Luau/IrBuilder.h" #include "BitUtils.h" diff --git a/CodeGen/src/NativeState.h b/CodeGen/src/NativeState.h index 941db252..b4f74132 100644 --- a/CodeGen/src/NativeState.h +++ b/CodeGen/src/NativeState.h @@ -44,25 +44,25 @@ struct NativeContext void (*luaV_dolen)(lua_State* L, StkId ra, const TValue* rb) = nullptr; void (*luaV_gettable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr; void (*luaV_settable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr; - void (*luaV_getimport)(lua_State* L, Table* env, TValue* k, StkId res, uint32_t id, bool propagatenil) = nullptr; + void (*luaV_getimport)(lua_State* L, LuaTable* env, TValue* k, StkId res, uint32_t id, bool propagatenil) = nullptr; void (*luaV_concat)(lua_State* L, int total, int last) = nullptr; - int (*luaH_getn)(Table* t) = nullptr; - Table* (*luaH_new)(lua_State* L, int narray, int lnhash) = nullptr; - Table* (*luaH_clone)(lua_State* L, Table* tt) = nullptr; - void (*luaH_resizearray)(lua_State* L, Table* t, int nasize) = nullptr; - TValue* (*luaH_setnum)(lua_State* L, Table* t, int key); + int (*luaH_getn)(LuaTable* t) = nullptr; + LuaTable* (*luaH_new)(lua_State* L, int narray, int lnhash) = nullptr; + LuaTable* (*luaH_clone)(lua_State* L, LuaTable* tt) = nullptr; + void (*luaH_resizearray)(lua_State* L, LuaTable* t, int nasize) = nullptr; + TValue* (*luaH_setnum)(lua_State* L, LuaTable* t, int key); - void (*luaC_barriertable)(lua_State* L, Table* t, GCObject* v) = nullptr; + void (*luaC_barriertable)(lua_State* L, LuaTable* t, GCObject* v) = nullptr; void (*luaC_barrierf)(lua_State* L, GCObject* o, GCObject* v) = nullptr; void (*luaC_barrierback)(lua_State* L, GCObject* o, GCObject** gclist) = nullptr; size_t (*luaC_step)(lua_State* L, bool assist) = nullptr; void (*luaF_close)(lua_State* L, StkId level) = nullptr; UpVal* (*luaF_findupval)(lua_State* L, StkId level) = nullptr; - Closure* (*luaF_newLclosure)(lua_State* L, int nelems, Table* e, Proto* p) = nullptr; + Closure* (*luaF_newLclosure)(lua_State* L, int nelems, LuaTable* e, Proto* p) = nullptr; - const TValue* (*luaT_gettm)(Table* events, TMS event, TString* ename) = nullptr; + const TValue* (*luaT_gettm)(LuaTable* events, TMS event, TString* ename) = nullptr; const TString* (*luaT_objtypenamestr)(lua_State* L, const TValue* o) = nullptr; double (*libm_exp)(double) = nullptr; @@ -87,8 +87,8 @@ struct NativeContext double (*libm_modf)(double, double*) = nullptr; // Helper functions - bool (*forgLoopTableIter)(lua_State* L, Table* h, int index, TValue* ra) = nullptr; - bool (*forgLoopNodeIter)(lua_State* L, Table* h, int index, TValue* ra) = nullptr; + bool (*forgLoopTableIter)(lua_State* L, LuaTable* h, int index, TValue* ra) = nullptr; + bool (*forgLoopNodeIter)(lua_State* L, LuaTable* h, int index, TValue* ra) = nullptr; bool (*forgLoopNonTableFallback)(lua_State* L, int insnA, int aux) = nullptr; void (*forgPrepXnextFallback)(lua_State* L, TValue* ra, int pc) = nullptr; Closure* (*callProlog)(lua_State* L, TValue* ra, StkId argtop, int nresults) = nullptr; diff --git a/Sources.cmake b/Sources.cmake index 14bfe6a0..306d1530 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -78,6 +78,7 @@ target_sources(Luau.CodeGen PRIVATE CodeGen/include/Luau/CodeBlockUnwind.h CodeGen/include/Luau/CodeGen.h CodeGen/include/Luau/CodeGenCommon.h + CodeGen/include/Luau/CodeGenOptions.h CodeGen/include/Luau/ConditionA64.h CodeGen/include/Luau/ConditionX64.h CodeGen/include/Luau/IrAnalysis.h @@ -89,6 +90,7 @@ target_sources(Luau.CodeGen PRIVATE CodeGen/include/Luau/IrUtils.h CodeGen/include/Luau/IrVisitUseDef.h CodeGen/include/Luau/Label.h + CodeGen/include/Luau/LoweringStats.h CodeGen/include/Luau/NativeProtoExecData.h CodeGen/include/Luau/OperandX64.h CodeGen/include/Luau/OptimizeConstProp.h diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index 052d8c82..1a8af74d 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -64,7 +64,7 @@ const char* luau_ident = "$Luau: Copyright (C) 2019-2024 Roblox Corporation $\n" ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, ts->len) : -1; \ } -static Table* getcurrenv(lua_State* L) +static LuaTable* getcurrenv(lua_State* L) { if (L->ci == L->base_ci) // no enclosing function? return L->gt; // use global table as environment @@ -762,7 +762,7 @@ void lua_setreadonly(lua_State* L, int objindex, int enabled) { const TValue* o = index2addr(L, objindex); api_check(L, ttistable(o)); - Table* t = hvalue(o); + LuaTable* t = hvalue(o); api_check(L, t != hvalue(registry(L))); t->readonly = bool(enabled); } @@ -771,7 +771,7 @@ int lua_getreadonly(lua_State* L, int objindex) { const TValue* o = index2addr(L, objindex); api_check(L, ttistable(o)); - Table* t = hvalue(o); + LuaTable* t = hvalue(o); int res = t->readonly; return res; } @@ -780,14 +780,14 @@ void lua_setsafeenv(lua_State* L, int objindex, int enabled) { const TValue* o = index2addr(L, objindex); api_check(L, ttistable(o)); - Table* t = hvalue(o); + LuaTable* t = hvalue(o); t->safeenv = bool(enabled); } int lua_getmetatable(lua_State* L, int objindex) { luaC_threadbarrier(L); - Table* mt = NULL; + LuaTable* mt = NULL; const TValue* obj = index2addr(L, objindex); switch (ttype(obj)) { @@ -894,7 +894,7 @@ int lua_setmetatable(lua_State* L, int objindex) api_checknelems(L, 1); TValue* obj = index2addr(L, objindex); api_checkvalidindex(L, obj); - Table* mt = NULL; + LuaTable* mt = NULL; if (!ttisnil(L->top - 1)) { api_check(L, ttistable(L->top - 1)); @@ -1214,7 +1214,7 @@ int lua_rawiter(lua_State* L, int idx, int iter) api_check(L, ttistable(t)); api_check(L, iter >= 0); - Table* h = hvalue(t); + LuaTable* h = hvalue(t); int sizearray = h->sizearray; // first we advance iter through the array portion @@ -1293,7 +1293,7 @@ void* lua_newuserdatataggedwithmetatable(lua_State* L, size_t sz, int tag) // currently, we always allocate unmarked objects, so forward barrier can be skipped LUAU_ASSERT(!isblack(obj2gco(u))); - Table* h = L->global->udatamt[tag]; + LuaTable* h = L->global->udatamt[tag]; api_check(L, h != nullptr); u->metatable = h; @@ -1394,7 +1394,7 @@ int lua_ref(lua_State* L, int idx) StkId p = index2addr(L, idx); if (!ttisnil(p)) { - Table* reg = hvalue(registry(L)); + LuaTable* reg = hvalue(registry(L)); if (g->registryfree != 0) { // reuse existing slot @@ -1421,7 +1421,7 @@ void lua_unref(lua_State* L, int ref) return; global_State* g = L->global; - Table* reg = hvalue(registry(L)); + LuaTable* reg = hvalue(registry(L)); TValue* slot = luaH_setnum(L, reg, ref); setnvalue(slot, g->registryfree); // NB: no barrier needed because value isn't collectable g->registryfree = ref; @@ -1462,7 +1462,7 @@ void lua_getuserdatametatable(lua_State* L, int tag) api_check(L, unsigned(tag) < LUA_UTAG_LIMIT); luaC_threadbarrier(L); - if (Table* h = L->global->udatamt[tag]) + if (LuaTable* h = L->global->udatamt[tag]) { sethvalue(L, L->top, h); } @@ -1510,7 +1510,7 @@ void lua_cleartable(lua_State* L, int idx) { StkId t = index2addr(L, idx); api_check(L, ttistable(t)); - Table* tt = hvalue(t); + LuaTable* tt = hvalue(t); if (tt->readonly) luaG_readonlyerror(L); luaH_clear(tt); diff --git a/VM/src/lbuflib.cpp b/VM/src/lbuflib.cpp index 179e4171..643d3a9c 100644 --- a/VM/src/lbuflib.cpp +++ b/VM/src/lbuflib.cpp @@ -10,7 +10,7 @@ #include -LUAU_FASTFLAGVARIABLE(LuauBufferBitMethods) +LUAU_FASTFLAGVARIABLE(LuauBufferBitMethods2) // 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 @@ -370,7 +370,7 @@ static const luaL_Reg bufferlib[] = { int luaopen_buffer(lua_State* L) { - luaL_register(L, LUA_BUFFERLIBNAME, FFlag::LuauBufferBitMethods ? bufferlib : bufferlib_DEPRECATED); + luaL_register(L, LUA_BUFFERLIBNAME, FFlag::LuauBufferBitMethods2 ? bufferlib : bufferlib_DEPRECATED); return 1; } diff --git a/VM/src/lbuiltins.cpp b/VM/src/lbuiltins.cpp index 92234a0f..74702fe5 100644 --- a/VM/src/lbuiltins.cpp +++ b/VM/src/lbuiltins.cpp @@ -1000,7 +1000,7 @@ static int luauF_rawset(lua_State* L, StkId res, TValue* arg0, int nresults, Stk else if (ttisvector(key) && luai_vecisnan(vvalue(key))) return -1; - Table* t = hvalue(arg0); + LuaTable* t = hvalue(arg0); if (t->readonly) return -1; @@ -1017,7 +1017,7 @@ static int luauF_tinsert(lua_State* L, StkId res, TValue* arg0, int nresults, St { if (nparams == 2 && nresults <= 0 && ttistable(arg0)) { - Table* t = hvalue(arg0); + LuaTable* t = hvalue(arg0); if (t->readonly) return -1; @@ -1034,7 +1034,7 @@ static int luauF_tunpack(lua_State* L, StkId res, TValue* arg0, int nresults, St { if (nparams >= 1 && nresults < 0 && ttistable(arg0)) { - Table* t = hvalue(arg0); + LuaTable* t = hvalue(arg0); int n = -1; if (nparams == 1) @@ -1196,7 +1196,7 @@ static int luauF_rawlen(lua_State* L, StkId res, TValue* arg0, int nresults, Stk { if (ttistable(arg0)) { - Table* h = hvalue(arg0); + LuaTable* h = hvalue(arg0); setnvalue(res, double(luaH_getn(h))); return 1; } @@ -1240,7 +1240,7 @@ static int luauF_getmetatable(lua_State* L, StkId res, TValue* arg0, int nresult { if (nparams >= 1 && nresults <= 1) { - Table* mt = NULL; + LuaTable* mt = NULL; if (ttistable(arg0)) mt = hvalue(arg0)->metatable; else if (ttisuserdata(arg0)) @@ -1275,11 +1275,11 @@ static int luauF_setmetatable(lua_State* L, StkId res, TValue* arg0, int nresult // note: setmetatable(_, nil) is rare so we use fallback for it to optimize the fast path if (nparams >= 2 && nresults <= 1 && ttistable(arg0) && ttistable(args)) { - Table* t = hvalue(arg0); + LuaTable* t = hvalue(arg0); if (t->readonly || t->metatable != NULL) return -1; // note: overwriting non-null metatable is very rare but it requires __metatable check - Table* mt = hvalue(args); + LuaTable* mt = hvalue(args); t->metatable = mt; luaC_objbarrier(L, t, mt); diff --git a/VM/src/lfunc.cpp b/VM/src/lfunc.cpp index 2a1e45c4..b172d0ad 100644 --- a/VM/src/lfunc.cpp +++ b/VM/src/lfunc.cpp @@ -55,7 +55,7 @@ Proto* luaF_newproto(lua_State* L) return f; } -Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p) +Closure* luaF_newLclosure(lua_State* L, int nelems, LuaTable* e, Proto* p) { Closure* c = luaM_newgco(L, Closure, sizeLclosure(nelems), L->activememcat); luaC_init(L, c, LUA_TFUNCTION); @@ -70,7 +70,7 @@ Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p) return c; } -Closure* luaF_newCclosure(lua_State* L, int nelems, Table* e) +Closure* luaF_newCclosure(lua_State* L, int nelems, LuaTable* e) { Closure* c = luaM_newgco(L, Closure, sizeCclosure(nelems), L->activememcat); luaC_init(L, c, LUA_TFUNCTION); diff --git a/VM/src/lfunc.h b/VM/src/lfunc.h index 679836e7..453cf581 100644 --- a/VM/src/lfunc.h +++ b/VM/src/lfunc.h @@ -8,8 +8,8 @@ #define sizeLclosure(n) (offsetof(Closure, l.uprefs) + sizeof(TValue) * (n)) LUAI_FUNC Proto* luaF_newproto(lua_State* L); -LUAI_FUNC Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p); -LUAI_FUNC Closure* luaF_newCclosure(lua_State* L, int nelems, Table* e); +LUAI_FUNC Closure* luaF_newLclosure(lua_State* L, int nelems, LuaTable* e, Proto* p); +LUAI_FUNC Closure* luaF_newCclosure(lua_State* L, int nelems, LuaTable* e); LUAI_FUNC UpVal* luaF_findupval(lua_State* L, StkId level); LUAI_FUNC void luaF_close(lua_State* L, StkId level); LUAI_FUNC void luaF_closeupval(lua_State* L, UpVal* uv, bool dead); diff --git a/VM/src/lgc.cpp b/VM/src/lgc.cpp index d9843ddc..c5e16e43 100644 --- a/VM/src/lgc.cpp +++ b/VM/src/lgc.cpp @@ -244,7 +244,7 @@ static void reallymarkobject(global_State* g, GCObject* o) } case LUA_TUSERDATA: { - Table* mt = gco2u(o)->metatable; + LuaTable* mt = gco2u(o)->metatable; gray2black(o); // udata are never gray if (mt) markobject(g, mt); @@ -292,7 +292,7 @@ static void reallymarkobject(global_State* g, GCObject* o) } } -static const char* gettablemode(global_State* g, Table* h) +static const char* gettablemode(global_State* g, LuaTable* h) { const TValue* mode = gfasttm(g, h->metatable, TM_MODE); @@ -302,13 +302,13 @@ static const char* gettablemode(global_State* g, Table* h) return NULL; } -static int traversetable(global_State* g, Table* h) +static int traversetable(global_State* g, LuaTable* h) { int i; int weakkey = 0; int weakvalue = 0; if (h->metatable) - markobject(g, cast_to(Table*, h->metatable)); + markobject(g, cast_to(LuaTable*, h->metatable)); // is there a weak mode? if (const char* modev = gettablemode(g, h)) @@ -459,11 +459,11 @@ static size_t propagatemark(global_State* g) { case LUA_TTABLE: { - Table* h = gco2h(o); + LuaTable* h = gco2h(o); g->gray = h->gclist; if (traversetable(g, h)) // table is weak? black2gray(o); // keep it gray - return sizeof(Table) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h); + return sizeof(LuaTable) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h); } case LUA_TFUNCTION: { @@ -553,8 +553,8 @@ static size_t cleartable(lua_State* L, GCObject* l) size_t work = 0; while (l) { - Table* h = gco2h(l); - work += sizeof(Table) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h); + LuaTable* h = gco2h(l); + work += sizeof(LuaTable) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h); int i = h->sizearray; while (i--) @@ -1155,7 +1155,7 @@ void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v) makewhite(g, o); // mark as white just to avoid other barriers } -void luaC_barriertable(lua_State* L, Table* t, GCObject* v) +void luaC_barriertable(lua_State* L, LuaTable* t, GCObject* v) { global_State* g = L->global; GCObject* o = obj2gco(t); diff --git a/VM/src/lgc.h b/VM/src/lgc.h index 722de9d1..683542b6 100644 --- a/VM/src/lgc.h +++ b/VM/src/lgc.h @@ -131,7 +131,7 @@ LUAI_FUNC void luaC_fullgc(lua_State* L); LUAI_FUNC void luaC_initobj(lua_State* L, GCObject* o, uint8_t tt); LUAI_FUNC void luaC_upvalclosed(lua_State* L, UpVal* uv); LUAI_FUNC void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v); -LUAI_FUNC void luaC_barriertable(lua_State* L, Table* t, GCObject* v); +LUAI_FUNC void luaC_barriertable(lua_State* L, LuaTable* t, GCObject* v); LUAI_FUNC void luaC_barrierback(lua_State* L, GCObject* o, GCObject** gclist); LUAI_FUNC void luaC_validate(lua_State* L); LUAI_FUNC void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat)); diff --git a/VM/src/lgcdebug.cpp b/VM/src/lgcdebug.cpp index 768561cb..7a47ab86 100644 --- a/VM/src/lgcdebug.cpp +++ b/VM/src/lgcdebug.cpp @@ -34,7 +34,7 @@ static void validateref(global_State* g, GCObject* f, TValue* v) } } -static void validatetable(global_State* g, Table* h) +static void validatetable(global_State* g, LuaTable* h) { int sizenode = 1 << h->lsizenode; @@ -290,9 +290,9 @@ static void dumpstring(FILE* f, TString* ts) fprintf(f, "\"}"); } -static void dumptable(FILE* f, Table* h) +static void dumptable(FILE* f, LuaTable* h) { - size_t size = sizeof(Table) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue); + size_t size = sizeof(LuaTable) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue); fprintf(f, "{\"type\":\"table\",\"cat\":%d,\"size\":%d", h->memcat, int(size)); @@ -654,9 +654,9 @@ static void enumstring(EnumContext* ctx, TString* ts) enumnode(ctx, obj2gco(ts), ts->len, NULL); } -static void enumtable(EnumContext* ctx, Table* h) +static void enumtable(EnumContext* ctx, LuaTable* h) { - size_t size = sizeof(Table) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue); + size_t size = sizeof(LuaTable) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue); // Provide a name for a special registry table enumnode(ctx, obj2gco(h), size, h == hvalue(registry(ctx->L)) ? "registry" : NULL); @@ -754,7 +754,7 @@ static void enumudata(EnumContext* ctx, Udata* u) { const char* name = NULL; - if (Table* h = u->metatable) + if (LuaTable* h = u->metatable) { if (h->node != &luaH_dummynode) { diff --git a/VM/src/lmem.cpp b/VM/src/lmem.cpp index 6fe82b30..0738840b 100644 --- a/VM/src/lmem.cpp +++ b/VM/src/lmem.cpp @@ -121,7 +121,7 @@ static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header"); static_assert(offsetof(Udata, data) == ABISWITCH(16, 16, 12), "size mismatch for userdata header"); -static_assert(sizeof(Table) == ABISWITCH(48, 32, 32), "size mismatch for table header"); +static_assert(sizeof(LuaTable) == ABISWITCH(48, 32, 32), "size mismatch for table header"); static_assert(offsetof(Buffer, data) == ABISWITCH(8, 8, 8), "size mismatch for buffer header"); const size_t kSizeClasses = LUA_SIZECLASSES; diff --git a/VM/src/lobject.h b/VM/src/lobject.h index 18c69641..6719faaf 100644 --- a/VM/src/lobject.h +++ b/VM/src/lobject.h @@ -263,7 +263,7 @@ typedef struct Udata int len; - struct Table* metatable; + struct LuaTable* metatable; union { @@ -390,7 +390,7 @@ typedef struct Closure uint8_t preload; GCObject* gclist; - struct Table* env; + struct LuaTable* env; union { @@ -454,7 +454,7 @@ typedef struct LuaNode } // clang-format off -typedef struct Table +typedef struct LuaTable { CommonHeader; @@ -473,11 +473,11 @@ typedef struct Table }; - struct Table* metatable; + struct LuaTable* metatable; TValue* array; // array part LuaNode* node; GCObject* gclist; -} Table; +} LuaTable; // clang-format on /* diff --git a/VM/src/lstate.h b/VM/src/lstate.h index 3f4f9425..ad162391 100644 --- a/VM/src/lstate.h +++ b/VM/src/lstate.h @@ -198,7 +198,7 @@ typedef struct global_State struct lua_State* mainthread; UpVal uvhead; // head of double-linked list of all open upvalues - struct Table* mt[LUA_T_COUNT]; // metatables for basic types + struct LuaTable* mt[LUA_T_COUNT]; // metatables for basic types TString* ttname[LUA_T_COUNT]; // names for basic types TString* tmname[TM_N]; // array with tag-method names @@ -217,7 +217,7 @@ typedef struct global_State lua_ExecutionCallbacks ecb; void (*udatagc[LUA_UTAG_LIMIT])(lua_State*, void*); // for each userdata tag, a gc callback to be called immediately before freeing memory - Table* udatamt[LUA_UTAG_LIMIT]; // metatables for tagged userdata + LuaTable* udatamt[LUA_UTAG_LIMIT]; // metatables for tagged userdata TString* lightuserdataname[LUA_LUTAG_LIMIT]; // names for tagged lightuserdata @@ -266,7 +266,7 @@ struct lua_State int cachedslot; // when table operations or INDEX/NEWINDEX is invoked from Luau, what is the expected slot for lookup? - Table* gt; // table of globals + LuaTable* gt; // table of globals UpVal* openupval; // list of open upvalues in this stack GCObject* gclist; @@ -285,7 +285,7 @@ union GCObject struct TString ts; struct Udata u; struct Closure cl; - struct Table h; + struct LuaTable h; struct Proto p; struct UpVal uv; struct lua_State th; // thread diff --git a/VM/src/ltable.cpp b/VM/src/ltable.cpp index dafb2b3f..ee5ae7ec 100644 --- a/VM/src/ltable.cpp +++ b/VM/src/ltable.cpp @@ -58,7 +58,7 @@ const LuaNode luaH_dummynode = { #define hashstr(t, str) hashpow2(t, (str)->hash) #define hashboolean(t, p) hashpow2(t, p) -static LuaNode* hashpointer(const Table* t, const void* p) +static LuaNode* hashpointer(const LuaTable* t, const void* p) { // we discard the high 32-bit portion of the pointer on 64-bit platforms as it doesn't carry much entropy anyway unsigned int h = unsigned(uintptr_t(p)); @@ -73,7 +73,7 @@ static LuaNode* hashpointer(const Table* t, const void* p) return hashpow2(t, h); } -static LuaNode* hashnum(const Table* t, double n) +static LuaNode* hashnum(const LuaTable* t, double n) { static_assert(sizeof(double) == sizeof(unsigned int) * 2, "expected a 8-byte double"); unsigned int i[2]; @@ -99,7 +99,7 @@ static LuaNode* hashnum(const Table* t, double n) return hashpow2(t, h2); } -static LuaNode* hashvec(const Table* t, const float* v) +static LuaNode* hashvec(const LuaTable* t, const float* v) { unsigned int i[LUA_VECTOR_SIZE]; memcpy(i, v, sizeof(i)); @@ -130,7 +130,7 @@ static LuaNode* hashvec(const Table* t, const float* v) ** returns the `main' position of an element in a table (that is, the index ** of its hash value) */ -static LuaNode* mainposition(const Table* t, const TValue* key) +static LuaNode* mainposition(const LuaTable* t, const TValue* key) { switch (ttype(key)) { @@ -166,7 +166,7 @@ static int arrayindex(double key) ** elements in the array part, then elements in the hash part. The ** beginning of a traversal is signalled by -1. */ -static int findindex(lua_State* L, Table* t, StkId key) +static int findindex(lua_State* L, LuaTable* t, StkId key) { int i; if (ttisnil(key)) @@ -194,7 +194,7 @@ static int findindex(lua_State* L, Table* t, StkId key) } } -int luaH_next(lua_State* L, Table* t, StkId key) +int luaH_next(lua_State* L, LuaTable* t, StkId key) { int i = findindex(L, t, key); // find original element for (i++; i < t->sizearray; i++) @@ -270,7 +270,7 @@ static int countint(double key, int* nums) return 0; } -static int numusearray(const Table* t, int* nums) +static int numusearray(const LuaTable* t, int* nums) { int lg; int ttlg; // 2^lg @@ -298,7 +298,7 @@ static int numusearray(const Table* t, int* nums) return ause; } -static int numusehash(const Table* t, int* nums, int* pnasize) +static int numusehash(const LuaTable* t, int* nums, int* pnasize) { int totaluse = 0; // total number of elements int ause = 0; // summation of `nums' @@ -317,7 +317,7 @@ static int numusehash(const Table* t, int* nums, int* pnasize) return totaluse; } -static void setarrayvector(lua_State* L, Table* t, int size) +static void setarrayvector(lua_State* L, LuaTable* t, int size) { if (size > MAXSIZE) luaG_runerror(L, "table overflow"); @@ -328,7 +328,7 @@ static void setarrayvector(lua_State* L, Table* t, int size) t->sizearray = size; } -static void setnodevector(lua_State* L, Table* t, int size) +static void setnodevector(lua_State* L, LuaTable* t, int size) { int lsize; if (size == 0) @@ -357,9 +357,9 @@ static void setnodevector(lua_State* L, Table* t, int size) t->lastfree = size; // all positions are free } -static TValue* newkey(lua_State* L, Table* t, const TValue* key); +static TValue* newkey(lua_State* L, LuaTable* t, const TValue* key); -static TValue* arrayornewkey(lua_State* L, Table* t, const TValue* key) +static TValue* arrayornewkey(lua_State* L, LuaTable* t, const TValue* key) { if (ttisnumber(key)) { @@ -373,7 +373,7 @@ static TValue* arrayornewkey(lua_State* L, Table* t, const TValue* key) return newkey(L, t, key); } -static void resize(lua_State* L, Table* t, int nasize, int nhsize) +static void resize(lua_State* L, LuaTable* t, int nasize, int nhsize) { if (nasize > MAXSIZE || nhsize > MAXSIZE) luaG_runerror(L, "table overflow"); @@ -424,7 +424,7 @@ static void resize(lua_State* L, Table* t, int nasize, int nhsize) luaM_freearray(L, nold, twoto(oldhsize), LuaNode, t->memcat); // free old array } -static int adjustasize(Table* t, int size, const TValue* ek) +static int adjustasize(LuaTable* t, int size, const TValue* ek) { bool tbound = t->node != dummynode || size < t->sizearray; int ekindex = ek && ttisnumber(ek) ? arrayindex(nvalue(ek)) : -1; @@ -434,19 +434,19 @@ static int adjustasize(Table* t, int size, const TValue* ek) return size; } -void luaH_resizearray(lua_State* L, Table* t, int nasize) +void luaH_resizearray(lua_State* L, LuaTable* t, int nasize) { int nsize = (t->node == dummynode) ? 0 : sizenode(t); int asize = adjustasize(t, nasize, NULL); resize(L, t, asize, nsize); } -void luaH_resizehash(lua_State* L, Table* t, int nhsize) +void luaH_resizehash(lua_State* L, LuaTable* t, int nhsize) { resize(L, t, t->sizearray, nhsize); } -static void rehash(lua_State* L, Table* t, const TValue* ek) +static void rehash(lua_State* L, LuaTable* t, const TValue* ek) { int nums[MAXBITS + 1]; // nums[i] = number of keys between 2^(i-1) and 2^i for (int i = 0; i <= MAXBITS; i++) @@ -491,9 +491,9 @@ static void rehash(lua_State* L, Table* t, const TValue* ek) ** }============================================================= */ -Table* luaH_new(lua_State* L, int narray, int nhash) +LuaTable* luaH_new(lua_State* L, int narray, int nhash) { - Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat); + LuaTable* t = luaM_newgco(L, LuaTable, sizeof(LuaTable), L->activememcat); luaC_init(L, t, LUA_TTABLE); t->metatable = NULL; t->tmcache = cast_byte(~0); @@ -512,16 +512,16 @@ Table* luaH_new(lua_State* L, int narray, int nhash) return t; } -void luaH_free(lua_State* L, Table* t, lua_Page* page) +void luaH_free(lua_State* L, LuaTable* t, lua_Page* page) { if (t->node != dummynode) luaM_freearray(L, t->node, sizenode(t), LuaNode, t->memcat); if (t->array) luaM_freearray(L, t->array, t->sizearray, TValue, t->memcat); - luaM_freegco(L, t, sizeof(Table), t->memcat, page); + luaM_freegco(L, t, sizeof(LuaTable), t->memcat, page); } -static LuaNode* getfreepos(Table* t) +static LuaNode* getfreepos(LuaTable* t) { while (t->lastfree > 0) { @@ -541,7 +541,7 @@ static LuaNode* getfreepos(Table* t) ** put new key in its main position; otherwise (colliding node is in its main ** position), new key goes to an empty position. */ -static TValue* newkey(lua_State* L, Table* t, const TValue* key) +static TValue* newkey(lua_State* L, LuaTable* t, const TValue* key) { // enforce boundary invariant if (ttisnumber(key) && nvalue(key) == t->sizearray + 1) @@ -601,7 +601,7 @@ static TValue* newkey(lua_State* L, Table* t, const TValue* key) /* ** search function for integers */ -const TValue* luaH_getnum(Table* t, int key) +const TValue* luaH_getnum(LuaTable* t, int key) { // (1 <= key && key <= t->sizearray) if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray)) @@ -627,7 +627,7 @@ const TValue* luaH_getnum(Table* t, int key) /* ** search function for strings */ -const TValue* luaH_getstr(Table* t, TString* key) +const TValue* luaH_getstr(LuaTable* t, TString* key) { LuaNode* n = hashstr(t, key); for (;;) @@ -644,7 +644,7 @@ const TValue* luaH_getstr(Table* t, TString* key) /* ** main search function */ -const TValue* luaH_get(Table* t, const TValue* key) +const TValue* luaH_get(LuaTable* t, const TValue* key) { switch (ttype(key)) { @@ -677,7 +677,7 @@ const TValue* luaH_get(Table* t, const TValue* key) } } -TValue* luaH_set(lua_State* L, Table* t, const TValue* key) +TValue* luaH_set(lua_State* L, LuaTable* t, const TValue* key) { const TValue* p = luaH_get(t, key); invalidateTMcache(t); @@ -687,7 +687,7 @@ TValue* luaH_set(lua_State* L, Table* t, const TValue* key) return luaH_newkey(L, t, key); } -TValue* luaH_newkey(lua_State* L, Table* t, const TValue* key) +TValue* luaH_newkey(lua_State* L, LuaTable* t, const TValue* key) { if (ttisnil(key)) luaG_runerror(L, "table index is nil"); @@ -698,7 +698,7 @@ TValue* luaH_newkey(lua_State* L, Table* t, const TValue* key) return newkey(L, t, key); } -TValue* luaH_setnum(lua_State* L, Table* t, int key) +TValue* luaH_setnum(lua_State* L, LuaTable* t, int key) { // (1 <= key && key <= t->sizearray) if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray)) @@ -715,7 +715,7 @@ TValue* luaH_setnum(lua_State* L, Table* t, int key) } } -TValue* luaH_setstr(lua_State* L, Table* t, TString* key) +TValue* luaH_setstr(lua_State* L, LuaTable* t, TString* key) { const TValue* p = luaH_getstr(t, key); invalidateTMcache(t); @@ -729,7 +729,7 @@ TValue* luaH_setstr(lua_State* L, Table* t, TString* key) } } -static int updateaboundary(Table* t, int boundary) +static int updateaboundary(LuaTable* t, int boundary) { if (boundary < t->sizearray && ttisnil(&t->array[boundary - 1])) { @@ -752,7 +752,7 @@ static int updateaboundary(Table* t, int boundary) ** Try to find a boundary in table `t'. A `boundary' is an integer index ** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil). */ -int luaH_getn(Table* t) +int luaH_getn(LuaTable* t) { int boundary = getaboundary(t); @@ -793,9 +793,9 @@ int luaH_getn(Table* t) } } -Table* luaH_clone(lua_State* L, Table* tt) +LuaTable* luaH_clone(lua_State* L, LuaTable* tt) { - Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat); + LuaTable* t = luaM_newgco(L, LuaTable, sizeof(LuaTable), L->activememcat); luaC_init(L, t, LUA_TTABLE); t->metatable = tt->metatable; t->tmcache = tt->tmcache; @@ -830,7 +830,7 @@ Table* luaH_clone(lua_State* L, Table* tt) return t; } -void luaH_clear(Table* tt) +void luaH_clear(LuaTable* tt) { // clear array part for (int i = 0; i < tt->sizearray; ++i) diff --git a/VM/src/ltable.h b/VM/src/ltable.h index 021f21bf..50d1e643 100644 --- a/VM/src/ltable.h +++ b/VM/src/ltable.h @@ -14,21 +14,21 @@ // reset cache of absent metamethods, cache is updated in luaT_gettm #define invalidateTMcache(t) t->tmcache = 0 -LUAI_FUNC const TValue* luaH_getnum(Table* t, int key); -LUAI_FUNC TValue* luaH_setnum(lua_State* L, Table* t, int key); -LUAI_FUNC const TValue* luaH_getstr(Table* t, TString* key); -LUAI_FUNC TValue* luaH_setstr(lua_State* L, Table* t, TString* key); -LUAI_FUNC const TValue* luaH_get(Table* t, const TValue* key); -LUAI_FUNC TValue* luaH_set(lua_State* L, Table* t, const TValue* key); -LUAI_FUNC TValue* luaH_newkey(lua_State* L, Table* t, const TValue* key); -LUAI_FUNC Table* luaH_new(lua_State* L, int narray, int lnhash); -LUAI_FUNC void luaH_resizearray(lua_State* L, Table* t, int nasize); -LUAI_FUNC void luaH_resizehash(lua_State* L, Table* t, int nhsize); -LUAI_FUNC void luaH_free(lua_State* L, Table* t, struct lua_Page* page); -LUAI_FUNC int luaH_next(lua_State* L, Table* t, StkId key); -LUAI_FUNC int luaH_getn(Table* t); -LUAI_FUNC Table* luaH_clone(lua_State* L, Table* tt); -LUAI_FUNC void luaH_clear(Table* tt); +LUAI_FUNC const TValue* luaH_getnum(LuaTable* t, int key); +LUAI_FUNC TValue* luaH_setnum(lua_State* L, LuaTable* t, int key); +LUAI_FUNC const TValue* luaH_getstr(LuaTable* t, TString* key); +LUAI_FUNC TValue* luaH_setstr(lua_State* L, LuaTable* t, TString* key); +LUAI_FUNC const TValue* luaH_get(LuaTable* t, const TValue* key); +LUAI_FUNC TValue* luaH_set(lua_State* L, LuaTable* t, const TValue* key); +LUAI_FUNC TValue* luaH_newkey(lua_State* L, LuaTable* t, const TValue* key); +LUAI_FUNC LuaTable* luaH_new(lua_State* L, int narray, int lnhash); +LUAI_FUNC void luaH_resizearray(lua_State* L, LuaTable* t, int nasize); +LUAI_FUNC void luaH_resizehash(lua_State* L, LuaTable* t, int nhsize); +LUAI_FUNC void luaH_free(lua_State* L, LuaTable* t, struct lua_Page* page); +LUAI_FUNC int luaH_next(lua_State* L, LuaTable* t, StkId key); +LUAI_FUNC int luaH_getn(LuaTable* t); +LUAI_FUNC LuaTable* luaH_clone(lua_State* L, LuaTable* tt); +LUAI_FUNC void luaH_clear(LuaTable* tt); #define luaH_setslot(L, t, slot, key) (invalidateTMcache(t), (slot == luaO_nilobject ? luaH_newkey(L, t, key) : cast_to(TValue*, slot))) diff --git a/VM/src/ltablib.cpp b/VM/src/ltablib.cpp index 75d9f400..dbe60e4e 100644 --- a/VM/src/ltablib.cpp +++ b/VM/src/ltablib.cpp @@ -53,7 +53,7 @@ static int maxn(lua_State* L) double max = 0; luaL_checktype(L, 1, LUA_TTABLE); - Table* t = hvalue(L->base); + LuaTable* t = hvalue(L->base); for (int i = 0; i < t->sizearray; i++) { @@ -87,8 +87,8 @@ static int getn(lua_State* L) static void moveelements(lua_State* L, int srct, int dstt, int f, int e, int t) { - Table* src = hvalue(L->base + (srct - 1)); - Table* dst = hvalue(L->base + (dstt - 1)); + LuaTable* src = hvalue(L->base + (srct - 1)); + LuaTable* dst = hvalue(L->base + (dstt - 1)); if (dst->readonly) luaG_readonlyerror(L); @@ -213,7 +213,7 @@ static int tmove(lua_State* L) int n = e - f + 1; // number of elements to move luaL_argcheck(L, t <= INT_MAX - n + 1, 4, "destination wrap around"); - Table* dst = hvalue(L->base + (tt - 1)); + LuaTable* dst = hvalue(L->base + (tt - 1)); if (dst->readonly) // also checked in moveelements, but this blocks resizes of r/o tables luaG_readonlyerror(L); @@ -229,7 +229,7 @@ static int tmove(lua_State* L) return 1; } -static void addfield(lua_State* L, luaL_Strbuf* b, int i, Table* t) +static void addfield(lua_State* L, luaL_Strbuf* b, int i, LuaTable* t) { if (t && unsigned(i - 1) < unsigned(t->sizearray) && ttisstring(&t->array[i - 1])) { @@ -253,7 +253,7 @@ static int tconcat(lua_State* L) int i = luaL_optinteger(L, 3, 1); int last = luaL_opt(L, luaL_checkinteger, 4, lua_objlen(L, 1)); - Table* t = hvalue(L->base); + LuaTable* t = hvalue(L->base); luaL_Strbuf b; luaL_buffinit(L, &b); @@ -274,7 +274,7 @@ static int tpack(lua_State* L) int n = lua_gettop(L); // number of elements to pack lua_createtable(L, n, 1); // create result table - Table* t = hvalue(L->top - 1); + LuaTable* t = hvalue(L->top - 1); for (int i = 0; i < n; ++i) { @@ -292,7 +292,7 @@ static int tpack(lua_State* L) static int tunpack(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); - Table* t = hvalue(L->base); + LuaTable* t = hvalue(L->base); int i = luaL_optinteger(L, 2, 1); int e = luaL_opt(L, luaL_checkinteger, 3, lua_objlen(L, 1)); @@ -335,7 +335,7 @@ static int sort_func(lua_State* L, const TValue* l, const TValue* r) return !l_isfalse(L->top); } -inline void sort_swap(lua_State* L, Table* t, int i, int j) +inline void sort_swap(lua_State* L, LuaTable* t, int i, int j) { TValue* arr = t->array; int n = t->sizearray; @@ -348,7 +348,7 @@ inline void sort_swap(lua_State* L, Table* t, int i, int j) setobj2t(L, &arr[j], &temp); } -inline int sort_less(lua_State* L, Table* t, int i, int j, SortPredicate pred) +inline int sort_less(lua_State* L, LuaTable* t, int i, int j, SortPredicate pred) { TValue* arr = t->array; int n = t->sizearray; @@ -363,7 +363,7 @@ inline int sort_less(lua_State* L, Table* t, int i, int j, SortPredicate pred) return res; } -static void sort_siftheap(lua_State* L, Table* t, int l, int u, SortPredicate pred, int root) +static void sort_siftheap(lua_State* L, LuaTable* t, int l, int u, SortPredicate pred, int root) { LUAU_ASSERT(l <= u); int count = u - l + 1; @@ -389,7 +389,7 @@ static void sort_siftheap(lua_State* L, Table* t, int l, int u, SortPredicate pr sort_swap(L, t, l + root, l + lastleft); } -static void sort_heap(lua_State* L, Table* t, int l, int u, SortPredicate pred) +static void sort_heap(lua_State* L, LuaTable* t, int l, int u, SortPredicate pred) { LUAU_ASSERT(l <= u); int count = u - l + 1; @@ -404,7 +404,7 @@ static void sort_heap(lua_State* L, Table* t, int l, int u, SortPredicate pred) } } -static void sort_rec(lua_State* L, Table* t, int l, int u, int limit, SortPredicate pred) +static void sort_rec(lua_State* L, LuaTable* t, int l, int u, int limit, SortPredicate pred) { // sort range [l..u] (inclusive, 0-based) while (l < u) @@ -477,7 +477,7 @@ static void sort_rec(lua_State* L, Table* t, int l, int u, int limit, SortPredic static int tsort(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); - Table* t = hvalue(L->base); + LuaTable* t = hvalue(L->base); int n = luaH_getn(t); if (t->readonly) luaG_readonlyerror(L); @@ -504,7 +504,7 @@ static int tcreate(lua_State* L) if (!lua_isnoneornil(L, 2)) { lua_createtable(L, size, 0); - Table* t = hvalue(L->top - 1); + LuaTable* t = hvalue(L->top - 1); StkId v = L->base + 1; @@ -530,7 +530,7 @@ static int tfind(lua_State* L) if (init < 1) luaL_argerror(L, 3, "index out of range"); - Table* t = hvalue(L->base); + LuaTable* t = hvalue(L->base); StkId v = L->base + 1; for (int i = init;; ++i) @@ -554,7 +554,7 @@ static int tclear(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); - Table* tt = hvalue(L->base); + LuaTable* tt = hvalue(L->base); if (tt->readonly) luaG_readonlyerror(L); @@ -587,7 +587,7 @@ static int tclone(lua_State* L) luaL_checktype(L, 1, LUA_TTABLE); luaL_argcheck(L, !luaL_getmetafield(L, 1, "__metatable"), 1, "table has a protected metatable"); - Table* tt = luaH_clone(L, hvalue(L->base)); + LuaTable* tt = luaH_clone(L, hvalue(L->base)); TValue v; sethvalue(L, &v, tt); diff --git a/VM/src/ltm.cpp b/VM/src/ltm.cpp index f38ab80b..f6b0079a 100644 --- a/VM/src/ltm.cpp +++ b/VM/src/ltm.cpp @@ -86,7 +86,7 @@ void luaT_init(lua_State* L) ** function to be used with macro "fasttm": optimized for absence of ** tag methods. */ -const TValue* luaT_gettm(Table* events, TMS event, TString* ename) +const TValue* luaT_gettm(LuaTable* events, TMS event, TString* ename) { const TValue* tm = luaH_getstr(events, ename); LUAU_ASSERT(event <= TM_EQ); @@ -105,7 +105,7 @@ const TValue* luaT_gettmbyobj(lua_State* L, const TValue* o, TMS event) NB: Tag-methods were replaced by meta-methods in Lua 5.0, but the old names are still around (this function, for example). */ - Table* mt; + LuaTable* mt; switch (ttype(o)) { case LUA_TTABLE: @@ -147,7 +147,7 @@ const TString* luaT_objtypenamestr(lua_State* L, const TValue* o) } // For all types except userdata and table, a global metatable can be set with a global name override - if (Table* mt = L->global->mt[ttype(o)]) + if (LuaTable* mt = L->global->mt[ttype(o)]) { const TValue* type = luaH_getstr(mt, L->global->tmname[TM_TYPE]); diff --git a/VM/src/ltm.h b/VM/src/ltm.h index 7dafd4ed..f3294b64 100644 --- a/VM/src/ltm.h +++ b/VM/src/ltm.h @@ -51,7 +51,7 @@ typedef enum LUAI_DATA const char* const luaT_typenames[]; LUAI_DATA const char* const luaT_eventname[]; -LUAI_FUNC const TValue* luaT_gettm(Table* events, TMS event, TString* ename); +LUAI_FUNC const TValue* luaT_gettm(LuaTable* events, TMS event, TString* ename); LUAI_FUNC const TValue* luaT_gettmbyobj(lua_State* L, const TValue* o, TMS event); LUAI_FUNC const TString* luaT_objtypenamestr(lua_State* L, const TValue* o); diff --git a/VM/src/lvm.h b/VM/src/lvm.h index 0b8690be..6989bcee 100644 --- a/VM/src/lvm.h +++ b/VM/src/lvm.h @@ -26,7 +26,7 @@ LUAI_FUNC int luaV_tostring(lua_State* L, StkId obj); LUAI_FUNC void luaV_gettable(lua_State* L, const TValue* t, TValue* key, StkId val); LUAI_FUNC void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val); LUAI_FUNC void luaV_concat(lua_State* L, int total, int last); -LUAI_FUNC void luaV_getimport(lua_State* L, Table* env, TValue* k, StkId res, uint32_t id, bool propagatenil); +LUAI_FUNC void luaV_getimport(lua_State* L, LuaTable* env, TValue* k, StkId res, uint32_t id, bool propagatenil); LUAI_FUNC void luaV_prepareFORN(lua_State* L, StkId plimit, StkId pstep, StkId pinit); LUAI_FUNC void luaV_callTM(lua_State* L, int nparams, int res); LUAI_FUNC void luaV_tryfuncTM(lua_State* L, StkId func); diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index e3a310ca..ce07d878 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -330,7 +330,7 @@ reentry: LUAU_ASSERT(ttisstring(kv)); // fast-path: value is in expected slot - Table* h = cl->env; + LuaTable* h = cl->env; int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -361,7 +361,7 @@ reentry: LUAU_ASSERT(ttisstring(kv)); // fast-path: value is in expected slot - Table* h = cl->env; + LuaTable* h = cl->env; int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -451,7 +451,7 @@ reentry: // fast-path: built-in table if (LUAU_LIKELY(ttistable(rb))) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -568,7 +568,7 @@ reentry: // fast-path: built-in table if (LUAU_LIKELY(ttistable(rb))) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -642,7 +642,7 @@ reentry: // fast-path: array lookup if (ttistable(rb) && ttisnumber(rc)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); double indexd = nvalue(rc); int index = int(indexd); @@ -672,7 +672,7 @@ reentry: // fast-path: array assign if (ttistable(rb) && ttisnumber(rc)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); double indexd = nvalue(rc); int index = int(indexd); @@ -703,7 +703,7 @@ reentry: // fast-path: array lookup if (ttistable(rb)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); if (LUAU_LIKELY(unsigned(c) < unsigned(h->sizearray) && !h->metatable)) { @@ -731,7 +731,7 @@ reentry: // fast-path: array assign if (ttistable(rb)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); if (LUAU_LIKELY(unsigned(c) < unsigned(h->sizearray) && !h->metatable && !h->readonly)) { @@ -804,7 +804,7 @@ reentry: if (LUAU_LIKELY(ttistable(rb))) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); // note: we can't use nodemask8 here because we need to query the main position of the table, and 8-bit nodemask8 only works // for predictive lookups LuaNode* n = &h->node[tsvalue(kv)->hash & (sizenode(h) - 1)]; @@ -844,7 +844,7 @@ reentry: } else { - Table* mt = ttisuserdata(rb) ? uvalue(rb)->metatable : L->global->mt[ttype(rb)]; + LuaTable* mt = ttisuserdata(rb) ? uvalue(rb)->metatable : L->global->mt[ttype(rb)]; const TValue* tmi = 0; // fast-path: metatable with __namecall @@ -858,7 +858,7 @@ reentry: } else if ((tmi = fasttm(L, mt, TM_INDEX)) && ttistable(tmi)) { - Table* h = hvalue(tmi); + LuaTable* h = hvalue(tmi); int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -2126,7 +2126,7 @@ reentry: // fast-path #1: tables if (LUAU_LIKELY(ttistable(rb))) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); if (fastnotm(h->metatable, TM_LEN)) { @@ -2196,7 +2196,7 @@ reentry: L->top = L->ci->top; } - Table* h = hvalue(ra); + LuaTable* h = hvalue(ra); // TODO: we really don't need this anymore if (!ttistable(ra)) @@ -2281,7 +2281,7 @@ reentry: } else { - Table* mt = ttistable(ra) ? hvalue(ra)->metatable : ttisuserdata(ra) ? uvalue(ra)->metatable : cast_to(Table*, NULL); + LuaTable* mt = ttistable(ra) ? hvalue(ra)->metatable : ttisuserdata(ra) ? uvalue(ra)->metatable : cast_to(LuaTable*, NULL); if (const TValue* fn = fasttm(L, mt, TM_ITER)) { @@ -2340,7 +2340,7 @@ reentry: // TODO: remove the table check per guarantee above if (ttisnil(ra) && ttistable(ra + 1)) { - Table* h = hvalue(ra + 1); + LuaTable* h = hvalue(ra + 1); int index = int(reinterpret_cast(pvalue(ra + 2))); int sizearray = h->sizearray; diff --git a/VM/src/lvmload.cpp b/VM/src/lvmload.cpp index aa248fc1..2a3443eb 100644 --- a/VM/src/lvmload.cpp +++ b/VM/src/lvmload.cpp @@ -72,7 +72,7 @@ private: size_t originalThreshold = 0; }; -void luaV_getimport(lua_State* L, Table* env, TValue* k, StkId res, uint32_t id, bool propagatenil) +void luaV_getimport(lua_State* L, LuaTable* env, TValue* k, StkId res, uint32_t id, bool propagatenil) { int count = id >> 30; LUAU_ASSERT(count > 0); @@ -141,7 +141,7 @@ static TString* readString(TempBuffer& strings, const char* data, size return id == 0 ? NULL : strings[id - 1]; } -static void resolveImportSafe(lua_State* L, Table* env, TValue* k, uint32_t id) +static void resolveImportSafe(lua_State* L, LuaTable* env, TValue* k, uint32_t id) { struct ResolveImport { @@ -273,7 +273,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size const ScopedSetGCThreshold pauseGC{L->global, SIZE_MAX}; // env is 0 for current environment and a stack index otherwise - Table* envt = (env == 0) ? L->gt : hvalue(luaA_toobject(L, env)); + LuaTable* envt = (env == 0) ? L->gt : hvalue(luaA_toobject(L, env)); TString* source = luaS_new(L, chunkname); @@ -481,7 +481,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size case LBC_CONSTANT_TABLE: { int keys = readVarInt(data, size, offset); - Table* h = luaH_new(L, 0, keys); + LuaTable* h = luaH_new(L, 0, keys); for (int i = 0; i < keys; ++i) { int key = readVarInt(data, size, offset); diff --git a/VM/src/lvmutils.cpp b/VM/src/lvmutils.cpp index 0cf9d206..5c49139f 100644 --- a/VM/src/lvmutils.cpp +++ b/VM/src/lvmutils.cpp @@ -101,7 +101,7 @@ void luaV_gettable(lua_State* L, const TValue* t, TValue* key, StkId val) const TValue* tm; if (ttistable(t)) { // `t' is a table? - Table* h = hvalue(t); + LuaTable* h = hvalue(t); const TValue* res = luaH_get(h, key); // do a primitive get @@ -137,7 +137,7 @@ void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val) const TValue* tm; if (ttistable(t)) { // `t' is a table? - Table* h = hvalue(t); + LuaTable* h = hvalue(t); const TValue* oldval = luaH_get(h, key); @@ -185,7 +185,7 @@ static int call_binTM(lua_State* L, const TValue* p1, const TValue* p2, StkId re return 1; } -static const TValue* get_compTM(lua_State* L, Table* mt1, Table* mt2, TMS event) +static const TValue* get_compTM(lua_State* L, LuaTable* mt1, LuaTable* mt2, TMS event) { const TValue* tm1 = fasttm(L, mt1, event); const TValue* tm2; @@ -533,7 +533,7 @@ void luaV_dolen(lua_State* L, StkId ra, const TValue* rb) { case LUA_TTABLE: { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); if ((tm = fasttm(L, h->metatable, TM_LEN)) == NULL) { setnvalue(ra, cast_num(luaH_getn(h))); diff --git a/tests/AnyTypeSummary.test.cpp b/tests/AnyTypeSummary.test.cpp index 6c55a140..7184ef76 100644 --- a/tests/AnyTypeSummary.test.cpp +++ b/tests/AnyTypeSummary.test.cpp @@ -19,6 +19,8 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(StudioReportLuauAny2) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) +LUAU_FASTFLAG(LuauAlwaysFillInFunctionCallDiscriminantTypes) +LUAU_FASTFLAG(LuauRemoveNotAnyHack) struct ATSFixture : BuiltinsFixture @@ -410,6 +412,8 @@ TEST_CASE_FIXTURE(ATSFixture, "CannotExtendTable") ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, {FFlag::StudioReportLuauAny2, true}, + {FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes, true}, + {FFlag::LuauRemoveNotAnyHack, true}, }; fileResolver.source["game/Gui/Modules/A"] = R"( @@ -425,7 +429,7 @@ end )"; CheckResult result1 = frontend.check("game/Gui/Modules/A"); - LUAU_REQUIRE_ERROR_COUNT(3, result1); + LUAU_REQUIRE_ERROR_COUNT(1, result1); ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); @@ -502,6 +506,8 @@ TEST_CASE_FIXTURE(ATSFixture, "racing_collision_2") ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, {FFlag::StudioReportLuauAny2, true}, + {FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes, true}, + {FFlag::LuauRemoveNotAnyHack, true}, }; fileResolver.source["game/Gui/Modules/A"] = R"( @@ -565,7 +571,7 @@ initialize() )"; CheckResult result1 = frontend.check("game/Gui/Modules/A"); - LUAU_REQUIRE_ERROR_COUNT(5, result1); + LUAU_REQUIRE_ERROR_COUNT(3, result1); ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); diff --git a/tests/AstQuery.test.cpp b/tests/AstQuery.test.cpp index e730171f..702be46b 100644 --- a/tests/AstQuery.test.cpp +++ b/tests/AstQuery.test.cpp @@ -8,8 +8,6 @@ using namespace Luau; -LUAU_FASTFLAG(LuauDocumentationAtPosition) - struct DocumentationSymbolFixture : BuiltinsFixture { std::optional getDocSymbol(const std::string& source, Position position) @@ -167,7 +165,6 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "table_overloaded_function_prop") TEST_CASE_FIXTURE(DocumentationSymbolFixture, "string_metatable_method") { - ScopedFastFlag sff{FFlag::LuauDocumentationAtPosition, true}; std::optional symbol = getDocSymbol( R"( local x: string = "Foo" @@ -181,7 +178,6 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "string_metatable_method") TEST_CASE_FIXTURE(DocumentationSymbolFixture, "parent_class_method") { - ScopedFastFlag sff{FFlag::LuauDocumentationAtPosition, true}; loadDefinition(R"( declare class Foo function bar(self, x: string): number diff --git a/tests/CodeAllocator.test.cpp b/tests/CodeAllocator.test.cpp index 058a1100..c236b49c 100644 --- a/tests/CodeAllocator.test.cpp +++ b/tests/CodeAllocator.test.cpp @@ -3,6 +3,7 @@ #include "Luau/AssemblyBuilderA64.h" #include "Luau/CodeAllocator.h" #include "Luau/CodeBlockUnwind.h" +#include "Luau/CodeGen.h" #include "Luau/UnwindBuilder.h" #include "Luau/UnwindBuilderDwarf2.h" #include "Luau/UnwindBuilderWin.h" diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 9653397d..fdf868df 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -40,8 +40,9 @@ LUAU_FASTFLAG(LuauVectorLibNativeCodegen) LUAU_FASTFLAG(LuauVectorLibNativeDot) LUAU_FASTFLAG(LuauVectorMetatable) LUAU_FASTFLAG(LuauVector2Constructor) -LUAU_FASTFLAG(LuauBufferBitMethods) +LUAU_FASTFLAG(LuauBufferBitMethods2) LUAU_FASTFLAG(LuauCodeGenLimitLiveSlotReuse) +LUAU_FASTFLAG(LuauMathMapDefinition) static lua_CompileOptions defaultOptions() { @@ -655,7 +656,7 @@ TEST_CASE("Basic") TEST_CASE("Buffers") { - ScopedFastFlag luauBufferBitMethods{FFlag::LuauBufferBitMethods, true}; + ScopedFastFlag luauBufferBitMethods{FFlag::LuauBufferBitMethods2, true}; runConformance("buffers.lua"); } @@ -989,7 +990,8 @@ static void populateRTTI(lua_State* L, Luau::TypeId type) TEST_CASE("Types") { ScopedFastFlag luauVector2Constructor{FFlag::LuauVector2Constructor, true}; - ScopedFastFlag luauMathLerp{FFlag::LuauMathLerp, false}; // waiting for math.lerp to be added to embedded type definitions + ScopedFastFlag luauMathLerp{FFlag::LuauMathLerp, true}; + ScopedFastFlag luauMathMapDefinition{FFlag::LuauMathMapDefinition, true}; runConformance( "types.lua", diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index c31cfb65..434701b2 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -19,6 +19,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauAllowComplexTypesInGenericParams) LUAU_FASTFLAG(LuauErrorRecoveryForTableTypes) LUAU_FASTFLAG(LuauErrorRecoveryForClassNames) +LUAU_FASTFLAG(LuauFixFunctionNameStartPosition) namespace { @@ -3743,5 +3744,27 @@ TEST_CASE_FIXTURE(Fixture, "recover_from_bad_table_type") CHECK_EQ(result.errors.size(), 2); } +TEST_CASE_FIXTURE(Fixture, "function_name_has_correct_start_location") +{ + ScopedFastFlag _{FFlag::LuauFixFunctionNameStartPosition, true}; + AstStatBlock* block = parse(R"( + function simple() + end + + function T:complex() + end + )"); + + REQUIRE_EQ(2, block->body.size); + + const auto function1 = block->body.data[0]->as(); + LUAU_ASSERT(function1); + CHECK_EQ(Position{1, 17}, function1->name->location.begin); + + const auto function2 = block->body.data[1]->as(); + LUAU_ASSERT(function2); + CHECK_EQ(Position{4, 17}, function2->name->location.begin); +} + TEST_SUITE_END(); diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index aef63d06..66c397a1 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -14,6 +14,8 @@ LUAU_FASTFLAG(LuauUserTypeFunPrintToError) LUAU_FASTFLAG(LuauUserTypeFunExportedAndLocal) LUAU_FASTFLAG(LuauUserTypeFunThreadBuffer) LUAU_FASTFLAG(LuauUserTypeFunUpdateAllEnvs) +LUAU_FASTFLAG(LuauUserTypeFunGenerics) +LUAU_FASTFLAG(LuauUserTypeFunCloneTail) TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); @@ -1414,4 +1416,479 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "print_to_error_plus_no_result") CHECK(toString(result.errors[3]) == R"(Type function instance t0 is uninhabited)"); } +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_serialization_1") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + return arg +end + +type test = (T, { x: (y: T) -> (), y: U }, U) -> () + +local function ok(idx: pass): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_serialization_2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + return arg +end + +type test = (T) -> (T, U...) + +local function ok(idx: pass): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_serialization_3") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + return arg +end + +local function m(a, b) + return {x = a, y = b} +end + +type test = typeof(m) + +local function ok(idx: pass): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_cloning_1") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + return types.copy(arg) +end + +type test = (T, { x: (y: T) -> (), y: U }, U) -> () + +local function ok(idx: pass): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_cloning_2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + ScopedFastFlag luauUserTypeFunCloneTail{FFlag::LuauUserTypeFunCloneTail, true}; + + CheckResult result = check(R"( +type function pass(arg) + return types.copy(arg) +end + +type test = (T) -> (T, U...) + +local function ok(idx: pass): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_equality") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + return types.singleton(types.copy(arg) == arg) +end + +type test = (T) -> (T, U...) + +local function ok(idx: pass): true return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_1") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local generics = arg:generics() + local T = generics[1] + return types.newfunction({ head = {T} }, { head = {T} }, {T}) +end + +type test = (T, { x: (y: T) -> (), y: U }, U) -> () + +local function ok(idx: pass): (T) -> (T) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local generics = arg:generics() + local T = generics[1] + local f = types.newfunction() + f:setparameters({T, T}); + f:setreturns({T}); + f:setgenerics({T}); + return f +end + +type test = (T, { x: (y: T) -> (), y: U }, U) -> () + +local function ok(idx: pass): (T, T) -> (T) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_3") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass() + local T = types.generic("T") + assert(T.tag == "generic") + assert(T:name() == "T") + assert(T:ispack() == false) + + local Us, Vs = types.generic("U", true), types.generic("V", true) + assert(Us.tag == "generic") + assert(Us:name() == "U") + assert(Us:ispack() == true) + + local f = types.newfunction() + f:setparameters({T}, Us); + f:setreturns({T}, Vs); + f:setgenerics({T, Us, Vs}); + return f +end + +local function ok(idx: pass<>): (T, U...) -> (T, V...) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_4") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass() + local T, U = types.generic("T"), types.generic("U") + + -- (T) -> () + local func = types.newfunction({ head = {T} }, {}, {T}); + + -- { x: (T) -> (), y: U } + local tbl = types.newtable({ [types.singleton("x")] = func, [types.singleton("y")] = U }) + + -- (T, { x: (T) -> (), y: U }, U) -> () + return types.newfunction({ head = {T, tbl, U } }, {}, {T, U}) +end + +type test = (T, { x: (y: T) -> (), y: U }, U) -> () + +local function ok(idx: pass<>): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_5") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass() + local T = types.generic("T") + return types.newfunction({ head = {T} }, {}, {types.copy(T)}) +end + +local function ok(idx: pass<>): (T) -> () return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_6") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local generics = arg:generics() + local T, U = generics[1], generics[2] + local f = types.newfunction() + f:setparameters({T}); + f:setreturns({U}); + f:setgenerics({T, U}); + return f +end + +local function m(a, b) + return {x = a, y = b} +end + +type test = typeof(m) + +local function ok(idx: pass): (T) -> (U) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_7") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local p, r = arg:parameters(), arg:returns() + local f = types.newfunction() + f:setparameters(p.head, p.tail); + f:setreturns(r.head, r.tail); + f:setgenerics(arg:generics()); + return f +end + +type test = (T, U...) -> (T, U...) + +local function ok(idx: pass): (T, U...) -> (T, U...) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_8") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local p, r = arg:parameters(), arg:returns() + local f = types.newfunction() + f:setparameters(p.head, p.tail); + f:setreturns(r.head, r.tail); + f:setgenerics(arg:generics()); + return f +end + +type test = (U...) -> (U...) + +local function ok(idx: pass): (T, T) -> (T) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_equality_2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + + local tbl1 = types.newtable({ [types.singleton("x")] = T }) + local tbl2 = types.newtable({ [types.singleton("x")] = Us }) -- it is possible to have invalid types in-flight + + return types.singleton(tbl1 == tbl2) +end + +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_1") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + return types.newfunction({}, {}, {Us, T}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK( + toString(result.errors[0]) == + R"('get' type function errored at runtime: [string "get"]:4: types.newfunction: generic type cannot follow a generic pack)" + ); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + return types.newfunction({ head = {T} }, {}, {}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Generic type 'T' is not in a scope of the active generic function)"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_3") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, U = types.generic("T"), types.generic("U") + + -- (U) -> () + local func = types.newfunction({ head = {U} }, {}, {U}); + + -- broken: (T, (U) -> (), U) -> () + return types.newfunction({ head = {T, func, U } }, {}, {T}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Generic type 'U' is not in a scope of the active generic function)"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_4") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + return types.newfunction({ head = {T} }, { tail = Us }, {T, T}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Duplicate type parameter 'T')"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_5") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Ts = types.generic("T"), types.generic("T", true) + return types.newfunction({ head = {T} }, { tail = Ts }, {T, Ts}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Duplicate type parameter 'T')"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_6") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + return types.newfunction({ head = {Us} }, {}, {T, Us}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Generic type pack 'U...' cannot be placed in a type position)"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_7") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + return types.newfunction({ tail = Us }, {}, {T}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Generic type pack 'U...' is not in a scope of the active generic function)"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_variadic_api") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local p, r = arg:parameters(), arg:returns() + local f = types.newfunction() + f:setparameters({p.tail}, p.head[1]); + f:setreturns({r.tail}, r.head[1]); + return f +end + +type test = (string, ...number) -> (number, ...string) + +local function ok(idx: pass): (number, ...string) -> (string, ...number) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index d9c4c13e..34e430ea 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -12,6 +12,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTypestateBuiltins2) LUAU_FASTFLAG(LuauStringFormatArityFix) +LUAU_FASTFLAG(LuauStringFormatErrorSuppression) TEST_SUITE_BEGIN("BuiltinTests"); @@ -1586,4 +1587,19 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_find_should_not_crash") )")); } +TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_should_support_any") +{ + ScopedFastFlag _{FFlag::LuauSolverV2, true}; + + CheckResult result = check(R"( + local x: any = "world" + print(string.format("Hello, %s!", x)) + )"); + + if (FFlag::LuauStringFormatErrorSuppression) + LUAU_REQUIRE_NO_ERRORS(result); + else + LUAU_REQUIRE_ERROR_COUNT(1, result); +} + TEST_SUITE_END(); diff --git a/tests/main.cpp b/tests/main.cpp index bd5a0517..005a3e61 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,6 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Common.h" +#include "Luau/CodeGenCommon.h" + #define DOCTEST_CONFIG_IMPLEMENT // Our calls to parseOption/parseFlag don't provide a prefix so set the prefix to the empty string. #define DOCTEST_CONFIG_OPTIONS_PREFIX "" diff --git a/tools/natvis/VM.natvis b/tools/natvis/VM.natvis index 59bc43c4..adf603eb 100644 --- a/tools/natvis/VM.natvis +++ b/tools/natvis/VM.natvis @@ -77,7 +77,7 @@ --- - + table metatable