mirror of
https://github.com/luau-lang/luau.git
synced 2025-04-01 17:30:53 +01:00
Merge remote-tracking branch 'upstream/master' into patch-2
This commit is contained in:
commit
74a3d586c6
48 changed files with 1132 additions and 1010 deletions
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
@ -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:
|
||||
|
|
4
.github/workflows/new-release.yml
vendored
4
.github/workflows/new-release.yml
vendored
|
@ -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:
|
||||
|
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 {};
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(">");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
28
README.md
28
README.md
|
@ -3,19 +3,19 @@ Luau  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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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? }
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
Jinja2==3.1.4
|
||||
Jinja2==3.1.5
|
||||
MarkupSafe==2.1.3
|
||||
|
|
Loading…
Add table
Reference in a new issue