Merge remote-tracking branch 'upstream/master' into patch-2

This commit is contained in:
ramdoys 2025-02-20 22:55:19 -05:00
commit 74a3d586c6
48 changed files with 1132 additions and 1010 deletions

View file

@ -86,7 +86,7 @@ jobs:
Debug/luau-compile tests/conformance/assert.luau
coverage:
runs-on: ubuntu-20.04 # needed for clang++-10 to avoid gcov compatibility issues
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: install
@ -94,7 +94,7 @@ jobs:
sudo apt install llvm
- name: make coverage
run: |
CXX=clang++-10 make -j2 config=coverage native=1 coverage
CXX=clang++ make -j2 config=coverage native=1 coverage
- name: upload coverage
uses: codecov/codecov-action@v3
with:

View file

@ -29,8 +29,8 @@ jobs:
build:
needs: ["create-release"]
strategy:
matrix: # using ubuntu-20.04 to build a Linux binary targeting older glibc to improve compatibility
os: [{name: ubuntu, version: ubuntu-20.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}]
matrix: # not using ubuntu-latest to improve compatibility
os: [{name: ubuntu, version: ubuntu-22.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}]
name: ${{matrix.os.name}}
runs-on: ${{matrix.os.version}}
steps:

View file

@ -13,8 +13,8 @@ on:
jobs:
build:
strategy:
matrix: # using ubuntu-20.04 to build a Linux binary targeting older glibc to improve compatibility
os: [{name: ubuntu, version: ubuntu-20.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}]
matrix: # not using ubuntu-latest to improve compatibility
os: [{name: ubuntu, version: ubuntu-22.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}]
name: ${{matrix.os.name}}
runs-on: ${{matrix.os.version}}
steps:

View file

@ -392,7 +392,7 @@ private:
**/
std::vector<std::pair<Name, GenericTypeDefinition>> createGenerics(
const ScopePtr& scope,
AstArray<AstGenericType> generics,
AstArray<AstGenericType*> generics,
bool useCache = false,
bool addTypes = true
);
@ -409,7 +409,7 @@ private:
**/
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(
const ScopePtr& scope,
AstArray<AstGenericTypePack> packs,
AstArray<AstGenericTypePack*> packs,
bool useCache = false,
bool addTypes = true
);

View file

@ -221,8 +221,8 @@ private:
void visitTypeList(AstTypeList l);
void visitGenerics(AstArray<AstGenericType> g);
void visitGenericPacks(AstArray<AstGenericTypePack> g);
void visitGenerics(AstArray<AstGenericType*> g);
void visitGenericPacks(AstArray<AstGenericTypePack*> g);
};
} // namespace Luau

View file

@ -21,6 +21,12 @@ LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection)
namespace Luau
{
using LogLuauProc = void (*)(std::string_view);
extern LogLuauProc logLuau;
void setLogLuau(LogLuauProc ll);
void resetLogLuauProc();
struct Module;
struct AnyTypeSummary;

View file

@ -175,7 +175,7 @@ private:
void visit(AstExprInterpString* interpString);
void visit(AstExprError* expr);
TypeId flattenPack(TypePackId pack);
void visitGenerics(AstArray<AstGenericType> generics, AstArray<AstGenericTypePack> genericPacks);
void visitGenerics(AstArray<AstGenericType*> generics, AstArray<AstGenericTypePack*> genericPacks);
void visit(AstType* ty);
void visit(AstTypeReference* ty);
void visit(AstTypeTable* table);

View file

@ -399,8 +399,8 @@ private:
const ScopePtr& scope,
std::optional<TypeLevel> levelOpt,
const AstNode& node,
const AstArray<AstGenericType>& genericNames,
const AstArray<AstGenericTypePack>& genericPackNames,
const AstArray<AstGenericType*>& genericNames,
const AstArray<AstGenericTypePack*>& genericPackNames,
bool useCache = false
);

View file

@ -49,6 +49,27 @@ struct UnifierSharedState
DenseHashSet<TypePackId> tempSeenTp{nullptr};
UnifierCounters counters;
bool reentrantTypeReduction = false;
};
struct TypeReductionRentrancyGuard final
{
explicit TypeReductionRentrancyGuard(NotNull<UnifierSharedState> sharedState)
: sharedState{sharedState}
{
sharedState->reentrantTypeReduction = true;
}
~TypeReductionRentrancyGuard()
{
sharedState->reentrantTypeReduction = false;
}
TypeReductionRentrancyGuard(const TypeReductionRentrancyGuard&) = delete;
TypeReductionRentrancyGuard(TypeReductionRentrancyGuard&&) = delete;
private:
NotNull<UnifierSharedState> sharedState;
};
} // namespace Luau

View file

@ -34,6 +34,7 @@ LUAU_FASTFLAG(AutocompleteRequirePathSuggestions2)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauFreezeIgnorePersistent)
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze)
namespace Luau
{
@ -1459,7 +1460,8 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCallContext& context)
{
TypeArena* arena = context.solver->arena;
if (FFlag::LuauFollowTableFreeze)
inputType = follow(inputType);
if (auto mt = get<MetatableType>(inputType))
{
std::optional<TypeId> frozenTable = freezeTable(mt->table, context);

View file

@ -37,8 +37,8 @@ LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses)
LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(InferGlobalTypes)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments)
namespace Luau
{
@ -1025,37 +1025,49 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat
TypePackId rvaluePack = checkPack(scope, statLocal->values, expectedTypes).tp;
Checkpoint end = checkpoint(this);
if (hasAnnotation)
if (FFlag::LuauInferLocalTypesInMultipleAssignments)
{
std::vector<TypeId> deferredTypes;
auto [head, tail] = flatten(rvaluePack);
for (size_t i = 0; i < statLocal->vars.size; ++i)
{
LUAU_ASSERT(get<BlockedType>(assignees[i]));
TypeIds* localDomain = localTypes.find(assignees[i]);
LUAU_ASSERT(localDomain);
localDomain->insert(annotatedTypes[i]);
if (statLocal->vars.data[i]->annotation)
{
localDomain->insert(annotatedTypes[i]);
}
else
{
if (i < head.size())
{
localDomain->insert(head[i]);
}
else if (tail)
{
deferredTypes.push_back(arena->addType(BlockedType{}));
localDomain->insert(deferredTypes.back());
}
else
{
localDomain->insert(builtinTypes->nilType);
}
}
}
TypePackId annotatedPack = arena->addTypePack(std::move(annotatedTypes));
addConstraint(scope, statLocal->location, PackSubtypeConstraint{rvaluePack, annotatedPack});
}
else
{
std::vector<TypeId> valueTypes;
valueTypes.reserve(statLocal->vars.size);
auto [head, tail] = flatten(rvaluePack);
if (head.size() >= statLocal->vars.size)
if (hasAnnotation)
{
for (size_t i = 0; i < statLocal->vars.size; ++i)
valueTypes.push_back(head[i]);
TypePackId annotatedPack = arena->addTypePack(std::move(annotatedTypes));
addConstraint(scope, statLocal->location, PackSubtypeConstraint{rvaluePack, annotatedPack});
}
else
{
for (size_t i = 0; i < statLocal->vars.size; ++i)
valueTypes.push_back(arena->addType(BlockedType{}));
auto uc = addConstraint(scope, statLocal->location, UnpackConstraint{valueTypes, rvaluePack});
if (!deferredTypes.empty())
{
LUAU_ASSERT(tail);
NotNull<Constraint> uc = addConstraint(scope, statLocal->location, UnpackConstraint{deferredTypes, *tail});
forEachConstraint(
start,
@ -1063,20 +1075,69 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat
this,
[&uc](const ConstraintPtr& runBefore)
{
uc->dependencies.push_back(NotNull{runBefore.get()});
uc->dependencies.emplace_back(runBefore.get());
}
);
for (TypeId t : valueTypes)
for (TypeId t : deferredTypes)
getMutable<BlockedType>(t)->setOwner(uc);
}
for (size_t i = 0; i < statLocal->vars.size; ++i)
}
else
{
if (hasAnnotation)
{
LUAU_ASSERT(get<BlockedType>(assignees[i]));
TypeIds* localDomain = localTypes.find(assignees[i]);
LUAU_ASSERT(localDomain);
localDomain->insert(valueTypes[i]);
for (size_t i = 0; i < statLocal->vars.size; ++i)
{
LUAU_ASSERT(get<BlockedType>(assignees[i]));
TypeIds* localDomain = localTypes.find(assignees[i]);
LUAU_ASSERT(localDomain);
localDomain->insert(annotatedTypes[i]);
}
TypePackId annotatedPack = arena->addTypePack(std::move(annotatedTypes));
addConstraint(scope, statLocal->location, PackSubtypeConstraint{rvaluePack, annotatedPack});
}
else
{
std::vector<TypeId> valueTypes;
valueTypes.reserve(statLocal->vars.size);
auto [head, tail] = flatten(rvaluePack);
if (head.size() >= statLocal->vars.size)
{
for (size_t i = 0; i < statLocal->vars.size; ++i)
valueTypes.push_back(head[i]);
}
else
{
for (size_t i = 0; i < statLocal->vars.size; ++i)
valueTypes.push_back(arena->addType(BlockedType{}));
auto uc = addConstraint(scope, statLocal->location, UnpackConstraint{valueTypes, rvaluePack});
forEachConstraint(
start,
end,
this,
[&uc](const ConstraintPtr& runBefore)
{
uc->dependencies.push_back(NotNull{runBefore.get()});
}
);
for (TypeId t : valueTypes)
getMutable<BlockedType>(t)->setOwner(uc);
}
for (size_t i = 0; i < statLocal->vars.size; ++i)
{
LUAU_ASSERT(get<BlockedType>(assignees[i]));
TypeIds* localDomain = localTypes.find(assignees[i]);
LUAU_ASSERT(localDomain);
localDomain->insert(valueTypes[i]);
}
}
}
@ -2810,13 +2871,10 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprGlobal* glob
DefId def = dfg->getDef(global);
rootScope->lvalueTypes[def] = rhsType;
if (FFlag::InferGlobalTypes)
{
// Sketchy: We're specifically looking for BlockedTypes that were
// initially created by ConstraintGenerator::prepopulateGlobalScope.
if (auto bt = get<BlockedType>(follow(*annotatedTy)); bt && !bt->getOwner())
emplaceType<BoundType>(asMutable(*annotatedTy), rhsType);
}
// Sketchy: We're specifically looking for BlockedTypes that were
// initially created by ConstraintGenerator::prepopulateGlobalScope.
if (auto bt = get<BlockedType>(follow(*annotatedTy)); bt && !bt->getOwner())
emplaceType<BoundType>(asMutable(*annotatedTy), rhsType);
addConstraint(scope, global->location, SubtypeConstraint{rhsType, *annotatedTy});
}
@ -3535,33 +3593,34 @@ TypePackId ConstraintGenerator::resolveTypePack(const ScopePtr& scope, const Ast
std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGenerator::createGenerics(
const ScopePtr& scope,
AstArray<AstGenericType> generics,
AstArray<AstGenericType*> generics,
bool useCache,
bool addTypes
)
{
std::vector<std::pair<Name, GenericTypeDefinition>> result;
for (const auto& generic : generics)
for (const auto* generic : generics)
{
TypeId genericTy = nullptr;
if (auto it = scope->parent->typeAliasTypeParameters.find(generic.name.value); useCache && it != scope->parent->typeAliasTypeParameters.end())
if (auto it = scope->parent->typeAliasTypeParameters.find(generic->name.value);
useCache && it != scope->parent->typeAliasTypeParameters.end())
genericTy = it->second;
else
{
genericTy = arena->addType(GenericType{scope.get(), generic.name.value});
scope->parent->typeAliasTypeParameters[generic.name.value] = genericTy;
genericTy = arena->addType(GenericType{scope.get(), generic->name.value});
scope->parent->typeAliasTypeParameters[generic->name.value] = genericTy;
}
std::optional<TypeId> defaultTy = std::nullopt;
if (generic.defaultValue)
defaultTy = resolveType(scope, generic.defaultValue, /* inTypeArguments */ false);
if (generic->defaultValue)
defaultTy = resolveType(scope, generic->defaultValue, /* inTypeArguments */ false);
if (addTypes)
scope->privateTypeBindings[generic.name.value] = TypeFun{genericTy};
scope->privateTypeBindings[generic->name.value] = TypeFun{genericTy};
result.push_back({generic.name.value, GenericTypeDefinition{genericTy, defaultTy}});
result.emplace_back(generic->name.value, GenericTypeDefinition{genericTy, defaultTy});
}
return result;
@ -3569,34 +3628,34 @@ std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGenerator::createG
std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGenerator::createGenericPacks(
const ScopePtr& scope,
AstArray<AstGenericTypePack> generics,
AstArray<AstGenericTypePack*> generics,
bool useCache,
bool addTypes
)
{
std::vector<std::pair<Name, GenericTypePackDefinition>> result;
for (const auto& generic : generics)
for (const auto* generic : generics)
{
TypePackId genericTy;
if (auto it = scope->parent->typeAliasTypePackParameters.find(generic.name.value);
if (auto it = scope->parent->typeAliasTypePackParameters.find(generic->name.value);
useCache && it != scope->parent->typeAliasTypePackParameters.end())
genericTy = it->second;
else
{
genericTy = arena->addTypePack(TypePackVar{GenericTypePack{scope.get(), generic.name.value}});
scope->parent->typeAliasTypePackParameters[generic.name.value] = genericTy;
genericTy = arena->addTypePack(TypePackVar{GenericTypePack{scope.get(), generic->name.value}});
scope->parent->typeAliasTypePackParameters[generic->name.value] = genericTy;
}
std::optional<TypePackId> defaultTy = std::nullopt;
if (generic.defaultValue)
defaultTy = resolveTypePack(scope, generic.defaultValue, /* inTypeArguments */ false);
if (generic->defaultValue)
defaultTy = resolveTypePack(scope, generic->defaultValue, /* inTypeArguments */ false);
if (addTypes)
scope->privateTypePackBindings[generic.name.value] = genericTy;
scope->privateTypePackBindings[generic->name.value] = genericTy;
result.push_back({generic.name.value, GenericTypePackDefinition{genericTy, defaultTy}});
result.emplace_back(generic->name.value, GenericTypePackDefinition{genericTy, defaultTy});
}
return result;
@ -3739,18 +3798,15 @@ struct GlobalPrepopulator : AstVisitor
bool visit(AstStatAssign* assign) override
{
if (FFlag::InferGlobalTypes)
for (const Luau::AstExpr* expr : assign->vars)
{
for (const Luau::AstExpr* expr : assign->vars)
if (const AstExprGlobal* g = expr->as<AstExprGlobal>())
{
if (const AstExprGlobal* g = expr->as<AstExprGlobal>())
{
if (!globalScope->lookup(g->name))
globalScope->globalsToWarn.insert(g->name.value);
if (!globalScope->lookup(g->name))
globalScope->globalsToWarn.insert(g->name.value);
TypeId bt = arena->addType(BlockedType{});
globalScope->bindings[g->name] = Binding{bt, g->location};
}
TypeId bt = arena->addType(BlockedType{});
globalScope->bindings[g->name] = Binding{bt, g->location};
}
}

View file

@ -37,6 +37,7 @@ LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauAlwaysFillInFunctionCallDiscriminantTypes)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes)
namespace Luau
{
@ -438,6 +439,10 @@ void ConstraintSolver::run()
snapshot = logger->prepareStepSnapshot(rootScope, c, force, unsolvedConstraints);
}
std::optional<DenseHashSet<TypeId>> mutatedFreeTypes = std::nullopt;
if (FFlag::LuauPrecalculateMutatedFreeTypes)
mutatedFreeTypes = c->getMaybeMutatedFreeTypes();
bool success = tryDispatch(c, force);
progress |= success;
@ -447,20 +452,42 @@ void ConstraintSolver::run()
unblock(c);
unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i));
// decrement the referenced free types for this constraint if we dispatched successfully!
for (auto ty : c->getMaybeMutatedFreeTypes())
if (FFlag::LuauPrecalculateMutatedFreeTypes)
{
size_t& refCount = unresolvedConstraints[ty];
if (refCount > 0)
refCount -= 1;
for (auto ty : c->getMaybeMutatedFreeTypes())
mutatedFreeTypes->insert(ty);
for (auto ty : *mutatedFreeTypes)
{
size_t& refCount = unresolvedConstraints[ty];
if (refCount > 0)
refCount -= 1;
// We have two constraints that are designed to wait for the
// refCount on a free type to be equal to 1: the
// PrimitiveTypeConstraint and ReduceConstraint. We
// therefore wake any constraint waiting for a free type's
// refcount to be 1 or 0.
if (refCount <= 1)
unblock(ty, Location{});
// We have two constraints that are designed to wait for the
// refCount on a free type to be equal to 1: the
// PrimitiveTypeConstraint and ReduceConstraint. We
// therefore wake any constraint waiting for a free type's
// refcount to be 1 or 0.
if (refCount <= 1)
unblock(ty, Location{});
}
}
else
{
// decrement the referenced free types for this constraint if we dispatched successfully!
for (auto ty : c->getMaybeMutatedFreeTypes())
{
size_t& refCount = unresolvedConstraints[ty];
if (refCount > 0)
refCount -= 1;
// We have two constraints that are designed to wait for the
// refCount on a free type to be equal to 1: the
// PrimitiveTypeConstraint and ReduceConstraint. We
// therefore wake any constraint waiting for a free type's
// refcount to be 1 or 0.
if (refCount <= 1)
unblock(ty, Location{});
}
}
if (logger)

View file

@ -1260,21 +1260,21 @@ void DataFlowGraphBuilder::visitTypeList(AstTypeList l)
visitTypePack(l.tailType);
}
void DataFlowGraphBuilder::visitGenerics(AstArray<AstGenericType> g)
void DataFlowGraphBuilder::visitGenerics(AstArray<AstGenericType*> g)
{
for (AstGenericType generic : g)
for (AstGenericType* generic : g)
{
if (generic.defaultValue)
visitType(generic.defaultValue);
if (generic->defaultValue)
visitType(generic->defaultValue);
}
}
void DataFlowGraphBuilder::visitGenericPacks(AstArray<AstGenericTypePack> g)
void DataFlowGraphBuilder::visitGenericPacks(AstArray<AstGenericTypePack*> g)
{
for (AstGenericTypePack generic : g)
for (AstGenericTypePack* generic : g)
{
if (generic.defaultValue)
visitTypePack(generic.defaultValue);
if (generic->defaultValue)
visitTypePack(generic->defaultValue);
}
}

View file

@ -2,205 +2,11 @@
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauBufferBitMethods2)
LUAU_FASTFLAGVARIABLE(LuauMathMapDefinition)
LUAU_FASTFLAG(LuauVector2Constructor)
namespace Luau
{
static const std::string kBuiltinDefinitionLuaSrcChecked_DEPRECATED = R"BUILTIN_SRC(
declare bit32: {
band: @checked (...number) -> number,
bor: @checked (...number) -> number,
bxor: @checked (...number) -> number,
btest: @checked (number, ...number) -> boolean,
rrotate: @checked (x: number, disp: number) -> number,
lrotate: @checked (x: number, disp: number) -> number,
lshift: @checked (x: number, disp: number) -> number,
arshift: @checked (x: number, disp: number) -> number,
rshift: @checked (x: number, disp: number) -> number,
bnot: @checked (x: number) -> number,
extract: @checked (n: number, field: number, width: number?) -> number,
replace: @checked (n: number, v: number, field: number, width: number?) -> number,
countlz: @checked (n: number) -> number,
countrz: @checked (n: number) -> number,
byteswap: @checked (n: number) -> number,
}
declare math: {
frexp: @checked (n: number) -> (number, number),
ldexp: @checked (s: number, e: number) -> number,
fmod: @checked (x: number, y: number) -> number,
modf: @checked (n: number) -> (number, number),
pow: @checked (x: number, y: number) -> number,
exp: @checked (n: number) -> number,
ceil: @checked (n: number) -> number,
floor: @checked (n: number) -> number,
abs: @checked (n: number) -> number,
sqrt: @checked (n: number) -> number,
log: @checked (n: number, base: number?) -> number,
log10: @checked (n: number) -> number,
rad: @checked (n: number) -> number,
deg: @checked (n: number) -> number,
sin: @checked (n: number) -> number,
cos: @checked (n: number) -> number,
tan: @checked (n: number) -> number,
sinh: @checked (n: number) -> number,
cosh: @checked (n: number) -> number,
tanh: @checked (n: number) -> number,
atan: @checked (n: number) -> number,
acos: @checked (n: number) -> number,
asin: @checked (n: number) -> number,
atan2: @checked (y: number, x: number) -> number,
min: @checked (number, ...number) -> number,
max: @checked (number, ...number) -> number,
pi: number,
huge: number,
randomseed: @checked (seed: number) -> (),
random: @checked (number?, number?) -> number,
sign: @checked (n: number) -> number,
clamp: @checked (n: number, min: number, max: number) -> number,
noise: @checked (x: number, y: number?, z: number?) -> number,
round: @checked (n: number) -> number,
map: @checked (x: number, inmin: number, inmax: number, outmin: number, outmax: number) -> number,
}
type DateTypeArg = {
year: number,
month: number,
day: number,
hour: number?,
min: number?,
sec: number?,
isdst: boolean?,
}
type DateTypeResult = {
year: number,
month: number,
wday: number,
yday: number,
day: number,
hour: number,
min: number,
sec: number,
isdst: boolean,
}
declare os: {
time: (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,
}
@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...>(...: T...)
declare function type<T>(value: T): string
declare function typeof<T>(value: T): string
-- `assert` has a magic function attached that will give more detailed type information
declare function assert<T>(value: T, errorMessage: string?): T
declare function error<T>(message: T, level: number?): never
declare function tostring<T>(value: T): string
declare function tonumber<T>(value: T, radix: number?): number?
declare function rawequal<T1, T2>(a: T1, b: T2): boolean
declare function rawget<K, V>(tab: {[K]: V}, k: K): V
declare function rawset<K, V>(tab: {[K]: V}, k: K, v: V): {[K]: V}
declare function rawlen<K, V>(obj: {[K]: V} | string): number
declare function setfenv<T..., R...>(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)?
declare function ipairs<V>(tab: {V}): (({V}, number) -> (number?, V), {V}, number)
declare function pcall<A..., R...>(f: (A...) -> R..., ...: A...): (boolean, R...)
-- FIXME: The actual type of `xpcall` is:
-- <E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., A...) -> (true, R1...) | (false, R2...)
-- Since we can't represent the return value, we use (boolean, R1...).
declare function xpcall<E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., ...: A...): (boolean, R1...)
-- `select` has a magic function attached to provide more detailed type information
declare function select<A...>(i: string | number, ...: A...): ...any
-- FIXME: This type is not entirely correct - `loadstring` returns a function or
-- (nil, string).
declare function loadstring<A...>(src: string, chunkname: string?): (((A...) -> any)?, string?)
@checked declare function newproxy(mt: boolean?): any
declare coroutine: {
create: <A..., R...>(f: (A...) -> R...) -> thread,
resume: <A..., R...>(co: thread, A...) -> (boolean, R...),
running: () -> thread,
status: @checked (co: thread) -> "dead" | "running" | "normal" | "suspended",
wrap: <A..., R...>(f: (A...) -> R...) -> ((A...) -> R...),
yield: <A..., R...>(A...) -> R...,
isyieldable: () -> boolean,
close: @checked (co: thread) -> (boolean, any)
}
declare table: {
concat: <V>(t: {V}, sep: string?, i: number?, j: number?) -> string,
insert: (<V>(t: {V}, value: V) -> ()) & (<V>(t: {V}, pos: number, value: V) -> ()),
maxn: <V>(t: {V}) -> number,
remove: <V>(t: {V}, number?) -> V?,
sort: <V>(t: {V}, comp: ((V, V) -> boolean)?) -> (),
create: <V>(count: number, value: V?) -> {V},
find: <V>(haystack: {V}, needle: V, init: number?) -> number?,
unpack: <V>(list: {V}, i: number?, j: number?) -> ...V,
pack: <V>(...V) -> { n: number, [number]: V },
getn: <V>(t: {V}) -> number,
foreach: <K, V>(t: {[K]: V}, f: (K, V) -> ()) -> (),
foreachi: <V>({V}, (number, V) -> ()) -> (),
move: <V>(src: {V}, a: number, b: number, t: number, dst: {V}?) -> {V},
clear: <K, V>(table: {[K]: V}) -> (),
isfrozen: <K, V>(t: {[K]: V}) -> boolean,
}
declare debug: {
info: (<R...>(thread: thread, level: number, options: string) -> R...) & (<R...>(level: number, options: string) -> R...) & (<A..., R1..., R2...>(func: (A...) -> R1..., options: string) -> R2...),
traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string),
}
declare utf8: {
char: @checked (...number) -> string,
charpattern: string,
codes: @checked (str: string) -> ((string, number) -> (number, number), string, number),
codepoint: @checked (str: string, i: number?, j: number?) -> ...number,
len: @checked (s: string, i: number?, j: number?) -> (number?, number?),
offset: @checked (s: string, n: number?, i: number?) -> number,
}
-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype.
declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionBaseSrc = R"BUILTIN_SRC(
@checked declare function require(target: any): any
@ -549,18 +355,15 @@ declare vector: {
std::string getBuiltinDefinitionSource()
{
std::string result = FFlag::LuauMathMapDefinition ? kBuiltinDefinitionBaseSrc : kBuiltinDefinitionLuaSrcChecked_DEPRECATED;
std::string result = kBuiltinDefinitionBaseSrc;
if (FFlag::LuauMathMapDefinition)
{
result += kBuiltinDefinitionBit32Src;
result += kBuiltinDefinitionMathSrc;
result += kBuiltinDefinitionOsSrc;
result += kBuiltinDefinitionCoroutineSrc;
result += kBuiltinDefinitionTableSrc;
result += kBuiltinDefinitionDebugSrc;
result += kBuiltinDefinitionUtf8Src;
}
result += kBuiltinDefinitionBit32Src;
result += kBuiltinDefinitionMathSrc;
result += kBuiltinDefinitionOsSrc;
result += kBuiltinDefinitionCoroutineSrc;
result += kBuiltinDefinitionTableSrc;
result += kBuiltinDefinitionDebugSrc;
result += kBuiltinDefinitionUtf8Src;
result += FFlag::LuauBufferBitMethods2 ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED;

View file

@ -34,7 +34,7 @@ LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver)
LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf)
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule)
LUAU_FASTFLAGVARIABLE(LogFragmentsFromAutocomplete)
namespace
{
template<typename T>
@ -335,6 +335,8 @@ std::optional<FragmentParseResult> parseFragment(
FragmentParseResult fragmentResult;
fragmentResult.fragmentToParse = std::string(dbg.data(), parseLength);
// For the duration of the incremental parse, we want to allow the name table to re-use duplicate names
if (FFlag::LogFragmentsFromAutocomplete)
logLuau(dbg);
ParseOptions opts;
opts.allowDeclarationSyntax = false;
@ -650,7 +652,8 @@ FragmentAutocompleteResult fragmentAutocomplete(
return {};
auto globalScope = (opts && opts->forAutocomplete) ? frontend.globalsForAutocomplete.globalScope.get() : frontend.globals.globalScope.get();
if (FFlag::LogFragmentsFromAutocomplete)
logLuau(src);
TypeArena arenaForFragmentAutocomplete;
auto result = Luau::autocomplete_(
tcResult.incrementalModule,

View file

@ -20,6 +20,26 @@ LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteCommentDetection)
namespace Luau
{
static void defaultLogLuau(std::string_view input)
{
// The default is to do nothing because we don't want to mess with
// the xml parsing done by the dcr script.
}
Luau::LogLuauProc logLuau = &defaultLogLuau;
void setLogLuau(LogLuauProc ll)
{
logLuau = ll;
}
void resetLogLuauProc()
{
logLuau = &defaultLogLuau;
}
static bool contains_DEPRECATED(Position pos, Comment comment)
{
if (comment.location.contains(pos))

View file

@ -14,13 +14,15 @@
#include "Luau/TypeFunction.h"
#include "Luau/Def.h"
#include "Luau/ToString.h"
#include "Luau/TypeFwd.h"
#include "Luau/TypeUtils.h"
#include <iostream>
#include <iterator>
LUAU_FASTFLAGVARIABLE(LuauCountSelfCallsNonstrict)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
namespace Luau
{
@ -342,8 +344,9 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatIf* ifStatement)
{
NonStrictContext condB = visit(ifStatement->condition);
NonStrictContext condB = visit(ifStatement->condition, ValueContext::RValue);
NonStrictContext branchContext;
// If there is no else branch, don't bother generating warnings for the then branch - we can't prove there is an error
if (ifStatement->elsebody)
{
@ -351,17 +354,32 @@ struct NonStrictTypeChecker
NonStrictContext elseBody = visit(ifStatement->elsebody);
branchContext = NonStrictContext::conjunction(builtinTypes, arena, thenBody, elseBody);
}
return NonStrictContext::disjunction(builtinTypes, arena, condB, branchContext);
}
NonStrictContext visit(AstStatWhile* whileStatement)
{
return {};
if (FFlag::LuauNonStrictVisitorImprovements)
{
NonStrictContext condition = visit(whileStatement->condition, ValueContext::RValue);
NonStrictContext body = visit(whileStatement->body);
return NonStrictContext::disjunction(builtinTypes, arena, condition, body);
}
else
return {};
}
NonStrictContext visit(AstStatRepeat* repeatStatement)
{
return {};
if (FFlag::LuauNonStrictVisitorImprovements)
{
NonStrictContext body = visit(repeatStatement->body);
NonStrictContext condition = visit(repeatStatement->condition, ValueContext::RValue);
return NonStrictContext::disjunction(builtinTypes, arena, body, condition);
}
else
return {};
}
NonStrictContext visit(AstStatBreak* breakStatement)
@ -376,49 +394,94 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatReturn* returnStatement)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{
// TODO: this is believing existing code, but i'm not sure if this makes sense
// for how the contexts are handled
for (AstExpr* expr : returnStatement->list)
visit(expr, ValueContext::RValue);
}
return {};
}
NonStrictContext visit(AstStatExpr* expr)
{
return visit(expr->expr);
return visit(expr->expr, ValueContext::RValue);
}
NonStrictContext visit(AstStatLocal* local)
{
for (AstExpr* rhs : local->values)
visit(rhs);
visit(rhs, ValueContext::RValue);
return {};
}
NonStrictContext visit(AstStatFor* forStatement)
{
return {};
if (FFlag::LuauNonStrictVisitorImprovements)
{
// TODO: throwing out context based on same principle as existing code?
if (forStatement->from)
visit(forStatement->from, ValueContext::RValue);
if (forStatement->to)
visit(forStatement->to, ValueContext::RValue);
if (forStatement->step)
visit(forStatement->step, ValueContext::RValue);
return visit(forStatement->body);
}
else
{
return {};
}
}
NonStrictContext visit(AstStatForIn* forInStatement)
{
return {};
if (FFlag::LuauNonStrictVisitorImprovements)
{
for (AstExpr* rhs : forInStatement->values)
visit(rhs, ValueContext::RValue);
return visit(forInStatement->body);
}
else
{
return {};
}
}
NonStrictContext visit(AstStatAssign* assign)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{
for (AstExpr* lhs : assign->vars)
visit(lhs, ValueContext::LValue);
for (AstExpr* rhs : assign->values)
visit(rhs, ValueContext::RValue);
}
return {};
}
NonStrictContext visit(AstStatCompoundAssign* compoundAssign)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{
visit(compoundAssign->var, ValueContext::LValue);
visit(compoundAssign->value, ValueContext::RValue);
}
return {};
}
NonStrictContext visit(AstStatFunction* statFn)
{
return visit(statFn->func);
return visit(statFn->func, ValueContext::RValue);
}
NonStrictContext visit(AstStatLocalFunction* localFn)
{
return visit(localFn->func);
return visit(localFn->func, ValueContext::RValue);
}
NonStrictContext visit(AstStatTypeAlias* typeAlias)
@ -448,14 +511,22 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatError* error)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{
for (AstStat* stat : error->statements)
visit(stat);
for (AstExpr* expr : error->expressions)
visit(expr, ValueContext::RValue);
}
return {};
}
NonStrictContext visit(AstExpr* expr)
NonStrictContext visit(AstExpr* expr, ValueContext context)
{
auto pusher = pushStack(expr);
if (auto e = expr->as<AstExprGroup>())
return visit(e);
return visit(e, context);
else if (auto e = expr->as<AstExprConstantNil>())
return visit(e);
else if (auto e = expr->as<AstExprConstantBool>())
@ -465,17 +536,17 @@ struct NonStrictTypeChecker
else if (auto e = expr->as<AstExprConstantString>())
return visit(e);
else if (auto e = expr->as<AstExprLocal>())
return visit(e);
return visit(e, context);
else if (auto e = expr->as<AstExprGlobal>())
return visit(e);
return visit(e, context);
else if (auto e = expr->as<AstExprVarargs>())
return visit(e);
else if (auto e = expr->as<AstExprCall>())
return visit(e);
else if (auto e = expr->as<AstExprIndexName>())
return visit(e);
return visit(e, context);
else if (auto e = expr->as<AstExprIndexExpr>())
return visit(e);
return visit(e, context);
else if (auto e = expr->as<AstExprFunction>())
return visit(e);
else if (auto e = expr->as<AstExprTable>())
@ -499,9 +570,12 @@ struct NonStrictTypeChecker
}
}
NonStrictContext visit(AstExprGroup* group)
NonStrictContext visit(AstExprGroup* group, ValueContext context)
{
return {};
if (FFlag::LuauNonStrictVisitorImprovements)
return visit(group->expr, context);
else
return {};
}
NonStrictContext visit(AstExprConstantNil* expr)
@ -524,17 +598,30 @@ struct NonStrictTypeChecker
return {};
}
NonStrictContext visit(AstExprLocal* local)
NonStrictContext visit(AstExprLocal* local, ValueContext context)
{
return {};
}
NonStrictContext visit(AstExprGlobal* global)
NonStrictContext visit(AstExprGlobal* global, ValueContext context)
{
if (FFlag::LuauNewNonStrictWarnOnUnknownGlobals)
{
// We don't file unknown symbols for LValues.
if (context == ValueContext::LValue)
return {};
NotNull<Scope> scope = stack.back();
if (!scope->lookup(global->name))
{
reportError(UnknownSymbol{global->name.value, UnknownSymbol::Binding}, global->location);
}
}
return {};
}
NonStrictContext visit(AstExprVarargs* global)
NonStrictContext visit(AstExprVarargs* varargs)
{
return {};
}
@ -763,14 +850,24 @@ struct NonStrictTypeChecker
return fresh;
}
NonStrictContext visit(AstExprIndexName* indexName)
NonStrictContext visit(AstExprIndexName* indexName, ValueContext context)
{
return {};
if (FFlag::LuauNonStrictVisitorImprovements)
return visit(indexName->expr, context);
else
return {};
}
NonStrictContext visit(AstExprIndexExpr* indexExpr)
NonStrictContext visit(AstExprIndexExpr* indexExpr, ValueContext context)
{
return {};
if (FFlag::LuauNonStrictVisitorImprovements)
{
NonStrictContext expr = visit(indexExpr->expr, context);
NonStrictContext index = visit(indexExpr->index, ValueContext::RValue);
return NonStrictContext::disjunction(builtinTypes, arena, expr, index);
}
else
return {};
}
NonStrictContext visit(AstExprFunction* exprFn)
@ -789,39 +886,74 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstExprTable* table)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{
for (auto [_, key, value] : table->items)
{
if (key)
visit(key, ValueContext::RValue);
visit(value, ValueContext::RValue);
}
}
return {};
}
NonStrictContext visit(AstExprUnary* unary)
{
return {};
if (FFlag::LuauNonStrictVisitorImprovements)
return visit(unary->expr, ValueContext::RValue);
else
return {};
}
NonStrictContext visit(AstExprBinary* binary)
{
return {};
if (FFlag::LuauNonStrictVisitorImprovements)
{
NonStrictContext lhs = visit(binary->left, ValueContext::RValue);
NonStrictContext rhs = visit(binary->right, ValueContext::RValue);
return NonStrictContext::disjunction(builtinTypes, arena, lhs, rhs);
}
else
return {};
}
NonStrictContext visit(AstExprTypeAssertion* typeAssertion)
{
return {};
if (FFlag::LuauNonStrictVisitorImprovements)
return visit(typeAssertion->expr, ValueContext::RValue);
else
return {};
}
NonStrictContext visit(AstExprIfElse* ifElse)
{
NonStrictContext condB = visit(ifElse->condition);
NonStrictContext thenB = visit(ifElse->trueExpr);
NonStrictContext elseB = visit(ifElse->falseExpr);
NonStrictContext condB = visit(ifElse->condition, ValueContext::RValue);
NonStrictContext thenB = visit(ifElse->trueExpr, ValueContext::RValue);
NonStrictContext elseB = visit(ifElse->falseExpr, ValueContext::RValue);
return NonStrictContext::disjunction(builtinTypes, arena, condB, NonStrictContext::conjunction(builtinTypes, arena, thenB, elseB));
}
NonStrictContext visit(AstExprInterpString* interpString)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{
for (AstExpr* expr : interpString->expressions)
visit(expr, ValueContext::RValue);
}
return {};
}
NonStrictContext visit(AstExprError* error)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{
for (AstExpr* expr : error->expressions)
visit(expr, ValueContext::RValue);
}
return {};
}

View file

@ -10,6 +10,7 @@
#include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauDontInPlaceMutateTableType)
LUAU_FASTFLAGVARIABLE(LuauAllowNonSharedTableTypesInLiteral)
namespace Luau
{
@ -251,8 +252,19 @@ TypeId matchLiteralType(
Property& prop = it->second;
// Table literals always initially result in shared read-write types
LUAU_ASSERT(prop.isShared());
if (FFlag::LuauAllowNonSharedTableTypesInLiteral)
{
// If we encounter a duplcate property, we may have already
// set it to be read-only. If that's the case, the only thing
// that will definitely crash is trying to access a write
// only property.
LUAU_ASSERT(!prop.isWriteOnly());
}
else
{
// Table literals always initially result in shared read-write types
LUAU_ASSERT(prop.isShared());
}
TypeId propTy = *prop.readTy;
auto it2 = expectedTableTy->props.find(keyStr);

View file

@ -898,14 +898,14 @@ struct Printer_DEPRECATED
{
comma();
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.advance(o->location.begin);
writer.identifier(o->name.value);
if (o.defaultValue)
if (o->defaultValue)
{
writer.maybeSpace(o.defaultValue->location.begin, 2);
writer.maybeSpace(o->defaultValue->location.begin, 2);
writer.symbol("=");
visualizeTypeAnnotation(*o.defaultValue);
visualizeTypeAnnotation(*o->defaultValue);
}
}
@ -913,15 +913,15 @@ struct Printer_DEPRECATED
{
comma();
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.advance(o->location.begin);
writer.identifier(o->name.value);
writer.symbol("...");
if (o.defaultValue)
if (o->defaultValue)
{
writer.maybeSpace(o.defaultValue->location.begin, 2);
writer.maybeSpace(o->defaultValue->location.begin, 2);
writer.symbol("=");
visualizeTypePackAnnotation(*o.defaultValue, false);
visualizeTypePackAnnotation(*o->defaultValue, false);
}
}
@ -978,15 +978,15 @@ struct Printer_DEPRECATED
{
comma();
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.advance(o->location.begin);
writer.identifier(o->name.value);
}
for (const auto& o : func.genericPacks)
{
comma();
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.advance(o->location.begin);
writer.identifier(o->name.value);
writer.symbol("...");
}
writer.symbol(">");
@ -1115,15 +1115,15 @@ struct Printer_DEPRECATED
{
comma();
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.advance(o->location.begin);
writer.identifier(o->name.value);
}
for (const auto& o : a->genericPacks)
{
comma();
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.advance(o->location.begin);
writer.identifier(o->name.value);
writer.symbol("...");
}
writer.symbol(">");
@ -1690,7 +1690,10 @@ struct Printer
if (writeTypes)
{
writer.maybeSpace(a->annotation->location.begin, 2);
if (const auto* cstNode = lookupCstNode<CstExprTypeAssertion>(a))
advance(cstNode->opPosition);
else
writer.maybeSpace(a->annotation->location.begin, 2);
writer.symbol("::");
visualizeTypeAnnotation(*a->annotation);
}
@ -2047,14 +2050,14 @@ struct Printer
{
comma();
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.advance(o->location.begin);
writer.identifier(o->name.value);
if (o.defaultValue)
if (o->defaultValue)
{
writer.maybeSpace(o.defaultValue->location.begin, 2);
writer.maybeSpace(o->defaultValue->location.begin, 2);
writer.symbol("=");
visualizeTypeAnnotation(*o.defaultValue);
visualizeTypeAnnotation(*o->defaultValue);
}
}
@ -2062,15 +2065,15 @@ struct Printer
{
comma();
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.advance(o->location.begin);
writer.identifier(o->name.value);
writer.symbol("...");
if (o.defaultValue)
if (o->defaultValue)
{
writer.maybeSpace(o.defaultValue->location.begin, 2);
writer.maybeSpace(o->defaultValue->location.begin, 2);
writer.symbol("=");
visualizeTypePackAnnotation(*o.defaultValue, false);
visualizeTypePackAnnotation(*o->defaultValue, false);
}
}
@ -2131,15 +2134,15 @@ struct Printer
{
comma();
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.advance(o->location.begin);
writer.identifier(o->name.value);
}
for (const auto& o : func.genericPacks)
{
comma();
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.advance(o->location.begin);
writer.identifier(o->name.value);
writer.symbol("...");
}
writer.symbol(">");
@ -2312,15 +2315,15 @@ struct Printer
{
comma();
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.advance(o->location.begin);
writer.identifier(o->name.value);
}
for (const auto& o : a->genericPacks)
{
comma();
writer.advance(o.location.begin);
writer.identifier(o.name.value);
writer.advance(o->location.begin);
writer.identifier(o->name.value);
writer.symbol("...");
}
writer.symbol(">");

View file

@ -261,24 +261,24 @@ public:
if (hasSeen(&ftv))
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("<Cycle>"), std::nullopt, Location());
AstArray<AstGenericType> generics;
AstArray<AstGenericType*> generics;
generics.size = ftv.generics.size();
generics.data = static_cast<AstGenericType*>(allocator->allocate(sizeof(AstGenericType) * generics.size));
generics.data = static_cast<AstGenericType**>(allocator->allocate(sizeof(AstGenericType) * generics.size));
size_t numGenerics = 0;
for (auto it = ftv.generics.begin(); it != ftv.generics.end(); ++it)
{
if (auto gtv = get<GenericType>(*it))
generics.data[numGenerics++] = {AstName(gtv->name.c_str()), Location(), nullptr};
generics.data[numGenerics++] = allocator->alloc<AstGenericType>(Location(), AstName(gtv->name.c_str()), nullptr);
}
AstArray<AstGenericTypePack> genericPacks;
AstArray<AstGenericTypePack*> genericPacks;
genericPacks.size = ftv.genericPacks.size();
genericPacks.data = static_cast<AstGenericTypePack*>(allocator->allocate(sizeof(AstGenericTypePack) * genericPacks.size));
genericPacks.data = static_cast<AstGenericTypePack**>(allocator->allocate(sizeof(AstGenericTypePack) * genericPacks.size));
size_t numGenericPacks = 0;
for (auto it = ftv.genericPacks.begin(); it != ftv.genericPacks.end(); ++it)
{
if (auto gtv = get<GenericTypePack>(*it))
genericPacks.data[numGenericPacks++] = {AstName(gtv->name.c_str()), Location(), nullptr};
genericPacks.data[numGenericPacks++] = allocator->alloc<AstGenericTypePack>(Location(), AstName(gtv->name.c_str()), nullptr);
}
AstArray<AstType*> argTypes;

View file

@ -29,7 +29,6 @@
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(InferGlobalTypes)
LUAU_FASTFLAGVARIABLE(LuauTableKeysAreRValues)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
@ -1357,7 +1356,7 @@ void TypeChecker2::visit(AstExprGlobal* expr)
{
reportError(UnknownSymbol{expr->name.value, UnknownSymbol::Binding}, expr->location);
}
else if (FFlag::InferGlobalTypes)
else
{
if (scope->shouldWarnGlobal(expr->name.value) && !warnedGlobals.contains(expr->name.value))
{
@ -2379,30 +2378,30 @@ TypeId TypeChecker2::flattenPack(TypePackId pack)
ice->ice("flattenPack got a weird pack!");
}
void TypeChecker2::visitGenerics(AstArray<AstGenericType> generics, AstArray<AstGenericTypePack> genericPacks)
void TypeChecker2::visitGenerics(AstArray<AstGenericType*> generics, AstArray<AstGenericTypePack*> genericPacks)
{
DenseHashSet<AstName> seen{AstName{}};
for (const auto& g : generics)
for (const auto* g : generics)
{
if (seen.contains(g.name))
reportError(DuplicateGenericParameter{g.name.value}, g.location);
if (seen.contains(g->name))
reportError(DuplicateGenericParameter{g->name.value}, g->location);
else
seen.insert(g.name);
seen.insert(g->name);
if (g.defaultValue)
visit(g.defaultValue);
if (g->defaultValue)
visit(g->defaultValue);
}
for (const auto& g : genericPacks)
for (const auto* g : genericPacks)
{
if (seen.contains(g.name))
reportError(DuplicateGenericParameter{g.name.value}, g.location);
if (seen.contains(g->name))
reportError(DuplicateGenericParameter{g->name.value}, g->location);
else
seen.insert(g.name);
seen.insert(g->name);
if (g.defaultValue)
visit(g.defaultValue);
if (g->defaultValue)
visit(g->defaultValue);
}
}

View file

@ -50,6 +50,7 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauClipNestedAndRecursiveUnion)
LUAU_FASTFLAGVARIABLE(LuauDoNotGeneralizeInTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauPreventReentrantTypeFunctionReduction)
namespace Luau
{
@ -446,19 +447,49 @@ static FunctionGraphReductionResult reduceFunctionsInternal(
TypeFunctionReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), std::move(cyclics), location, ctx, force};
int iterationCount = 0;
while (!reducer.done())
if (FFlag::LuauPreventReentrantTypeFunctionReduction)
{
reducer.step();
// If we are reducing a type function while reducing a type function,
// we're probably doing something clowny. One known place this can
// occur is type function reduction => overload selection => subtyping
// => back to type function reduction. At worst, if there's a reduction
// that _doesn't_ loop forever and _needs_ reentrancy, we'll fail to
// handle that and potentially emit an error when we didn't need to.
if (ctx.normalizer->sharedState->reentrantTypeReduction)
return {};
++iterationCount;
if (iterationCount > DFInt::LuauTypeFamilyGraphReductionMaximumSteps)
TypeReductionRentrancyGuard _{ctx.normalizer->sharedState};
while (!reducer.done())
{
reducer.result.errors.emplace_back(location, CodeTooComplex{});
break;
reducer.step();
++iterationCount;
if (iterationCount > DFInt::LuauTypeFamilyGraphReductionMaximumSteps)
{
reducer.result.errors.emplace_back(location, CodeTooComplex{});
break;
}
}
return std::move(reducer.result);
}
else
{
while (!reducer.done())
{
reducer.step();
++iterationCount;
if (iterationCount > DFInt::LuauTypeFamilyGraphReductionMaximumSteps)
{
reducer.result.errors.emplace_back(location, CodeTooComplex{});
break;
}
}
return std::move(reducer.result);
}
return std::move(reducer.result);
}
FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force)

View file

@ -14,9 +14,8 @@
#include <vector>
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixInner)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunGenerics)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunCloneTail)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypeofReturnsType)
LUAU_FASTFLAGVARIABLE(LuauTypeFunPrintFix)
namespace Luau
{
@ -158,7 +157,7 @@ static std::string getTag(lua_State* L, TypeFunctionTypeId ty)
return "function";
else if (get<TypeFunctionClassType>(ty))
return "class";
else if (FFlag::LuauUserTypeFunGenerics && get<TypeFunctionGenericType>(ty))
else if (get<TypeFunctionGenericType>(ty))
return "generic";
LUAU_UNREACHABLE();
@ -427,21 +426,11 @@ static int getNegatedValue(lua_State* L)
luaL_error(L, "type.inner: expected 1 argument, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1);
if (FFlag::LuauUserTypeFunFixInner)
{
if (auto tfnt = get<TypeFunctionNegationType>(self); tfnt)
allocTypeUserData(L, tfnt->type->type);
else
luaL_error(L, "type.inner: cannot call inner method on non-negation type: `%s` type", getTag(L, self).c_str());
}
if (auto tfnt = get<TypeFunctionNegationType>(self); tfnt)
allocTypeUserData(L, tfnt->type->type);
else
{
if (auto tfnt = get<TypeFunctionNegationType>(self); !tfnt)
allocTypeUserData(L, tfnt->type->type);
else
luaL_error(L, "type.inner: cannot call inner method on non-negation type: `%s` type", getTag(L, self).c_str());
}
luaL_error(L, "type.inner: cannot call inner method on non-negation type: `%s` type", getTag(L, self).c_str());
return 1;
}
@ -941,99 +930,6 @@ static void pushTypePack(lua_State* L, TypeFunctionTypePackId tp)
}
}
static int createFunction_DEPRECATED(lua_State* L)
{
int argumentCount = lua_gettop(L);
if (argumentCount > 2)
luaL_error(L, "types.newfunction: expected 0-2 arguments, but got %d", argumentCount);
TypeFunctionTypePackId argTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{});
if (lua_istable(L, 1))
{
std::vector<TypeFunctionTypeId> head{};
lua_getfield(L, 1, "head");
if (lua_istable(L, -1))
{
int argSize = lua_objlen(L, -1);
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
}
}
lua_pop(L, 1); // Pop the "head" field
std::optional<TypeFunctionTypePackId> tail;
lua_getfield(L, 1, "tail");
if (auto type = optionalTypeUserData(L, -1))
tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type});
lua_pop(L, 1); // Pop the "tail" field
if (head.size() == 0 && tail.has_value())
argTypes = *tail;
else
argTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail});
}
else if (!lua_isnoneornil(L, 1))
luaL_typeerrorL(L, 1, "table");
TypeFunctionTypePackId retTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{});
if (lua_istable(L, 2))
{
std::vector<TypeFunctionTypeId> head{};
lua_getfield(L, 2, "head");
if (lua_istable(L, -1))
{
int argSize = lua_objlen(L, -1);
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
}
}
lua_pop(L, 1); // Pop the "head" field
std::optional<TypeFunctionTypePackId> tail;
lua_getfield(L, 2, "tail");
if (auto type = optionalTypeUserData(L, -1))
tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type});
lua_pop(L, 1); // Pop the "tail" field
if (head.size() == 0 && tail.has_value())
retTypes = *tail;
else
retTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail});
}
else if (!lua_isnoneornil(L, 2))
luaL_typeerrorL(L, 2, "table");
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)
@ -1102,45 +998,7 @@ 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());
if (FFlag::LuauUserTypeFunGenerics)
{
tfft->argTypes = getTypePack(L, 2, 3);
}
else
{
std::vector<TypeFunctionTypeId> 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);
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
}
}
else if (!lua_isnoneornil(L, 2))
luaL_typeerrorL(L, 2, "table");
std::optional<TypeFunctionTypePackId> 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});
}
tfft->argTypes = getTypePack(L, 2, 3);
return 0;
}
@ -1158,59 +1016,7 @@ 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 (FFlag::LuauUserTypeFunGenerics)
{
pushTypePack(L, tfft->argTypes);
}
else
{
if (auto tftp = get<TypeFunctionTypePack>(tfft->argTypes))
{
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)
{
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");
}
if (tftp->tail.has_value())
{
auto tfvp = get<TypeFunctionVariadicTypePack>(*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 (auto tfvp = get<TypeFunctionVariadicTypePack>(tfft->argTypes))
{
lua_createtable(L, 0, 1);
allocTypeUserData(L, tfvp->type->type);
lua_setfield(L, -2, "tail");
return 1;
}
lua_createtable(L, 0, 0);
}
pushTypePack(L, tfft->argTypes);
return 1;
}
@ -1228,45 +1034,7 @@ 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());
if (FFlag::LuauUserTypeFunGenerics)
{
tfft->retTypes = getTypePack(L, 2, 3);
}
else
{
std::vector<TypeFunctionTypeId> 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);
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
}
}
else if (!lua_isnoneornil(L, 2))
luaL_typeerrorL(L, 2, "table");
std::optional<TypeFunctionTypePackId> 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});
}
tfft->retTypes = getTypePack(L, 2, 3);
return 0;
}
@ -1284,59 +1052,7 @@ 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 (FFlag::LuauUserTypeFunGenerics)
{
pushTypePack(L, tfft->retTypes);
}
else
{
if (auto tftp = get<TypeFunctionTypePack>(tfft->retTypes))
{
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)
{
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");
}
if (tftp->tail.has_value())
{
auto tfvp = get<TypeFunctionVariadicTypePack>(*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 (auto tfvp = get<TypeFunctionVariadicTypePack>(tfft->retTypes))
{
lua_createtable(L, 0, 1);
allocTypeUserData(L, tfvp->type->type);
lua_setfield(L, -2, "tail");
return 1;
}
lua_createtable(L, 0, 0);
}
pushTypePack(L, tfft->retTypes);
return 1;
}
@ -1761,9 +1477,9 @@ void registerTypesLibrary(lua_State* L)
{"unionof", createUnion},
{"intersectionof", createIntersection},
{"newtable", createTable},
{"newfunction", FFlag::LuauUserTypeFunGenerics ? createFunction : createFunction_DEPRECATED},
{"newfunction", createFunction},
{"copy", deepCopy},
{FFlag::LuauUserTypeFunGenerics ? "generic" : nullptr, FFlag::LuauUserTypeFunGenerics ? createGeneric : nullptr},
{"generic", createGeneric},
{nullptr, nullptr}
};
@ -1838,18 +1554,24 @@ void registerTypeUserData(lua_State* L)
{"parent", getClassParent},
// Function type methods (cont.)
{FFlag::LuauUserTypeFunGenerics ? "setgenerics" : nullptr, FFlag::LuauUserTypeFunGenerics ? setFunctionGenerics : nullptr},
{FFlag::LuauUserTypeFunGenerics ? "generics" : nullptr, FFlag::LuauUserTypeFunGenerics ? getFunctionGenerics : nullptr},
{"setgenerics", setFunctionGenerics},
{"generics", getFunctionGenerics},
// Generic type methods
{FFlag::LuauUserTypeFunGenerics ? "name" : nullptr, FFlag::LuauUserTypeFunGenerics ? getGenericName : nullptr},
{FFlag::LuauUserTypeFunGenerics ? "ispack" : nullptr, FFlag::LuauUserTypeFunGenerics ? getGenericIsPack : nullptr},
{"name", getGenericName},
{"ispack", getGenericIsPack},
{nullptr, nullptr}
};
// Create and register metatable for type userdata
luaL_newmetatable(L, "type");
if (FFlag::LuauUserTypeFunTypeofReturnsType)
{
lua_pushstring(L, "type");
lua_setfield(L, -2, "__type");
}
// Protect metatable from being changed
lua_pushstring(L, "The metatable is locked");
@ -1889,7 +1611,12 @@ static int print(lua_State* L)
size_t l = 0;
const char* s = luaL_tolstring(L, i, &l); // convert to string using __tostring et al
if (i > 1)
result.append('\t', 1);
{
if (FFlag::LuauTypeFunPrintFix)
result.append(1, '\t');
else
result.append('\t', 1);
}
result.append(s, l);
lua_pop(L, 1);
}
@ -2097,25 +1824,22 @@ 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 (lhs.generics.size() != rhs.generics.size())
if (!areEqual(seen, **l, **r))
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;
if (lhs.genericPacks.size() != rhs.genericPacks.size())
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;
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))
@ -2218,14 +1942,11 @@ bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType
return areEqual(seen, *lf, *rf);
}
if (FFlag::LuauUserTypeFunGenerics)
{
{
const TypeFunctionGenericType* lg = get<TypeFunctionGenericType>(&lhs);
const TypeFunctionGenericType* rg = get<TypeFunctionGenericType>(&rhs);
if (lg && rg)
return lg->isNamed == rg->isNamed && lg->isPack == rg->isPack && lg->name == rg->name;
}
const TypeFunctionGenericType* lg = get<TypeFunctionGenericType>(&lhs);
const TypeFunctionGenericType* rg = get<TypeFunctionGenericType>(&rhs);
if (lg && rg)
return lg->isNamed == rg->isNamed && lg->isPack == rg->isPack && lg->name == rg->name;
}
return false;
@ -2274,14 +1995,11 @@ bool areEqual(SeenSet& seen, const TypeFunctionTypePackVar& lhs, const TypeFunct
return areEqual(seen, *lv, *rv);
}
if (FFlag::LuauUserTypeFunGenerics)
{
{
const TypeFunctionGenericTypePack* lg = get<TypeFunctionGenericTypePack>(&lhs);
const TypeFunctionGenericTypePack* rg = get<TypeFunctionGenericTypePack>(&rhs);
if (lg && rg)
return lg->isNamed == rg->isNamed && lg->name == rg->name;
}
const TypeFunctionGenericTypePack* lg = get<TypeFunctionGenericTypePack>(&lhs);
const TypeFunctionGenericTypePack* rg = get<TypeFunctionGenericTypePack>(&rhs);
if (lg && rg)
return lg->isNamed == rg->isNamed && lg->name == rg->name;
}
return false;
@ -2510,7 +2228,7 @@ private:
}
else if (auto c = get<TypeFunctionClassType>(ty))
target = ty; // Don't copy a class since they are immutable
else if (auto g = get<TypeFunctionGenericType>(ty); FFlag::LuauUserTypeFunGenerics && g)
else if (auto g = get<TypeFunctionGenericType>(ty))
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionGenericType{g->isNamed, g->isPack, g->name});
else
LUAU_ASSERT(!"Unknown type");
@ -2531,7 +2249,7 @@ private:
target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{{}});
else if (auto vPack = get<TypeFunctionVariadicTypePack>(tp))
target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionVariadicTypePack{});
else if (auto gPack = get<TypeFunctionGenericTypePack>(tp); gPack && FFlag::LuauUserTypeFunGenerics)
else if (auto gPack = get<TypeFunctionGenericTypePack>(tp))
target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionGenericTypePack{gPack->isNamed, gPack->name});
else
LUAU_ASSERT(!"Unknown type");
@ -2565,8 +2283,7 @@ private:
cloneChildren(f1, f2);
else if (auto [c1, c2] = std::tuple{getMutable<TypeFunctionClassType>(ty), getMutable<TypeFunctionClassType>(tfti)}; c1 && c2)
cloneChildren(c1, c2);
else if (auto [g1, g2] = std::tuple{getMutable<TypeFunctionGenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)};
FFlag::LuauUserTypeFunGenerics && g1 && g2)
else if (auto [g1, g2] = std::tuple{getMutable<TypeFunctionGenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2)
cloneChildren(g1, g2);
else
LUAU_ASSERT(!"Unknown pair?"); // First and argument should always represent the same types
@ -2580,7 +2297,7 @@ private:
vPack1 && vPack2)
cloneChildren(vPack1, vPack2);
else if (auto [gPack1, gPack2] = std::tuple{getMutable<TypeFunctionGenericTypePack>(tp), getMutable<TypeFunctionGenericTypePack>(tftp)};
FFlag::LuauUserTypeFunGenerics && gPack1 && gPack2)
gPack1 && gPack2)
cloneChildren(gPack1, gPack2);
else
LUAU_ASSERT(!"Unknown pair?"); // First and argument should always represent the same types
@ -2662,16 +2379,13 @@ 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->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->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);
@ -2692,11 +2406,8 @@ private:
for (TypeFunctionTypeId& ty : t1->head)
t2->head.push_back(shallowClone(ty));
if (FFlag::LuauUserTypeFunCloneTail)
{
if (t1->tail)
t2->tail = shallowClone(*t1->tail);
}
if (t1->tail)
t2->tail = shallowClone(*t1->tail);
}
void cloneChildren(TypeFunctionVariadicTypePack* v1, TypeFunctionVariadicTypePack* v2)

View file

@ -20,8 +20,6 @@
// currently, controls serialization, deserialization, and `type.copy`
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000);
LUAU_FASTFLAG(LuauUserTypeFunGenerics)
namespace Luau
{
@ -212,7 +210,7 @@ private:
state->classesSerialized[c->name] = ty;
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, c->name});
}
else if (auto g = get<GenericType>(ty); FFlag::LuauUserTypeFunGenerics && g)
else if (auto g = get<GenericType>(ty))
{
Name name = g->name;
@ -245,7 +243,7 @@ private:
target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{{}});
else if (auto vPack = get<VariadicTypePack>(tp))
target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionVariadicTypePack{});
else if (auto gPack = get<GenericTypePack>(tp); FFlag::LuauUserTypeFunGenerics && gPack)
else if (auto gPack = get<GenericTypePack>(tp))
{
Name name = gPack->name;
@ -291,8 +289,7 @@ private:
serializeChildren(f1, f2);
else if (auto [c1, c2] = std::tuple{get<ClassType>(ty), getMutable<TypeFunctionClassType>(tfti)}; c1 && c2)
serializeChildren(c1, c2);
else if (auto [g1, g2] = std::tuple{get<GenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)};
FFlag::LuauUserTypeFunGenerics && g1 && g2)
else if (auto [g1, g2] = std::tuple{get<GenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2)
serializeChildren(g1, g2);
else
{ // Either this or ty and tfti do not represent the same type
@ -307,8 +304,7 @@ private:
serializeChildren(tPack1, tPack2);
else if (auto [vPack1, vPack2] = std::tuple{get<VariadicTypePack>(tp), getMutable<TypeFunctionVariadicTypePack>(tftp)}; vPack1 && vPack2)
serializeChildren(vPack1, vPack2);
else if (auto [gPack1, gPack2] = std::tuple{get<GenericTypePack>(tp), getMutable<TypeFunctionGenericTypePack>(tftp)};
FFlag::LuauUserTypeFunGenerics && gPack1 && gPack2)
else if (auto [gPack1, gPack2] = std::tuple{get<GenericTypePack>(tp), getMutable<TypeFunctionGenericTypePack>(tftp)}; gPack1 && gPack2)
serializeChildren(gPack1, gPack2);
else
{ // Either this or ty and tfti do not represent the same type
@ -399,16 +395,13 @@ 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->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->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);
@ -573,14 +566,11 @@ private:
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())
{
// 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();
}
closeFunctionScope(functionScopes.back().function);
functionScopes.pop_back();
}
}
}
@ -702,7 +692,7 @@ private:
else
state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious class type is being deserialized");
}
else if (auto g = get<TypeFunctionGenericType>(ty); FFlag::LuauUserTypeFunGenerics && g)
else if (auto g = get<TypeFunctionGenericType>(ty))
{
if (g->isPack)
{
@ -752,7 +742,7 @@ private:
{
target = state->ctx->arena->addTypePack(VariadicTypePack{});
}
else if (auto gPack = get<TypeFunctionGenericTypePack>(tp); FFlag::LuauUserTypeFunGenerics && gPack)
else if (auto gPack = get<TypeFunctionGenericTypePack>(tp))
{
auto it = std::find_if(
genericPacks.rbegin(),
@ -809,8 +799,7 @@ private:
deserializeChildren(f2, f1);
else if (auto [c1, c2] = std::tuple{getMutable<ClassType>(ty), getMutable<TypeFunctionClassType>(tfti)}; c1 && c2)
deserializeChildren(c2, c1);
else if (auto [g1, g2] = std::tuple{getMutable<GenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)};
FFlag::LuauUserTypeFunGenerics && g1 && g2)
else if (auto [g1, g2] = std::tuple{getMutable<GenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2)
deserializeChildren(g2, g1);
else
state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized");
@ -823,8 +812,7 @@ private:
else if (auto [vPack1, vPack2] = std::tuple{getMutable<VariadicTypePack>(tp), getMutable<TypeFunctionVariadicTypePack>(tftp)};
vPack1 && vPack2)
deserializeChildren(vPack2, vPack1);
else if (auto [gPack1, gPack2] = std::tuple{getMutable<GenericTypePack>(tp), getMutable<TypeFunctionGenericTypePack>(tftp)};
FFlag::LuauUserTypeFunGenerics && gPack1 && gPack2)
else if (auto [gPack1, gPack2] = std::tuple{getMutable<GenericTypePack>(tp), getMutable<TypeFunctionGenericTypePack>(tftp)}; gPack1 && gPack2)
deserializeChildren(gPack2, gPack1);
else
state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized");
@ -909,64 +897,60 @@ private:
void deserializeChildren(TypeFunctionFunctionType* f2, FunctionType* f1)
{
if (FFlag::LuauUserTypeFunGenerics)
functionScopes.push_back({queue.size(), f2});
std::set<std::pair<bool, std::string>> genericNames;
// Introduce generic function parameters into scope
for (auto ty : f2->generics)
{
functionScopes.push_back({queue.size(), f2});
auto gty = get<TypeFunctionGenericType>(ty);
LUAU_ASSERT(gty && !gty->isPack);
std::set<std::pair<bool, std::string>> genericNames;
std::pair<bool, std::string> nameKey = std::make_pair(gty->isNamed, gty->name);
// Introduce generic function parameters into scope
for (auto ty : f2->generics)
// Duplicates are not allowed
if (genericNames.find(nameKey) != genericNames.end())
{
auto gty = get<TypeFunctionGenericType>(ty);
LUAU_ASSERT(gty && !gty->isPack);
std::pair<bool, std::string> 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});
state->errors.push_back(format("Duplicate type parameter '%s'", gty->name.c_str()));
return;
}
for (auto tp : f2->genericPacks)
{
auto gtp = get<TypeFunctionGenericTypePack>(tp);
LUAU_ASSERT(gtp);
genericNames.insert(nameKey);
std::pair<bool, std::string> 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));
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<TypeFunctionGenericTypePack>(tp);
LUAU_ASSERT(gtp);
std::pair<bool, std::string> 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);

View file

@ -5913,8 +5913,8 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(
const ScopePtr& scope,
std::optional<TypeLevel> levelOpt,
const AstNode& node,
const AstArray<AstGenericType>& genericNames,
const AstArray<AstGenericTypePack>& genericPackNames,
const AstArray<AstGenericType*>& genericNames,
const AstArray<AstGenericTypePack*>& genericPackNames,
bool useCache
)
{
@ -5924,14 +5924,14 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(
std::vector<GenericTypeDefinition> generics;
for (const AstGenericType& generic : genericNames)
for (const AstGenericType* generic : genericNames)
{
std::optional<TypeId> defaultValue;
if (generic.defaultValue)
defaultValue = resolveType(scope, *generic.defaultValue);
if (generic->defaultValue)
defaultValue = resolveType(scope, *generic->defaultValue);
Name n = generic.name.value;
Name n = generic->name.value;
// These generics are the only thing that will ever be added to scope, so we can be certain that
// a collision can only occur when two generic types have the same name.
@ -5960,14 +5960,14 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(
std::vector<GenericTypePackDefinition> genericPacks;
for (const AstGenericTypePack& genericPack : genericPackNames)
for (const AstGenericTypePack* genericPack : genericPackNames)
{
std::optional<TypePackId> defaultValue;
if (genericPack.defaultValue)
defaultValue = resolveTypePack(scope, *genericPack.defaultValue);
if (genericPack->defaultValue)
defaultValue = resolveTypePack(scope, *genericPack->defaultValue);
Name n = genericPack.name.value;
Name n = genericPack->name.value;
// These generics are the only thing that will ever be added to scope, so we can be certain that
// a collision can only occur when two generic types have the same name.

View file

@ -120,20 +120,6 @@ struct AstTypeList
using AstArgumentName = std::pair<AstName, Location>; // TODO: remove and replace when we get a common struct for this pair instead of AstName
struct AstGenericType
{
AstName name;
Location location;
AstType* defaultValue = nullptr;
};
struct AstGenericTypePack
{
AstName name;
Location location;
AstTypePack* defaultValue = nullptr;
};
extern int gAstRttiIndex;
template<typename T>
@ -253,6 +239,32 @@ public:
bool hasSemicolon;
};
class AstGenericType : public AstNode
{
public:
LUAU_RTTI(AstGenericType)
explicit AstGenericType(const Location& location, AstName name, AstType* defaultValue = nullptr);
void visit(AstVisitor* visitor) override;
AstName name;
AstType* defaultValue = nullptr;
};
class AstGenericTypePack : public AstNode
{
public:
LUAU_RTTI(AstGenericTypePack)
explicit AstGenericTypePack(const Location& location, AstName name, AstTypePack* defaultValue = nullptr);
void visit(AstVisitor* visitor) override;
AstName name;
AstTypePack* defaultValue = nullptr;
};
class AstExprGroup : public AstExpr
{
public:
@ -424,8 +436,8 @@ public:
AstExprFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstArray<AstGenericType>& generics,
const AstArray<AstGenericTypePack>& genericPacks,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
AstLocal* self,
const AstArray<AstLocal*>& args,
bool vararg,
@ -443,8 +455,8 @@ public:
bool hasNativeAttribute() const;
AstArray<AstAttr*> attributes;
AstArray<AstGenericType> generics;
AstArray<AstGenericTypePack> genericPacks;
AstArray<AstGenericType*> generics;
AstArray<AstGenericTypePack*> genericPacks;
AstLocal* self;
AstArray<AstLocal*> args;
std::optional<AstTypeList> returnAnnotation;
@ -857,8 +869,8 @@ public:
const Location& location,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType>& generics,
const AstArray<AstGenericTypePack>& genericPacks,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
AstType* type,
bool exported
);
@ -867,8 +879,8 @@ public:
AstName name;
Location nameLocation;
AstArray<AstGenericType> generics;
AstArray<AstGenericTypePack> genericPacks;
AstArray<AstGenericType*> generics;
AstArray<AstGenericTypePack*> genericPacks;
AstType* type;
bool exported;
};
@ -911,8 +923,8 @@ public:
const Location& location,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType>& generics,
const AstArray<AstGenericTypePack>& genericPacks,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& params,
const AstArray<AstArgumentName>& paramNames,
bool vararg,
@ -925,8 +937,8 @@ public:
const AstArray<AstAttr*>& attributes,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType>& generics,
const AstArray<AstGenericTypePack>& genericPacks,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& params,
const AstArray<AstArgumentName>& paramNames,
bool vararg,
@ -942,8 +954,8 @@ public:
AstArray<AstAttr*> attributes;
AstName name;
Location nameLocation;
AstArray<AstGenericType> generics;
AstArray<AstGenericTypePack> genericPacks;
AstArray<AstGenericType*> generics;
AstArray<AstGenericTypePack*> genericPacks;
AstTypeList params;
AstArray<AstArgumentName> paramNames;
bool vararg = false;
@ -1074,8 +1086,8 @@ public:
AstTypeFunction(
const Location& location,
const AstArray<AstGenericType>& generics,
const AstArray<AstGenericTypePack>& genericPacks,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
const AstTypeList& returnTypes
@ -1084,8 +1096,8 @@ public:
AstTypeFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstArray<AstGenericType>& generics,
const AstArray<AstGenericTypePack>& genericPacks,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
const AstTypeList& returnTypes
@ -1096,8 +1108,8 @@ public:
bool isCheckedFunction() const;
AstArray<AstAttr*> attributes;
AstArray<AstGenericType> generics;
AstArray<AstGenericTypePack> genericPacks;
AstArray<AstGenericType*> generics;
AstArray<AstGenericTypePack*> genericPacks;
AstTypeList argTypes;
AstArray<std::optional<AstArgumentName>> argNames;
AstTypeList returnTypes;
@ -1276,6 +1288,16 @@ public:
return visit(static_cast<AstNode*>(node));
}
virtual bool visit(class AstGenericType* node)
{
return visit(static_cast<AstNode*>(node));
}
virtual bool visit(class AstGenericTypePack* node)
{
return visit(static_cast<AstNode*>(node));
}
virtual bool visit(class AstExpr* node)
{
return visit(static_cast<AstNode*>(node));

View file

@ -141,6 +141,16 @@ public:
Position opPosition;
};
class CstExprTypeAssertion : public CstNode
{
public:
LUAU_CST_RTTI(CstExprTypeAssertion)
explicit CstExprTypeAssertion(Position opPosition);
Position opPosition;
};
class CstExprIfElse : public CstNode
{
public:

View file

@ -222,8 +222,8 @@ private:
AstType* parseFunctionTypeTail(
const Lexeme& begin,
const AstArray<AstAttr*>& attributes,
AstArray<AstGenericType> generics,
AstArray<AstGenericTypePack> genericPacks,
AstArray<AstGenericType*> generics,
AstArray<AstGenericTypePack*> genericPacks,
AstArray<AstType*> params,
AstArray<std::optional<AstArgumentName>> paramNames,
AstTypePack* varargAnnotation
@ -294,7 +294,7 @@ private:
Name parseIndexName(const char* context, const Position& previous);
// `<' namelist `>'
std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> parseGenericTypeList(bool withDefaultValues);
std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> parseGenericTypeList(bool withDefaultValues);
// `<' Type[, ...] `>'
AstArray<AstTypeOrPack> parseTypeParams(
@ -474,8 +474,8 @@ private:
std::vector<AstExprTable::Item> scratchItem;
std::vector<CstExprTable::Item> scratchCstItem;
std::vector<AstArgumentName> scratchArgName;
std::vector<AstGenericType> scratchGenericTypes;
std::vector<AstGenericTypePack> scratchGenericTypePacks;
std::vector<AstGenericType*> scratchGenericTypes;
std::vector<AstGenericTypePack*> scratchGenericTypePacks;
std::vector<std::optional<AstArgumentName>> scratchOptArgName;
std::vector<Position> scratchPosition;
std::string scratchData;

View file

@ -28,6 +28,38 @@ void AstAttr::visit(AstVisitor* visitor)
int gAstRttiIndex = 0;
AstGenericType::AstGenericType(const Location& location, AstName name, AstType* defaultValue)
: AstNode(ClassIndex(), location)
, name(name)
, defaultValue(defaultValue)
{
}
void AstGenericType::visit(AstVisitor* visitor)
{
if (visitor->visit(this))
{
if (defaultValue)
defaultValue->visit(visitor);
}
}
AstGenericTypePack::AstGenericTypePack(const Location& location, AstName name, AstTypePack* defaultValue)
: AstNode(ClassIndex(), location)
, name(name)
, defaultValue(defaultValue)
{
}
void AstGenericTypePack::visit(AstVisitor* visitor)
{
if (visitor->visit(this))
{
if (defaultValue)
defaultValue->visit(visitor);
}
}
AstExprGroup::AstExprGroup(const Location& location, AstExpr* expr)
: AstExpr(ClassIndex(), location)
, expr(expr)
@ -185,8 +217,8 @@ void AstExprIndexExpr::visit(AstVisitor* visitor)
AstExprFunction::AstExprFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstArray<AstGenericType>& generics,
const AstArray<AstGenericTypePack>& genericPacks,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
AstLocal* self,
const AstArray<AstLocal*>& args,
bool vararg,
@ -721,8 +753,8 @@ AstStatTypeAlias::AstStatTypeAlias(
const Location& location,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType>& generics,
const AstArray<AstGenericTypePack>& genericPacks,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
AstType* type,
bool exported
)
@ -740,16 +772,14 @@ void AstStatTypeAlias::visit(AstVisitor* visitor)
{
if (visitor->visit(this))
{
for (const AstGenericType& el : generics)
for (AstGenericType* el : generics)
{
if (el.defaultValue)
el.defaultValue->visit(visitor);
el->visit(visitor);
}
for (const AstGenericTypePack& el : genericPacks)
for (AstGenericTypePack* el : genericPacks)
{
if (el.defaultValue)
el.defaultValue->visit(visitor);
el->visit(visitor);
}
type->visit(visitor);
@ -795,8 +825,8 @@ AstStatDeclareFunction::AstStatDeclareFunction(
const Location& location,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType>& generics,
const AstArray<AstGenericTypePack>& genericPacks,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& params,
const AstArray<AstArgumentName>& paramNames,
bool vararg,
@ -822,8 +852,8 @@ AstStatDeclareFunction::AstStatDeclareFunction(
const AstArray<AstAttr*>& attributes,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType>& generics,
const AstArray<AstGenericTypePack>& genericPacks,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& params,
const AstArray<AstArgumentName>& paramNames,
bool vararg,
@ -970,8 +1000,8 @@ void AstTypeTable::visit(AstVisitor* visitor)
AstTypeFunction::AstTypeFunction(
const Location& location,
const AstArray<AstGenericType>& generics,
const AstArray<AstGenericTypePack>& genericPacks,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
const AstTypeList& returnTypes
@ -990,8 +1020,8 @@ AstTypeFunction::AstTypeFunction(
AstTypeFunction::AstTypeFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstArray<AstGenericType>& generics,
const AstArray<AstGenericTypePack>& genericPacks,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
const AstTypeList& returnTypes

View file

@ -50,6 +50,12 @@ CstExprOp::CstExprOp(Position opPosition)
{
}
CstExprTypeAssertion::CstExprTypeAssertion(Position opPosition)
: CstNode(CstClassIndex())
, opPosition(opPosition)
{
}
CstExprIfElse::CstExprIfElse(Position thenPosition, Position elsePosition, bool isElseIf)
: CstNode(CstClassIndex())
, thenPosition(thenPosition)

View file

@ -1066,8 +1066,8 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod()
Name fnName = parseName("function name");
// TODO: generic method declarations CLI-39909
AstArray<AstGenericType> generics;
AstArray<AstGenericTypePack> genericPacks;
AstArray<AstGenericType*> generics;
AstArray<AstGenericTypePack*> genericPacks;
generics.size = 0;
generics.data = nullptr;
genericPacks.size = 0;
@ -2035,8 +2035,8 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray<AstAttr*>
AstType* Parser::parseFunctionTypeTail(
const Lexeme& begin,
const AstArray<AstAttr*>& attributes,
AstArray<AstGenericType> generics,
AstArray<AstGenericTypePack> genericPacks,
AstArray<AstGenericType*> generics,
AstArray<AstGenericTypePack*> genericPacks,
AstArray<AstType*> params,
AstArray<std::optional<AstArgumentName>> paramNames,
AstTypePack* varargAnnotation
@ -2824,9 +2824,18 @@ AstExpr* Parser::parseAssertionExpr()
if (lexer.current().type == Lexeme::DoubleColon)
{
CstExprTypeAssertion* cstNode = nullptr;
if (FFlag::LuauStoreCSTData && options.storeCstData)
{
Position opPosition = lexer.current().location.begin;
cstNode = allocator.alloc<CstExprTypeAssertion>(opPosition);
}
nextLexeme();
AstType* annotation = parseType();
return allocator.alloc<AstExprTypeAssertion>(Location(start, annotation->location), expr, annotation);
AstExprTypeAssertion* node = allocator.alloc<AstExprTypeAssertion>(Location(start, annotation->location), expr, annotation);
if (FFlag::LuauStoreCSTData && options.storeCstData)
cstNodeMap[node] = cstNode;
return node;
}
else
return expr;
@ -3305,10 +3314,10 @@ Parser::Name Parser::parseIndexName(const char* context, const Position& previou
return Name(nameError, location);
}
std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> Parser::parseGenericTypeList(bool withDefaultValues)
std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::parseGenericTypeList(bool withDefaultValues)
{
TempVector<AstGenericType> names{scratchGenericTypes};
TempVector<AstGenericTypePack> namePacks{scratchGenericTypePacks};
TempVector<AstGenericType*> names{scratchGenericTypes};
TempVector<AstGenericTypePack*> namePacks{scratchGenericTypePacks};
if (lexer.current().type == '<')
{
@ -3340,7 +3349,7 @@ std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> Parser::parseG
{
AstTypePack* typePack = parseTypePack();
namePacks.push_back({name, nameLocation, typePack});
namePacks.push_back(allocator.alloc<AstGenericTypePack>(nameLocation, name, typePack));
}
else
{
@ -3349,7 +3358,7 @@ std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> Parser::parseG
if (type)
report(type->location, "Expected type pack after '=', got type");
namePacks.push_back({name, nameLocation, typePack});
namePacks.push_back(allocator.alloc<AstGenericTypePack>(nameLocation, name, typePack));
}
}
else
@ -3357,7 +3366,7 @@ std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> Parser::parseG
if (seenDefault)
report(lexer.current().location, "Expected default type pack after type pack name");
namePacks.push_back({name, nameLocation, nullptr});
namePacks.push_back(allocator.alloc<AstGenericTypePack>(nameLocation, name, nullptr));
}
}
else
@ -3369,14 +3378,14 @@ std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> Parser::parseG
AstType* defaultType = parseType();
names.push_back({name, nameLocation, defaultType});
names.push_back(allocator.alloc<AstGenericType>(nameLocation, name, defaultType));
}
else
{
if (seenDefault)
report(lexer.current().location, "Expected default type after type name");
names.push_back({name, nameLocation, nullptr});
names.push_back(allocator.alloc<AstGenericType>(nameLocation, name, nullptr));
}
}
@ -3397,8 +3406,8 @@ std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> Parser::parseG
expectMatchAndConsume('>', begin);
}
AstArray<AstGenericType> generics = copy(names);
AstArray<AstGenericTypePack> genericPacks = copy(namePacks);
AstArray<AstGenericType*> generics = copy(names);
AstArray<AstGenericTypePack*> genericPacks = copy(namePacks);
return {generics, genericPacks};
}

View file

@ -23,12 +23,13 @@ Of course, feel free to [create a pull request](https://help.github.com/articles
## Features
If you're thinking of adding a new feature to the language, library, analysis tools, etc., please *don't* start by submitting a pull request.
Luau team has internal priorities and a roadmap that may or may not align with specific features, so before starting to work on a feature please submit an issue describing the missing feature that you'd like to add.
The Luau team has internal priorities and a roadmap that may or may not align with specific features, so before starting to work on a feature, please submit an issue describing the missing feature that you'd like to add.
For features that result in observable change of language syntax or semantics, you'd need to [create an RFC](https://github.com/luau-lang/rfcs/blob/master/README.md) to make sure that the feature is needed and well designed.
For features that result in an observable change to the language's syntax or semantics, you'll need to [create an RFC](https://github.com/luau-lang/rfcs/blob/master/README.md) to make sure that the feature is needed and well-designed.
Finally, please note that Luau tries to carry a minimal feature set. All features must be evaluated not just for the benefits that they provide, but also for the downsides/costs in terms of language simplicity, maintainability, cross-feature interaction etc.
Finally, please note that Luau tries to carry a minimal feature set. All features must be evaluated not just for the benefits that they provide, but also for the downsides/costs in terms of language simplicity, maintainability, cross-feature interaction, etc.
As such, feature requests may not be accepted even if a comprehensive RFC is written - don't expect Luau to gain a feature just because another programming language has it.
We generally apply a standard similar to the C\# team's famous [Minus 100 Points](https://learn.microsoft.com/en-us/archive/blogs/ericgu/minus-100-points).
## Code style
@ -48,9 +49,9 @@ When making code changes please try to make sure they are covered by an existing
## Performance
One of the central feature of Luau is performance; our runtime in particular is heavily optimized for high performance and low memory consumption, and code is generally carefully tuned to result in close to optimal assembly for x64 and AArch64 architectures. The analysis code is not optimized to the same level of detail, but performance is still very important to make sure that we can support interactive IDE features.
One of the central features of Luau is performance; our runtime in particular is heavily optimized for high performance and low memory consumption, and code is generally carefully tuned to result in close-to-optimal assembly for x64 and AArch64 architectures. The analysis code is not optimized to the same level of detail, but performance is still very important to make sure that we can support interactive IDE features.
As such, it's important to make sure that the changes, including bug fixes, improve or at least do not regress performance. For VM this can be validated by running `bench.py` script from `bench` folder on two binaries built in Release mode, before and after the changes, although note that our benchmark coverage is not complete and in some cases additional performance testing will be necessary to determine if the change can be merged.
As such, it's important to make sure that the changes, including bug fixes, improve (or at least do not regress) performance. For the VM, this can be validated by running `bench/bench.py` on two binaries built in Release mode, before and after the changes. Note that our benchmark coverage is not complete, and in some cases, additional performance testing will be necessary to determine if the change can be merged.
## Feature flags

View file

@ -4298,8 +4298,8 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
AstExprFunction main(
root->location,
/* attributes= */ AstArray<AstAttr*>({nullptr, 0}),
/* generics= */ AstArray<AstGenericType>(),
/* genericPacks= */ AstArray<AstGenericTypePack>(),
/* generics= */ AstArray<AstGenericType*>(),
/* genericPacks= */ AstArray<AstGenericTypePack*>(),
/* self= */ nullptr,
AstArray<AstLocal*>(),
/* vararg= */ true,

View file

@ -6,10 +6,10 @@
namespace Luau
{
static bool isGeneric(AstName name, const AstArray<AstGenericType>& generics)
static bool isGeneric(AstName name, const AstArray<AstGenericType*>& generics)
{
for (const AstGenericType& gt : generics)
if (gt.name == name)
for (const AstGenericType* gt : generics)
if (gt->name == name)
return true;
return false;
@ -39,7 +39,7 @@ static LuauBytecodeType getPrimitiveType(AstName name)
static LuauBytecodeType getType(
const AstType* ty,
const AstArray<AstGenericType>& generics,
const AstArray<AstGenericType*>& generics,
const DenseHashMap<AstName, AstStatTypeAlias*>& typeAliases,
bool resolveAliases,
const char* hostVectorType,

View file

@ -3,19 +3,19 @@ Luau ![CI](https://github.com/luau-lang/luau/actions/workflows/build.yml/badge.s
Luau (lowercase u, /ˈlu.aʊ/) is a fast, small, safe, gradually typed embeddable scripting language derived from [Lua](https://lua.org).
It is designed to be backwards compatible with Lua 5.1, as well as incorporating [some features](https://luau.org/compatibility) from future Lua releases, but also expands the feature set (most notably with type annotations). Luau is largely implemented from scratch, with the language runtime being a very heavily modified version of Lua 5.1 runtime, with completely rewritten interpreter and other [performance innovations](https://luau.org/performance). The runtime mostly preserves Lua 5.1 API, so existing bindings should be more or less compatible with a few caveats.
It is designed to be backwards compatible with Lua 5.1, as well as incorporating [some features](https://luau.org/compatibility) from future Lua releases, but also expands the feature set (most notably with type annotations and a state-of-the-art type inference system). Luau is largely implemented from scratch, with the language runtime being a very heavily modified version of Lua 5.1 runtime, with completely rewritten interpreter and other [performance innovations](https://luau.org/performance). The runtime mostly preserves Lua 5.1 API, so existing bindings should be more or less compatible with a few caveats.
Luau is used by Roblox game developers to write game code, as well as by Roblox engineers to implement large parts of the user-facing application code as well as portions of the editor (Roblox Studio) as plugins. Roblox chose to open-source Luau to foster collaboration within the Roblox community as well as to allow other companies and communities to benefit from the ongoing language and runtime innovation. As a consequence, Luau is now also used by games like Alan Wake 2 and Warframe.
Luau is used by Roblox game developers to write game code, and by Roblox engineers to implement large parts of the user-facing application code as well as portions of the editor (Roblox Studio) as plugins. Roblox chose to open-source Luau to foster collaboration within the Roblox community as well as to allow other companies and communities to benefit from the ongoing language and runtime innovation. More recently, Luau has seen adoption in games like Alan Wake 2, Farming Simulator 2025, Second Life, and Warframe.
This repository hosts source code for the language implementation and associated tooling. Documentation for the language is available at https://luau.org/ and accepts contributions via [site repository](https://github.com/luau-lang/site); the language is evolved through RFCs that are located in [rfcs repository](https://github.com/luau-lang/rfcs).
# Usage
Luau is an embeddable language, but it also comes with two command-line tools by default, `luau` and `luau-analyze`.
Luau is an embeddable programming language, but it also comes with two command-line tools by default, `luau` and `luau-analyze`.
`luau` is a command-line REPL and can also run input files. Note that REPL runs in a sandboxed environment and as such doesn't have access to the underlying file system except for ability to `require` modules.
`luau-analyze` is a command-line type checker and linter; given a set of input files, it produces errors/warnings according to the file configuration, which can be customized by using `--!` comments in the files or [`.luaurc`](https://rfcs.luau.org/config-luaurc) files. For details please refer to [type checking]( https://luau.org/typecheck) and [linting](https://luau.org/lint) documentation.
`luau-analyze` is a command-line type checker and linter; given a set of input files, it produces errors/warnings according to the file configuration, which can be customized by using `--!` comments in the files or [`.luaurc`](https://rfcs.luau.org/config-luaurc) files. For details, please refer to our [type checking](https://luau.org/typecheck) and [linting](https://luau.org/lint) documentation. Our community maintains a language server frontend for `luau-analyze` called [luau-lsp](https://github.com/JohnnyMorganz/luau-lsp) for use with text editors.
# Installation
@ -41,13 +41,13 @@ cmake --build . --target Luau.Repl.CLI --config RelWithDebInfo
cmake --build . --target Luau.Analyze.CLI --config RelWithDebInfo
```
Alternatively, on Linux/macOS you can use `make`:
Alternatively, on Linux and macOS, you can also use `make`:
```sh
make config=release luau luau-analyze
```
To integrate Luau into your CMake application projects as a library, at the minimum you'll need to depend on `Luau.Compiler` and `Luau.VM` projects. From there you need to create a new Luau state (using Lua 5.x API such as `lua_newstate`), compile source to bytecode and load it into the VM like this:
To integrate Luau into your CMake application projects as a library, at the minimum, you'll need to depend on `Luau.Compiler` and `Luau.VM` projects. From there you need to create a new Luau state (using Lua 5.x API such as `lua_newstate`), compile source to bytecode and load it into the VM like this:
```cpp
// needs lua.h and luacode.h
@ -60,24 +60,24 @@ if (result == 0)
return 1; /* return chunk main function */
```
For more details about the use of host API you currently need to consult [Lua 5.x API](https://www.lua.org/manual/5.1/manual.html#3). Luau closely tracks that API but has a few deviations, such as the need to compile source separately (which is important to be able to deploy VM without a compiler), or lack of `__gc` support (use `lua_newuserdatadtor` instead).
For more details about the use of the host API, you currently need to consult [Lua 5.x API](https://www.lua.org/manual/5.1/manual.html#3). Luau closely tracks that API but has a few deviations, such as the need to compile source separately (which is important to be able to deploy VM without a compiler), and the lack of `__gc` support (use `lua_newuserdatadtor` instead).
To gain advantage of many performance improvements it's highly recommended to use `safeenv` feature, which sandboxes individual scripts' global tables from each other as well as protects builtin libraries from monkey-patching. For this to work you need to call `luaL_sandbox` for the global state and `luaL_sandboxthread` for each new script's execution thread.
To gain advantage of many performance improvements, it's highly recommended to use the `safeenv` feature, which sandboxes individual scripts' global tables from each other, and protects builtin libraries from monkey-patching. For this to work, you must call `luaL_sandbox` on the global state and `luaL_sandboxthread` for each new script's execution thread.
# Testing
Luau has an internal test suite; in CMake builds it is split into two targets, `Luau.UnitTest` (for bytecode compiler and type checker/linter tests) and `Luau.Conformance` (for VM tests). The unit tests are written in C++, whereas the conformance tests are largely written in Luau (see `tests/conformance`).
Luau has an internal test suite; in CMake builds, it is split into two targets, `Luau.UnitTest` (for the bytecode compiler and type checker/linter tests) and `Luau.Conformance` (for the VM tests). The unit tests are written in C++, whereas the conformance tests are largely written in Luau (see `tests/conformance`).
Makefile builds combine both into a single target and can be ran via `make test`.
Makefile builds combine both into a single target that can be run via `make test`.
# Dependencies
Luau uses C++ as its implementation language. The runtime requires C++11, whereas the compiler and analysis components require C++17. It should build without issues using Microsoft Visual Studio 2017 or later, or gcc-7 or clang-7 or later.
Luau uses C++ as its implementation language. The runtime requires C++11, while the compiler and analysis components require C++17. It should build without issues using Microsoft Visual Studio 2017 or later, or gcc-7 or clang-7 or later.
Other than the STL/CRT, Luau library components don't have external dependencies. The test suite depends on [doctest](https://github.com/onqtam/doctest) testing framework, and the REPL command-line depends on [isocline](https://github.com/daanx/isocline).
Other than the STL/CRT, Luau library components don't have external dependencies. The test suite depends on the [doctest](https://github.com/onqtam/doctest) testing framework, and the REPL command-line depends on [isocline](https://github.com/daanx/isocline).
# License
Luau implementation is distributed under the terms of [MIT License](https://github.com/luau-lang/luau/blob/master/LICENSE.txt). It is based on Lua 5.x implementation that is MIT licensed as well.
Luau implementation is distributed under the terms of [MIT License](https://github.com/luau-lang/luau/blob/master/LICENSE.txt). It is based on the Lua 5.x implementation, also under the MIT License.
When Luau is integrated into external projects, we ask to honor the license agreement and include Luau attribution into the user-facing product documentation. The attribution using [Luau logo](https://github.com/luau-lang/site/blob/master/logo.svg) is also encouraged.
When Luau is integrated into external projects, we ask that you honor the license agreement and include Luau attribution into the user-facing product documentation. Attribution making use of the [Luau logo](https://github.com/luau-lang/site/blob/master/logo.svg) is also encouraged when reasonable.

View file

@ -6,9 +6,9 @@ Any source code can not result in memory safety errors or crashes during its com
Note that Luau does not provide termination guarantees - some code may exhaust CPU or RAM resources on the system during compilation or execution.
The runtime expects valid bytecode as an input. Feeding bytecode that was not produced by Luau compiler into the VM is not supported and
The runtime expects valid bytecode as an input. Feeding bytecode that was not produced by Luau compiler into the VM is not supported, and
doesn't come with any security guarantees; make sure to sign and/or encrypt the bytecode when it crosses a network or file system boundary to avoid tampering.
# Reporting a Vulnerability
You can report security bugs via [Hackerone](https://hackerone.com/roblox). Please refer to the linked page for rules of the bounty program.
You can report security bugs via [HackerOne](https://hackerone.com/roblox). Please refer to the linked page for rules of the bounty program.

View file

@ -11,6 +11,8 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauLibWhereErrorAutoreserve)
// convert a stack index to positive
#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1)
@ -67,6 +69,7 @@ static l_noret tag_error(lua_State* L, int narg, int tag)
luaL_typeerrorL(L, narg, lua_typename(L, tag));
}
// Can be called without stack space reservation
void luaL_where(lua_State* L, int level)
{
lua_Debug ar;
@ -75,9 +78,14 @@ void luaL_where(lua_State* L, int level)
lua_pushfstring(L, "%s:%d: ", ar.short_src, ar.currentline);
return;
}
if (FFlag::LuauLibWhereErrorAutoreserve)
lua_rawcheckstack(L, 1);
lua_pushliteral(L, ""); // else, no information available...
}
// Can be called without stack space reservation
l_noret luaL_errorL(lua_State* L, const char* fmt, ...)
{
va_list argp;

View file

@ -8,6 +8,8 @@
#include <string.h>
#include <stdio.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauStringFormatFixC, false)
// macro to `unsign' a character
#define uchar(c) ((unsigned char)(c))
@ -999,8 +1001,17 @@ static int str_format(lua_State* L)
{
case 'c':
{
snprintf(buff, sizeof(buff), form, (int)luaL_checknumber(L, arg));
break;
if (DFFlag::LuauStringFormatFixC)
{
int count = snprintf(buff, sizeof(buff), form, (int)luaL_checknumber(L, arg));
luaL_addlstring(&b, buff, count);
continue; // skip the 'luaL_addlstring' at the end
}
else
{
snprintf(buff, sizeof(buff), form, (int)luaL_checknumber(L, arg));
break;
}
}
case 'd':
case 'i':

View file

@ -31,6 +31,7 @@ extern int optimizationLevel;
void luaC_fullgc(lua_State* L);
void luaC_validate(lua_State* L);
LUAU_FASTFLAG(LuauLibWhereErrorAutoreserve)
LUAU_FASTFLAG(LuauMathLerp)
LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
@ -40,7 +41,7 @@ LUAU_FASTFLAG(LuauVectorLibNativeDot)
LUAU_FASTFLAG(LuauVector2Constructor)
LUAU_FASTFLAG(LuauBufferBitMethods2)
LUAU_FASTFLAG(LuauCodeGenLimitLiveSlotReuse)
LUAU_FASTFLAG(LuauMathMapDefinition)
LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC)
static lua_CompileOptions defaultOptions()
{
@ -718,6 +719,8 @@ TEST_CASE("Clear")
TEST_CASE("Strings")
{
ScopedFastFlag luauStringFormatFixC{DFFlag::LuauStringFormatFixC, true};
runConformance("strings.luau");
}
@ -988,7 +991,6 @@ TEST_CASE("Types")
{
ScopedFastFlag luauVector2Constructor{FFlag::LuauVector2Constructor, true};
ScopedFastFlag luauMathLerp{FFlag::LuauMathLerp, true};
ScopedFastFlag luauMathMapDefinition{FFlag::LuauMathMapDefinition, true};
runConformance(
"types.luau",
@ -1719,7 +1721,31 @@ TEST_CASE("ApiBuffer")
lua_pop(L, 1);
}
TEST_CASE("AllocApi")
int slowlyOverflowStack(lua_State* L)
{
for (int i = 0; i < LUAI_MAXCSTACK * 2; i++)
{
luaL_checkstack(L, 1, "test");
lua_pushnumber(L, 1.0);
}
return 0;
}
TEST_CASE("ApiStack")
{
ScopedFastFlag luauLibWhereErrorAutoreserve{FFlag::LuauLibWhereErrorAutoreserve, true};
StateRef globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get();
lua_pushcfunction(L, slowlyOverflowStack, "foo");
int result = lua_pcall(L, 0, 0, 0);
REQUIRE(result == LUA_ERRRUN);
CHECK(strcmp(luaL_checkstring(L, -1), "stack overflow (test)") == 0);
}
TEST_CASE("ApiAlloc")
{
int ud = 0;
StateRef globalState(lua_newstate(limitedRealloc, &ud), lua_close);

View file

@ -6,6 +6,7 @@
#include "Luau/Ast.h"
#include "Luau/BuiltinDefinitions.h"
#include "Luau/Common.h"
#include "Luau/Error.h"
#include "Luau/IostreamHelpers.h"
#include "Luau/ModuleResolver.h"
#include "Luau/VisitType.h"
@ -16,6 +17,8 @@
LUAU_FASTFLAG(LuauCountSelfCallsNonstrict)
LUAU_FASTFLAG(LuauVector2Constructor)
LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAG(LuauNonStrictVisitorImprovements)
using namespace Luau;
@ -490,6 +493,40 @@ foo.bar("hi")
NONSTRICT_REQUIRE_CHECKED_ERR(Position(1, 8), "foo.bar", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "exprgroup_is_checked")
{
ScopedFastFlag sff{FFlag::LuauNonStrictVisitorImprovements, true};
CheckResult result = checkNonStrict(R"(
local foo = (abs("foo"))
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
auto r1 = get<CheckedFunctionCallError>(result.errors[0]);
LUAU_ASSERT(r1);
CHECK_EQ("abs", r1->checkedFunctionName);
CHECK_EQ("number", toString(r1->expected));
CHECK_EQ("string", toString(r1->passed));
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "binop_is_checked")
{
ScopedFastFlag sff{FFlag::LuauNonStrictVisitorImprovements, true};
CheckResult result = checkNonStrict(R"(
local foo = 4 + abs("foo")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
auto r1 = get<CheckedFunctionCallError>(result.errors[0]);
LUAU_ASSERT(r1);
CHECK_EQ("abs", r1->checkedFunctionName);
CHECK_EQ("number", toString(r1->expected));
CHECK_EQ("string", toString(r1->passed));
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "incorrect_arg_count")
{
CheckResult result = checkNonStrict(R"(
@ -602,4 +639,22 @@ TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "nonstrict_method_calls")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_non_strict")
{
ScopedFastFlag flags[] = {
{FFlag::LuauNonStrictVisitorImprovements, true},
{FFlag::LuauNewNonStrictWarnOnUnknownGlobals, true}
};
CheckResult result = check(Mode::Nonstrict, R"(
foo = 5
local wrong1 = foob
local x = 12
local wrong2 = x + foblm
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
}
TEST_SUITE_END();

View file

@ -944,6 +944,16 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_assertion")
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE_FIXTURE(Fixture, "type_assertion_spaces_around_tokens")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = "local a = 5 :: number";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a = 5 :: number";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else")
{
std::string code = "local a = if 1 then 2 else 3";

View file

@ -8,10 +8,9 @@
using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauUserTypeFunFixInner)
LUAU_FASTFLAG(LuauUserTypeFunGenerics)
LUAU_FASTFLAG(LuauUserTypeFunCloneTail)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauUserTypeFunTypeofReturnsType)
LUAU_FASTFLAG(LuauTypeFunPrintFix)
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
@ -475,7 +474,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_negation_methods_work")
TEST_CASE_FIXTURE(ClassFixture, "udtf_negation_inner")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunFixInner{FFlag::LuauUserTypeFunFixInner, true};
CheckResult result = check(R"(
type function pass(t)
@ -1404,7 +1402,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "print_to_error_plus_no_result")
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)
@ -1422,7 +1419,6 @@ local function ok(idx: pass<test>): test return idx end
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)
@ -1440,7 +1436,6 @@ local function ok(idx: pass<test>): test return idx end
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)
@ -1462,7 +1457,6 @@ local function ok(idx: pass<test>): test return idx end
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)
@ -1480,8 +1474,6 @@ local function ok(idx: pass<test>): test return idx end
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)
@ -1499,7 +1491,6 @@ local function ok(idx: pass<test>): test return idx end
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)
@ -1517,7 +1508,6 @@ local function ok(idx: pass<test>): true return idx end
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)
@ -1537,7 +1527,6 @@ local function ok(idx: pass<test>): <T>(T) -> (T) return idx end
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)
@ -1561,7 +1550,6 @@ local function ok(idx: pass<test>): <T>(T, T) -> (T) return idx end
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()
@ -1591,7 +1579,6 @@ local function ok(idx: pass<>): <T, U..., V...>(T, U...) -> (T, V...) return idx
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()
@ -1618,7 +1605,6 @@ local function ok(idx: pass<>): test return idx end
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()
@ -1635,7 +1621,6 @@ local function ok(idx: pass<>): <T>(T) -> () return idx end
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)
@ -1663,7 +1648,6 @@ local function ok(idx: pass<test>): <T, U>(T) -> (U) return idx end
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)
@ -1686,7 +1670,6 @@ local function ok(idx: pass<test>): <T, U...>(T, U...) -> (T, U...) return idx e
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)
@ -1709,7 +1692,6 @@ local function ok(idx: pass<test>): <T>(T, T) -> (T) return idx end
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()
@ -1730,7 +1712,6 @@ local function ok(idx: get<>): false return idx end
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()
@ -1750,7 +1731,6 @@ local function ok(idx: get<>): false return idx end
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()
@ -1767,7 +1747,6 @@ local function ok(idx: get<>): false return idx end
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()
@ -1789,7 +1768,6 @@ local function ok(idx: get<>): false return idx end
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()
@ -1806,7 +1784,6 @@ local function ok(idx: get<>): false return idx end
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()
@ -1823,7 +1800,6 @@ local function ok(idx: get<>): false return idx end
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()
@ -1840,7 +1816,6 @@ local function ok(idx: get<>): false return idx end
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()
@ -1857,7 +1832,6 @@ local function ok(idx: get<>): false return idx end
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)
@ -1878,7 +1852,7 @@ local function ok(idx: pass<test>): (number, ...string) -> (string, ...number) r
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_eqsat_opaque")
{
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauUserTypeFunGenerics, true}, {FFlag::DebugLuauEqSatSimplification, true}};
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::DebugLuauEqSatSimplification, true}};
CheckResult _ = check(R"(
type function t0(a)
@ -1894,4 +1868,42 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_eqsat_opaque")
CHECK_EQ("t0<number & string>", toString(simplified->result)); // NOLINT(bugprone-unchecked-optional-access)
}
TEST_CASE_FIXTURE(BuiltinsFixture, "typeof_type_userdata_returns_type")
{
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunTypeofReturnsType{FFlag::LuauUserTypeFunTypeofReturnsType, true};
CheckResult result = check(R"(
type function test(t)
print(typeof(t))
return t
end
local _:test<number>
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(toString(result.errors[0]) == R"(type)");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_print_tab_char_fix")
{
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauTypeFunPrintFix, true}};
CheckResult result = check(R"(
type function test(t)
print(1,2)
return t
end
local _:test<number>
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
// It should be \t and not \x1
CHECK_EQ("1\t2", toString(result.errors[0]));
}
TEST_SUITE_END();

View file

@ -11,6 +11,8 @@ using namespace Luau;
LUAU_FASTFLAG(LuauNewSolverPrePopulateClasses)
LUAU_FASTFLAG(LuauClipNestedAndRecursiveUnion)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauPreventReentrantTypeFunctionReduction)
TEST_SUITE_BEGIN("DefinitionTests");
@ -557,5 +559,32 @@ TEST_CASE_FIXTURE(Fixture, "recursive_redefinition_reduces_rightfully")
)"));
}
TEST_CASE_FIXTURE(Fixture, "vector3_overflow")
{
ScopedFastFlag _{FFlag::LuauPreventReentrantTypeFunctionReduction, true};
// We set this to zero to ensure that we either run to completion or stack overflow here.
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};
loadDefinition(R"(
declare class Vector3
function __add(self, other: Vector3): Vector3
end
)");
CheckResult result = check(R"(
--!strict
local function graphPoint(t : number, points : { Vector3 }) : Vector3
local n : number = #points - 1
local p : Vector3 = (nil :: any)
for i = 0, n do
local x = points[i + 1]
p = p and p + x or x
end
return p
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -9,7 +9,6 @@
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(InferGlobalTypes)
LUAU_FASTFLAG(LuauGeneralizationRemoveRecursiveUpperBound)
using namespace Luau;
@ -1881,8 +1880,6 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "refine_a_param_that_got_resolved_duri
TEST_CASE_FIXTURE(Fixture, "refine_a_property_of_some_global")
{
ScopedFastFlag sff{FFlag::InferGlobalTypes, true};
CheckResult result = check(R"(
foo = { bar = 5 :: number? }

View file

@ -23,6 +23,9 @@ LUAU_FASTFLAG(LuauAllowNilAssignmentToIndexer)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAG(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAG(LuauDontInPlaceMutateTableType)
LUAU_FASTFLAG(LuauAllowNonSharedTableTypesInLiteral)
LUAU_FASTFLAG(LuauFollowTableFreeze)
LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes)
TEST_SUITE_BEGIN("TableTests");
@ -5005,17 +5008,22 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type")
TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager")
{
ScopedFastFlag _{FFlag::LuauSolverV2, true};
// NOTE: All of these examples should have no errors, but
// bidirectional inference is known to be broken.
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauPrecalculateMutatedFreeTypes, true},
};
// CLI-121540: All of these examples should have no errors.
LUAU_CHECK_ERROR_COUNT(3, check(R"(
auto result = check(R"(
local function doTheThing(_: { [string]: unknown }) end
doTheThing({
['foo'] = 5,
['bar'] = 'heyo',
})
)"));
)");
LUAU_CHECK_ERROR_COUNT(1, result);
LUAU_CHECK_NO_ERROR(result, ConstraintSolvingIncompleteError);
LUAU_CHECK_ERROR_COUNT(1, check(R"(
type Input = { [string]: unknown }
@ -5028,7 +5036,7 @@ TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager")
// This example previously asserted due to eagerly mutating the underlying
// table type.
LUAU_CHECK_ERROR_COUNT(3, check(R"(
result = check(R"(
type Input = { [string]: unknown }
local function doTheThing(_: Input) end
@ -5037,7 +5045,9 @@ TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager")
[('%s'):format('3.14')]=5,
['stringField']='Heyo'
})
)"));
)");
LUAU_CHECK_ERROR_COUNT(1, result);
LUAU_CHECK_NO_ERROR(result, ConstraintSolvingIncompleteError);
}
@ -5091,4 +5101,56 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "multiple_fields_in_literal")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "multiple_fields_from_fuzzer")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauDontInPlaceMutateTableType, true},
{FFlag::LuauAllowNonSharedTableTypesInLiteral, true},
};
// This would trigger an assert previously, so we really only care that
// there are errors (and there will be: lots of syntax errors).
LUAU_CHECK_ERRORS(check(R"(
function _(l0,l0) _(_,{n0=_,n0=_,},if l0:n0()[_] then _)
)"));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "write_only_table_field_duplicate")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauDontInPlaceMutateTableType, true},
{FFlag::LuauAllowNonSharedTableTypesInLiteral, true},
};
auto result = check(R"(
type WriteOnlyTable = { write x: number }
local wo: WriteOnlyTable = {
x = 42,
x = 13,
}
)");
LUAU_CHECK_ERROR_COUNT(1, result);
CHECK_EQ("write keyword is illegal here", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_musnt_assert")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauFollowTableFreeze, true},
};
auto result = check(R"(
local m = {}
function m.foo()
local self = { entries = entries, _caches = {}}
local self = setmetatable(self, {})
table.freeze(self)
end
)");
}
TEST_SUITE_END();

View file

@ -16,15 +16,16 @@
#include <algorithm>
LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr);
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
LUAU_FASTINT(LuauCheckRecursionLimit);
LUAU_FASTINT(LuauNormalizeCacheLimit);
LUAU_FASTINT(LuauRecursionLimit);
LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTFLAG(InferGlobalTypes)
LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTINT(LuauNormalizeCacheLimit)
LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauAstTypeGroup)
LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAG(LuauInferLocalTypesInMultipleAssignments)
using namespace Luau;
@ -819,7 +820,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "no_heap_use_after_free_error")
end
)");
if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2 && !FFlag::LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_REQUIRE_NO_ERRORS(result);
else
LUAU_REQUIRE_ERRORS(result);
@ -1770,7 +1771,6 @@ TEST_CASE_FIXTURE(Fixture, "avoid_double_reference_to_free_type")
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_types_of_globals")
{
ScopedFastFlag sff_LuauSolverV2{FFlag::LuauSolverV2, true};
ScopedFastFlag sff_InferGlobalTypes{FFlag::InferGlobalTypes, true};
CheckResult result = check(R"(
--!strict
@ -1784,4 +1784,25 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "infer_types_of_globals")
CHECK_EQ("Unknown global 'foo'", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "multiple_assignment")
{
ScopedFastFlag sff_LuauSolverV2{FFlag::LuauSolverV2, true};
ScopedFastFlag sff_InferLocalTypesInMultipleAssignments{FFlag::LuauInferLocalTypesInMultipleAssignments, true};
CheckResult result = check(R"(
local function requireString(arg: string) end
local function requireNumber(arg: number) end
local function f(): ...number end
local w: "a", x, y, z = "a", 1, f()
requireString(w)
requireNumber(x)
requireNumber(y)
requireNumber(z)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -61,7 +61,7 @@ assert(#"\0\0\0" == 3)
assert(#"1234567890" == 10)
assert(string.byte("a") == 97)
assert(string.byte("á") > 127)
assert(string.byte("\xe4") > 127)
assert(string.byte(string.char(255)) == 255)
assert(string.byte(string.char(0)) == 0)
assert(string.byte("\0") == 0)
@ -76,10 +76,10 @@ assert(string.byte("hi", 9, 10) == nil)
assert(string.byte("hi", 2, 1) == nil)
assert(string.char() == "")
assert(string.char(0, 255, 0) == "\0\255\0")
assert(string.char(0, string.byte("á"), 0) == "\0á\0")
assert(string.char(string.byte("ál\0óu", 1, -1)) == "ál\0óu")
assert(string.char(string.byte("ál\0óu", 1, 0)) == "")
assert(string.char(string.byte("ál\0óu", -10, 100)) == "ál\0óu")
assert(string.char(0, string.byte("\xe4"), 0) == "\0\xe4\0")
assert(string.char(string.byte("\xe4l\0óu", 1, -1)) == "\xe4l\0óu")
assert(string.char(string.byte("\xe4l\0óu", 1, 0)) == "")
assert(string.char(string.byte("\xe4l\0óu", -10, 100)) == "\xe4l\0óu")
assert(pcall(function() return string.char(256) end) == false)
assert(pcall(function() return string.char(-1) end) == false)
print('+')
@ -87,7 +87,7 @@ print('+')
assert(string.upper("ab\0c") == "AB\0C")
assert(string.lower("\0ABCc%$") == "\0abcc%$")
assert(string.rep('teste', 0) == '')
assert(string.rep('tés\00tê', 2) == 'tés\0têtés\000tê')
assert(string.rep('tés\00tê', 2) == 'tés\0têtés\000tê')
assert(string.rep('', 10) == '')
assert(string.rep('', 1e9) == '')
assert(pcall(string.rep, 'x', 2e9) == false)
@ -115,15 +115,18 @@ assert(pcall(function() return tostring(nothing()) end) == false)
print('+')
x = '"ílo"\n\\'
assert(string.format('%q%s', x, x) == '"\\"ílo\\"\\\n\\\\""ílo"\n\\')
x = '"ílo"\n\\'
assert(string.format('%q%s', x, x) == '"\\"ílo\\"\\\n\\\\""ílo"\n\\')
assert(string.format('%q', "\0") == [["\000"]])
assert(string.format('%q', "\r") == [["\r"]])
assert(string.format("\0%c\0%c%x\0", string.byte("á"), string.byte("b"), 140) ==
"\0á\0b8c\0")
assert(string.format("\0%c\0%c%x\0", string.byte("\xe4"), string.byte("b"), 140) ==
"\0\xe4\0b8c\0")
assert(string.format('') == "")
assert(string.format("%c",34)..string.format("%c",48)..string.format("%c",90)..string.format("%c",100) ==
string.format("%c%c%c%c", 34, 48, 90, 100))
assert(string.format("%c%c%c%c", 1, 0, 2, 3) == '\1\0\2\3')
assert(string.format("%5c%5c%5c%5c", 1, 0, 2, 3) == ' \1 \0 \2 \3')
assert(string.format("%-5c%-5c%-5c%-5c", 1, 0, 2, 3) == '\1 \0 \2 \3 ')
assert(string.format("%s\0 is not \0%s", 'not be', 'be') == 'not be\0 is not \0be')
assert(string.format("%%%d %010d", 10, 23) == "%10 0000000023")
assert(tonumber(string.format("%f", 10.3)) == 10.3)
@ -184,7 +187,7 @@ assert(pcall(function()
string.format("%#*", "bad form")
end) == false)
assert(loadstring("return 1\n--comentário sem EOL no final")() == 1)
assert(loadstring("return 1\n--comentário sem EOL no final")() == 1)
assert(table.concat{} == "")
@ -244,16 +247,16 @@ end
if not trylocale("collate") then
print("locale not supported")
else
assert("alo" < "álo" and "álo" < "amo")
assert("alo" < "álo" and "álo" < "amo")
end
if not trylocale("ctype") then
print("locale not supported")
else
assert(string.gsub("áéíóú", "%a", "x") == "xxxxx")
assert(string.gsub("áÁéÉ", "%l", "x") == "xÁxÉ")
assert(string.gsub("áÁéÉ", "%u", "x") == "áxéx")
assert(string.upper"áÁé{xuxu}ção" == "ÁÁÉ{XUXU}ÇÃO")
assert(string.gsub("áéíóú", "%a", "x") == "xxxxx")
assert(string.gsub("áÁéÉ", "%l", "x") == "xÁxÉ")
assert(string.gsub("áÁéÉ", "%u", "x") == "áxéx")
assert(string.upper"áÁé{xuxu}ção" == "ÁÁÉ{XUXU}ÇÃO")
end
os.setlocale("C")

View file

@ -1,2 +1,2 @@
Jinja2==3.1.4
Jinja2==3.1.5
MarkupSafe==2.1.3