Sync to origin/release/656

This commit is contained in:
Hunter Goldstein 2025-01-10 09:13:13 -08:00
parent 906a00d498
commit 765591c22f
90 changed files with 1482 additions and 754 deletions

View file

@ -15,6 +15,12 @@ namespace Luau
{ {
struct FrontendOptions; struct FrontendOptions;
enum class FragmentTypeCheckStatus
{
Success,
SkipAutocomplete,
};
struct FragmentAutocompleteAncestryResult struct FragmentAutocompleteAncestryResult
{ {
DenseHashMap<AstName, AstLocal*> localMap{AstName()}; DenseHashMap<AstName, AstLocal*> localMap{AstName()};
@ -29,6 +35,7 @@ struct FragmentParseResult
AstStatBlock* root = nullptr; AstStatBlock* root = nullptr;
std::vector<AstNode*> ancestry; std::vector<AstNode*> ancestry;
AstStat* nearestStatement = nullptr; AstStat* nearestStatement = nullptr;
std::vector<Comment> commentLocations;
std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>(); std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>();
}; };
@ -56,7 +63,7 @@ FragmentParseResult parseFragment(
std::optional<Position> fragmentEndPosition std::optional<Position> fragmentEndPosition
); );
FragmentTypeCheckResult typecheckFragment( std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
Frontend& frontend, Frontend& frontend,
const ModuleName& moduleName, const ModuleName& moduleName,
const Position& cursorPos, const Position& cursorPos,

View file

@ -16,6 +16,8 @@
#include <unordered_map> #include <unordered_map>
#include <optional> #include <optional>
LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection)
namespace Luau namespace Luau
{ {
@ -55,6 +57,7 @@ struct SourceModule
} }
}; };
bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos);
bool isWithinComment(const SourceModule& sourceModule, Position pos); bool isWithinComment(const SourceModule& sourceModule, Position pos);
bool isWithinComment(const ParseResult& result, Position pos); bool isWithinComment(const ParseResult& result, Position pos);

View file

@ -95,6 +95,8 @@ struct Scope
// we need that the generic type `T` in both cases is the same, so we use a cache. // we need that the generic type `T` in both cases is the same, so we use a cache.
std::unordered_map<Name, TypeId> typeAliasTypeParameters; std::unordered_map<Name, TypeId> typeAliasTypeParameters;
std::unordered_map<Name, TypePackId> typeAliasTypePackParameters; std::unordered_map<Name, TypePackId> typeAliasTypePackParameters;
std::optional<std::vector<TypeId>> interiorFreeTypes;
}; };
// Returns true iff the left scope encloses the right scope. A Scope* equal to // Returns true iff the left scope encloses the right scope. A Scope* equal to

View file

@ -626,7 +626,7 @@ struct TypeFunctionInstanceType
std::vector<TypeId> typeArguments; std::vector<TypeId> typeArguments;
std::vector<TypePackId> packArguments; std::vector<TypePackId> packArguments;
std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs
UserDefinedFunctionData userFuncData; UserDefinedFunctionData userFuncData;
TypeFunctionInstanceType( TypeFunctionInstanceType(

View file

@ -71,7 +71,7 @@ struct TypeFunctionContext
// The constraint being reduced in this run of the reduction // The constraint being reduced in this run of the reduction
const Constraint* constraint; const Constraint* constraint;
std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs
TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint); TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint);

View file

@ -269,8 +269,8 @@ bool isLiteral(const AstExpr* expr);
std::vector<TypeId> findBlockedTypesIn(AstExprTable* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes); std::vector<TypeId> findBlockedTypesIn(AstExprTable* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes);
/** /**
* Given a function call and a mapping from expression to type, determine * Given a function call and a mapping from expression to type, determine
* whether the type of any argument in said call in depends on a blocked types. * whether the type of any argument in said call in depends on a blocked types.
* This is used as a precondition for bidirectional inference: be warned that * This is used as a precondition for bidirectional inference: be warned that
* the behavior of this algorithm is tightly coupled to that of bidirectional * the behavior of this algorithm is tightly coupled to that of bidirectional
* inference. * inference.
@ -280,4 +280,13 @@ std::vector<TypeId> findBlockedTypesIn(AstExprTable* expr, NotNull<DenseHashMap<
*/ */
std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes); std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes);
/**
* Given a scope and a free type, find the closest parent that has a present
* `interiorFreeTypes` and append the given type to said list. This list will
* be generalized when the requiste `GeneralizationConstraint` is resolved.
* @param scope Initial scope this free type was attached to
* @param ty Free type to track.
*/
void trackInteriorFreeType(Scope* scope, TypeId ty);
} // namespace Luau } // namespace Luau

View file

@ -177,7 +177,6 @@ void AnyTypeSummary::visit(const Scope* scope, AstStatReturn* ret, const Module*
} }
} }
} }
} }
void AnyTypeSummary::visit(const Scope* scope, AstStatLocal* local, const Module* module, NotNull<BuiltinTypes> builtinTypes) void AnyTypeSummary::visit(const Scope* scope, AstStatLocal* local, const Module* module, NotNull<BuiltinTypes> builtinTypes)

View file

@ -25,6 +25,7 @@ LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUseLimits)
static const std::unordered_set<std::string> kStatementStartingKeywords = static const std::unordered_set<std::string> kStatementStartingKeywords =
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; {"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -177,6 +178,12 @@ static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, T
unifier.normalize = false; unifier.normalize = false;
unifier.checkInhabited = false; unifier.checkInhabited = false;
if (FFlag::LuauAutocompleteUseLimits)
{
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
}
return unifier.canUnify(subTy, superTy).empty(); return unifier.canUnify(subTy, superTy).empty();
} }
} }

View file

@ -611,7 +611,9 @@ static void dcrMagicFunctionTypeCheckFormat(MagicFunctionTypeCheckContext contex
if (!fmt) if (!fmt)
{ {
if (FFlag::LuauStringFormatArityFix) if (FFlag::LuauStringFormatArityFix)
context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location); context.typechecker->reportError(
CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location
);
return; return;
} }

View file

@ -62,7 +62,6 @@ struct ReferenceCountInitializer : TypeOnceVisitor
// of this type, hence: // of this type, hence:
return !FFlag::LuauDontRefCountTypesInTypeFunctions; return !FFlag::LuauDontRefCountTypesInTypeFunctions;
} }
}; };
bool isReferenceCountedType(const TypeId typ) bool isReferenceCountedType(const TypeId typ)

View file

@ -31,14 +31,15 @@
LUAU_FASTINT(LuauCheckRecursionLimit) LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTypestateBuiltins2) LUAU_FASTFLAG(LuauTypestateBuiltins2)
LUAU_FASTFLAG(LuauUserTypeFunUpdateAllEnvs) LUAU_FASTFLAG(LuauUserTypeFunUpdateAllEnvs)
LUAU_FASTFLAGVARIABLE(LuauNewSolverVisitErrorExprLvalues) LUAU_FASTFLAGVARIABLE(LuauNewSolverVisitErrorExprLvalues)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunExportedAndLocal) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunExportedAndLocal)
LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses)
LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations) LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunNoExtraConstraint) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunNoExtraConstraint)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(InferGlobalTypes) LUAU_FASTFLAGVARIABLE(InferGlobalTypes)
@ -232,8 +233,17 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
Checkpoint end = checkpoint(this); Checkpoint end = checkpoint(this);
TypeId result = arena->addType(BlockedType{}); TypeId result = arena->addType(BlockedType{});
NotNull<Constraint> genConstraint = NotNull<Constraint> genConstraint = addConstraint(
addConstraint(scope, block->location, GeneralizationConstraint{result, moduleFnTy, std::move(interiorTypes.back())}); scope,
block->location,
GeneralizationConstraint{
result, moduleFnTy, FFlag::LuauTrackInteriorFreeTypesOnScope ? std::vector<TypeId>{} : std::move(interiorTypes.back())
}
);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
scope->interiorFreeTypes = std::move(interiorTypes.back());
getMutable<BlockedType>(result)->setOwner(genConstraint); getMutable<BlockedType>(result)->setOwner(genConstraint);
forEachConstraint( forEachConstraint(
start, start,
@ -302,9 +312,19 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat
} }
} }
TypeId ConstraintGenerator::freshType(const ScopePtr& scope) TypeId ConstraintGenerator::freshType(const ScopePtr& scope)
{ {
return Luau::freshType(arena, builtinTypes, scope.get()); if (FFlag::LuauTrackInteriorFreeTypesOnScope)
{
auto ft = Luau::freshType(arena, builtinTypes, scope.get());
interiorTypes.back().push_back(ft);
return ft;
}
else
{
return Luau::freshType(arena, builtinTypes, scope.get());
}
} }
TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope) TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope)
@ -667,6 +687,7 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* block) void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* block)
{ {
std::unordered_map<Name, Location> aliasDefinitionLocations; std::unordered_map<Name, Location> aliasDefinitionLocations;
std::unordered_map<Name, Location> classDefinitionLocations;
// In order to enable mutually-recursive type aliases, we need to // In order to enable mutually-recursive type aliases, we need to
// populate the type bindings before we actually check any of the // populate the type bindings before we actually check any of the
@ -773,6 +794,32 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
aliasDefinitionLocations[function->name.value] = function->location; aliasDefinitionLocations[function->name.value] = function->location;
} }
else if (auto classDeclaration = stat->as<AstStatDeclareClass>())
{
if (!FFlag::LuauNewSolverPrePopulateClasses)
continue;
if (scope->exportedTypeBindings.count(classDeclaration->name.value))
{
auto it = classDefinitionLocations.find(classDeclaration->name.value);
LUAU_ASSERT(it != classDefinitionLocations.end());
reportError(classDeclaration->location, DuplicateTypeDefinition{classDeclaration->name.value, it->second});
continue;
}
// A class might have no name if the code is syntactically
// illegal. We mustn't prepopulate anything in this case.
if (classDeclaration->name == kParseNameError)
continue;
ScopePtr defnScope = childScope(classDeclaration, scope);
TypeId initialType = arena->addType(BlockedType{});
TypeFun initialFun{initialType};
scope->exportedTypeBindings[classDeclaration->name.value] = std::move(initialFun);
classDefinitionLocations[classDeclaration->name.value] = classDeclaration->location;
}
} }
if (FFlag::LuauUserTypeFunExportedAndLocal) if (FFlag::LuauUserTypeFunExportedAndLocal)
@ -1623,6 +1670,11 @@ static bool isMetamethod(const Name& name)
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClass* declaredClass) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClass* declaredClass)
{ {
// If a class with the same name was already defined, we skip over
auto bindingIt = scope->exportedTypeBindings.find(declaredClass->name.value);
if (FFlag::LuauNewSolverPrePopulateClasses && bindingIt == scope->exportedTypeBindings.end())
return ControlFlow::None;
std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType); std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType);
if (declaredClass->superName) if (declaredClass->superName)
{ {
@ -1637,7 +1689,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
// We don't have generic classes, so this assertion _should_ never be hit. // We don't have generic classes, so this assertion _should_ never be hit.
LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0); LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
superTy = lookupType->type; if (FFlag::LuauNewSolverPrePopulateClasses)
superTy = follow(lookupType->type);
else
superTy = lookupType->type;
if (!get<ClassType>(follow(*superTy))) if (!get<ClassType>(follow(*superTy)))
{ {
@ -1660,7 +1715,14 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
ctv->metatable = metaTy; ctv->metatable = metaTy;
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
if (FFlag::LuauNewSolverPrePopulateClasses)
{
TypeId classBindTy = bindingIt->second.type;
emplaceType<BoundType>(asMutable(classBindTy), classTy);
}
else
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
if (declaredClass->indexer) if (declaredClass->indexer)
{ {
@ -2365,8 +2427,17 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
Checkpoint endCheckpoint = checkpoint(this); Checkpoint endCheckpoint = checkpoint(this);
TypeId generalizedTy = arena->addType(BlockedType{}); TypeId generalizedTy = arena->addType(BlockedType{});
NotNull<Constraint> gc = NotNull<Constraint> gc = addConstraint(
addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature, std::move(interiorTypes.back())}); sig.signatureScope,
func->location,
GeneralizationConstraint{
generalizedTy, sig.signature, FFlag::LuauTrackInteriorFreeTypesOnScope ? std::vector<TypeId>{} : std::move(interiorTypes.back())
}
);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
sig.signatureScope->interiorFreeTypes = std::move(interiorTypes.back());
getMutable<BlockedType>(generalizedTy)->setOwner(gc); getMutable<BlockedType>(generalizedTy)->setOwner(gc);
interiorTypes.pop_back(); interiorTypes.pop_back();
@ -2932,11 +3003,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
ty, ty,
expr, expr,
toBlock toBlock
); );
// The visitor we ran prior should ensure that there are no // The visitor we ran prior should ensure that there are no
// blocked types that we would encounter while matching on // blocked types that we would encounter while matching on
// this expression. // this expression.
LUAU_ASSERT(toBlock.empty()); LUAU_ASSERT(toBlock.empty());
} }
} }
@ -3898,20 +3969,7 @@ TypeId ConstraintGenerator::createTypeFunctionInstance(
TypeId ConstraintGenerator::simplifyUnion(const ScopePtr& scope, Location location, TypeId left, TypeId right) TypeId ConstraintGenerator::simplifyUnion(const ScopePtr& scope, Location location, TypeId left, TypeId right)
{ {
if (FFlag::DebugLuauEqSatSimplification) return ::Luau::simplifyUnion(builtinTypes, arena, left, right).result;
{
TypeId ty = arena->addType(UnionType{{left, right}});
std::optional<EqSatSimplificationResult> res = eqSatSimplify(simplifier, ty);
if (!res)
return ty;
for (TypeId tyFun : res->newTypeFunctions)
addConstraint(scope, location, ReduceConstraint{tyFun});
return res->result;
}
else
return ::Luau::simplifyUnion(builtinTypes, arena, left, right).result;
} }
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints) std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints)

View file

@ -36,6 +36,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations) LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations)
LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer) LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer)
LUAU_FASTFLAG(LuauUserTypeFunNoExtraConstraint) LUAU_FASTFLAG(LuauUserTypeFunNoExtraConstraint)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
namespace Luau namespace Luau
{ {
@ -724,8 +725,20 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
bind(constraint, c.generalizedType, builtinTypes->errorRecoveryType()); bind(constraint, c.generalizedType, builtinTypes->errorRecoveryType());
} }
for (TypeId ty : c.interiorTypes) if (FFlag::LuauTrackInteriorFreeTypesOnScope)
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false); {
// We check if this member is initialized and then access it, but
// clang-tidy doesn't understand this is safe.
if (constraint->scope->interiorFreeTypes)
for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access)
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false);
}
else
{
for (TypeId ty : c.interiorTypes)
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false);
}
return true; return true;
} }
@ -801,6 +814,11 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
{ {
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope); TypeId keyTy = freshType(arena, builtinTypes, constraint->scope);
TypeId valueTy = freshType(arena, builtinTypes, constraint->scope); TypeId valueTy = freshType(arena, builtinTypes, constraint->scope);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
{
trackInteriorFreeType(constraint->scope, keyTy);
trackInteriorFreeType(constraint->scope, valueTy);
}
TypeId tableTy = TypeId tableTy =
arena->addType(TableType{TableType::Props{}, TableIndexer{keyTy, valueTy}, TypeLevel{}, constraint->scope, TableState::Free}); arena->addType(TableType{TableType::Props{}, TableIndexer{keyTy, valueTy}, TypeLevel{}, constraint->scope, TableState::Free});
@ -1445,7 +1463,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
} }
} }
} }
else if (expr->is<AstExprConstantBool>() || expr->is<AstExprConstantString>() || expr->is<AstExprConstantNumber>() || expr->is<AstExprConstantNil>()) else if (expr->is<AstExprConstantBool>() || expr->is<AstExprConstantString>() || expr->is<AstExprConstantNumber>() ||
expr->is<AstExprConstantNil>())
{ {
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}}; Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}};
u2.unify(actualArgTy, expectedArgTy); u2.unify(actualArgTy, expectedArgTy);
@ -2061,6 +2080,8 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
// constitute any meaningful constraint, so we replace it // constitute any meaningful constraint, so we replace it
// with a free type. // with a free type.
TypeId f = freshType(arena, builtinTypes, constraint->scope); TypeId f = freshType(arena, builtinTypes, constraint->scope);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(constraint->scope, f);
shiftReferences(resultTy, f); shiftReferences(resultTy, f);
emplaceType<BoundType>(asMutable(resultTy), f); emplaceType<BoundType>(asMutable(resultTy), f);
} }
@ -2196,6 +2217,11 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
{ {
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope); TypeId keyTy = freshType(arena, builtinTypes, constraint->scope);
TypeId valueTy = freshType(arena, builtinTypes, constraint->scope); TypeId valueTy = freshType(arena, builtinTypes, constraint->scope);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
{
trackInteriorFreeType(constraint->scope, keyTy);
trackInteriorFreeType(constraint->scope, valueTy);
}
TypeId tableTy = arena->addType(TableType{TableState::Sealed, {}, constraint->scope}); TypeId tableTy = arena->addType(TableType{TableState::Sealed, {}, constraint->scope});
getMutable<TableType>(tableTy)->indexer = TableIndexer{keyTy, valueTy}; getMutable<TableType>(tableTy)->indexer = TableIndexer{keyTy, valueTy};
@ -2452,6 +2478,8 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
if (ttv->state == TableState::Free) if (ttv->state == TableState::Free)
{ {
TypeId result = freshType(arena, builtinTypes, ttv->scope); TypeId result = freshType(arena, builtinTypes, ttv->scope);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(ttv->scope, result);
switch (context) switch (context)
{ {
case ValueContext::RValue: case ValueContext::RValue:
@ -2561,6 +2589,9 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
LUAU_ASSERT(tt); LUAU_ASSERT(tt);
TypeId propType = freshType(arena, builtinTypes, scope); TypeId propType = freshType(arena, builtinTypes, scope);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(scope, propType);
switch (context) switch (context)
{ {
case ValueContext::RValue: case ValueContext::RValue:

View file

@ -1,236 +1,13 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauMathMap)
LUAU_FASTFLAGVARIABLE(LuauVectorDefinitions)
LUAU_FASTFLAGVARIABLE(LuauVectorDefinitionsExtra) LUAU_FASTFLAGVARIABLE(LuauVectorDefinitionsExtra)
LUAU_FASTFLAG(LuauBufferBitMethods)
namespace Luau namespace Luau
{ {
// TODO: there has to be a better way, like splitting up per library // TODO: there has to be a better way, like splitting up per library
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,
}
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
--- Buffer API
declare buffer: {
create: @checked (size: number) -> buffer,
fromstring: @checked (str: string) -> buffer,
tostring: @checked (b: buffer) -> string,
len: @checked (b: buffer) -> number,
copy: @checked (target: buffer, targetOffset: number, source: buffer, sourceOffset: number?, count: number?) -> (),
fill: @checked (b: buffer, offset: number, value: number, count: number?) -> (),
readi8: @checked (b: buffer, offset: number) -> number,
readu8: @checked (b: buffer, offset: number) -> number,
readi16: @checked (b: buffer, offset: number) -> number,
readu16: @checked (b: buffer, offset: number) -> number,
readi32: @checked (b: buffer, offset: number) -> number,
readu32: @checked (b: buffer, offset: number) -> number,
readf32: @checked (b: buffer, offset: number) -> number,
readf64: @checked (b: buffer, offset: number) -> number,
writei8: @checked (b: buffer, offset: number, value: number) -> (),
writeu8: @checked (b: buffer, offset: number, value: number) -> (),
writei16: @checked (b: buffer, offset: number, value: number) -> (),
writeu16: @checked (b: buffer, offset: number, value: number) -> (),
writei32: @checked (b: buffer, offset: number, value: number) -> (),
writeu32: @checked (b: buffer, offset: number, value: number) -> (),
writef32: @checked (b: buffer, offset: number, value: number) -> (),
writef64: @checked (b: buffer, offset: number, value: number) -> (),
readstring: @checked (b: buffer, offset: number, count: number) -> string,
writestring: @checked (b: buffer, offset: number, value: string, count: number?) -> (),
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionLuaSrcChecked = R"BUILTIN_SRC( static const std::string kBuiltinDefinitionLuaSrcChecked = R"BUILTIN_SRC(
declare bit32: { declare bit32: {
@ -422,7 +199,9 @@ declare utf8: {
-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype. -- 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 declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionBufferSrc_DEPRECATED = R"BUILTIN_SRC(
--- Buffer API --- Buffer API
declare buffer: { declare buffer: {
create: @checked (size: number) -> buffer, create: @checked (size: number) -> buffer,
@ -453,6 +232,39 @@ declare buffer: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionBufferSrc = R"BUILTIN_SRC(
--- Buffer API
declare buffer: {
create: @checked (size: number) -> buffer,
fromstring: @checked (str: string) -> buffer,
tostring: @checked (b: buffer) -> string,
len: @checked (b: buffer) -> number,
copy: @checked (target: buffer, targetOffset: number, source: buffer, sourceOffset: number?, count: number?) -> (),
fill: @checked (b: buffer, offset: number, value: number, count: number?) -> (),
readi8: @checked (b: buffer, offset: number) -> number,
readu8: @checked (b: buffer, offset: number) -> number,
readi16: @checked (b: buffer, offset: number) -> number,
readu16: @checked (b: buffer, offset: number) -> number,
readi32: @checked (b: buffer, offset: number) -> number,
readu32: @checked (b: buffer, offset: number) -> number,
readf32: @checked (b: buffer, offset: number) -> number,
readf64: @checked (b: buffer, offset: number) -> number,
writei8: @checked (b: buffer, offset: number, value: number) -> (),
writeu8: @checked (b: buffer, offset: number, value: number) -> (),
writei16: @checked (b: buffer, offset: number, value: number) -> (),
writeu16: @checked (b: buffer, offset: number, value: number) -> (),
writei32: @checked (b: buffer, offset: number, value: number) -> (),
writeu32: @checked (b: buffer, offset: number, value: number) -> (),
writef32: @checked (b: buffer, offset: number, value: number) -> (),
writef64: @checked (b: buffer, offset: number, value: number) -> (),
readstring: @checked (b: buffer, offset: number, count: number) -> string,
writestring: @checked (b: buffer, offset: number, value: string, count: number?) -> (),
readbits: @checked (b: buffer, bitOffset: number, bitCount: number) -> number,
writebits: @checked (b: buffer, bitOffset: number, bitCount: number, value: number) -> (),
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionVectorSrc_DEPRECATED = R"BUILTIN_SRC( static const std::string kBuiltinDefinitionVectorSrc_DEPRECATED = R"BUILTIN_SRC(
-- TODO: this will be replaced with a built-in primitive type -- TODO: this will be replaced with a built-in primitive type
@ -511,11 +323,13 @@ declare vector: {
std::string getBuiltinDefinitionSource() std::string getBuiltinDefinitionSource()
{ {
std::string result = FFlag::LuauMathMap ? kBuiltinDefinitionLuaSrcChecked : kBuiltinDefinitionLuaSrcChecked_DEPRECATED; std::string result = kBuiltinDefinitionLuaSrcChecked;
result += FFlag::LuauBufferBitMethods ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED;
if (FFlag::LuauVectorDefinitionsExtra) if (FFlag::LuauVectorDefinitionsExtra)
result += kBuiltinDefinitionVectorSrc; result += kBuiltinDefinitionVectorSrc;
else if (FFlag::LuauVectorDefinitions) else
result += kBuiltinDefinitionVectorSrc_DEPRECATED; result += kBuiltinDefinitionVectorSrc_DEPRECATED;
return result; return result;

View file

@ -193,9 +193,8 @@ static bool areTerminalAndDefinitelyDisjoint(const EType& lhs, const EType& rhs)
// - Whether one of the enodes is a large semantic set such as TAny, // - Whether one of the enodes is a large semantic set such as TAny,
// TUnknown, or TError. // TUnknown, or TError.
return !( return !(
lhs.index() == rhs.index() || lhs.index() == rhs.index() || lhs.get<TUnknown>() || rhs.get<TUnknown>() || lhs.get<TAny>() || rhs.get<TAny>() || lhs.get<TNoRefine>() ||
lhs.get<TUnknown>() || rhs.get<TUnknown>() || lhs.get<TAny>() || rhs.get<TAny>() || lhs.get<TNoRefine>() || rhs.get<TNoRefine>() || rhs.get<TNoRefine>() || lhs.get<TError>() || rhs.get<TError>() || lhs.get<TOpaque>() || rhs.get<TOpaque>()
lhs.get<TError>() || rhs.get<TError>() || lhs.get<TOpaque>() || rhs.get<TOpaque>()
); );
} }
@ -694,7 +693,8 @@ TypeId flattenTableNode(
StringId propName = t->propNames[i]; StringId propName = t->propNames[i];
const Id propType = t->propTypes()[i]; const Id propType = t->propTypes()[i];
resultTable.props[strings.asString(propName)] = Property{fromId(egraph, strings, builtinTypes, arena, bestNodes, seen, newTypeFunctions, propType)}; resultTable.props[strings.asString(propName)] =
Property{fromId(egraph, strings, builtinTypes, arena, bestNodes, seen, newTypeFunctions, propType)};
} }
} }
@ -937,12 +937,20 @@ std::string mkDesc(
const int RULE_PADDING = 35; const int RULE_PADDING = 35;
const std::string rulePadding(std::max<size_t>(0, RULE_PADDING - rule.size()), ' '); const std::string rulePadding(std::max<size_t>(0, RULE_PADDING - rule.size()), ' ');
const std::string fromIdStr = ""; // "(" + std::to_string(uint32_t(from)) + ") "; const std::string fromIdStr = ""; // "(" + std::to_string(uint32_t(from)) + ") ";
const std::string toIdStr = ""; // "(" + std::to_string(uint32_t(to)) + ") "; const std::string toIdStr = ""; // "(" + std::to_string(uint32_t(to)) + ") ";
return rule + ":" + rulePadding + fromIdStr + toString(fromTy, opts) + " <=> " + toIdStr + toString(toTy, opts); return rule + ":" + rulePadding + fromIdStr + toString(fromTy, opts) + " <=> " + toIdStr + toString(toTy, opts);
} }
std::string mkDesc(EGraph& egraph, const StringCache& strings, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Id from, Id to, const std::string& rule) std::string mkDesc(
EGraph& egraph,
const StringCache& strings,
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
Id from,
Id to,
const std::string& rule
)
{ {
if (!FFlag::DebugLuauLogSimplification) if (!FFlag::DebugLuauLogSimplification)
return ""; return "";
@ -1879,7 +1887,12 @@ void Simplifier::intersectWithNegatedClass(Id id)
isTag<SBoolean>(iNode) || isTag<SString>(iNode) || isTag<TFunction>(iNode) || isTag<TNever>(iNode)) isTag<SBoolean>(iNode) || isTag<SString>(iNode) || isTag<TFunction>(iNode) || isTag<TNever>(iNode))
{ {
// eg string & ~SomeClass // eg string & ~SomeClass
subst(id, iId, "intersectClassWithNegatedClass", {{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}}); subst(
id,
iId,
"intersectClassWithNegatedClass",
{{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}}
);
return; return;
} }
@ -1887,27 +1900,37 @@ void Simplifier::intersectWithNegatedClass(Id id)
{ {
switch (relateClasses(class_, negatedClass)) switch (relateClasses(class_, negatedClass))
{ {
case LeftSuper: case LeftSuper:
// eg Instance & ~Part // eg Instance & ~Part
// This cannot be meaningfully reduced. // This cannot be meaningfully reduced.
continue; continue;
case RightSuper: case RightSuper:
subst(id, egraph.add(TNever{}), "intersectClassWithNegatedClass", {{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}}); subst(
return; id,
case Unrelated: egraph.add(TNever{}),
// Part & ~Folder == Part "intersectClassWithNegatedClass",
{{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}}
);
return;
case Unrelated:
// Part & ~Folder == Part
{
std::vector<Id> newParts;
newParts.reserve(intersection->operands().size() - 1);
for (Id part : intersection->operands())
{ {
std::vector<Id> newParts; if (part != jId)
newParts.reserve(intersection->operands().size() - 1); newParts.push_back(part);
for (Id part : intersection->operands())
{
if (part != jId)
newParts.push_back(part);
}
Id substId = egraph.add(Intersection{newParts.begin(), newParts.end()});
subst(id, substId, "intersectClassWithNegatedClass", {{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}});
} }
Id substId = egraph.add(Intersection{newParts.begin(), newParts.end()});
subst(
id,
substId,
"intersectClassWithNegatedClass",
{{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}}
);
}
} }
} }
} }

View file

@ -245,7 +245,6 @@ FragmentParseResult parseFragment(
opts.captureComments = true; opts.captureComments = true;
opts.parseFragment = FragmentParseResumeSettings{std::move(result.localMap), std::move(result.localStack), startPos}; opts.parseFragment = FragmentParseResumeSettings{std::move(result.localMap), std::move(result.localStack), startPos};
ParseResult p = Luau::Parser::parse(srcStart, parseLength, *nameTbl, *fragmentResult.alloc.get(), opts); ParseResult p = Luau::Parser::parse(srcStart, parseLength, *nameTbl, *fragmentResult.alloc.get(), opts);
std::vector<AstNode*> fabricatedAncestry = std::move(result.ancestry); std::vector<AstNode*> fabricatedAncestry = std::move(result.ancestry);
// Get the ancestry for the fragment at the offset cursor position. // Get the ancestry for the fragment at the offset cursor position.
@ -258,6 +257,7 @@ FragmentParseResult parseFragment(
fragmentResult.root = std::move(p.root); fragmentResult.root = std::move(p.root);
fragmentResult.ancestry = std::move(fabricatedAncestry); fragmentResult.ancestry = std::move(fabricatedAncestry);
fragmentResult.nearestStatement = nearestStatement; fragmentResult.nearestStatement = nearestStatement;
fragmentResult.commentLocations = std::move(p.commentLocations);
return fragmentResult; return fragmentResult;
} }
@ -444,7 +444,7 @@ FragmentTypeCheckResult typecheckFragment_(
} }
FragmentTypeCheckResult typecheckFragment( std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
Frontend& frontend, Frontend& frontend,
const ModuleName& moduleName, const ModuleName& moduleName,
const Position& cursorPos, const Position& cursorPos,
@ -469,12 +469,15 @@ FragmentTypeCheckResult typecheckFragment(
} }
FragmentParseResult parseResult = parseFragment(*sourceModule, src, cursorPos, fragmentEndPosition); FragmentParseResult parseResult = parseFragment(*sourceModule, src, cursorPos, fragmentEndPosition);
if (isWithinComment(parseResult.commentLocations, fragmentEndPosition.value_or(cursorPos)))
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
FrontendOptions frontendOptions = opts.value_or(frontend.options); FrontendOptions frontendOptions = opts.value_or(frontend.options);
const ScopePtr& closestScope = findClosestScope(module, parseResult.nearestStatement); const ScopePtr& closestScope = findClosestScope(module, parseResult.nearestStatement);
FragmentTypeCheckResult result = FragmentTypeCheckResult result =
typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions); typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions);
result.ancestry = std::move(parseResult.ancestry); result.ancestry = std::move(parseResult.ancestry);
return result; return {FragmentTypeCheckStatus::Success, result};
} }
@ -498,7 +501,14 @@ FragmentAutocompleteResult fragmentAutocomplete(
return {}; return {};
} }
auto tcResult = typecheckFragment(frontend, moduleName, cursorPosition, opts, src, fragmentEndPosition); // If the cursor is within a comment in the stale source module we should avoid providing a recommendation
if (isWithinComment(*sourceModule, fragmentEndPosition.value_or(cursorPosition)))
return {};
auto [tcStatus, tcResult] = typecheckFragment(frontend, moduleName, cursorPosition, opts, src, fragmentEndPosition);
if (tcStatus == FragmentTypeCheckStatus::SkipAutocomplete)
return {};
auto globalScope = (opts && opts->forAutocomplete) ? frontend.globalsForAutocomplete.globalScope.get() : frontend.globals.globalScope.get(); auto globalScope = (opts && opts->forAutocomplete) ? frontend.globalsForAutocomplete.globalScope.get() : frontend.globals.globalScope.get();
TypeArena arenaForFragmentAutocomplete; TypeArena arenaForFragmentAutocomplete;

View file

@ -977,7 +977,8 @@ struct TypeCacher : TypeOnceVisitor
return false; return false;
} }
bool visit(TypePackId tp, const BoundTypePack& btp) override { bool visit(TypePackId tp, const BoundTypePack& btp) override
{
traverse(btp.boundTo); traverse(btp.boundTo);
if (isUncacheable(btp.boundTo)) if (isUncacheable(btp.boundTo))
markUncacheable(tp); markUncacheable(tp);

View file

@ -15,11 +15,12 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteCommentDetection)
namespace Luau namespace Luau
{ {
static bool contains(Position pos, Comment comment) static bool contains_DEPRECATED(Position pos, Comment comment)
{ {
if (comment.location.contains(pos)) if (comment.location.contains(pos))
return true; return true;
@ -32,7 +33,22 @@ static bool contains(Position pos, Comment comment)
return false; return false;
} }
static bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos) static bool contains(Position pos, Comment comment)
{
if (comment.location.contains(pos))
return true;
else if (comment.type == Lexeme::BrokenComment && comment.location.begin <= pos) // Broken comments are broken specifically because they don't
// have an end
return true;
// comments actually span the whole line - in incremental mode, we could pass a cursor outside of the current parsed comment range span, but it
// would still be 'within' the comment So, the cursor must be on the same line and the comment itself must come strictly after the `begin`
else if (comment.type == Lexeme::Comment && comment.location.end.line == pos.line && comment.location.begin <= pos)
return true;
else
return false;
}
bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
{ {
auto iter = std::lower_bound( auto iter = std::lower_bound(
commentLocations.begin(), commentLocations.begin(),
@ -40,6 +56,11 @@ static bool isWithinComment(const std::vector<Comment>& commentLocations, Positi
Comment{Lexeme::Comment, Location{pos, pos}}, Comment{Lexeme::Comment, Location{pos, pos}},
[](const Comment& a, const Comment& b) [](const Comment& a, const Comment& b)
{ {
if (FFlag::LuauIncrementalAutocompleteCommentDetection)
{
if (a.type == Lexeme::Comment)
return a.location.end.line < b.location.end.line;
}
return a.location.end < b.location.end; return a.location.end < b.location.end;
} }
); );
@ -47,7 +68,7 @@ static bool isWithinComment(const std::vector<Comment>& commentLocations, Positi
if (iter == commentLocations.end()) if (iter == commentLocations.end())
return false; return false;
if (contains(pos, *iter)) if (FFlag::LuauIncrementalAutocompleteCommentDetection ? contains(pos, *iter) : contains_DEPRECATED(pos, *iter))
return true; return true;
// Due to the nature of std::lower_bound, it is possible that iter points at a comment that ends // Due to the nature of std::lower_bound, it is possible that iter points at a comment that ends
@ -149,9 +170,9 @@ struct ClonePublicInterface : Substitution
freety->scope->location, freety->scope->location,
module->name, module->name,
InternalError{"Free type is escaping its module; please report this bug at " InternalError{"Free type is escaping its module; please report this bug at "
"https://github.com/luau-lang/luau/issues"} "https://github.com/luau-lang/luau/issues"}
); );
result = builtinTypes->errorRecoveryType(); result = builtinTypes->errorRecoveryType();
} }
else if (auto genericty = getMutable<GenericType>(result)) else if (auto genericty = getMutable<GenericType>(result))
{ {
@ -173,8 +194,8 @@ struct ClonePublicInterface : Substitution
ftp->scope->location, ftp->scope->location,
module->name, module->name,
InternalError{"Free type pack is escaping its module; please report this bug at " InternalError{"Free type pack is escaping its module; please report this bug at "
"https://github.com/luau-lang/luau/issues"} "https://github.com/luau-lang/luau/issues"}
); );
clonedTp = builtinTypes->errorRecoveryTypePack(); clonedTp = builtinTypes->errorRecoveryTypePack();
} }
else if (auto gtp = getMutable<GenericTypePack>(clonedTp)) else if (auto gtp = getMutable<GenericTypePack>(clonedTp))

View file

@ -1809,7 +1809,8 @@ NormalizationResult Normalizer::unionNormalWithTy(
} }
else if (get<UnknownType>(here.tops)) else if (get<UnknownType>(here.tops))
return NormalizationResult::True; return NormalizationResult::True;
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) || get<TypeFunctionInstanceType>(there)) else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) ||
get<TypeFunctionInstanceType>(there))
{ {
if (tyvarIndex(there) <= ignoreSmallerTyvars) if (tyvarIndex(there) <= ignoreSmallerTyvars)
return NormalizationResult::True; return NormalizationResult::True;
@ -3162,7 +3163,8 @@ NormalizationResult Normalizer::intersectNormalWithTy(
} }
return NormalizationResult::True; return NormalizationResult::True;
} }
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) || get<TypeFunctionInstanceType>(there)) else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) ||
get<TypeFunctionInstanceType>(there))
{ {
NormalizedType thereNorm{builtinTypes}; NormalizedType thereNorm{builtinTypes};
NormalizedType topNorm{builtinTypes}; NormalizedType topNorm{builtinTypes};

View file

@ -420,7 +420,8 @@ static std::optional<TypeId> selectOverload(
TypePackId argsPack TypePackId argsPack
) )
{ {
auto resolver = std::make_unique<OverloadResolver>(builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, scope, iceReporter, limits, location); auto resolver =
std::make_unique<OverloadResolver>(builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, scope, iceReporter, limits, location);
auto [status, overload] = resolver->selectOverload(fn, argsPack); auto [status, overload] = resolver->selectOverload(fn, argsPack);
if (status == OverloadResolver::Analysis::Ok) if (status == OverloadResolver::Analysis::Ok)

View file

@ -1480,9 +1480,8 @@ SubtypingResult Subtyping::isCovariantWith(
if (auto variadic = get<VariadicTypePack>(tail); variadic && variadic->hidden) if (auto variadic = get<VariadicTypePack>(tail); variadic && variadic->hidden)
{ {
result.orElse( result.orElse(isContravariantWith(env, subFunction->argTypes, arena->addTypePack(TypePack{arguments}), scope)
isContravariantWith(env, subFunction->argTypes, arena->addTypePack(TypePack{arguments}), scope).withBothComponent(TypePath::PackField::Arguments) .withBothComponent(TypePath::PackField::Arguments));
);
} }
} }
} }

View file

@ -1020,7 +1020,8 @@ void TypeChecker2::visit(AstStatForIn* forInStatement)
{ {
reportError(OptionalValueAccess{iteratorTy}, forInStatement->values.data[0]->location); reportError(OptionalValueAccess{iteratorTy}, forInStatement->values.data[0]->location);
} }
else if (std::optional<TypeId> iterMmTy = findMetatableEntry(builtinTypes, module->errors, iteratorTy, "__iter", forInStatement->values.data[0]->location)) else if (std::optional<TypeId> iterMmTy =
findMetatableEntry(builtinTypes, module->errors, iteratorTy, "__iter", forInStatement->values.data[0]->location))
{ {
Instantiation instantiation{TxnLog::empty(), &arena, builtinTypes, TypeLevel{}, scope}; Instantiation instantiation{TxnLog::empty(), &arena, builtinTypes, TypeLevel{}, scope};

View file

@ -832,7 +832,12 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
{ {
if (FFlag::LuauUserTypeFunPrintToError) if (FFlag::LuauUserTypeFunPrintToError)
return { return {
std::nullopt, Reduction::Erroneous, {}, {}, format("'%s' type function: returned a non-type value", name.value), ctx->typeFunctionRuntime->messages std::nullopt,
Reduction::Erroneous,
{},
{},
format("'%s' type function: returned a non-type value", name.value),
ctx->typeFunctionRuntime->messages
}; };
else else
return {std::nullopt, Reduction::Erroneous, {}, {}, format("'%s' type function: returned a non-type value", name.value)}; return {std::nullopt, Reduction::Erroneous, {}, {}, format("'%s' type function: returned a non-type value", name.value)};
@ -2064,7 +2069,7 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
if (ctx->solver) if (ctx->solver)
{ {
for (TypeId newTf : simplifyResult->newTypeFunctions) for (TypeId newTf : simplifyResult->newTypeFunctions)
ctx->solver->pushConstraint(ctx->scope, ctx->constraint->location, ReduceConstraint{newTf}); ctx->pushConstraint(ReduceConstraint{newTf});
} }
return {simplifyResult->result, {}}; return {simplifyResult->result, {}};

View file

@ -464,7 +464,9 @@ public:
, typeFunctionRuntime(state->ctx->typeFunctionRuntime) , typeFunctionRuntime(state->ctx->typeFunctionRuntime)
, queue({}) , queue({})
, types({}) , types({})
, packs({}){}; , packs({})
{
}
TypeId deserialize(TypeFunctionTypeId ty) TypeId deserialize(TypeFunctionTypeId ty)
{ {

View file

@ -2799,10 +2799,10 @@ TypeId TypeChecker::checkRelationalOperation(
reportError( reportError(
expr.location, expr.location,
GenericError{ GenericError{
format("Type '%s' cannot be compared with relational operator %s", toString(leftType).c_str(), toString(expr.op).c_str()) format("Type '%s' cannot be compared with relational operator %s", toString(leftType).c_str(), toString(expr.op).c_str())
} }
); );
} }
return booleanType; return booleanType;
} }

View file

@ -11,6 +11,7 @@
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete); LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete);
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope);
namespace Luau namespace Luau
{ {
@ -318,6 +319,8 @@ TypePack extendTypePack(
{ {
FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType}; FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType};
t = arena.addType(ft); t = arena.addType(ft);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(ftp->scope, t);
} }
else else
t = arena.freshType(ftp->scope); t = arena.freshType(ftp->scope);
@ -533,7 +536,7 @@ std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMa
{ {
std::vector<TypeId> toBlock; std::vector<TypeId> toBlock;
BlockedTypeInLiteralVisitor v{astTypes, NotNull{&toBlock}}; BlockedTypeInLiteralVisitor v{astTypes, NotNull{&toBlock}};
for (auto arg: expr->args) for (auto arg : expr->args)
{ {
if (isLiteral(arg) || arg->is<AstExprGroup>()) if (isLiteral(arg) || arg->is<AstExprGroup>())
{ {
@ -543,5 +546,21 @@ std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMa
return toBlock; return toBlock;
} }
void trackInteriorFreeType(Scope* scope, TypeId ty)
{
LUAU_ASSERT(FFlag::LuauSolverV2 && FFlag::LuauTrackInteriorFreeTypesOnScope);
for (; scope; scope = scope->parent.get())
{
if (scope->interiorFreeTypes)
{
scope->interiorFreeTypes->push_back(ty);
return;
}
}
// There should at least be *one* generalization constraint per module
// where `interiorFreeTypes` is present, which would be the one made
// by ConstraintGenerator::visitModuleRoot.
LUAU_ASSERT(!"No scopes in parent chain had a present `interiorFreeTypes` member.");
}
} // namespace Luau } // namespace Luau

View file

@ -45,4 +45,4 @@ private:
size_t offset; size_t offset;
}; };
} } // namespace Luau

View file

@ -14,12 +14,37 @@ struct Position
{ {
} }
bool operator==(const Position& rhs) const; bool operator==(const Position& rhs) const
bool operator!=(const Position& rhs) const; {
bool operator<(const Position& rhs) const; return this->column == rhs.column && this->line == rhs.line;
bool operator>(const Position& rhs) const; }
bool operator<=(const Position& rhs) const;
bool operator>=(const Position& rhs) const; bool operator!=(const Position& rhs) const
{
return !(*this == rhs);
}
bool operator<(const Position& rhs) const
{
if (line == rhs.line)
return column < rhs.column;
else
return line < rhs.line;
}
bool operator>(const Position& rhs) const
{
if (line == rhs.line)
return column > rhs.column;
else
return line > rhs.line;
}
bool operator<=(const Position& rhs) const
{
return *this == rhs || *this < rhs;
}
bool operator>=(const Position& rhs) const
{
return *this == rhs || *this > rhs;
}
void shift(const Position& start, const Position& oldEnd, const Position& newEnd); void shift(const Position& start, const Position& oldEnd, const Position& newEnd);
}; };
@ -52,8 +77,14 @@ struct Location
{ {
} }
bool operator==(const Location& rhs) const; bool operator==(const Location& rhs) const
bool operator!=(const Location& rhs) const; {
return this->begin == rhs.begin && this->end == rhs.end;
}
bool operator!=(const Location& rhs) const
{
return !(*this == rhs);
}
bool encloses(const Location& l) const; bool encloses(const Location& l) const;
bool overlaps(const Location& l) const; bool overlaps(const Location& l) const;

View file

@ -63,4 +63,4 @@ void* Allocator::allocate(size_t size)
return page->data; return page->data;
} }
} } // namespace Luau

View file

@ -1151,10 +1151,7 @@ void AstTypePackGeneric::visit(AstVisitor* visitor)
bool isLValue(const AstExpr* expr) bool isLValue(const AstExpr* expr)
{ {
return expr->is<AstExprLocal>() return expr->is<AstExprLocal>() || expr->is<AstExprGlobal>() || expr->is<AstExprIndexName>() || expr->is<AstExprIndexExpr>();
|| expr->is<AstExprGlobal>()
|| expr->is<AstExprIndexName>()
|| expr->is<AstExprIndexExpr>();
} }
AstName getIdentifier(AstExpr* node) AstName getIdentifier(AstExpr* node)

View file

@ -9,6 +9,8 @@
#include <limits.h> #include <limits.h>
LUAU_FASTFLAGVARIABLE(LexerResumesFromPosition2) LUAU_FASTFLAGVARIABLE(LexerResumesFromPosition2)
LUAU_FASTFLAGVARIABLE(LexerFixInterpStringStart)
namespace Luau namespace Luau
{ {
@ -759,7 +761,7 @@ Lexeme Lexer::readNext()
return Lexeme(Location(start, 1), '}'); return Lexeme(Location(start, 1), '}');
} }
return readInterpolatedStringSection(position(), Lexeme::InterpStringMid, Lexeme::InterpStringEnd); return readInterpolatedStringSection(FFlag::LexerFixInterpStringStart ? start : position(), Lexeme::InterpStringMid, Lexeme::InterpStringEnd);
} }
case '=': case '=':

View file

@ -4,42 +4,6 @@
namespace Luau namespace Luau
{ {
bool Position::operator==(const Position& rhs) const
{
return this->column == rhs.column && this->line == rhs.line;
}
bool Position::operator!=(const Position& rhs) const
{
return !(*this == rhs);
}
bool Position::operator<(const Position& rhs) const
{
if (line == rhs.line)
return column < rhs.column;
else
return line < rhs.line;
}
bool Position::operator>(const Position& rhs) const
{
if (line == rhs.line)
return column > rhs.column;
else
return line > rhs.line;
}
bool Position::operator<=(const Position& rhs) const
{
return *this == rhs || *this < rhs;
}
bool Position::operator>=(const Position& rhs) const
{
return *this == rhs || *this > rhs;
}
void Position::shift(const Position& start, const Position& oldEnd, const Position& newEnd) void Position::shift(const Position& start, const Position& oldEnd, const Position& newEnd)
{ {
if (*this >= start) if (*this >= start)
@ -54,16 +18,6 @@ void Position::shift(const Position& start, const Position& oldEnd, const Positi
} }
} }
bool Location::operator==(const Location& rhs) const
{
return this->begin == rhs.begin && this->end == rhs.end;
}
bool Location::operator!=(const Location& rhs) const
{
return !(*this == rhs);
}
bool Location::encloses(const Location& l) const bool Location::encloses(const Location& l) const
{ {
return begin <= l.begin && end >= l.end; return begin <= l.begin && end >= l.end;

View file

@ -18,7 +18,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
// flag so that we don't break production games by reverting syntax changes. // flag so that we don't break production games by reverting syntax changes.
// See docs/SyntaxChanges.md for an explanation. // See docs/SyntaxChanges.md for an explanation.
LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunParseExport)
LUAU_FASTFLAGVARIABLE(LuauAllowFragmentParsing) LUAU_FASTFLAGVARIABLE(LuauAllowFragmentParsing)
LUAU_FASTFLAGVARIABLE(LuauAllowComplexTypesInGenericParams) LUAU_FASTFLAGVARIABLE(LuauAllowComplexTypesInGenericParams)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForTableTypes) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForTableTypes)
@ -936,12 +935,6 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported)
Lexeme matchFn = lexer.current(); Lexeme matchFn = lexer.current();
nextLexeme(); nextLexeme();
if (!FFlag::LuauUserDefinedTypeFunParseExport)
{
if (exported)
report(start, "Type function cannot be exported");
}
// parse the name of the type function // parse the name of the type function
std::optional<Name> fnName = parseNameOpt("type function name"); std::optional<Name> fnName = parseNameOpt("type function name");
if (!fnName) if (!fnName)
@ -2239,7 +2232,8 @@ std::optional<AstExprBinary::Op> Parser::checkBinaryConfusables(const BinaryOpPr
report(Location(start, next.location), "Unexpected '||'; did you mean 'or'?"); report(Location(start, next.location), "Unexpected '||'; did you mean 'or'?");
return AstExprBinary::Or; return AstExprBinary::Or;
} }
else if (curr.type == '!' && next.type == '=' && curr.location.end == next.location.begin && binaryPriority[AstExprBinary::CompareNe].left > limit) else if (curr.type == '!' && next.type == '=' && curr.location.end == next.location.begin &&
binaryPriority[AstExprBinary::CompareNe].left > limit)
{ {
nextLexeme(); nextLexeme();
report(Location(start, next.location), "Unexpected '!='; did you mean '~='?"); report(Location(start, next.location), "Unexpected '!='; did you mean '~='?");
@ -2587,7 +2581,8 @@ AstExpr* Parser::parseSimpleExpr()
{ {
return parseNumber(); return parseNumber();
} }
else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::InterpStringSimple) else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString ||
lexer.current().type == Lexeme::InterpStringSimple)
{ {
return parseString(); return parseString();
} }

View file

@ -341,7 +341,8 @@ static bool compileFile(const char* name, CompileFormat format, Luau::CodeGen::A
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Remarks); bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Remarks);
bcb.setDumpSource(*source); bcb.setDumpSource(*source);
} }
else if (format == CompileFormat::Codegen || format == CompileFormat::CodegenAsm || format == CompileFormat::CodegenIr || format == CompileFormat::CodegenVerbose) else if (format == CompileFormat::Codegen || format == CompileFormat::CodegenAsm || format == CompileFormat::CodegenIr ||
format == CompileFormat::CodegenVerbose)
{ {
bcb.setDumpFlags( bcb.setDumpFlags(
Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals | Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals |

View file

@ -147,7 +147,7 @@ std::string resolvePath(std::string_view path, std::string_view baseFilePath)
if (baseFilePathComponents.empty()) if (baseFilePathComponents.empty())
{ {
if (isResolvedPathRelative) if (isResolvedPathRelative)
numPrependedParents++; // "../" will later be added to the beginning of the resolved path numPrependedParents++; // "../" will later be added to the beginning of the resolved path
} }
else if (baseFilePathComponents.back() != "..") else if (baseFilePathComponents.back() != "..")
{ {

View file

@ -7,6 +7,8 @@
#include <string> #include <string>
#include <vector> #include <vector>
struct Proto;
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -23,6 +25,7 @@ struct IrToStringContext
const std::vector<IrBlock>& blocks; const std::vector<IrBlock>& blocks;
const std::vector<IrConst>& constants; const std::vector<IrConst>& constants;
const CfgInfo& cfg; const CfgInfo& cfg;
Proto* proto = nullptr;
}; };
void toString(IrToStringContext& ctx, const IrInst& inst, uint32_t index); void toString(IrToStringContext& ctx, const IrInst& inst, uint32_t index);

View file

@ -235,7 +235,7 @@ static uint8_t getBytecodeConstantTag(Proto* proto, unsigned ki)
return LBC_TYPE_ANY; return LBC_TYPE_ANY;
} }
static void applyBuiltinCall(int bfid, BytecodeTypes& types) static void applyBuiltinCall(LuauBuiltinFunction bfid, BytecodeTypes& types)
{ {
switch (bfid) switch (bfid)
{ {
@ -549,6 +549,12 @@ static void applyBuiltinCall(int bfid, BytecodeTypes& types)
types.b = LBC_TYPE_VECTOR; types.b = LBC_TYPE_VECTOR;
types.c = LBC_TYPE_VECTOR; // We can mark optional arguments types.c = LBC_TYPE_VECTOR; // We can mark optional arguments
break; break;
case LBF_MATH_LERP:
types.result = LBC_TYPE_NUMBER;
types.a = LBC_TYPE_NUMBER;
types.b = LBC_TYPE_NUMBER;
types.c = LBC_TYPE_NUMBER;
break;
} }
} }
@ -842,7 +848,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[ra] = LBC_TYPE_NUMBER; regTags[ra] = LBC_TYPE_NUMBER;
else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == LBC_TYPE_VECTOR) else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR; regTags[ra] = LBC_TYPE_VECTOR;
else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) else if (hostHooks.userdataMetamethodBytecodeType &&
(isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b)))
regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op));
bcType.result = regTags[ra]; bcType.result = regTags[ra];
@ -873,7 +880,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR) if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR; regTags[ra] = LBC_TYPE_VECTOR;
} }
else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) else if (hostHooks.userdataMetamethodBytecodeType &&
(isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b)))
{ {
regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op));
} }
@ -895,7 +903,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER) if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER)
regTags[ra] = LBC_TYPE_NUMBER; regTags[ra] = LBC_TYPE_NUMBER;
else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) else if (hostHooks.userdataMetamethodBytecodeType &&
(isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b)))
regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op));
bcType.result = regTags[ra]; bcType.result = regTags[ra];
@ -917,7 +926,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[ra] = LBC_TYPE_NUMBER; regTags[ra] = LBC_TYPE_NUMBER;
else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == LBC_TYPE_VECTOR) else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR; regTags[ra] = LBC_TYPE_VECTOR;
else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) else if (hostHooks.userdataMetamethodBytecodeType &&
(isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b)))
regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op));
bcType.result = regTags[ra]; bcType.result = regTags[ra];
@ -948,7 +958,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR) if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR; regTags[ra] = LBC_TYPE_VECTOR;
} }
else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) else if (hostHooks.userdataMetamethodBytecodeType &&
(isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b)))
{ {
regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op));
} }
@ -970,7 +981,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER) if (bcType.a == LBC_TYPE_NUMBER && bcType.b == LBC_TYPE_NUMBER)
regTags[ra] = LBC_TYPE_NUMBER; regTags[ra] = LBC_TYPE_NUMBER;
else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) else if (hostHooks.userdataMetamethodBytecodeType &&
(isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b)))
regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op));
bcType.result = regTags[ra]; bcType.result = regTags[ra];
@ -991,7 +1003,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[ra] = LBC_TYPE_NUMBER; regTags[ra] = LBC_TYPE_NUMBER;
else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == LBC_TYPE_VECTOR) else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR; regTags[ra] = LBC_TYPE_VECTOR;
else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) else if (hostHooks.userdataMetamethodBytecodeType &&
(isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b)))
regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op));
bcType.result = regTags[ra]; bcType.result = regTags[ra];
@ -1020,7 +1033,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR) if (bcType.b == LBC_TYPE_NUMBER || bcType.b == LBC_TYPE_VECTOR)
regTags[ra] = LBC_TYPE_VECTOR; regTags[ra] = LBC_TYPE_VECTOR;
} }
else if (hostHooks.userdataMetamethodBytecodeType && (isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b))) else if (hostHooks.userdataMetamethodBytecodeType &&
(isCustomUserdataBytecodeType(bcType.a) || isCustomUserdataBytecodeType(bcType.b)))
{ {
regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op)); regTags[ra] = hostHooks.userdataMetamethodBytecodeType(bcType.a, bcType.b, opcodeToHostMetamethod(op));
} }
@ -1086,7 +1100,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
CODEGEN_ASSERT(LUAU_INSN_OP(call) == LOP_CALL); CODEGEN_ASSERT(LUAU_INSN_OP(call) == LOP_CALL);
int ra = LUAU_INSN_A(call); int ra = LUAU_INSN_A(call);
applyBuiltinCall(bfid, bcType); applyBuiltinCall(LuauBuiltinFunction(bfid), bcType);
regTags[ra + 1] = bcType.a; regTags[ra + 1] = bcType.a;
regTags[ra + 2] = bcType.b; regTags[ra + 2] = bcType.b;
regTags[ra + 3] = bcType.c; regTags[ra + 3] = bcType.c;
@ -1105,7 +1119,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
CODEGEN_ASSERT(LUAU_INSN_OP(call) == LOP_CALL); CODEGEN_ASSERT(LUAU_INSN_OP(call) == LOP_CALL);
int ra = LUAU_INSN_A(call); int ra = LUAU_INSN_A(call);
applyBuiltinCall(bfid, bcType); applyBuiltinCall(LuauBuiltinFunction(bfid), bcType);
regTags[LUAU_INSN_B(*pc)] = bcType.a; regTags[LUAU_INSN_B(*pc)] = bcType.a;
regTags[ra] = bcType.result; regTags[ra] = bcType.result;
@ -1122,7 +1136,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
CODEGEN_ASSERT(LUAU_INSN_OP(call) == LOP_CALL); CODEGEN_ASSERT(LUAU_INSN_OP(call) == LOP_CALL);
int ra = LUAU_INSN_A(call); int ra = LUAU_INSN_A(call);
applyBuiltinCall(bfid, bcType); applyBuiltinCall(LuauBuiltinFunction(bfid), bcType);
regTags[LUAU_INSN_B(*pc)] = bcType.a; regTags[LUAU_INSN_B(*pc)] = bcType.a;
regTags[int(pc[1])] = bcType.b; regTags[int(pc[1])] = bcType.b;
@ -1141,7 +1155,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
CODEGEN_ASSERT(LUAU_INSN_OP(call) == LOP_CALL); CODEGEN_ASSERT(LUAU_INSN_OP(call) == LOP_CALL);
int ra = LUAU_INSN_A(call); int ra = LUAU_INSN_A(call);
applyBuiltinCall(bfid, bcType); applyBuiltinCall(LuauBuiltinFunction(bfid), bcType);
regTags[LUAU_INSN_B(*pc)] = bcType.a; regTags[LUAU_INSN_B(*pc)] = bcType.a;
regTags[aux & 0xff] = bcType.b; regTags[aux & 0xff] = bcType.b;

View file

@ -166,7 +166,7 @@ bool isSupported()
if (sizeof(LuaNode) != 32) if (sizeof(LuaNode) != 32)
return false; return false;
// Windows CRT uses stack unwinding in longjmp so we have to use unwind data; on other platforms, it's only necessary for C++ EH. // Windows CRT uses stack unwinding in longjmp so we have to use unwind data; on other platforms, it's only necessary for C++ EH.
#if defined(_WIN32) #if defined(_WIN32)
if (!isUnwindSupported()) if (!isUnwindSupported())
return false; return false;

View file

@ -101,7 +101,7 @@ inline bool lowerImpl(
bool outputEnabled = options.includeAssembly || options.includeIr; bool outputEnabled = options.includeAssembly || options.includeIr;
IrToStringContext ctx{build.text, function.blocks, function.constants, function.cfg}; IrToStringContext ctx{build.text, function.blocks, function.constants, function.cfg, function.proto};
// We use this to skip outlined fallback blocks from IR/asm text output // We use this to skip outlined fallback blocks from IR/asm text output
size_t textSize = build.text.length(); size_t textSize = build.text.length();

View file

@ -18,6 +18,8 @@
#include <string.h> #include <string.h>
LUAU_DYNAMIC_FASTFLAG(LuauPopIncompleteCi)
// All external function calls that can cause stack realloc or Lua calls have to be wrapped in VM_PROTECT // All external function calls that can cause stack realloc or Lua calls have to be wrapped in VM_PROTECT
// This makes sure that we save the pc (in case the Lua call needs to generate a backtrace) before the call, // This makes sure that we save the pc (in case the Lua call needs to generate a backtrace) before the call,
// and restores the stack pointer after in case stack gets reallocated // and restores the stack pointer after in case stack gets reallocated
@ -191,7 +193,14 @@ Closure* callProlog(lua_State* L, TValue* ra, StkId argtop, int nresults)
// note: this reallocs stack, but we don't need to VM_PROTECT this // note: this reallocs stack, but we don't need to VM_PROTECT this
// this is because we're going to modify base/savedpc manually anyhow // this is because we're going to modify base/savedpc manually anyhow
// crucially, we can't use ra/argtop after this line // crucially, we can't use ra/argtop after this line
luaD_checkstack(L, ccl->stacksize); if (DFFlag::LuauPopIncompleteCi)
{
luaD_checkstackfornewci(L, ccl->stacksize);
}
else
{
luaD_checkstack(L, ccl->stacksize);
}
return ccl; return ccl;
} }
@ -261,7 +270,14 @@ Closure* callFallback(lua_State* L, StkId ra, StkId argtop, int nresults)
// note: this reallocs stack, but we don't need to VM_PROTECT this // note: this reallocs stack, but we don't need to VM_PROTECT this
// this is because we're going to modify base/savedpc manually anyhow // this is because we're going to modify base/savedpc manually anyhow
// crucially, we can't use ra/argtop after this line // crucially, we can't use ra/argtop after this line
luaD_checkstack(L, ccl->stacksize); if (DFFlag::LuauPopIncompleteCi)
{
luaD_checkstackfornewci(L, ccl->stacksize);
}
else
{
luaD_checkstack(L, ccl->stacksize);
}
LUAU_ASSERT(ci->top <= L->stack_last); LUAU_ASSERT(ci->top <= L->stack_last);

View file

@ -684,7 +684,7 @@ void computeCfgDominanceTreeChildren(IrFunction& function)
info.domChildrenOffsets[domParent]++; info.domChildrenOffsets[domParent]++;
} }
// Convert counds to offsets using prefix sum // Convert counts to offsets using prefix sum
uint32_t total = 0; uint32_t total = 0;
for (size_t blockIdx = 0; blockIdx < function.blocks.size(); blockIdx++) for (size_t blockIdx = 0; blockIdx < function.blocks.size(); blockIdx++)

View file

@ -4,6 +4,8 @@
#include "Luau/IrUtils.h" #include "Luau/IrUtils.h"
#include "lua.h" #include "lua.h"
#include "lobject.h"
#include "lstate.h"
#include <stdarg.h> #include <stdarg.h>
@ -19,6 +21,7 @@ static const char* textForCondition[] =
static_assert(sizeof(textForCondition) / sizeof(textForCondition[0]) == size_t(IrCondition::Count), "all conditions have to be covered"); static_assert(sizeof(textForCondition) / sizeof(textForCondition[0]) == size_t(IrCondition::Count), "all conditions have to be covered");
const int kDetailsAlignColumn = 60; const int kDetailsAlignColumn = 60;
const unsigned kMaxStringConstantPrintLength = 16;
LUAU_PRINTF_ATTR(2, 3) LUAU_PRINTF_ATTR(2, 3)
static void append(std::string& result, const char* fmt, ...) static void append(std::string& result, const char* fmt, ...)
@ -39,6 +42,17 @@ static void padToDetailColumn(std::string& result, size_t lineStart)
result.append(pad, ' '); result.append(pad, ' ');
} }
static bool isPrintableStringConstant(const char* str, size_t len)
{
for (size_t i = 0; i < len; ++i)
{
if (unsigned(str[i]) < ' ')
return false;
}
return true;
}
static const char* getTagName(uint8_t tag) static const char* getTagName(uint8_t tag)
{ {
switch (tag) switch (tag)
@ -431,6 +445,53 @@ void toString(IrToStringContext& ctx, const IrBlock& block, uint32_t index)
append(ctx.result, "%s_%u", getBlockKindName(block.kind), index); append(ctx.result, "%s_%u", getBlockKindName(block.kind), index);
} }
static void appendVmConstant(std::string& result, Proto* proto, int index)
{
TValue constant = proto->k[index];
if (constant.tt == LUA_TNIL)
{
append(result, "nil");
}
else if (constant.tt == LUA_TBOOLEAN)
{
append(result, constant.value.b != 0 ? "true" : "false");
}
else if (constant.tt == LUA_TNUMBER)
{
if (constant.value.n != constant.value.n)
append(result, "nan");
else
append(result, "%.17g", constant.value.n);
}
else if (constant.tt == LUA_TSTRING)
{
TString* str = gco2ts(constant.value.gc);
const char* data = getstr(str);
if (isPrintableStringConstant(data, str->len))
{
if (str->len < kMaxStringConstantPrintLength)
append(result, "'%.*s'", int(str->len), data);
else
append(result, "'%.*s'...", int(kMaxStringConstantPrintLength), data);
}
}
else if (constant.tt == LUA_TVECTOR)
{
const float* v = constant.value.v;
#if LUA_VECTOR_SIZE == 4
if (v[3] != 0)
append(result, "%.9g, %.9g, %.9g, %.9g", v[0], v[1], v[2], v[3]);
else
append(result, "%.9g, %.9g, %.9g", v[0], v[1], v[2]);
#else
append(result, "%.9g, %.9g, %.9g", v[0], v[1], v[2]);
#endif
}
}
void toString(IrToStringContext& ctx, IrOp op) void toString(IrToStringContext& ctx, IrOp op)
{ {
switch (op.kind) switch (op.kind)
@ -458,6 +519,14 @@ void toString(IrToStringContext& ctx, IrOp op)
break; break;
case IrOpKind::VmConst: case IrOpKind::VmConst:
append(ctx.result, "K%d", vmConstOp(op)); append(ctx.result, "K%d", vmConstOp(op));
if (ctx.proto)
{
append(ctx.result, " (");
appendVmConstant(ctx.result, ctx.proto, vmConstOp(op));
append(ctx.result, ")");
}
break; break;
case IrOpKind::VmUpvalue: case IrOpKind::VmUpvalue:
append(ctx.result, "U%d", vmUpvalueOp(op)); append(ctx.result, "U%d", vmUpvalueOp(op));
@ -770,7 +839,7 @@ void toStringDetailed(
std::string toString(const IrFunction& function, IncludeUseInfo includeUseInfo) std::string toString(const IrFunction& function, IncludeUseInfo includeUseInfo)
{ {
std::string result; std::string result;
IrToStringContext ctx{result, function.blocks, function.constants, function.cfg}; IrToStringContext ctx{result, function.blocks, function.constants, function.cfg, function.proto};
for (size_t i = 0; i < function.blocks.size(); i++) for (size_t i = 0; i < function.blocks.size(); i++)
{ {
@ -877,7 +946,7 @@ static void appendBlocks(IrToStringContext& ctx, const IrFunction& function, boo
std::string toDot(const IrFunction& function, bool includeInst) std::string toDot(const IrFunction& function, bool includeInst)
{ {
std::string result; std::string result;
IrToStringContext ctx{result, function.blocks, function.constants, function.cfg}; IrToStringContext ctx{result, function.blocks, function.constants, function.cfg, function.proto};
append(ctx.result, "digraph CFG {\n"); append(ctx.result, "digraph CFG {\n");
append(ctx.result, "node[shape=record]\n"); append(ctx.result, "node[shape=record]\n");
@ -924,7 +993,7 @@ std::string toDot(const IrFunction& function, bool includeInst)
std::string toDotCfg(const IrFunction& function) std::string toDotCfg(const IrFunction& function)
{ {
std::string result; std::string result;
IrToStringContext ctx{result, function.blocks, function.constants, function.cfg}; IrToStringContext ctx{result, function.blocks, function.constants, function.cfg, function.proto};
append(ctx.result, "digraph CFG {\n"); append(ctx.result, "digraph CFG {\n");
append(ctx.result, "node[shape=record]\n"); append(ctx.result, "node[shape=record]\n");
@ -947,7 +1016,7 @@ std::string toDotCfg(const IrFunction& function)
std::string toDotDjGraph(const IrFunction& function) std::string toDotDjGraph(const IrFunction& function)
{ {
std::string result; std::string result;
IrToStringContext ctx{result, function.blocks, function.constants, function.cfg}; IrToStringContext ctx{result, function.blocks, function.constants, function.cfg, function.proto};
append(ctx.result, "digraph CFG {\n"); append(ctx.result, "digraph CFG {\n");

View file

@ -18,9 +18,11 @@
LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3) LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3)
LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64) LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64)
LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64) LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64)
LUAU_FASTINTVARIABLE(LuauCodeGenLiveSlotReuseLimit, 8)
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks)
LUAU_FASTFLAG(LuauVectorLibNativeDot); LUAU_FASTFLAG(LuauVectorLibNativeDot)
LUAU_FASTFLAGVARIABLE(LuauCodeGenArithOpt); LUAU_FASTFLAGVARIABLE(LuauCodeGenArithOpt)
LUAU_FASTFLAGVARIABLE(LuauCodeGenLimitLiveSlotReuse)
namespace Luau namespace Luau
{ {
@ -50,6 +52,14 @@ struct RegisterLink
uint32_t version = 0; uint32_t version = 0;
}; };
// Reference to an instruction together with the position of that instruction in the current block chain and the last position of reuse
struct NumberedInstruction
{
uint32_t instIdx = 0;
uint32_t startPos = 0;
uint32_t finishPos = 0;
};
// Data we know about the current VM state // Data we know about the current VM state
struct ConstPropState struct ConstPropState
{ {
@ -190,7 +200,11 @@ struct ConstPropState
// Same goes for table array elements as well // Same goes for table array elements as well
void invalidateHeapTableData() void invalidateHeapTableData()
{ {
getSlotNodeCache.clear(); if (FFlag::LuauCodeGenLimitLiveSlotReuse)
getSlotNodeCache.clear();
else
getSlotNodeCache_DEPRECATED.clear();
checkSlotMatchCache.clear(); checkSlotMatchCache.clear();
getArrAddrCache.clear(); getArrAddrCache.clear();
@ -409,6 +423,64 @@ struct ConstPropState
valueMap[versionedVmRegLoad(loadCmd, storeInst.a)] = storeInst.b.index; valueMap[versionedVmRegLoad(loadCmd, storeInst.a)] = storeInst.b.index;
} }
// Used to compute the pressure of the cached value 'set' on the spill registers
// We want to find out the maximum live range intersection count between the cached value at 'slot' and current instruction
// Note that this pressure is approximate, as some values that might have been live at one point could have been marked dead later
int getMaxInternalOverlap(std::vector<NumberedInstruction>& set, size_t slot)
{
CODEGEN_ASSERT(FFlag::LuauCodeGenLimitLiveSlotReuse);
// Start with one live range for the slot we want to reuse
int curr = 1;
// For any slots where lifetime began before the slot of interest, mark as live if lifetime end is still active
// This saves us from processing slots [0; slot] in the range sweep later, which requires sorting the lifetime end points
for (size_t i = 0; i < slot; i++)
{
if (set[i].finishPos >= set[slot].startPos)
curr++;
}
int max = curr;
// Collect lifetime end points and sort them
rangeEndTemp.clear();
for (size_t i = slot + 1; i < set.size(); i++)
rangeEndTemp.push_back(set[i].finishPos);
std::sort(rangeEndTemp.begin(), rangeEndTemp.end());
// Go over the lifetime begin/end ranges that we store as separate array and walk based on the smallest of values
for (size_t i1 = slot + 1, i2 = 0; i1 < set.size() && i2 < rangeEndTemp.size();)
{
if (rangeEndTemp[i2] == set[i1].startPos)
{
i1++;
i2++;
}
else if (rangeEndTemp[i2] < set[i1].startPos)
{
CODEGEN_ASSERT(curr > 0);
curr--;
i2++;
}
else
{
curr++;
i1++;
if (curr > max)
max = curr;
}
}
// We might have unprocessed lifetime end entries, but we will never have unprocessed lifetime start entries
// Not that lifetime end entries can only decrease the current value and do not affect the end result (maximum)
return max;
}
void clear() void clear()
{ {
for (int i = 0; i <= maxReg; ++i) for (int i = 0; i <= maxReg; ++i)
@ -416,6 +488,9 @@ struct ConstPropState
maxReg = 0; maxReg = 0;
if (FFlag::LuauCodeGenLimitLiveSlotReuse)
instPos = 0u;
inSafeEnv = false; inSafeEnv = false;
checkedGc = false; checkedGc = false;
@ -436,6 +511,9 @@ struct ConstPropState
// For range/full invalidations, we only want to visit a limited number of data that we have recorded // For range/full invalidations, we only want to visit a limited number of data that we have recorded
int maxReg = 0; int maxReg = 0;
// Number of the instruction being processed
uint32_t instPos = 0;
bool inSafeEnv = false; bool inSafeEnv = false;
bool checkedGc = false; bool checkedGc = false;
@ -447,7 +525,8 @@ struct ConstPropState
std::vector<uint32_t> tryNumToIndexCache; // Fallback block argument might be different std::vector<uint32_t> tryNumToIndexCache; // Fallback block argument might be different
// Heap changes might affect table state // Heap changes might affect table state
std::vector<uint32_t> getSlotNodeCache; // Additionally, pcpos argument might be different std::vector<NumberedInstruction> getSlotNodeCache; // Additionally, pcpos argument might be different
std::vector<uint32_t> getSlotNodeCache_DEPRECATED; // Additionally, pcpos argument might be different
std::vector<uint32_t> checkSlotMatchCache; // Additionally, fallback block argument might be different std::vector<uint32_t> checkSlotMatchCache; // Additionally, fallback block argument might be different
std::vector<uint32_t> getArrAddrCache; std::vector<uint32_t> getArrAddrCache;
@ -457,6 +536,8 @@ struct ConstPropState
// Userdata tag cache can point to both NEW_USERDATA and CHECK_USERDATA_TAG instructions // Userdata tag cache can point to both NEW_USERDATA and CHECK_USERDATA_TAG instructions
std::vector<uint32_t> useradataTagCache; // Additionally, fallback block argument might be different std::vector<uint32_t> useradataTagCache; // Additionally, fallback block argument might be different
std::vector<uint32_t> rangeEndTemp;
}; };
static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid, uint32_t firstReturnReg, int nresults) static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid, uint32_t firstReturnReg, int nresults)
@ -550,6 +631,7 @@ static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid
case LBF_VECTOR_CLAMP: case LBF_VECTOR_CLAMP:
case LBF_VECTOR_MIN: case LBF_VECTOR_MIN:
case LBF_VECTOR_MAX: case LBF_VECTOR_MAX:
case LBF_MATH_LERP:
break; break;
case LBF_TABLE_INSERT: case LBF_TABLE_INSERT:
state.invalidateHeap(); state.invalidateHeap();
@ -569,6 +651,9 @@ static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid
static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& function, IrBlock& block, IrInst& inst, uint32_t index) static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& function, IrBlock& block, IrInst& inst, uint32_t index)
{ {
if (FFlag::LuauCodeGenLimitLiveSlotReuse)
state.instPos++;
switch (inst.cmd) switch (inst.cmd)
{ {
case IrCmd::LOAD_TAG: case IrCmd::LOAD_TAG:
@ -771,7 +856,8 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
if (tag == LUA_TBOOLEAN && if (tag == LUA_TBOOLEAN &&
(value.kind == IrOpKind::Inst || (value.kind == IrOpKind::Constant && function.constOp(value).kind == IrConstKind::Int))) (value.kind == IrOpKind::Inst || (value.kind == IrOpKind::Constant && function.constOp(value).kind == IrConstKind::Int)))
canSplitTvalueStore = true; canSplitTvalueStore = true;
else if (tag == LUA_TNUMBER && (value.kind == IrOpKind::Inst || (value.kind == IrOpKind::Constant && function.constOp(value).kind == IrConstKind::Double))) else if (tag == LUA_TNUMBER &&
(value.kind == IrOpKind::Inst || (value.kind == IrOpKind::Constant && function.constOp(value).kind == IrConstKind::Double)))
canSplitTvalueStore = true; canSplitTvalueStore = true;
else if (tag != 0xff && isGCO(tag) && value.kind == IrOpKind::Inst) else if (tag != 0xff && isGCO(tag) && value.kind == IrOpKind::Inst)
canSplitTvalueStore = true; canSplitTvalueStore = true;
@ -1174,19 +1260,49 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
state.getArrAddrCache.push_back(index); state.getArrAddrCache.push_back(index);
break; break;
case IrCmd::GET_SLOT_NODE_ADDR: case IrCmd::GET_SLOT_NODE_ADDR:
for (uint32_t prevIdx : state.getSlotNodeCache) if (FFlag::LuauCodeGenLimitLiveSlotReuse)
{ {
const IrInst& prev = function.instructions[prevIdx]; for (size_t i = 0; i < state.getSlotNodeCache.size(); i++)
if (prev.a == inst.a && prev.c == inst.c)
{ {
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx}); auto&& [prevIdx, num, lastNum] = state.getSlotNodeCache[i];
return; // Break out from both the loop and the switch
}
}
if (int(state.getSlotNodeCache.size()) < FInt::LuauCodeGenReuseSlotLimit) const IrInst& prev = function.instructions[prevIdx];
state.getSlotNodeCache.push_back(index);
if (prev.a == inst.a && prev.c == inst.c)
{
// Check if this reuse will increase the overall register pressure over the limit
int limit = FInt::LuauCodeGenLiveSlotReuseLimit;
if (int(state.getSlotNodeCache.size()) > limit && state.getMaxInternalOverlap(state.getSlotNodeCache, i) > limit)
return;
// Update live range of the value from the optimization standpoint
lastNum = state.instPos;
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
return; // Break out from both the loop and the switch
}
}
if (int(state.getSlotNodeCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
state.getSlotNodeCache.push_back({index, state.instPos, state.instPos});
}
else
{
for (uint32_t prevIdx : state.getSlotNodeCache_DEPRECATED)
{
const IrInst& prev = function.instructions[prevIdx];
if (prev.a == inst.a && prev.c == inst.c)
{
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
return; // Break out from both the loop and the switch
}
}
if (int(state.getSlotNodeCache_DEPRECATED.size()) < FInt::LuauCodeGenReuseSlotLimit)
state.getSlotNodeCache_DEPRECATED.push_back(index);
}
break; break;
case IrCmd::GET_HASH_NODE_ADDR: case IrCmd::GET_HASH_NODE_ADDR:
case IrCmd::GET_CLOSURE_UPVAL_ADDR: case IrCmd::GET_CLOSURE_UPVAL_ADDR:

View file

@ -613,6 +613,9 @@ enum LuauBuiltinFunction
LBF_VECTOR_CLAMP, LBF_VECTOR_CLAMP,
LBF_VECTOR_MIN, LBF_VECTOR_MIN,
LBF_VECTOR_MAX, LBF_VECTOR_MAX,
// math.lerp
LBF_MATH_LERP,
}; };
// Capture type, used in LOP_CAPTURE // Capture type, used in LOP_CAPTURE

View file

@ -13,7 +13,7 @@ inline bool isFlagExperimental(const char* flag)
static const char* const kList[] = { static const char* const kList[] = {
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative "LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
"StudioReportLuauAny2", // takes telemetry data for usage of any types "StudioReportLuauAny2", // takes telemetry data for usage of any types
"LuauSolverV2", "LuauSolverV2",
// makes sure we always have at least one entry // makes sure we always have at least one entry
nullptr, nullptr,

View file

@ -5,6 +5,8 @@
#include <math.h> #include <math.h>
LUAU_FASTFLAG(LuauCompileMathLerp)
namespace Luau namespace Luau
{ {
namespace Compile namespace Compile
@ -479,6 +481,19 @@ Constant foldBuiltin(int bfid, const Constant* args, size_t count)
return cvector(args[0].valueNumber, args[1].valueNumber, args[2].valueNumber, args[3].valueNumber); return cvector(args[0].valueNumber, args[1].valueNumber, args[2].valueNumber, args[3].valueNumber);
} }
break; break;
case LBF_MATH_LERP:
if (FFlag::LuauCompileMathLerp && count == 3 && args[0].type == Constant::Type_Number && args[1].type == Constant::Type_Number &&
args[2].type == Constant::Type_Number)
{
double a = args[0].valueNumber;
double b = args[1].valueNumber;
double t = args[2].valueNumber;
double v = (t == 1.0) ? b : a + (b - a) * t;
return cnum(v);
}
break;
} }
return cvar(); return cvar();

View file

@ -7,8 +7,8 @@
#include <array> #include <array>
LUAU_FASTFLAGVARIABLE(LuauVectorBuiltins)
LUAU_FASTFLAGVARIABLE(LuauCompileDisabledBuiltins) LUAU_FASTFLAGVARIABLE(LuauCompileDisabledBuiltins)
LUAU_FASTFLAGVARIABLE(LuauCompileMathLerp)
namespace Luau namespace Luau
{ {
@ -140,6 +140,8 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op
return LBF_MATH_SIGN; return LBF_MATH_SIGN;
if (builtin.method == "round") if (builtin.method == "round")
return LBF_MATH_ROUND; return LBF_MATH_ROUND;
if (FFlag::LuauCompileMathLerp && builtin.method == "lerp")
return LBF_MATH_LERP;
} }
if (builtin.object == "bit32") if (builtin.object == "bit32")
@ -226,7 +228,7 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op
return LBF_BUFFER_WRITEF64; return LBF_BUFFER_WRITEF64;
} }
if (FFlag::LuauVectorBuiltins && builtin.object == "vector") if (builtin.object == "vector")
{ {
if (builtin.method == "create") if (builtin.method == "create")
return LBF_VECTOR; return LBF_VECTOR;
@ -556,6 +558,10 @@ BuiltinInfo getBuiltinInfo(int bfid)
case LBF_VECTOR_MIN: case LBF_VECTOR_MIN:
case LBF_VECTOR_MAX: case LBF_VECTOR_MAX:
return {-1, 1}; // variadic return {-1, 1}; // variadic
case LBF_MATH_LERP:
LUAU_ASSERT(FFlag::LuauCompileMathLerp);
return {3, 1, BuiltinInfo::Flag_NoneSafe};
} }
LUAU_UNREACHABLE(); LUAU_UNREACHABLE();

View file

@ -1751,7 +1751,8 @@ void BytecodeBuilder::validateVariadic() const
// variadic sequence since they are never executed if FASTCALL does anything, so it's okay to skip their validation until CALL // variadic sequence since they are never executed if FASTCALL does anything, so it's okay to skip their validation until CALL
// (we can't simply start a variadic sequence here because that would trigger assertions during linked CALL validation) // (we can't simply start a variadic sequence here because that would trigger assertions during linked CALL validation)
} }
else if (op == LOP_CLOSEUPVALS || op == LOP_NAMECALL || op == LOP_GETIMPORT || op == LOP_MOVE || op == LOP_GETUPVAL || op == LOP_GETGLOBAL || op == LOP_GETTABLEKS || op == LOP_COVERAGE) else if (op == LOP_CLOSEUPVALS || op == LOP_NAMECALL || op == LOP_GETIMPORT || op == LOP_MOVE || op == LOP_GETUPVAL || op == LOP_GETGLOBAL ||
op == LOP_GETTABLEKS || op == LOP_COVERAGE)
{ {
// instructions inside a variadic sequence must be neutral (can't change L->top) // instructions inside a variadic sequence must be neutral (can't change L->top)
// while there are many neutral instructions like this, here we check that the instruction is one of the few // while there are many neutral instructions like this, here we check that the instruction is one of the few

View file

@ -1624,7 +1624,8 @@ struct Compiler
return; return;
} }
} }
else if (FFlag::LuauCompileOptimizeRevArith && options.optimizationLevel >= 2 && (expr->op == AstExprBinary::Add || expr->op == AstExprBinary::Mul)) else if (FFlag::LuauCompileOptimizeRevArith && options.optimizationLevel >= 2 &&
(expr->op == AstExprBinary::Add || expr->op == AstExprBinary::Mul))
{ {
// Optimization: replace k*r with r*k when r is known to be a number (otherwise metamethods may be called) // Optimization: replace k*r with r*k when r is known to be a number (otherwise metamethods may be called)
if (LuauBytecodeType* ty = exprTypes.find(expr); ty && *ty == LBC_TYPE_NUMBER) if (LuauBytecodeType* ty = exprTypes.find(expr); ty && *ty == LBC_TYPE_NUMBER)
@ -4408,7 +4409,7 @@ void setCompileConstantString(CompileConstant* constant, const char* s, size_t l
CompileError::raise({}, "Exceeded custom string constant length limit"); CompileError::raise({}, "Exceeded custom string constant length limit");
target->type = Compile::Constant::Type_String; target->type = Compile::Constant::Type_String;
target->stringLength = l; target->stringLength = unsigned(l);
target->valueString = s; target->valueString = s;
} }

View file

@ -130,7 +130,8 @@ struct CostVisitor : AstVisitor
{ {
return model(expr->expr); return model(expr->expr);
} }
else if (node->is<AstExprConstantNil>() || node->is<AstExprConstantBool>() || node->is<AstExprConstantNumber>() || node->is<AstExprConstantString>()) else if (node->is<AstExprConstantNil>() || node->is<AstExprConstantBool>() || node->is<AstExprConstantNumber>() ||
node->is<AstExprConstantString>())
{ {
return Cost(0, Cost::kLiteral); return Cost(0, Cost::kLiteral);
} }

View file

@ -3,7 +3,6 @@
#include "Luau/BytecodeBuilder.h" #include "Luau/BytecodeBuilder.h"
LUAU_FASTFLAGVARIABLE(LuauCompileVectorTypeInfo)
LUAU_FASTFLAG(LuauCompileLibraryConstants) LUAU_FASTFLAG(LuauCompileLibraryConstants)
namespace Luau namespace Luau
@ -32,7 +31,7 @@ static LuauBytecodeType getPrimitiveType(AstName name)
return LBC_TYPE_THREAD; return LBC_TYPE_THREAD;
else if (name == "buffer") else if (name == "buffer")
return LBC_TYPE_BUFFER; return LBC_TYPE_BUFFER;
else if (FFlag::LuauCompileVectorTypeInfo && name == "vector") else if (name == "vector")
return LBC_TYPE_VECTOR; return LBC_TYPE_VECTOR;
else if (name == "any" || name == "unknown") else if (name == "any" || name == "unknown")
return LBC_TYPE_ANY; return LBC_TYPE_ANY;
@ -747,6 +746,7 @@ struct TypeMapVisitor : AstVisitor
case LBF_BUFFER_READF64: case LBF_BUFFER_READF64:
case LBF_VECTOR_MAGNITUDE: case LBF_VECTOR_MAGNITUDE:
case LBF_VECTOR_DOT: case LBF_VECTOR_DOT:
case LBF_MATH_LERP:
recordResolvedType(node, &builtinTypes.numberType); recordResolvedType(node, &builtinTypes.numberType);
break; break;

View file

@ -305,7 +305,8 @@ static Error parseJson(const std::string& contents, Action action)
arrayTop = (lexer.current().type == '['); arrayTop = (lexer.current().type == '[');
next(lexer); next(lexer);
} }
else if (lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::ReservedTrue || lexer.current().type == Lexeme::ReservedFalse) else if (lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::ReservedTrue ||
lexer.current().type == Lexeme::ReservedFalse)
{ {
std::string value = lexer.current().type == Lexeme::QuotedString std::string value = lexer.current().type == Lexeme::QuotedString
? std::string(lexer.current().data, lexer.current().getLength()) ? std::string(lexer.current().data, lexer.current().getLength())

View file

@ -198,7 +198,13 @@ private:
{ {
// An e-node 𝑛 is canonical iff 𝑛 = canonicalize(𝑛), where // An e-node 𝑛 is canonical iff 𝑛 = canonicalize(𝑛), where
// canonicalize(𝑓(𝑎1, 𝑎2, ...)) = 𝑓(find(𝑎1), find(𝑎2), ...). // canonicalize(𝑓(𝑎1, 𝑎2, ...)) = 𝑓(find(𝑎1), find(𝑎2), ...).
Luau::EqSat::canonicalize(enode, [&](Id id) { return find(id); }); Luau::EqSat::canonicalize(
enode,
[&](Id id)
{
return find(id);
}
);
} }
bool isCanonical(const L& enode) const bool isCanonical(const L& enode) const

View file

@ -244,7 +244,7 @@ private:
template<typename Phantom, typename T> template<typename Phantom, typename T>
struct NodeSet struct NodeSet
{ {
template <typename P_, typename T_, typename Find> template<typename P_, typename T_, typename Find>
friend void canonicalize(NodeSet<P_, T_>& node, Find&& find); friend void canonicalize(NodeSet<P_, T_>& node, Find&& find);
template<typename... Args> template<typename... Args>
@ -302,7 +302,7 @@ struct Language final
template<typename T> template<typename T>
using WithinDomain = std::disjunction<std::is_same<std::decay_t<T>, Ts>...>; using WithinDomain = std::disjunction<std::is_same<std::decay_t<T>, Ts>...>;
template <typename Find, typename... Vs> template<typename Find, typename... Vs>
friend void canonicalize(Language<Vs...>& enode, Find&& find); friend void canonicalize(Language<Vs...>& enode, Find&& find);
template<typename T> template<typename T>
@ -388,7 +388,7 @@ private:
VariantTy v; VariantTy v;
}; };
template <typename Node, typename Find> template<typename Node, typename Find>
void canonicalize(Node& node, Find&& find) void canonicalize(Node& node, Find&& find)
{ {
// An e-node 𝑛 is canonical iff 𝑛 = canonicalize(𝑛), where // An e-node 𝑛 is canonical iff 𝑛 = canonicalize(𝑛), where
@ -398,7 +398,7 @@ void canonicalize(Node& node, Find&& find)
} }
// Canonicalizing the Ids in a NodeSet may result in the set decreasing in size. // Canonicalizing the Ids in a NodeSet may result in the set decreasing in size.
template <typename Phantom, typename T, typename Find> template<typename Phantom, typename T, typename Find>
void canonicalize(NodeSet<Phantom, T>& node, Find&& find) void canonicalize(NodeSet<Phantom, T>& node, Find&& find)
{ {
for (Id& id : node.vector) for (Id& id : node.vector)
@ -409,7 +409,7 @@ void canonicalize(NodeSet<Phantom, T>& node, Find&& find)
node.vector.erase(endIt, end(node.vector)); node.vector.erase(endIt, end(node.vector));
} }
template <typename Find, typename... Vs> template<typename Find, typename... Vs>
void canonicalize(Language<Vs...>& enode, Find&& find) void canonicalize(Language<Vs...>& enode, Find&& find)
{ {
visit( visit(

View file

@ -10,6 +10,8 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAGVARIABLE(LuauBufferBitMethods)
// while C API returns 'size_t' for binary compatibility in case of future extensions, // while C API returns 'size_t' for binary compatibility in case of future extensions,
// in the current implementation, length and offset are limited to 31 bits // in the current implementation, length and offset are limited to 31 bits
// because offset is limited to an integer, a single 64bit comparison can be used and will not overflow // because offset is limited to an integer, a single 64bit comparison can be used and will not overflow
@ -247,7 +249,68 @@ static int buffer_fill(lua_State* L)
return 0; return 0;
} }
static const luaL_Reg bufferlib[] = { static int buffer_readbits(lua_State* L)
{
size_t len = 0;
void* buf = luaL_checkbuffer(L, 1, &len);
int64_t bitoffset = (int64_t)luaL_checknumber(L, 2);
int bitcount = luaL_checkinteger(L, 3);
if (bitoffset < 0)
luaL_error(L, "buffer access out of bounds");
if (unsigned(bitcount) > 32)
luaL_error(L, "bit count is out of range of [0; 32]");
if (uint64_t(bitoffset + bitcount) > len * 8)
luaL_error(L, "buffer access out of bounds");
unsigned startbyte = unsigned(bitoffset / 8);
unsigned endbyte = unsigned((bitoffset + bitcount + 7) / 8);
uint64_t data = 0;
memcpy(&data, (char*)buf + startbyte, endbyte - startbyte);
uint64_t subbyteoffset = bitoffset & 0x7;
uint64_t mask = (1ull << bitcount) - 1;
lua_pushunsigned(L, unsigned((data >> subbyteoffset) & mask));
return 1;
}
static int buffer_writebits(lua_State* L)
{
size_t len = 0;
void* buf = luaL_checkbuffer(L, 1, &len);
int64_t bitoffset = (int64_t)luaL_checknumber(L, 2);
int bitcount = luaL_checkinteger(L, 3);
unsigned value = luaL_checkunsigned(L, 4);
if (bitoffset < 0)
luaL_error(L, "buffer access out of bounds");
if (unsigned(bitcount) > 32)
luaL_error(L, "bit count is out of range of [0; 32]");
if (uint64_t(bitoffset + bitcount) > len * 8)
luaL_error(L, "buffer access out of bounds");
unsigned startbyte = unsigned(bitoffset / 8);
unsigned endbyte = unsigned((bitoffset + bitcount + 7) / 8);
uint64_t data = 0;
memcpy(&data, (char*)buf + startbyte, endbyte - startbyte);
uint64_t subbyteoffset = bitoffset & 0x7;
uint64_t mask = ((1ull << bitcount) - 1) << subbyteoffset;
data = (data & ~mask) | ((uint64_t(value) << subbyteoffset) & mask);
memcpy((char*)buf + startbyte, &data, endbyte - startbyte);
return 0;
}
static const luaL_Reg bufferlib_DEPRECATED[] = {
{"create", buffer_create}, {"create", buffer_create},
{"fromstring", buffer_fromstring}, {"fromstring", buffer_fromstring},
{"tostring", buffer_tostring}, {"tostring", buffer_tostring},
@ -275,9 +338,39 @@ static const luaL_Reg bufferlib[] = {
{NULL, NULL}, {NULL, NULL},
}; };
static const luaL_Reg bufferlib[] = {
{"create", buffer_create},
{"fromstring", buffer_fromstring},
{"tostring", buffer_tostring},
{"readi8", buffer_readinteger<int8_t>},
{"readu8", buffer_readinteger<uint8_t>},
{"readi16", buffer_readinteger<int16_t>},
{"readu16", buffer_readinteger<uint16_t>},
{"readi32", buffer_readinteger<int32_t>},
{"readu32", buffer_readinteger<uint32_t>},
{"readf32", buffer_readfp<float, uint32_t>},
{"readf64", buffer_readfp<double, uint64_t>},
{"writei8", buffer_writeinteger<int8_t>},
{"writeu8", buffer_writeinteger<uint8_t>},
{"writei16", buffer_writeinteger<int16_t>},
{"writeu16", buffer_writeinteger<uint16_t>},
{"writei32", buffer_writeinteger<int32_t>},
{"writeu32", buffer_writeinteger<uint32_t>},
{"writef32", buffer_writefp<float, uint32_t>},
{"writef64", buffer_writefp<double, uint64_t>},
{"readstring", buffer_readstring},
{"writestring", buffer_writestring},
{"len", buffer_len},
{"copy", buffer_copy},
{"fill", buffer_fill},
{"readbits", buffer_readbits},
{"writebits", buffer_writebits},
{NULL, NULL},
};
int luaopen_buffer(lua_State* L) int luaopen_buffer(lua_State* L)
{ {
luaL_register(L, LUA_BUFFERLIBNAME, bufferlib); luaL_register(L, LUA_BUFFERLIBNAME, FFlag::LuauBufferBitMethods ? bufferlib : bufferlib_DEPRECATED);
return 1; return 1;
} }

View file

@ -1694,6 +1694,23 @@ static int luauF_vectormax(lua_State* L, StkId res, TValue* arg0, int nresults,
return -1; return -1;
} }
static int luauF_lerp(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
{
if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1))
{
double a = nvalue(arg0);
double b = nvalue(args);
double t = nvalue(args + 1);
double r = (t == 1.0) ? b : a + (b - a) * t;
setnvalue(res, r);
return 1;
}
return -1;
}
static int luauF_missing(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) static int luauF_missing(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
{ {
return -1; return -1;
@ -1889,6 +1906,8 @@ const luau_FastFunction luauF_table[256] = {
luauF_vectormin, luauF_vectormin,
luauF_vectormax, luauF_vectormax,
luauF_lerp,
// When adding builtins, add them above this line; what follows is 64 "dummy" entries with luauF_missing fallback. // When adding builtins, add them above this line; what follows is 64 "dummy" entries with luauF_missing fallback.
// This is important so that older versions of the runtime that don't support newer builtins automatically fall back via luauF_missing. // This is important so that older versions of the runtime that don't support newer builtins automatically fall back via luauF_missing.
// Given the builtin addition velocity this should always provide a larger compatibility window than bytecode versions suggest. // Given the builtin addition velocity this should always provide a larger compatibility window than bytecode versions suggest.

View file

@ -18,6 +18,7 @@
#include <string.h> #include <string.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauStackLimit, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauStackLimit, false)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauPopIncompleteCi, false)
// keep max stack allocation request under 1GB // keep max stack allocation request under 1GB
#define MAX_STACK_SIZE (int(1024 / sizeof(TValue)) * 1024 * 1024) #define MAX_STACK_SIZE (int(1024 / sizeof(TValue)) * 1024 * 1024)
@ -179,11 +180,23 @@ static void correctstack(lua_State* L, TValue* oldstack)
L->base = (L->base - oldstack) + L->stack; L->base = (L->base - oldstack) + L->stack;
} }
void luaD_reallocstack(lua_State* L, int newsize) void luaD_reallocstack(lua_State* L, int newsize, int fornewci)
{ {
// throw 'out of memory' error because space for a custom error message cannot be guaranteed here // throw 'out of memory' error because space for a custom error message cannot be guaranteed here
if (DFFlag::LuauStackLimit && newsize > MAX_STACK_SIZE) if (DFFlag::LuauStackLimit && newsize > MAX_STACK_SIZE)
{
// reallocation was performaed to setup a new CallInfo frame, which we have to remove
if (DFFlag::LuauPopIncompleteCi && fornewci)
{
CallInfo* cip = L->ci - 1;
L->ci = cip;
L->base = cip->base;
L->top = cip->top;
}
luaD_throw(L, LUA_ERRMEM); luaD_throw(L, LUA_ERRMEM);
}
TValue* oldstack = L->stack; TValue* oldstack = L->stack;
int realsize = newsize + EXTRA_STACK; int realsize = newsize + EXTRA_STACK;
@ -208,10 +221,17 @@ void luaD_reallocCI(lua_State* L, int newsize)
void luaD_growstack(lua_State* L, int n) void luaD_growstack(lua_State* L, int n)
{ {
if (n <= L->stacksize) // double size is enough? if (DFFlag::LuauPopIncompleteCi)
luaD_reallocstack(L, 2 * L->stacksize); {
luaD_reallocstack(L, getgrownstacksize(L, n), 0);
}
else else
luaD_reallocstack(L, L->stacksize + n); {
if (n <= L->stacksize) // double size is enough?
luaD_reallocstack(L, 2 * L->stacksize, 0);
else
luaD_reallocstack(L, L->stacksize + n, 0);
}
} }
CallInfo* luaD_growCI(lua_State* L) CallInfo* luaD_growCI(lua_State* L)

View file

@ -7,11 +7,21 @@
#include "luaconf.h" #include "luaconf.h"
#include "ldebug.h" #include "ldebug.h"
// returns target stack for 'n' extra elements to reallocate
// if possible, stack size growth factor is 2x
#define getgrownstacksize(L, n) ((n) <= L->stacksize ? 2 * L->stacksize : L->stacksize + (n))
#define luaD_checkstackfornewci(L, n) \
if ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue)) \
luaD_reallocstack(L, getgrownstacksize(L, (n)), 1); \
else \
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK, 1));
#define luaD_checkstack(L, n) \ #define luaD_checkstack(L, n) \
if ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue)) \ if ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue)) \
luaD_growstack(L, n); \ luaD_growstack(L, n); \
else \ else \
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK)); condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK, 0));
#define incr_top(L) \ #define incr_top(L) \
{ \ { \
@ -47,7 +57,7 @@ LUAI_FUNC CallInfo* luaD_growCI(lua_State* L);
LUAI_FUNC void luaD_call(lua_State* L, StkId func, int nresults); LUAI_FUNC void luaD_call(lua_State* L, StkId func, int nresults);
LUAI_FUNC int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t oldtop, ptrdiff_t ef); LUAI_FUNC int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t oldtop, ptrdiff_t ef);
LUAI_FUNC void luaD_reallocCI(lua_State* L, int newsize); LUAI_FUNC void luaD_reallocCI(lua_State* L, int newsize);
LUAI_FUNC void luaD_reallocstack(lua_State* L, int newsize); LUAI_FUNC void luaD_reallocstack(lua_State* L, int newsize, int fornewci);
LUAI_FUNC void luaD_growstack(lua_State* L, int n); LUAI_FUNC void luaD_growstack(lua_State* L, int n);
LUAI_FUNC void luaD_checkCstack(lua_State* L); LUAI_FUNC void luaD_checkCstack(lua_State* L);

View file

@ -442,7 +442,7 @@ static void shrinkstack(lua_State* L)
condhardstacktests(luaD_reallocCI(L, ci_used + 1)); condhardstacktests(luaD_reallocCI(L, ci_used + 1));
if (3 * size_t(s_used) < size_t(L->stacksize) && 2 * (BASIC_STACK_SIZE + EXTRA_STACK) < L->stacksize) if (3 * size_t(s_used) < size_t(L->stacksize) && 2 * (BASIC_STACK_SIZE + EXTRA_STACK) < L->stacksize)
luaD_reallocstack(L, L->stacksize / 2); // still big enough... luaD_reallocstack(L, L->stacksize / 2, 0); // still big enough...
condhardstacktests(luaD_reallocstack(L, s_used)); condhardstacktests(luaD_reallocstack(L, s_used));
} }

View file

@ -7,7 +7,7 @@
#include <math.h> #include <math.h>
#include <time.h> #include <time.h>
LUAU_FASTFLAGVARIABLE(LuauMathMap) LUAU_FASTFLAGVARIABLE(LuauMathLerp)
#undef PI #undef PI
#define PI (3.14159265358979323846) #define PI (3.14159265358979323846)
@ -418,6 +418,17 @@ static int math_map(lua_State* L)
return 1; return 1;
} }
static int math_lerp(lua_State* L)
{
double a = luaL_checknumber(L, 1);
double b = luaL_checknumber(L, 2);
double t = luaL_checknumber(L, 3);
double r = (t == 1.0) ? b : a + (b - a) * t;
lua_pushnumber(L, r);
return 1;
}
static const luaL_Reg mathlib[] = { static const luaL_Reg mathlib[] = {
{"abs", math_abs}, {"abs", math_abs},
{"acos", math_acos}, {"acos", math_acos},
@ -451,6 +462,7 @@ static const luaL_Reg mathlib[] = {
{"clamp", math_clamp}, {"clamp", math_clamp},
{"sign", math_sign}, {"sign", math_sign},
{"round", math_round}, {"round", math_round},
{"map", math_map},
{NULL, NULL}, {NULL, NULL},
}; };
@ -471,10 +483,10 @@ int luaopen_math(lua_State* L)
lua_pushnumber(L, HUGE_VAL); lua_pushnumber(L, HUGE_VAL);
lua_setfield(L, -2, "huge"); lua_setfield(L, -2, "huge");
if (FFlag::LuauMathMap) if (FFlag::LuauMathLerp)
{ {
lua_pushcfunction(L, math_map, "map"); lua_pushcfunction(L, math_lerp, "lerp");
lua_setfield(L, -2, "map"); lua_setfield(L, -2, "lerp");
} }
return 1; return 1;

View file

@ -192,7 +192,7 @@ struct SizeClassConfig
const SizeClassConfig kSizeClassConfig; const SizeClassConfig kSizeClassConfig;
// size class for a block of size sz; returns -1 for size=0 because empty allocations take no space // size class for a block of size sz; returns -1 for size=0 because empty allocations take no space
#define sizeclass(sz) (size_t((sz)-1) < kMaxSmallSizeUsed ? kSizeClassConfig.classForSize[sz] : -1) #define sizeclass(sz) (size_t((sz) - 1) < kMaxSmallSizeUsed ? kSizeClassConfig.classForSize[sz] : -1)
// metadata for a block is stored in the first pointer of the block // metadata for a block is stored in the first pointer of the block
#define metadata(block) (*(void**)(block)) #define metadata(block) (*(void**)(block))

View file

@ -149,7 +149,7 @@ void lua_resetthread(lua_State* L)
L->nCcalls = L->baseCcalls = 0; L->nCcalls = L->baseCcalls = 0;
// clear thread stack // clear thread stack
if (L->stacksize != BASIC_STACK_SIZE + EXTRA_STACK) if (L->stacksize != BASIC_STACK_SIZE + EXTRA_STACK)
luaD_reallocstack(L, BASIC_STACK_SIZE); luaD_reallocstack(L, BASIC_STACK_SIZE, 0);
for (int i = 0; i < L->stacksize; i++) for (int i = 0; i < L->stacksize; i++)
setnilvalue(L->stack + i); setnilvalue(L->stack + i);
} }

View file

@ -16,6 +16,8 @@
#include <string.h> #include <string.h>
LUAU_DYNAMIC_FASTFLAG(LuauPopIncompleteCi)
// Disable c99-designator to avoid the warning in CGOTO dispatch table // Disable c99-designator to avoid the warning in CGOTO dispatch table
#ifdef __clang__ #ifdef __clang__
#if __has_warning("-Wc99-designator") #if __has_warning("-Wc99-designator")
@ -935,7 +937,14 @@ reentry:
// note: this reallocs stack, but we don't need to VM_PROTECT this // note: this reallocs stack, but we don't need to VM_PROTECT this
// this is because we're going to modify base/savedpc manually anyhow // this is because we're going to modify base/savedpc manually anyhow
// crucially, we can't use ra/argtop after this line // crucially, we can't use ra/argtop after this line
luaD_checkstack(L, ccl->stacksize); if (DFFlag::LuauPopIncompleteCi)
{
luaD_checkstackfornewci(L, ccl->stacksize);
}
else
{
luaD_checkstack(L, ccl->stacksize);
}
LUAU_ASSERT(ci->top <= L->stack_last); LUAU_ASSERT(ci->top <= L->stack_last);
@ -3071,7 +3080,14 @@ int luau_precall(lua_State* L, StkId func, int nresults)
L->base = ci->base; L->base = ci->base;
// Note: L->top is assigned externally // Note: L->top is assigned externally
luaD_checkstack(L, ccl->stacksize); if (DFFlag::LuauPopIncompleteCi)
{
luaD_checkstackfornewci(L, ccl->stacksize);
}
else
{
luaD_checkstack(L, ccl->stacksize);
}
LUAU_ASSERT(ci->top <= L->stack_last); LUAU_ASSERT(ci->top <= L->stack_last);
if (!ccl->isC) if (!ccl->isC)

View file

@ -18,6 +18,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(StudioReportLuauAny2) LUAU_FASTFLAG(StudioReportLuauAny2)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
struct ATSFixture : BuiltinsFixture struct ATSFixture : BuiltinsFixture
@ -97,7 +98,10 @@ end
LUAU_ASSERT(module->ats.typeInfo.size() == 3); LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::TypePk); LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::TypePk);
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function fallible(t: number): ...any\n if t > 0 then\n return true, t\n end\n return false, 'must be positive'\nend"); LUAU_ASSERT(
module->ats.typeInfo[0].node ==
"local function fallible(t: number): ...any\n if t > 0 then\n return true, t\n end\n return false, 'must be positive'\nend"
);
} }
TEST_CASE_FIXTURE(ATSFixture, "typepacks_no_ret") TEST_CASE_FIXTURE(ATSFixture, "typepacks_no_ret")
@ -581,6 +585,9 @@ TEST_CASE_FIXTURE(ATSFixture, "racing_spawning_1")
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::StudioReportLuauAny2, true}, {FFlag::StudioReportLuauAny2, true},
// Previously we'd report an error because number <: 'a is not a
// supertype.
{FFlag::LuauTrackInteriorFreeTypesOnScope, true}
}; };
fileResolver.source["game/Gui/Modules/A"] = R"( fileResolver.source["game/Gui/Modules/A"] = R"(
@ -632,7 +639,7 @@ initialize()
)"; )";
CheckResult result1 = frontend.check("game/Gui/Modules/A"); CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_ERROR_COUNT(5, result1); LUAU_REQUIRE_ERROR_COUNT(4, result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");

View file

@ -2232,7 +2232,7 @@ local ec = e(f@5)
TEST_CASE_FIXTURE(ACFixture, "type_correct_suggestion_for_overloads") TEST_CASE_FIXTURE(ACFixture, "type_correct_suggestion_for_overloads")
{ {
if (FFlag::LuauSolverV2) // CLI-116814 Autocomplete needs to populate expected types for function arguments correctly if (FFlag::LuauSolverV2) // CLI-116814 Autocomplete needs to populate expected types for function arguments correctly
// (overloads and singletons) // (overloads and singletons)
return; return;
check(R"( check(R"(
local target: ((number) -> string) & ((string) -> number)) local target: ((number) -> string) & ((string) -> number))
@ -2582,7 +2582,7 @@ end
TEST_CASE_FIXTURE(ACFixture, "suggest_table_keys") TEST_CASE_FIXTURE(ACFixture, "suggest_table_keys")
{ {
if (FFlag::LuauSolverV2) // CLI-116812 AutocompleteTest.suggest_table_keys needs to populate expected types for nested if (FFlag::LuauSolverV2) // CLI-116812 AutocompleteTest.suggest_table_keys needs to populate expected types for nested
// tables without an annotation // tables without an annotation
return; return;
check(R"( check(R"(
@ -3069,7 +3069,7 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_on_string_singletons")
TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons") TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons")
{ {
if (FFlag::LuauSolverV2) // CLI-116814 Autocomplete needs to populate expected types for function arguments correctly if (FFlag::LuauSolverV2) // CLI-116814 Autocomplete needs to populate expected types for function arguments correctly
// (overloads and singletons) // (overloads and singletons)
return; return;
check(R"( check(R"(
@ -4293,8 +4293,7 @@ end
foo(@1) foo(@1)
)"); )");
const std::optional<std::string> EXPECTED_INSERT = const std::optional<std::string> EXPECTED_INSERT = FFlag::LuauSolverV2 ? "function(...: number): number end" : "function(...): number end";
FFlag::LuauSolverV2 ? "function(...: number): number end" : "function(...): number end";
auto ac = autocomplete('1'); auto ac = autocomplete('1');

View file

@ -23,10 +23,8 @@ LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost)
LUAU_FASTINT(LuauCompileLoopUnrollThreshold) LUAU_FASTINT(LuauCompileLoopUnrollThreshold)
LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost) LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost)
LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTFLAG(LuauCompileVectorTypeInfo)
LUAU_FASTFLAG(LuauCompileOptimizeRevArith) LUAU_FASTFLAG(LuauCompileOptimizeRevArith)
LUAU_FASTFLAG(LuauCompileLibraryConstants) LUAU_FASTFLAG(LuauCompileLibraryConstants)
LUAU_FASTFLAG(LuauVectorBuiltins)
LUAU_FASTFLAG(LuauVectorFolding) LUAU_FASTFLAG(LuauVectorFolding)
LUAU_FASTFLAG(LuauCompileDisabledBuiltins) LUAU_FASTFLAG(LuauCompileDisabledBuiltins)
@ -1492,7 +1490,6 @@ RETURN R0 1
TEST_CASE("ConstantFoldVectorArith") TEST_CASE("ConstantFoldVectorArith")
{ {
ScopedFastFlag luauVectorBuiltins{FFlag::LuauVectorBuiltins, true};
ScopedFastFlag luauVectorFolding{FFlag::LuauVectorFolding, true}; ScopedFastFlag luauVectorFolding{FFlag::LuauVectorFolding, true};
CHECK_EQ("\n" + compileFunction("local n = 2; local a, b = vector.create(1, 2, 3), vector.create(2, 4, 8); return a + b", 0, 2), R"( CHECK_EQ("\n" + compileFunction("local n = 2; local a, b = vector.create(1, 2, 3), vector.create(2, 4, 8); return a + b", 0, 2), R"(
@ -1560,7 +1557,6 @@ RETURN R0 1
TEST_CASE("ConstantFoldVectorArith4Wide") TEST_CASE("ConstantFoldVectorArith4Wide")
{ {
ScopedFastFlag luauVectorBuiltins{FFlag::LuauVectorBuiltins, true};
ScopedFastFlag luauVectorFolding{FFlag::LuauVectorFolding, true}; ScopedFastFlag luauVectorFolding{FFlag::LuauVectorFolding, true};
CHECK_EQ("\n" + compileFunction("local n = 2; local a, b = vector.create(1, 2, 3, 4), vector.create(2, 4, 8, 1); return a + b", 0, 2), R"( CHECK_EQ("\n" + compileFunction("local n = 2; local a, b = vector.create(1, 2, 3, 4), vector.create(2, 4, 8, 1); return a + b", 0, 2), R"(
@ -5137,7 +5133,6 @@ RETURN R0 1
TEST_CASE("VectorConstantFields") TEST_CASE("VectorConstantFields")
{ {
ScopedFastFlag luauVectorBuiltins{FFlag::LuauVectorBuiltins, true};
ScopedFastFlag luauCompileLibraryConstants{FFlag::LuauCompileLibraryConstants, true}; ScopedFastFlag luauCompileLibraryConstants{FFlag::LuauCompileLibraryConstants, true};
CHECK_EQ("\n" + compileFunction("return vector.one, vector.zero", 0, 2), R"( CHECK_EQ("\n" + compileFunction("return vector.one, vector.zero", 0, 2), R"(
@ -8666,8 +8661,6 @@ end
TEST_CASE("BuiltinTypeVector") TEST_CASE("BuiltinTypeVector")
{ {
ScopedFastFlag luauCompileVectorTypeInfo{FFlag::LuauCompileVectorTypeInfo, true};
CHECK_EQ( CHECK_EQ(
"\n" + compileTypeTable(R"( "\n" + compileTypeTable(R"(
function myfunc(test: Instance, pos: vector) function myfunc(test: Instance, pos: vector)

View file

@ -31,16 +31,16 @@ extern int optimizationLevel;
void luaC_fullgc(lua_State* L); void luaC_fullgc(lua_State* L);
void luaC_validate(lua_State* L); void luaC_validate(lua_State* L);
LUAU_FASTFLAG(LuauMathMap) LUAU_FASTFLAG(LuauMathLerp)
LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_DYNAMIC_FASTFLAG(LuauStackLimit) LUAU_DYNAMIC_FASTFLAG(LuauStackLimit)
LUAU_FASTFLAG(LuauVectorDefinitions)
LUAU_DYNAMIC_FASTFLAG(LuauDebugInfoInvArgLeftovers) LUAU_DYNAMIC_FASTFLAG(LuauDebugInfoInvArgLeftovers)
LUAU_FASTFLAG(LuauVectorLibNativeCodegen) LUAU_FASTFLAG(LuauVectorLibNativeCodegen)
LUAU_FASTFLAG(LuauVectorLibNativeDot) LUAU_FASTFLAG(LuauVectorLibNativeDot)
LUAU_FASTFLAG(LuauVectorBuiltins)
LUAU_FASTFLAG(LuauVectorMetatable) LUAU_FASTFLAG(LuauVectorMetatable)
LUAU_FASTFLAG(LuauBufferBitMethods)
LUAU_FASTFLAG(LuauCodeGenLimitLiveSlotReuse)
static lua_CompileOptions defaultOptions() static lua_CompileOptions defaultOptions()
{ {
@ -654,12 +654,14 @@ TEST_CASE("Basic")
TEST_CASE("Buffers") TEST_CASE("Buffers")
{ {
ScopedFastFlag luauBufferBitMethods{FFlag::LuauBufferBitMethods, true};
runConformance("buffers.lua"); runConformance("buffers.lua");
} }
TEST_CASE("Math") TEST_CASE("Math")
{ {
ScopedFastFlag LuauMathMap{FFlag::LuauMathMap, true}; ScopedFastFlag LuauMathLerp{FFlag::LuauMathLerp, true};
runConformance("math.lua"); runConformance("math.lua");
} }
@ -891,7 +893,6 @@ TEST_CASE("Vector")
TEST_CASE("VectorLibrary") TEST_CASE("VectorLibrary")
{ {
ScopedFastFlag luauVectorBuiltins{FFlag::LuauVectorBuiltins, true};
ScopedFastFlag luauVectorLibNativeCodegen{FFlag::LuauVectorLibNativeCodegen, true}; ScopedFastFlag luauVectorLibNativeCodegen{FFlag::LuauVectorLibNativeCodegen, true};
ScopedFastFlag luauVectorLibNativeDot{FFlag::LuauVectorLibNativeDot, true}; ScopedFastFlag luauVectorLibNativeDot{FFlag::LuauVectorLibNativeDot, true};
ScopedFastFlag luauVectorMetatable{FFlag::LuauVectorMetatable, true}; ScopedFastFlag luauVectorMetatable{FFlag::LuauVectorMetatable, true};
@ -911,9 +912,7 @@ TEST_CASE("VectorLibrary")
copts.optimizationLevel = 2; copts.optimizationLevel = 2;
} }
runConformance( runConformance("vector_library.lua", [](lua_State* L) {}, nullptr, nullptr, &copts);
"vector_library.lua", [](lua_State* L) {}, nullptr, nullptr, &copts
);
} }
static void populateRTTI(lua_State* L, Luau::TypeId type) static void populateRTTI(lua_State* L, Luau::TypeId type)
@ -987,7 +986,7 @@ static void populateRTTI(lua_State* L, Luau::TypeId type)
TEST_CASE("Types") TEST_CASE("Types")
{ {
ScopedFastFlag luauVectorDefinitions{FFlag::LuauVectorDefinitions, true}; ScopedFastFlag luauMathLerp{FFlag::LuauMathLerp, false}; // waiting for math.lerp to be added to embedded type definitions
runConformance( runConformance(
"types.lua", "types.lua",
@ -2578,6 +2577,8 @@ TEST_CASE("SafeEnv")
TEST_CASE("Native") TEST_CASE("Native")
{ {
ScopedFastFlag luauCodeGenLimitLiveSlotReuse{FFlag::LuauCodeGenLimitLiveSlotReuse, true};
// This tests requires code to run natively, otherwise all 'is_native' checks will fail // This tests requires code to run natively, otherwise all 'is_native' checks will fail
if (!codegen || !luau_codegen_supported()) if (!codegen || !luau_codegen_supported())
return; return;

View file

@ -23,15 +23,11 @@ struct ESFixture : Fixture
TypeId genericT = arena_.addType(GenericType{"T"}); TypeId genericT = arena_.addType(GenericType{"T"});
TypeId genericU = arena_.addType(GenericType{"U"}); TypeId genericU = arena_.addType(GenericType{"U"});
TypeId numberToString = arena_.addType(FunctionType{ TypeId numberToString =
arena_.addTypePack({builtinTypes->numberType}), arena_.addType(FunctionType{arena_.addTypePack({builtinTypes->numberType}), arena_.addTypePack({builtinTypes->stringType})});
arena_.addTypePack({builtinTypes->stringType})
});
TypeId stringToNumber = arena_.addType(FunctionType{ TypeId stringToNumber =
arena_.addTypePack({builtinTypes->stringType}), arena_.addType(FunctionType{arena_.addTypePack({builtinTypes->stringType}), arena_.addTypePack({builtinTypes->numberType})});
arena_.addTypePack({builtinTypes->numberType})
});
ESFixture() ESFixture()
: simplifier(newSimplifier(arena, builtinTypes)) : simplifier(newSimplifier(arena, builtinTypes))
@ -163,10 +159,11 @@ TEST_CASE_FIXTURE(ESFixture, "never & string")
TEST_CASE_FIXTURE(ESFixture, "string & (unknown | never)") TEST_CASE_FIXTURE(ESFixture, "string & (unknown | never)")
{ {
CHECK("string" == simplifyStr(arena->addType(IntersectionType{{ CHECK(
builtinTypes->stringType, "string" == simplifyStr(arena->addType(
arena->addType(UnionType{{builtinTypes->unknownType, builtinTypes->neverType}}) IntersectionType{{builtinTypes->stringType, arena->addType(UnionType{{builtinTypes->unknownType, builtinTypes->neverType}})}}
}}))); ))
);
} }
TEST_CASE_FIXTURE(ESFixture, "true | false") TEST_CASE_FIXTURE(ESFixture, "true | false")
@ -211,112 +208,97 @@ TEST_CASE_FIXTURE(ESFixture, "error | unknown")
TEST_CASE_FIXTURE(ESFixture, "\"hello\" | string") TEST_CASE_FIXTURE(ESFixture, "\"hello\" | string")
{ {
CHECK("string" == simplifyStr(arena->addType(UnionType{{ CHECK("string" == simplifyStr(arena->addType(UnionType{{arena->addType(SingletonType{StringSingleton{"hello"}}), builtinTypes->stringType}})));
arena->addType(SingletonType{StringSingleton{"hello"}}), builtinTypes->stringType
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "\"hello\" | \"world\" | \"hello\"") TEST_CASE_FIXTURE(ESFixture, "\"hello\" | \"world\" | \"hello\"")
{ {
CHECK("\"hello\" | \"world\"" == simplifyStr(arena->addType(UnionType{{ CHECK(
arena->addType(SingletonType{StringSingleton{"hello"}}), "\"hello\" | \"world\"" == simplifyStr(arena->addType(UnionType{{
arena->addType(SingletonType{StringSingleton{"world"}}), arena->addType(SingletonType{StringSingleton{"hello"}}),
arena->addType(SingletonType{StringSingleton{"hello"}}), arena->addType(SingletonType{StringSingleton{"world"}}),
}}))); arena->addType(SingletonType{StringSingleton{"hello"}}),
}}))
);
} }
TEST_CASE_FIXTURE(ESFixture, "nil | boolean | number | string | thread | function | table | class | buffer") TEST_CASE_FIXTURE(ESFixture, "nil | boolean | number | string | thread | function | table | class | buffer")
{ {
CHECK("unknown" == simplifyStr(arena->addType(UnionType{{ CHECK(
builtinTypes->nilType, "unknown" == simplifyStr(arena->addType(UnionType{{
builtinTypes->booleanType, builtinTypes->nilType,
builtinTypes->numberType, builtinTypes->booleanType,
builtinTypes->stringType, builtinTypes->numberType,
builtinTypes->threadType, builtinTypes->stringType,
builtinTypes->functionType, builtinTypes->threadType,
builtinTypes->tableType, builtinTypes->functionType,
builtinTypes->classType, builtinTypes->tableType,
builtinTypes->bufferType, builtinTypes->classType,
}}))); builtinTypes->bufferType,
}}))
);
} }
TEST_CASE_FIXTURE(ESFixture, "Parent & number") TEST_CASE_FIXTURE(ESFixture, "Parent & number")
{ {
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{ CHECK("never" == simplifyStr(arena->addType(IntersectionType{{parentClass, builtinTypes->numberType}})));
parentClass, builtinTypes->numberType
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "Child & Parent") TEST_CASE_FIXTURE(ESFixture, "Child & Parent")
{ {
CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{ CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{childClass, parentClass}})));
childClass, parentClass
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "Child & Unrelated") TEST_CASE_FIXTURE(ESFixture, "Child & Unrelated")
{ {
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{ CHECK("never" == simplifyStr(arena->addType(IntersectionType{{childClass, unrelatedClass}})));
childClass, unrelatedClass
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "Child | Parent") TEST_CASE_FIXTURE(ESFixture, "Child | Parent")
{ {
CHECK("Parent" == simplifyStr(arena->addType(UnionType{{ CHECK("Parent" == simplifyStr(arena->addType(UnionType{{childClass, parentClass}})));
childClass, parentClass
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "class | Child") TEST_CASE_FIXTURE(ESFixture, "class | Child")
{ {
CHECK("class" == simplifyStr(arena->addType(UnionType{{ CHECK("class" == simplifyStr(arena->addType(UnionType{{builtinTypes->classType, childClass}})));
builtinTypes->classType, childClass
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "Parent | class | Child") TEST_CASE_FIXTURE(ESFixture, "Parent | class | Child")
{ {
CHECK("class" == simplifyStr(arena->addType(UnionType{{ CHECK("class" == simplifyStr(arena->addType(UnionType{{parentClass, builtinTypes->classType, childClass}})));
parentClass, builtinTypes->classType, childClass
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "Parent | Unrelated") TEST_CASE_FIXTURE(ESFixture, "Parent | Unrelated")
{ {
CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{ CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{parentClass, unrelatedClass}})));
parentClass, unrelatedClass
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "never | Parent | Unrelated") TEST_CASE_FIXTURE(ESFixture, "never | Parent | Unrelated")
{ {
CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{ CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{builtinTypes->neverType, parentClass, unrelatedClass}})));
builtinTypes->neverType, parentClass, unrelatedClass
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "never | Parent | (number & string) | Unrelated") TEST_CASE_FIXTURE(ESFixture, "never | Parent | (number & string) | Unrelated")
{ {
CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{ CHECK(
builtinTypes->neverType, parentClass, "Parent | Unrelated" == simplifyStr(arena->addType(UnionType{
arena->addType(IntersectionType{{builtinTypes->numberType, builtinTypes->stringType}}), {builtinTypes->neverType,
unrelatedClass parentClass,
}}))); arena->addType(IntersectionType{{builtinTypes->numberType, builtinTypes->stringType}}),
unrelatedClass}
}))
);
} }
TEST_CASE_FIXTURE(ESFixture, "T & U") TEST_CASE_FIXTURE(ESFixture, "T & U")
{ {
CHECK("T & U" == simplifyStr(arena->addType(IntersectionType{{ CHECK("T & U" == simplifyStr(arena->addType(IntersectionType{{genericT, genericU}})));
genericT, genericU
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "boolean & true") TEST_CASE_FIXTURE(ESFixture, "boolean & true")
{ {
CHECK("true" == simplifyStr(arena->addType(IntersectionType{{ CHECK("true" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->booleanType, builtinTypes->trueType}})));
builtinTypes->booleanType, builtinTypes->trueType
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "boolean & (true | number | string | thread | function | table | class | buffer)") TEST_CASE_FIXTURE(ESFixture, "boolean & (true | number | string | thread | function | table | class | buffer)")
@ -332,23 +314,17 @@ TEST_CASE_FIXTURE(ESFixture, "boolean & (true | number | string | thread | funct
builtinTypes->bufferType, builtinTypes->bufferType,
}}); }});
CHECK("true" == simplifyStr(arena->addType(IntersectionType{{ CHECK("true" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->booleanType, truthy}})));
builtinTypes->booleanType, truthy
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "boolean & ~(false?)") TEST_CASE_FIXTURE(ESFixture, "boolean & ~(false?)")
{ {
CHECK("true" == simplifyStr(arena->addType(IntersectionType{{ CHECK("true" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->booleanType, builtinTypes->truthyType}})));
builtinTypes->booleanType, builtinTypes->truthyType
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "false & ~(false?)") TEST_CASE_FIXTURE(ESFixture, "false & ~(false?)")
{ {
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{ CHECK("never" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->falseType, builtinTypes->truthyType}})));
builtinTypes->falseType, builtinTypes->truthyType
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "(number) -> string & (number) -> string") TEST_CASE_FIXTURE(ESFixture, "(number) -> string & (number) -> string")
@ -399,28 +375,25 @@ TEST_CASE_FIXTURE(ESFixture, "(number) -> string | (string) -> number")
TEST_CASE_FIXTURE(ESFixture, "add<number, number>") TEST_CASE_FIXTURE(ESFixture, "add<number, number>")
{ {
CHECK("number" == simplifyStr(arena->addType( CHECK(
TypeFunctionInstanceType{builtinTypeFunctions().addFunc, { "number" ==
builtinTypes->numberType, builtinTypes->numberType simplifyStr(arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().addFunc, {builtinTypes->numberType, builtinTypes->numberType}}))
}} );
)));
} }
TEST_CASE_FIXTURE(ESFixture, "union<number, number>") TEST_CASE_FIXTURE(ESFixture, "union<number, number>")
{ {
CHECK("number" == simplifyStr(arena->addType( CHECK(
TypeFunctionInstanceType{builtinTypeFunctions().unionFunc, { "number" ==
builtinTypes->numberType, builtinTypes->numberType simplifyStr(arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().unionFunc, {builtinTypes->numberType, builtinTypes->numberType}}))
}} );
)));
} }
TEST_CASE_FIXTURE(ESFixture, "never & ~string") TEST_CASE_FIXTURE(ESFixture, "never & ~string")
{ {
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{ CHECK(
builtinTypes->neverType, "never" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->neverType, arena->addType(NegationType{builtinTypes->stringType})}}))
arena->addType(NegationType{builtinTypes->stringType}) );
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "blocked & never") TEST_CASE_FIXTURE(ESFixture, "blocked & never")
@ -444,7 +417,9 @@ TEST_CASE_FIXTURE(ESFixture, "blocked & ~number & function")
TEST_CASE_FIXTURE(ESFixture, "(number | boolean | string | nil | table) & (false | nil)") TEST_CASE_FIXTURE(ESFixture, "(number | boolean | string | nil | table) & (false | nil)")
{ {
const TypeId t1 = arena->addType(UnionType{{builtinTypes->numberType, builtinTypes->booleanType, builtinTypes->stringType, builtinTypes->nilType, builtinTypes->tableType}}); const TypeId t1 = arena->addType(
UnionType{{builtinTypes->numberType, builtinTypes->booleanType, builtinTypes->stringType, builtinTypes->nilType, builtinTypes->tableType}}
);
CHECK("false?" == simplifyStr(arena->addType(IntersectionType{{t1, builtinTypes->falsyType}}))); CHECK("false?" == simplifyStr(arena->addType(IntersectionType{{t1, builtinTypes->falsyType}})));
} }
@ -493,26 +468,17 @@ TEST_CASE_FIXTURE(ESFixture, "(blocked & number) | (blocked & number)")
TEST_CASE_FIXTURE(ESFixture, "{} & unknown") TEST_CASE_FIXTURE(ESFixture, "{} & unknown")
{ {
CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{ CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{tbl({}), builtinTypes->unknownType}})));
tbl({}),
builtinTypes->unknownType
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "{} & table") TEST_CASE_FIXTURE(ESFixture, "{} & table")
{ {
CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{ CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{tbl({}), builtinTypes->tableType}})));
tbl({}),
builtinTypes->tableType
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "{} & ~(false?)") TEST_CASE_FIXTURE(ESFixture, "{} & ~(false?)")
{ {
CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{ CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{tbl({}), builtinTypes->truthyType}})));
tbl({}),
builtinTypes->truthyType
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "{x: number?} & {x: number}") TEST_CASE_FIXTURE(ESFixture, "{x: number?} & {x: number}")
@ -606,10 +572,7 @@ TEST_CASE_FIXTURE(ESFixture, "{ x: number } & ~boolean")
{ {
const TypeId tblTy = tbl(TableType::Props{{"x", builtinTypes->numberType}}); const TypeId tblTy = tbl(TableType::Props{{"x", builtinTypes->numberType}});
const TypeId ty = arena->addType(IntersectionType{{ const TypeId ty = arena->addType(IntersectionType{{tblTy, arena->addType(NegationType{builtinTypes->booleanType})}});
tblTy,
arena->addType(NegationType{builtinTypes->booleanType})
}});
CHECK("{ x: number }" == simplifyStr(ty)); CHECK("{ x: number }" == simplifyStr(ty));
} }
@ -634,10 +597,7 @@ TEST_CASE_FIXTURE(ESFixture, "string & (\"hi\" | \"bye\")")
const TypeId hi = arena->addType(SingletonType{StringSingleton{"hi"}}); const TypeId hi = arena->addType(SingletonType{StringSingleton{"hi"}});
const TypeId bye = arena->addType(SingletonType{StringSingleton{"bye"}}); const TypeId bye = arena->addType(SingletonType{StringSingleton{"bye"}});
CHECK("\"bye\" | \"hi\"" == simplifyStr(arena->addType(IntersectionType{{ CHECK("\"bye\" | \"hi\"" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->stringType, arena->addType(UnionType{{hi, bye}})}})));
builtinTypes->stringType,
arena->addType(UnionType{{hi, bye}})
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "(\"err\" | \"ok\") & ~\"ok\"") TEST_CASE_FIXTURE(ESFixture, "(\"err\" | \"ok\") & ~\"ok\"")
@ -646,46 +606,32 @@ TEST_CASE_FIXTURE(ESFixture, "(\"err\" | \"ok\") & ~\"ok\"")
TypeId ok1 = arena->addType(SingletonType{StringSingleton{"ok"}}); TypeId ok1 = arena->addType(SingletonType{StringSingleton{"ok"}});
TypeId ok2 = arena->addType(SingletonType{StringSingleton{"ok"}}); TypeId ok2 = arena->addType(SingletonType{StringSingleton{"ok"}});
TypeId ty = arena->addType(IntersectionType{{ TypeId ty = arena->addType(IntersectionType{{arena->addType(UnionType{{err, ok1}}), arena->addType(NegationType{ok2})}});
arena->addType(UnionType{{err, ok1}}),
arena->addType(NegationType{ok2})
}});
CHECK("\"err\"" == simplifyStr(ty)); CHECK("\"err\"" == simplifyStr(ty));
} }
TEST_CASE_FIXTURE(ESFixture, "(Child | Unrelated) & ~Child") TEST_CASE_FIXTURE(ESFixture, "(Child | Unrelated) & ~Child")
{ {
const TypeId ty = arena->addType(IntersectionType{{ const TypeId ty =
arena->addType(UnionType{{childClass, unrelatedClass}}), arena->addType(IntersectionType{{arena->addType(UnionType{{childClass, unrelatedClass}}), arena->addType(NegationType{childClass})}});
arena->addType(NegationType{childClass})
}});
CHECK("Unrelated" == simplifyStr(ty)); CHECK("Unrelated" == simplifyStr(ty));
} }
TEST_CASE_FIXTURE(ESFixture, "string & ~Child") TEST_CASE_FIXTURE(ESFixture, "string & ~Child")
{ {
CHECK("string" == simplifyStr(arena->addType(IntersectionType{{ CHECK("string" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->stringType, arena->addType(NegationType{childClass})}})));
builtinTypes->stringType,
arena->addType(NegationType{childClass})
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "(Child | Unrelated) & Child") TEST_CASE_FIXTURE(ESFixture, "(Child | Unrelated) & Child")
{ {
CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{ CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{arena->addType(UnionType{{childClass, unrelatedClass}}), childClass}})));
arena->addType(UnionType{{childClass, unrelatedClass}}),
childClass
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "(Child | AnotherChild) & ~Child") TEST_CASE_FIXTURE(ESFixture, "(Child | AnotherChild) & ~Child")
{ {
CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{ CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{arena->addType(UnionType{{childClass, anotherChild}}), childClass}})));
arena->addType(UnionType{{childClass, anotherChild}}),
childClass
}})));
} }
TEST_CASE_FIXTURE(ESFixture, "{ tag: \"Part\", x: never }") TEST_CASE_FIXTURE(ESFixture, "{ tag: \"Part\", x: never }")
@ -706,11 +652,7 @@ TEST_CASE_FIXTURE(ESFixture, "{ tag: \"Part\", x: number? } & { x: string }")
TEST_CASE_FIXTURE(ESFixture, "Child & add<Child | AnotherChild | string, Parent>") TEST_CASE_FIXTURE(ESFixture, "Child & add<Child | AnotherChild | string, Parent>")
{ {
const TypeId u = arena->addType(UnionType{{childClass, anotherChild, builtinTypes->stringType}}); const TypeId u = arena->addType(UnionType{{childClass, anotherChild, builtinTypes->stringType}});
const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{ const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().addFunc, {u, parentClass}, {}});
builtinTypeFunctions().addFunc,
{u, parentClass},
{}
});
const TypeId intersection = arena->addType(IntersectionType{{childClass, intersectTf}}); const TypeId intersection = arena->addType(IntersectionType{{childClass, intersectTf}});
@ -720,11 +662,7 @@ TEST_CASE_FIXTURE(ESFixture, "Child & add<Child | AnotherChild | string, Parent>
TEST_CASE_FIXTURE(ESFixture, "Child & intersect<Child | AnotherChild | string, Parent>") TEST_CASE_FIXTURE(ESFixture, "Child & intersect<Child | AnotherChild | string, Parent>")
{ {
const TypeId u = arena->addType(UnionType{{childClass, anotherChild, builtinTypes->stringType}}); const TypeId u = arena->addType(UnionType{{childClass, anotherChild, builtinTypes->stringType}});
const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{ const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().intersectFunc, {u, parentClass}, {}});
builtinTypeFunctions().intersectFunc,
{u, parentClass},
{}
});
const TypeId intersection = arena->addType(IntersectionType{{childClass, intersectTf}}); const TypeId intersection = arena->addType(IntersectionType{{childClass, intersectTf}});
@ -740,7 +678,8 @@ TEST_CASE_FIXTURE(ESFixture, "lt<number, _> == boolean")
{arena->addType(BlockedType{}), builtinTypes->stringType}, {arena->addType(BlockedType{}), builtinTypes->stringType},
}; };
for (const auto& [lhs, rhs] : cases) { for (const auto& [lhs, rhs] : cases)
{
const TypeId tfun = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().ltFunc, {lhs, rhs}}); const TypeId tfun = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().ltFunc, {lhs, rhs}});
CHECK("boolean" == simplifyStr(tfun)); CHECK("boolean" == simplifyStr(tfun));
} }

View file

@ -29,8 +29,7 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests) LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests)
LUAU_FASTFLAG(LuauVectorDefinitionsExtra) LUAU_FASTFLAG(LuauVectorDefinitionsExtra)
#define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) \ #define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests};
ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests};
#define DOES_NOT_PASS_NEW_SOLVER_GUARD() DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(__LINE__) #define DOES_NOT_PASS_NEW_SOLVER_GUARD() DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(__LINE__)

View file

@ -25,6 +25,7 @@ LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAG(LuauSymbolEquality); LUAU_FASTFLAG(LuauSymbolEquality);
LUAU_FASTFLAG(LuauStoreSolverTypeOnModule); LUAU_FASTFLAG(LuauStoreSolverTypeOnModule);
LUAU_FASTFLAG(LexerResumesFromPosition2) LUAU_FASTFLAG(LexerResumesFromPosition2)
LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection)
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ClassType*> ptr, std::optional<std::string> contents) static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ClassType*> ptr, std::optional<std::string> contents)
{ {
@ -91,7 +92,8 @@ struct FragmentAutocompleteFixtureImpl : BaseType
std::optional<Position> fragmentEndPosition = std::nullopt std::optional<Position> fragmentEndPosition = std::nullopt
) )
{ {
return Luau::typecheckFragment(this->frontend, "MainModule", cursorPos, getOptions(), document, fragmentEndPosition); auto [_, result] = Luau::typecheckFragment(this->frontend, "MainModule", cursorPos, getOptions(), document, fragmentEndPosition);
return result;
} }
FragmentAutocompleteResult autocompleteFragment( FragmentAutocompleteResult autocompleteFragment(
@ -1383,4 +1385,177 @@ t
); );
} }
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments_simple")
{
const std::string source = R"(
-- sel
-- retur
-- fo
-- if
-- end
-- the
)";
ScopedFastFlag sff{FFlag::LuauIncrementalAutocompleteCommentDetection, true};
autocompleteFragmentInBothSolvers(
source,
source,
Position{4, 6},
[](FragmentAutocompleteResult& result)
{
CHECK(result.acResults.entryMap.empty());
}
);
}
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments_blocks")
{
const std::string source = R"(
--[[
comment 1
]] local
-- [[ comment 2]]
--
-- sdfsdfsdf
--[[comment 3]]
--[[
foo
bar
baz
]]
)";
ScopedFastFlag sff{FFlag::LuauIncrementalAutocompleteCommentDetection, true};
autocompleteFragmentInBothSolvers(
source,
source,
Position{3, 0},
[](FragmentAutocompleteResult& result)
{
CHECK(result.acResults.entryMap.empty());
}
);
autocompleteFragmentInBothSolvers(
source,
source,
Position{3, 2},
[](FragmentAutocompleteResult& result)
{
CHECK(!result.acResults.entryMap.empty());
}
);
autocompleteFragmentInBothSolvers(
source,
source,
Position{8, 6},
[](FragmentAutocompleteResult& result)
{
CHECK(result.acResults.entryMap.empty());
}
);
autocompleteFragmentInBothSolvers(
source,
source,
Position{10, 0},
[](FragmentAutocompleteResult& result)
{
CHECK(result.acResults.entryMap.empty());
}
);
}
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments")
{
const std::string source = R"(
-- sel
-- retur
-- fo
--[[ sel ]]
local -- hello
)";
ScopedFastFlag sff{FFlag::LuauIncrementalAutocompleteCommentDetection, true};
autocompleteFragmentInBothSolvers(
source,
source,
Position{1, 7},
[](FragmentAutocompleteResult& result)
{
CHECK(result.acResults.entryMap.empty());
}
);
autocompleteFragmentInBothSolvers(
source,
source,
Position{2, 9},
[](FragmentAutocompleteResult& result)
{
CHECK(result.acResults.entryMap.empty());
}
);
autocompleteFragmentInBothSolvers(
source,
source,
Position{3, 6},
[](FragmentAutocompleteResult& result)
{
CHECK(result.acResults.entryMap.empty());
}
);
autocompleteFragmentInBothSolvers(
source,
source,
Position{4, 9},
[](FragmentAutocompleteResult& result)
{
CHECK(result.acResults.entryMap.empty());
}
);
autocompleteFragmentInBothSolvers(
source,
source,
Position{5, 6},
[](FragmentAutocompleteResult& result)
{
CHECK(!result.acResults.entryMap.empty());
}
);
autocompleteFragmentInBothSolvers(
source,
source,
Position{5, 14},
[](FragmentAutocompleteResult& result)
{
CHECK(result.acResults.entryMap.empty());
}
);
}
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments_in_incremental_fragment")
{
const std::string source = R"(
local x = 5
if x == 5
)";
const std::string updated = R"(
local x = 5
if x == 5 then -- a comment
)";
ScopedFastFlag sff{FFlag::LuauIncrementalAutocompleteCommentDetection, true};
autocompleteFragmentInBothSolvers(
source,
updated,
Position{2, 28},
[](FragmentAutocompleteResult& result)
{
CHECK(result.acResults.entryMap.empty());
}
);
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -520,9 +520,9 @@ bb_bytecode_0:
JUMP bb_2 JUMP bb_2
bb_2: bb_2:
CHECK_SAFE_ENV exit(3) CHECK_SAFE_ENV exit(3)
JUMP_EQ_TAG K1, tnil, bb_fallback_4, bb_3 JUMP_EQ_TAG K1 (nil), tnil, bb_fallback_4, bb_3
bb_3: bb_3:
%9 = LOAD_TVALUE K1 %9 = LOAD_TVALUE K1 (nil)
STORE_TVALUE R1, %9 STORE_TVALUE R1, %9
JUMP bb_5 JUMP bb_5
bb_5: bb_5:
@ -575,7 +575,7 @@ bb_0:
bb_2: bb_2:
JUMP bb_bytecode_1 JUMP bb_bytecode_1
bb_bytecode_1: bb_bytecode_1:
%4 = LOAD_TVALUE K0, 0i, tvector %4 = LOAD_TVALUE K0 (1, 2, 3), 0i, tvector
%11 = LOAD_TVALUE R0 %11 = LOAD_TVALUE R0
%12 = ADD_VEC %4, %11 %12 = ADD_VEC %4, %11
%13 = TAG_VECTOR %12 %13 = TAG_VECTOR %12
@ -602,7 +602,7 @@ bb_0:
bb_2: bb_2:
JUMP bb_bytecode_1 JUMP bb_bytecode_1
bb_bytecode_1: bb_bytecode_1:
FALLBACK_NAMECALL 0u, R1, R0, K0 FALLBACK_NAMECALL 0u, R1, R0, K0 ('Abs')
INTERRUPT 2u INTERRUPT 2u
SET_SAVEDPC 3u SET_SAVEDPC 3u
CALL R1, 1i, -1i CALL R1, 1i, -1i
@ -628,8 +628,8 @@ bb_0:
bb_2: bb_2:
JUMP bb_bytecode_1 JUMP bb_bytecode_1
bb_bytecode_1: bb_bytecode_1:
FALLBACK_GETTABLEKS 0u, R3, R0, K0 FALLBACK_GETTABLEKS 0u, R3, R0, K0 ('XX')
FALLBACK_GETTABLEKS 2u, R4, R0, K1 FALLBACK_GETTABLEKS 2u, R4, R0, K1 ('YY')
CHECK_TAG R3, tnumber, bb_fallback_3 CHECK_TAG R3, tnumber, bb_fallback_3
CHECK_TAG R4, tnumber, bb_fallback_3 CHECK_TAG R4, tnumber, bb_fallback_3
%14 = LOAD_DOUBLE R3 %14 = LOAD_DOUBLE R3
@ -639,7 +639,7 @@ bb_bytecode_1:
JUMP bb_4 JUMP bb_4
bb_4: bb_4:
CHECK_TAG R0, tvector, exit(5) CHECK_TAG R0, tvector, exit(5)
FALLBACK_GETTABLEKS 5u, R3, R0, K2 FALLBACK_GETTABLEKS 5u, R3, R0, K2 ('ZZ')
CHECK_TAG R2, tnumber, bb_fallback_5 CHECK_TAG R2, tnumber, bb_fallback_5
CHECK_TAG R3, tnumber, bb_fallback_5 CHECK_TAG R3, tnumber, bb_fallback_5
%30 = LOAD_DOUBLE R2 %30 = LOAD_DOUBLE R2
@ -857,8 +857,8 @@ bb_2:
JUMP bb_bytecode_1 JUMP bb_bytecode_1
bb_bytecode_1: bb_bytecode_1:
%8 = LOAD_POINTER R0 %8 = LOAD_POINTER R0
%9 = GET_SLOT_NODE_ADDR %8, 0u, K1 %9 = GET_SLOT_NODE_ADDR %8, 0u, K1 ('n')
CHECK_SLOT_MATCH %9, K1, bb_fallback_3 CHECK_SLOT_MATCH %9, K1 ('n'), bb_fallback_3
%11 = LOAD_TVALUE %9, 0i %11 = LOAD_TVALUE %9, 0i
STORE_TVALUE R3, %11 STORE_TVALUE R3, %11
JUMP bb_4 JUMP bb_4
@ -885,8 +885,8 @@ bb_4:
STORE_VECTOR R3, %30, %33, %36 STORE_VECTOR R3, %30, %33, %36
CHECK_TAG R0, ttable, exit(6) CHECK_TAG R0, ttable, exit(6)
%41 = LOAD_POINTER R0 %41 = LOAD_POINTER R0
%42 = GET_SLOT_NODE_ADDR %41, 6u, K3 %42 = GET_SLOT_NODE_ADDR %41, 6u, K3 ('b')
CHECK_SLOT_MATCH %42, K3, bb_fallback_5 CHECK_SLOT_MATCH %42, K3 ('b'), bb_fallback_5
%44 = LOAD_TVALUE %42, 0i %44 = LOAD_TVALUE %42, 0i
STORE_TVALUE R5, %44 STORE_TVALUE R5, %44
JUMP bb_6 JUMP bb_6
@ -929,8 +929,8 @@ bb_0:
bb_2: bb_2:
JUMP bb_bytecode_1 JUMP bb_bytecode_1
bb_bytecode_1: bb_bytecode_1:
FALLBACK_GETTABLEKS 0u, R2, R0, K0 FALLBACK_GETTABLEKS 0u, R2, R0, K0 ('x')
FALLBACK_GETTABLEKS 2u, R3, R0, K1 FALLBACK_GETTABLEKS 2u, R3, R0, K1 ('y')
CHECK_TAG R2, tnumber, bb_fallback_3 CHECK_TAG R2, tnumber, bb_fallback_3
CHECK_TAG R3, tnumber, bb_fallback_3 CHECK_TAG R3, tnumber, bb_fallback_3
%14 = LOAD_DOUBLE R2 %14 = LOAD_DOUBLE R2
@ -964,9 +964,9 @@ bb_2:
bb_bytecode_1: bb_bytecode_1:
STORE_DOUBLE R1, 3 STORE_DOUBLE R1, 3
STORE_TAG R1, tnumber STORE_TAG R1, tnumber
FALLBACK_SETTABLEKS 1u, R1, R0, K0 FALLBACK_SETTABLEKS 1u, R1, R0, K0 ('x')
STORE_DOUBLE R1, 4 STORE_DOUBLE R1, 4
FALLBACK_SETTABLEKS 4u, R1, R0, K1 FALLBACK_SETTABLEKS 4u, R1, R0, K1 ('y')
INTERRUPT 6u INTERRUPT 6u
RETURN R0, 0i RETURN R0, 0i
)" )"
@ -989,11 +989,11 @@ bb_0:
bb_2: bb_2:
JUMP bb_bytecode_1 JUMP bb_bytecode_1
bb_bytecode_1: bb_bytecode_1:
FALLBACK_NAMECALL 0u, R2, R0, K0 FALLBACK_NAMECALL 0u, R2, R0, K0 ('GetX')
INTERRUPT 2u INTERRUPT 2u
SET_SAVEDPC 3u SET_SAVEDPC 3u
CALL R2, 1i, 1i CALL R2, 1i, 1i
FALLBACK_NAMECALL 3u, R3, R0, K1 FALLBACK_NAMECALL 3u, R3, R0, K1 ('GetY')
INTERRUPT 5u INTERRUPT 5u
SET_SAVEDPC 6u SET_SAVEDPC 6u
CALL R3, 1i, 1i CALL R3, 1i, 1i
@ -1367,8 +1367,8 @@ bb_bytecode_1:
bb_4: bb_4:
CHECK_TAG R2, ttable, exit(1) CHECK_TAG R2, ttable, exit(1)
%23 = LOAD_POINTER R2 %23 = LOAD_POINTER R2
%24 = GET_SLOT_NODE_ADDR %23, 1u, K0 %24 = GET_SLOT_NODE_ADDR %23, 1u, K0 ('pos')
CHECK_SLOT_MATCH %24, K0, bb_fallback_5 CHECK_SLOT_MATCH %24, K0 ('pos'), bb_fallback_5
%26 = LOAD_TVALUE %24, 0i %26 = LOAD_TVALUE %24, 0i
STORE_TVALUE R4, %26 STORE_TVALUE R4, %26
JUMP bb_6 JUMP bb_6
@ -1476,13 +1476,13 @@ bb_bytecode_1:
bb_4: bb_4:
CHECK_TAG R3, ttable, bb_fallback_5 CHECK_TAG R3, ttable, bb_fallback_5
%23 = LOAD_POINTER R3 %23 = LOAD_POINTER R3
%24 = GET_SLOT_NODE_ADDR %23, 1u, K0 %24 = GET_SLOT_NODE_ADDR %23, 1u, K0 ('normal')
CHECK_SLOT_MATCH %24, K0, bb_fallback_5 CHECK_SLOT_MATCH %24, K0 ('normal'), bb_fallback_5
%26 = LOAD_TVALUE %24, 0i %26 = LOAD_TVALUE %24, 0i
STORE_TVALUE R2, %26 STORE_TVALUE R2, %26
JUMP bb_6 JUMP bb_6
bb_6: bb_6:
%31 = LOAD_TVALUE K1, 0i, tvector %31 = LOAD_TVALUE K1 (0.707000017, 0, 0.707000017), 0i, tvector
STORE_TVALUE R4, %31 STORE_TVALUE R4, %31
CHECK_TAG R2, tvector, exit(4) CHECK_TAG R2, tvector, exit(4)
%37 = LOAD_FLOAT R2, 0i %37 = LOAD_FLOAT R2, 0i
@ -1603,9 +1603,9 @@ bb_bytecode_1:
STORE_DOUBLE R1, 0 STORE_DOUBLE R1, 0
STORE_TAG R1, tnumber STORE_TAG R1, tnumber
CHECK_SAFE_ENV exit(1) CHECK_SAFE_ENV exit(1)
JUMP_EQ_TAG K1, tnil, bb_fallback_6, bb_5 JUMP_EQ_TAG K1 (nil), tnil, bb_fallback_6, bb_5
bb_5: bb_5:
%9 = LOAD_TVALUE K1 %9 = LOAD_TVALUE K1 (nil)
STORE_TVALUE R2, %9 STORE_TVALUE R2, %9
JUMP bb_7 JUMP bb_7
bb_7: bb_7:
@ -1627,8 +1627,8 @@ bb_9:
bb_bytecode_2: bb_bytecode_2:
CHECK_TAG R6, ttable, exit(6) CHECK_TAG R6, ttable, exit(6)
%35 = LOAD_POINTER R6 %35 = LOAD_POINTER R6
%36 = GET_SLOT_NODE_ADDR %35, 6u, K2 %36 = GET_SLOT_NODE_ADDR %35, 6u, K2 ('pos')
CHECK_SLOT_MATCH %36, K2, bb_fallback_10 CHECK_SLOT_MATCH %36, K2 ('pos'), bb_fallback_10
%38 = LOAD_TVALUE %36, 0i %38 = LOAD_TVALUE %36, 0i
STORE_TVALUE R8, %38 STORE_TVALUE R8, %38
JUMP bb_11 JUMP bb_11
@ -1829,8 +1829,8 @@ bb_0:
bb_2: bb_2:
JUMP bb_bytecode_1 JUMP bb_bytecode_1
bb_bytecode_1: bb_bytecode_1:
FALLBACK_GETTABLEKS 0u, R2, R0, K0 FALLBACK_GETTABLEKS 0u, R2, R0, K0 ('Row1')
FALLBACK_GETTABLEKS 2u, R3, R0, K1 FALLBACK_GETTABLEKS 2u, R3, R0, K1 ('Row2')
CHECK_TAG R2, tvector, exit(4) CHECK_TAG R2, tvector, exit(4)
CHECK_TAG R3, tvector, exit(4) CHECK_TAG R3, tvector, exit(4)
%14 = LOAD_TVALUE R2 %14 = LOAD_TVALUE R2
@ -2138,10 +2138,10 @@ bb_0:
bb_2: bb_2:
JUMP bb_bytecode_1 JUMP bb_bytecode_1
bb_bytecode_1: bb_bytecode_1:
%4 = LOAD_TVALUE K0, 0i, tvector %4 = LOAD_TVALUE K0 (1, 0, 0), 0i, tvector
%11 = LOAD_TVALUE R0 %11 = LOAD_TVALUE R0
%12 = MUL_VEC %4, %11 %12 = MUL_VEC %4, %11
%15 = LOAD_TVALUE K1, 0i, tvector %15 = LOAD_TVALUE K1 (0, 1, 0), 0i, tvector
%23 = ADD_VEC %12, %15 %23 = ADD_VEC %12, %15
%24 = TAG_VECTOR %23 %24 = TAG_VECTOR %23
STORE_TVALUE R1, %24 STORE_TVALUE R1, %24
@ -2176,7 +2176,7 @@ bb_0:
bb_2: bb_2:
JUMP bb_bytecode_1 JUMP bb_bytecode_1
bb_bytecode_1: bb_bytecode_1:
%4 = LOAD_TVALUE K0, 0i, tvector %4 = LOAD_TVALUE K0 (0, 0, 0), 0i, tvector
%11 = LOAD_TVALUE R0 %11 = LOAD_TVALUE R0
%12 = ADD_VEC %4, %11 %12 = ADD_VEC %4, %11
%13 = TAG_VECTOR %12 %13 = TAG_VECTOR %12
@ -2208,9 +2208,9 @@ bb_bytecode_0:
STORE_TAG R1, tboolean STORE_TAG R1, tboolean
STORE_DOUBLE R2, 4.75 STORE_DOUBLE R2, 4.75
STORE_TAG R2, tnumber STORE_TAG R2, tnumber
%5 = LOAD_TVALUE K1, 0i, tvector %5 = LOAD_TVALUE K1 (1, 2, 4), 0i, tvector
STORE_TVALUE R3, %5 STORE_TVALUE R3, %5
%7 = LOAD_TVALUE K2, 0i, tstring %7 = LOAD_TVALUE K2 ('test'), 0i, tstring
STORE_TVALUE R4, %7 STORE_TVALUE R4, %7
INTERRUPT 5u INTERRUPT 5u
RETURN R0, 5i RETURN R0, 5i

View file

@ -8,6 +8,8 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LexerFixInterpStringStart)
TEST_SUITE_BEGIN("LexerTests"); TEST_SUITE_BEGIN("LexerTests");
TEST_CASE("broken_string_works") TEST_CASE("broken_string_works")
@ -153,6 +155,8 @@ TEST_CASE("string_interpolation_basic")
Lexeme interpEnd = lexer.next(); Lexeme interpEnd = lexer.next();
CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd); CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd);
// The InterpStringEnd should start with }, not `.
CHECK_EQ(interpEnd.location.begin.column, FFlag::LexerFixInterpStringStart ? 11 : 12);
} }
TEST_CASE("string_interpolation_full") TEST_CASE("string_interpolation_full")
@ -173,6 +177,7 @@ TEST_CASE("string_interpolation_full")
Lexeme interpMid = lexer.next(); Lexeme interpMid = lexer.next();
CHECK_EQ(interpMid.type, Lexeme::InterpStringMid); CHECK_EQ(interpMid.type, Lexeme::InterpStringMid);
CHECK_EQ(interpMid.toString(), "} {"); CHECK_EQ(interpMid.toString(), "} {");
CHECK_EQ(interpMid.location.begin.column, FFlag::LexerFixInterpStringStart ? 11 : 12);
Lexeme quote2 = lexer.next(); Lexeme quote2 = lexer.next();
CHECK_EQ(quote2.type, Lexeme::QuotedString); CHECK_EQ(quote2.type, Lexeme::QuotedString);
@ -181,6 +186,7 @@ TEST_CASE("string_interpolation_full")
Lexeme interpEnd = lexer.next(); Lexeme interpEnd = lexer.next();
CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd); CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd);
CHECK_EQ(interpEnd.toString(), "} end`"); CHECK_EQ(interpEnd.toString(), "} end`");
CHECK_EQ(interpEnd.location.begin.column, FFlag::LexerFixInterpStringStart ? 19 : 20);
} }
TEST_CASE("string_interpolation_double_brace") TEST_CASE("string_interpolation_double_brace")

View file

@ -16,7 +16,6 @@ LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTINT(LuauTypeLengthLimit) LUAU_FASTINT(LuauTypeLengthLimit)
LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauUserDefinedTypeFunParseExport)
LUAU_FASTFLAG(LuauAllowComplexTypesInGenericParams) LUAU_FASTFLAG(LuauAllowComplexTypesInGenericParams)
LUAU_FASTFLAG(LuauErrorRecoveryForTableTypes) LUAU_FASTFLAG(LuauErrorRecoveryForTableTypes)
LUAU_FASTFLAG(LuauErrorRecoveryForClassNames) LUAU_FASTFLAG(LuauErrorRecoveryForClassNames)
@ -447,38 +446,62 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_span_is_correct")
TEST_CASE_FIXTURE(Fixture, "parse_error_messages") TEST_CASE_FIXTURE(Fixture, "parse_error_messages")
{ {
matchParseError(R"( matchParseError(
R"(
local a: (number, number) -> (string local a: (number, number) -> (string
)", "Expected ')' (to close '(' at line 2), got <eof>"); )",
"Expected ')' (to close '(' at line 2), got <eof>"
);
matchParseError(R"( matchParseError(
R"(
local a: (number, number) -> ( local a: (number, number) -> (
string string
)", "Expected ')' (to close '(' at line 2), got <eof>"); )",
"Expected ')' (to close '(' at line 2), got <eof>"
);
matchParseError(R"( matchParseError(
R"(
local a: (number, number) local a: (number, number)
)", "Expected '->' when parsing function type, got <eof>"); )",
"Expected '->' when parsing function type, got <eof>"
);
matchParseError(R"( matchParseError(
R"(
local a: (number, number local a: (number, number
)", "Expected ')' (to close '(' at line 2), got <eof>"); )",
"Expected ')' (to close '(' at line 2), got <eof>"
);
matchParseError(R"( matchParseError(
R"(
local a: {foo: string, local a: {foo: string,
)", "Expected identifier when parsing table field, got <eof>"); )",
"Expected identifier when parsing table field, got <eof>"
);
matchParseError(R"( matchParseError(
R"(
local a: {foo: string local a: {foo: string
)", "Expected '}' (to close '{' at line 2), got <eof>"); )",
"Expected '}' (to close '{' at line 2), got <eof>"
);
matchParseError(R"( matchParseError(
R"(
local a: { [string]: number, [number]: string } local a: { [string]: number, [number]: string }
)", "Cannot have more than one table indexer"); )",
"Cannot have more than one table indexer"
);
matchParseError(R"( matchParseError(
R"(
type T = <a>foo type T = <a>foo
)", "Expected '(' when parsing function parameters, got 'foo'"); )",
"Expected '(' when parsing function parameters, got 'foo'"
);
} }
TEST_CASE_FIXTURE(Fixture, "mixed_intersection_and_union_not_allowed") TEST_CASE_FIXTURE(Fixture, "mixed_intersection_and_union_not_allowed")
@ -613,9 +636,12 @@ TEST_CASE_FIXTURE(Fixture, "vertical_space")
TEST_CASE_FIXTURE(Fixture, "parse_error_type_name") TEST_CASE_FIXTURE(Fixture, "parse_error_type_name")
{ {
matchParseError(R"( matchParseError(
R"(
local a: Foo.= local a: Foo.=
)", "Expected identifier when parsing field name, got '='"); )",
"Expected identifier when parsing field name, got '='"
);
} }
TEST_CASE_FIXTURE(Fixture, "parse_numbers_decimal") TEST_CASE_FIXTURE(Fixture, "parse_numbers_decimal")
@ -677,9 +703,12 @@ TEST_CASE_FIXTURE(Fixture, "break_return_not_last_error")
TEST_CASE_FIXTURE(Fixture, "error_on_unicode") TEST_CASE_FIXTURE(Fixture, "error_on_unicode")
{ {
matchParseError(R"( matchParseError(
R"(
local = 10 local = 10
)", "Expected identifier when parsing variable name, got Unicode character U+2603"); )",
"Expected identifier when parsing variable name, got Unicode character U+2603"
);
} }
TEST_CASE_FIXTURE(Fixture, "allow_unicode_in_string") TEST_CASE_FIXTURE(Fixture, "allow_unicode_in_string")
@ -690,9 +719,12 @@ TEST_CASE_FIXTURE(Fixture, "allow_unicode_in_string")
TEST_CASE_FIXTURE(Fixture, "error_on_confusable") TEST_CASE_FIXTURE(Fixture, "error_on_confusable")
{ {
matchParseError(R"( matchParseError(
R"(
local pi = 313 local pi = 313
)", "Expected identifier when parsing expression, got Unicode character U+2024 (did you mean '.'?)"); )",
"Expected identifier when parsing expression, got Unicode character U+2024 (did you mean '.'?)"
);
} }
TEST_CASE_FIXTURE(Fixture, "error_on_non_utf8_sequence") TEST_CASE_FIXTURE(Fixture, "error_on_non_utf8_sequence")
@ -2341,8 +2373,6 @@ TEST_CASE_FIXTURE(Fixture, "invalid_type_forms")
TEST_CASE_FIXTURE(Fixture, "parse_user_defined_type_functions") TEST_CASE_FIXTURE(Fixture, "parse_user_defined_type_functions")
{ {
ScopedFastFlag sff2{FFlag::LuauUserDefinedTypeFunParseExport, true};
AstStat* stat = parse(R"( AstStat* stat = parse(R"(
type function foo() type function foo()
return types.number return types.number
@ -3656,7 +3686,7 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
auto unionTy = paramTy.type->as<AstTypeUnion>(); auto unionTy = paramTy.type->as<AstTypeUnion>();
LUAU_ASSERT(unionTy); LUAU_ASSERT(unionTy);
CHECK_EQ(unionTy->types.size, 2); CHECK_EQ(unionTy->types.size, 2);
CHECK(unionTy->types.data[0]->is<AstTypeFunction>()); // () -> () CHECK(unionTy->types.data[0]->is<AstTypeFunction>()); // () -> ()
CHECK(unionTy->types.data[1]->is<AstTypeReference>()); // nil CHECK(unionTy->types.data[1]->is<AstTypeReference>()); // nil
} }
@ -3702,11 +3732,14 @@ TEST_CASE_FIXTURE(Fixture, "recover_from_bad_table_type")
ScopedFastFlag _{FFlag::LuauErrorRecoveryForTableTypes, true}; ScopedFastFlag _{FFlag::LuauErrorRecoveryForTableTypes, true};
ParseOptions opts; ParseOptions opts;
opts.allowDeclarationSyntax = true; opts.allowDeclarationSyntax = true;
const auto result = tryParse(R"( const auto result = tryParse(
R"(
declare class Widget declare class Widget
state: {string: function(string, Widget)} state: {string: function(string, Widget)}
end end
)", opts); )",
opts
);
CHECK_EQ(result.errors.size(), 2); CHECK_EQ(result.errors.size(), 2);
} }

View file

@ -13,8 +13,6 @@
#include <string> #include <string>
#include <vector> #include <vector>
LUAU_FASTFLAG(LuauMathMap)
struct Completion struct Completion
{ {
std::string completion; std::string completion;
@ -175,7 +173,7 @@ TEST_CASE_FIXTURE(ReplFixture, "CompleteGlobalVariables")
CHECK(checkCompletion(completions, prefix, "myvariable1")); CHECK(checkCompletion(completions, prefix, "myvariable1"));
CHECK(checkCompletion(completions, prefix, "myvariable2")); CHECK(checkCompletion(completions, prefix, "myvariable2"));
} }
if (FFlag::LuauMathMap)
{ {
// Try completing some builtin functions // Try completing some builtin functions
CompletionSet completions = getCompletionSet("math.m"); CompletionSet completions = getCompletionSet("math.m");

View file

@ -211,8 +211,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "exhaustive_toString_of_cyclic_table")
CHECK( CHECK(
"t2 where " "t2 where "
"t1 = { __index: t1, __mul: ((t2, number) -> t2) & ((t2, t2) -> t2), new: () -> t2 } ; " "t1 = { __index: t1, __mul: ((t2, number) -> t2) & ((t2, t2) -> t2), new: () -> t2 } ; "
"t2 = { @metatable t1, { x: number, y: number, z: number } }" == "t2 = { @metatable t1, { x: number, y: number, z: number } }" == a
a
); );
} }
else else

View file

@ -11,7 +11,6 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauUserTypeFunFixNoReadWrite) LUAU_FASTFLAG(LuauUserTypeFunFixNoReadWrite)
LUAU_FASTFLAG(LuauUserTypeFunPrintToError) LUAU_FASTFLAG(LuauUserTypeFunPrintToError)
LUAU_FASTFLAG(LuauUserTypeFunExportedAndLocal) LUAU_FASTFLAG(LuauUserTypeFunExportedAndLocal)
LUAU_FASTFLAG(LuauUserDefinedTypeFunParseExport)
LUAU_FASTFLAG(LuauUserTypeFunThreadBuffer) LUAU_FASTFLAG(LuauUserTypeFunThreadBuffer)
LUAU_FASTFLAG(LuauUserTypeFunUpdateAllEnvs) LUAU_FASTFLAG(LuauUserTypeFunUpdateAllEnvs)
@ -1309,7 +1308,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "explicit_export")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunExportedAndLocal{FFlag::LuauUserTypeFunExportedAndLocal, true}; ScopedFastFlag luauUserTypeFunExportedAndLocal{FFlag::LuauUserTypeFunExportedAndLocal, true};
ScopedFastFlag luauUserDefinedTypeFunParseExport{FFlag::LuauUserDefinedTypeFunParseExport, true};
fileResolver.source["game/A"] = R"( fileResolver.source["game/A"] = R"(
export type function concat(a, b) export type function concat(a, b)

View file

@ -665,12 +665,11 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes")
)"); )");
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
CHECK( CHECK("Type 'boolean' could not be converted into 'number | string'" == toString(result.errors.at(0)));
"Type 'boolean' could not be converted into 'number | string'" == toString(result.errors.at(0))
);
else else
CHECK_EQ( CHECK_EQ(
toString(result.errors.at(0)), "Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible" toString(result.errors.at(0)),
"Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible"
); );
} }
{ {
@ -680,12 +679,11 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes")
)"); )");
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
CHECK( CHECK("Type 'boolean' could not be converted into 'number | string'" == toString(result.errors.at(0)));
"Type 'boolean' could not be converted into 'number | string'" == toString(result.errors.at(0))
);
else else
CHECK_EQ( CHECK_EQ(
toString(result.errors.at(0)), "Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible" toString(result.errors.at(0)),
"Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible"
); );
} }

View file

@ -9,6 +9,8 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauNewSolverPrePopulateClasses)
TEST_SUITE_BEGIN("DefinitionTests"); TEST_SUITE_BEGIN("DefinitionTests");
TEST_CASE_FIXTURE(Fixture, "definition_file_simple") TEST_CASE_FIXTURE(Fixture, "definition_file_simple")
@ -492,11 +494,8 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_indexer")
TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes") TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
{ {
unfreeze(frontend.globals.globalTypes); ScopedFastFlag _{FFlag::LuauNewSolverPrePopulateClasses, true};
LoadDefinitionFileResult result = frontend.loadDefinitionFile( loadDefinition(R"(
frontend.globals,
frontend.globals.globalScope,
R"(
declare class Channel declare class Channel
Messages: { Message } Messages: { Message }
OnMessage: (message: Message) -> () OnMessage: (message: Message) -> ()
@ -506,13 +505,19 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
Text: string Text: string
Channel: Channel Channel: Channel
end end
)", )");
"@test",
/* captureComments */ false
);
freeze(frontend.globals.globalTypes);
REQUIRE(result.success); CheckResult result = check(R"(
local a: Channel
local b = a.Messages[1]
local c = b.Channel
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "Channel");
CHECK_EQ(toString(requireType("b")), "Message");
CHECK_EQ(toString(requireType("c")), "Channel");
} }
TEST_CASE_FIXTURE(Fixture, "definition_file_has_source_module_name_set") TEST_CASE_FIXTURE(Fixture, "definition_file_has_source_module_name_set")

View file

@ -2566,10 +2566,7 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_return_type") TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_return_type")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDontRefCountTypesInTypeFunctions, true}};
{FFlag::LuauSolverV2, true},
{FFlag::LuauDontRefCountTypesInTypeFunctions, true}
};
// CLI-114134: This test: // CLI-114134: This test:
// a) Has a kind of weird result (suggesting `number | false` is not great); // a) Has a kind of weird result (suggesting `number | false` is not great);

View file

@ -800,7 +800,10 @@ TEST_CASE_FIXTURE(Fixture, "strict_binary_op_where_lhs_unknown")
"Operator '+' could not be applied to operands of types unknown and unknown; there is no corresponding overload for __add", "Operator '+' could not be applied to operands of types unknown and unknown; there is no corresponding overload for __add",
toString(result.errors[0]) toString(result.errors[0])
); );
CHECK_EQ("Operator '-' could not be applied to operands of types unknown and unknown; there is no corresponding overload for __sub", toString(result.errors[1])); CHECK_EQ(
"Operator '-' could not be applied to operands of types unknown and unknown; there is no corresponding overload for __sub",
toString(result.errors[1])
);
} }
else else
{ {

View file

@ -12,8 +12,6 @@
#include "doctest.h" #include "doctest.h"
LUAU_FASTFLAG(LuauVectorDefinitions)
using namespace Luau; using namespace Luau;
TEST_SUITE_BEGIN("TypeInferPrimitives"); TEST_SUITE_BEGIN("TypeInferPrimitives");

View file

@ -69,9 +69,9 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
const std::string expectedWithEqSat = R"( const std::string expectedWithEqSat = R"(
function f(a:{fn:()->(unknown,...unknown)}): () function f(a:{fn:()->(unknown,...unknown)}): ()
if type(a) == 'boolean'then if type(a) == 'boolean'then
local a1:never=a local a1:{fn:()->(unknown,...unknown)}&boolean=a
elseif a.fn()then elseif a.fn()then
local a2:{fn:()->(unknown,...unknown)}=a local a2:{fn:()->(unknown,...unknown)}&negate<boolean>=a
end end
end end
)"; )";

View file

@ -451,10 +451,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "call_an_incompatible_function_after_using_ty
LUAU_REQUIRE_ERROR_COUNT(2, result); LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK("Type 'string' could not be converted into 'number'" == toString(result.errors[0])); CHECK("Type 'string' could not be converted into 'number'" == toString(result.errors[0]));
CHECK(Location{{ 7, 18}, {7, 19}} == result.errors[0].location); CHECK(Location{{7, 18}, {7, 19}} == result.errors[0].location);
CHECK("Type 'string' could not be converted into 'number'" == toString(result.errors[1])); CHECK("Type 'string' could not be converted into 'number'" == toString(result.errors[1]));
CHECK(Location{{ 13, 18}, {13, 19}} == result.errors[1].location); CHECK(Location{{13, 18}, {13, 19}} == result.errors[1].location);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "impossible_type_narrow_is_not_an_error") TEST_CASE_FIXTURE(BuiltinsFixture, "impossible_type_narrow_is_not_an_error")
@ -742,11 +742,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
// CLI-115281 Types produced by refinements do not consistently get simplified // CLI-115281 Types produced by refinements do not consistently get simplified
CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil" CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil"
CHECK_EQ("(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil" CHECK_EQ(
"(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({6, 24}))
); // type(v) ~= "nil"
CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil" CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil"
CHECK_EQ("(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil" CHECK_EQ(
"(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({12, 24}))
); // equivalent to type(v) ~= "nil"
} }
else else
{ {

View file

@ -21,6 +21,7 @@ LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAG(LuauRetrySubtypingWithoutHiddenPack) LUAU_FASTFLAG(LuauRetrySubtypingWithoutHiddenPack)
LUAU_FASTFLAG(LuauTableKeysAreRValues) LUAU_FASTFLAG(LuauTableKeysAreRValues)
LUAU_FASTFLAG(LuauAllowNilAssignmentToIndexer) LUAU_FASTFLAG(LuauAllowNilAssignmentToIndexer)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
TEST_SUITE_BEGIN("TableTests"); TEST_SUITE_BEGIN("TableTests");
@ -3815,6 +3816,8 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compati
TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible") TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible")
{ {
ScopedFastFlag _{FFlag::LuauTrackInteriorFreeTypesOnScope, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(s): string local function f(s): string
local foo = s:absolutely_no_scalar_has_this_method() local foo = s:absolutely_no_scalar_has_this_method()
@ -3824,17 +3827,14 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
LUAU_REQUIRE_ERROR_COUNT(4, result); LUAU_REQUIRE_ERROR_COUNT(3, result);
CHECK(toString(result.errors[0]) == "Parameter 's' has been reduced to never. This function is not callable with any possible value."); CHECK(toString(result.errors[0]) == "Parameter 's' has been reduced to never. This function is not callable with any possible value.");
// FIXME: These free types should have been generalized by now.
CHECK( CHECK(
toString(result.errors[1]) == toString(result.errors[1]) ==
"Parameter 's' is required to be a subtype of '{- read absolutely_no_scalar_has_this_method: ('a <: (never) -> ('b, c...)) -}' here." "Parameter 's' is required to be a subtype of '{- read absolutely_no_scalar_has_this_method: (never) -> (unknown, ...unknown) -}' here."
); );
CHECK(toString(result.errors[2]) == "Parameter 's' is required to be a subtype of 'string' here."); CHECK(toString(result.errors[2]) == "Parameter 's' is required to be a subtype of 'string' here.");
CHECK(get<CannotCallNonFunction>(result.errors[3]));
CHECK_EQ("(never) -> string", toString(requireType("f"))); CHECK_EQ("(never) -> string", toString(requireType("f")));
} }
else else
@ -5002,7 +5002,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ( CHECK_EQ(
"Cannot add indexer to table '{ @metatable t1, (nil & ~(false?)) | { } } where t1 = { new: <a>(a) -> { @metatable t1, (a & ~(false?)) | { } } }'", "Cannot add indexer to table '{ @metatable t1, (nil & ~(false?)) | { } } where t1 = { new: <a>(a) -> { @metatable t1, (a & ~(false?)) | { "
"} } }'",
toString(result.errors[0]) toString(result.errors[0])
); );
} }

View file

@ -1709,10 +1709,7 @@ TEST_CASE_FIXTURE(Fixture, "react_lua_follow_free_type_ub")
TEST_CASE_FIXTURE(Fixture, "visit_error_nodes_in_lvalue") TEST_CASE_FIXTURE(Fixture, "visit_error_nodes_in_lvalue")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauNewSolverVisitErrorExprLvalues, true}};
{FFlag::LuauSolverV2, true},
{FFlag::LuauNewSolverVisitErrorExprLvalues, true}
};
// This should always fail to parse, but shouldn't assert. Previously this // This should always fail to parse, but shouldn't assert. Previously this
// would assert as we end up _roughly_ parsing this (with a lot of error // would assert as we end up _roughly_ parsing this (with a lot of error
@ -1734,10 +1731,7 @@ TEST_CASE_FIXTURE(Fixture, "visit_error_nodes_in_lvalue")
TEST_CASE_FIXTURE(Fixture, "avoid_blocking_type_function") TEST_CASE_FIXTURE(Fixture, "avoid_blocking_type_function")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDontRefCountTypesInTypeFunctions, true}};
{FFlag::LuauSolverV2, true},
{FFlag::LuauDontRefCountTypesInTypeFunctions, true}
};
LUAU_CHECK_NO_ERRORS(check(R"( LUAU_CHECK_NO_ERRORS(check(R"(
--!strict --!strict
@ -1750,10 +1744,7 @@ TEST_CASE_FIXTURE(Fixture, "avoid_blocking_type_function")
TEST_CASE_FIXTURE(Fixture, "avoid_double_reference_to_free_type") TEST_CASE_FIXTURE(Fixture, "avoid_double_reference_to_free_type")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDontRefCountTypesInTypeFunctions, true}};
{FFlag::LuauSolverV2, true},
{FFlag::LuauDontRefCountTypesInTypeFunctions, true}
};
LUAU_CHECK_NO_ERRORS(check(R"( LUAU_CHECK_NO_ERRORS(check(R"(
--!strict --!strict

View file

@ -953,11 +953,10 @@ a = b
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
const std::string expected = const std::string expected = "Type\n"
"Type\n" " '() -> (number, ...boolean)'\n"
" '() -> (number, ...boolean)'\n" "could not be converted into\n"
"could not be converted into\n" " '() -> (number, ...string)'; at returns().tail().variadic(), boolean is not a subtype of string";
" '() -> (number, ...string)'; at returns().tail().variadic(), boolean is not a subtype of string";
CHECK(expected == toString(result.errors[0])); CHECK(expected == toString(result.errors[0]));
} }

View file

@ -881,7 +881,9 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("(({ read x: unknown } & { x: number }) | ({ read x: unknown } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f"))); CHECK_EQ(
"(({ read x: unknown } & { x: number }) | ({ read x: unknown } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f"))
);
} }
TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types_2") TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types_2")

View file

@ -599,6 +599,90 @@ end
misc(table.create(16, 0)) misc(table.create(16, 0))
local function bitops(size, base)
local b = buffer.create(size)
buffer.writeu32(b, base / 8, 0x12345678)
assert(buffer.readbits(b, base, 8) == buffer.readu8(b, base / 8))
assert(buffer.readbits(b, base, 16) == buffer.readu16(b, base / 8))
assert(buffer.readbits(b, base, 32) == buffer.readu32(b, base / 8))
buffer.writebits(b, base, 32, 0)
buffer.writebits(b, base, 1, 1)
assert(buffer.readi8(b, base / 8) == 1)
buffer.writebits(b, base + 1, 1, 1)
assert(buffer.readi8(b, base / 8) == 3)
-- construct 00000010 00000000_01000000_00010000_00001000 00001000_00010000_01000010_00100101
buffer.writebits(b, base + 0, 1, 0b1)
buffer.writebits(b, base + 1, 2, 0b10)
buffer.writebits(b, base + 3, 3, 0b100)
buffer.writebits(b, base + 6, 4, 0b1000)
buffer.writebits(b, base + 10, 5, 0b10000)
buffer.writebits(b, base + 15, 6, 0b100000)
buffer.writebits(b, base + 21, 7, 0b1000000)
buffer.writebits(b, base + 28, 8, 0b10000000)
buffer.writebits(b, base + 36, 9, 0b100000000)
buffer.writebits(b, base + 45, 10, 0b1000000000)
buffer.writebits(b, base + 55, 11, 0b10000000000)
assert(buffer.readbits(b, base + 0, 32) == 0b00001000_00010000_01000010_00100101)
assert(buffer.readbits(b, base + 32, 32) == 0b00000000_01000000_00010000_00001000)
assert(buffer.readu32(b, base / 8 + 0) == 0b00001000_00010000_01000010_00100101)
assert(buffer.readu32(b, base / 8 + 4) == 0b00000000_01000000_00010000_00001000)
-- slide the window to touch 5 bytes
assert(buffer.readbits(b, base + 1, 32) == 0b00000100000010000010000100010010)
assert(buffer.readbits(b, base + 2, 32) == 0b00000010000001000001000010001001)
assert(buffer.readbits(b, base + 3, 32) == 0b00000001000000100000100001000100)
assert(buffer.readbits(b, base + 4, 32) == 0b10000000100000010000010000100010)
assert(buffer.readbits(b, base + 5, 32) == 0b01000000010000001000001000010001)
assert(buffer.readbits(b, base + 6, 32) == 0b00100000001000000100000100001000)
assert(buffer.readbits(b, base + 7, 32) == 0b00010000000100000010000010000100)
assert(buffer.readbits(b, base + 8, 32) == 0b00001000000010000001000001000010)
assert(buffer.readbits(b, base + 1, 15) == 0b010000100010010)
assert(buffer.readbits(b, base + 2, 15) == 0b001000010001001)
assert(buffer.readbits(b, base + 3, 15) == 0b000100001000100)
assert(buffer.readbits(b, base + 4, 15) == 0b000010000100010)
assert(buffer.readbits(b, base + 5, 15) == 0b000001000010001)
assert(buffer.readbits(b, base + 6, 15) == 0b100000100001000)
assert(buffer.readbits(b, base + 7, 15) == 0b010000010000100)
assert(buffer.readbits(b, base + 8, 15) == 0b001000001000010)
-- zero bit
buffer.writebits(b, base, 0, 0b1)
assert(buffer.readbits(b, base, 32) == 0b00001000_00010000_01000010_00100101)
assert(buffer.readbits(b, base, 0) == 0)
assert(buffer.readbits(b, size * 8, 0) == 0)
-- bounds
assert(ecall(function() buffer.readbits(b, -1, 0) end) == "buffer access out of bounds")
assert(ecall(function() buffer.readbits(b, size * 8, 1) end) == "buffer access out of bounds")
assert(ecall(function() buffer.readbits(b, size * 8 - 1, 2) end) == "buffer access out of bounds")
assert(ecall(function() buffer.readbits(b, 0, 64) end) == "bit count is out of range of [0; 32]")
assert(ecall(function() buffer.writebits(b, -1, 0, 1) end) == "buffer access out of bounds")
assert(ecall(function() buffer.writebits(b, size * 8, 1, 1) end) == "buffer access out of bounds")
assert(ecall(function() buffer.writebits(b, size * 8 - 1, 2, 1) end) == "buffer access out of bounds")
assert(ecall(function() buffer.writebits(b, 0, 64, 1) end) == "bit count is out of range of [0; 32]")
return b
end
do
bitops(16, 0)
bitops(17, 8)
-- a very large buffer and bit offsets can now be over 32 bits
bitops(1024 * 1024 * 1024, 6 * 1024 * 1024 * 1024)
end
local function testslowcalls() local function testslowcalls()
getfenv() getfenv()
@ -619,6 +703,7 @@ local function testslowcalls()
fromtostring() fromtostring()
fill() fill()
misc(table.create(16, 0)) misc(table.create(16, 0))
bitops(16, 0)
end end
testslowcalls() testslowcalls()

View file

@ -237,7 +237,7 @@ if not limitedstack then
end end
-- testing deep nested calls with a large thread stack -- testing deep nested calls with a large thread stack
do if not limitedstack then
function recurse(n, ...) return n <= 1 and (1 + #{...}) or recurse(n-1, table.unpack(table.create(4000, 1))) + 1 end function recurse(n, ...) return n <= 1 and (1 + #{...}) or recurse(n-1, table.unpack(table.create(4000, 1))) + 1 end
local ok, msg = pcall(recurse, 19000) local ok, msg = pcall(recurse, 19000)

View file

@ -402,6 +402,22 @@ assert(math.map(4, 4, 1, 2, 0) == 2)
assert(math.map(-8, 0, 4, 0, 2) == -4) assert(math.map(-8, 0, 4, 0, 2) == -4)
assert(math.map(16, 0, 4, 0, 2) == 8) assert(math.map(16, 0, 4, 0, 2) == 8)
-- lerp basics
assert(math.lerp(1, 5, 0) == 1)
assert(math.lerp(1, 5, 1) == 5)
assert(math.lerp(1, 5, 0.5) == 3)
assert(math.lerp(1, 5, 1.5) == 7)
assert(math.lerp(1, 5, -0.5) == -1)
-- lerp properties
local sq2, sq3 = math.sqrt(2), math.sqrt(3)
assert(math.lerp(sq2, sq3, 0) == sq2) -- exact at 0
assert(math.lerp(sq2, sq3, 1) == sq3) -- exact at 1
assert(math.lerp(-sq3, sq2, 1) == sq2) -- exact at 1 (fails for a + t*(b-a))
assert(math.lerp(sq2, sq2, sq2 / 2) <= math.lerp(sq2, sq2, 1)) -- monotonic (fails for a*t + b*(1-t))
assert(math.lerp(-sq3, sq2, 1) <= math.sqrt(2)) -- bounded (fails for a + t*(b-a))
assert(math.lerp(sq2, sq2, sq2 / 2) == sq2) -- consistent (fails for a*t + b*(1-t))
assert(tostring(math.pow(-2, 0.5)) == "nan") assert(tostring(math.pow(-2, 0.5)) == "nan")
-- test that fastcalls return correct number of results -- test that fastcalls return correct number of results
@ -464,5 +480,6 @@ assert(math.sign("2") == 1)
assert(math.sign("-2") == -1) assert(math.sign("-2") == -1)
assert(math.sign("0") == 0) assert(math.sign("0") == 0)
assert(math.round("1.8") == 2) assert(math.round("1.8") == 2)
assert(math.lerp("1", "5", 0.5) == 3)
return('OK') return('OK')

View file

@ -513,4 +513,68 @@ end
assert(extramath3(2) == "number") assert(extramath3(2) == "number")
assert(extramath3("2") == "number") assert(extramath3("2") == "number")
local function slotcachelimit1()
local tbl = {
f1 = function() return 1 end,
f2 = function() return 2 end,
f3 = function() return 3 end,
f4 = function() return 4 end,
f5 = function() return 5 end,
f6 = function() return 6 end,
f7 = function() return 7 end,
f8 = function() return 8 end,
f9 = function() return 9 end,
f10 = function() return 10 end,
f11 = function() return 11 end,
f12 = function() return 12 end,
f13 = function() return 13 end,
f14 = function() return 14 end,
f15 = function() return 15 end,
f16 = function() return 16 end,
}
local lookup = {
[tbl.f1] = 1,
[tbl.f2] = 2,
[tbl.f3] = 3,
[tbl.f4] = 4,
[tbl.f5] = 5,
[tbl.f6] = 6,
[tbl.f7] = 7,
[tbl.f8] = 8,
[tbl.f9] = 9,
[tbl.f10] = 10,
[tbl.f11] = 11,
[tbl.f12] = 12,
[tbl.f13] = 13,
[tbl.f14] = 14,
[tbl.f15] = 15,
[tbl.f16] = 16,
}
assert(is_native())
return lookup
end
slotcachelimit1()
local function slotcachelimit2(foo, size)
local c1 = foo(vector.create(size.X, size.Y, size.Z))
local c2 = foo(vector.create(-size.X, size.Y, size.Z))
local c3 = foo(vector.create(-size.X, -size.Y, size.Z))
local c4 = foo(vector.create(-size.X, -size.Y, -size.Z))
local c5 = foo(vector.create(size.X, -size.Y, -size.Z))
local c6 = foo(vector.create(size.X, size.Y, -size.Z))
local c7 = foo(vector.create(size.X, -size.Y, size.Z))
local c8 = foo(vector.create(-size.X, size.Y, -size.Z))
local max = vector.create(math.max(c1.X, c2.X, c3.X, c4.X, c5.X, c6.X, c7.X, c8.X), math.max(c1.Y, c2.Y, c3.Y, c4.Y, c5.Y, c6.Y, c7.Y, c8.Y), math.max(c1.Z, c2.Z, c3.Z, c4.Z, c5.Z, c6.Z, c7.Z, c8.Z))
local min = vector.create(math.min(c1.X, c2.X, c3.X, c4.X, c5.X, c6.X, c7.X, c8.X), math.min(c1.Y, c2.Y, c3.Y, c4.Y, c5.Y, c6.Y, c7.Y, c8.Y), math.min(c1.Z, c2.Z, c3.Z, c4.Z, c5.Z, c6.Z, c7.Z, c8.Z))
assert(is_native())
return max - min
end
slotcachelimit2(function(a) return -a end, vector.create(1, 2, 3))
return('OK') return('OK')