Sync to upstream/release/656 (#1612)
Some checks failed
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Has been cancelled
build / macos (push) Has been cancelled
build / macos-arm (push) Has been cancelled
build / ubuntu (push) Has been cancelled
build / windows (Win32) (push) Has been cancelled
build / windows (x64) (push) Has been cancelled
build / coverage (push) Has been cancelled
build / web (push) Has been cancelled
release / macos (push) Has been cancelled
release / ubuntu (push) Has been cancelled
release / windows (push) Has been cancelled
release / web (push) Has been cancelled

# General

All code has been re-formatted by `clang-format`; this is not
mechanically enforced, so Luau may go out-of-sync over the course of the
year.

# New Solver

* Track free types interior to a block of code on `Scope`, which should
reduce the number of free types that remain un-generalized after type
checking is complete (e.g.: less errors like `'a <: number is
incompatible with number`).

# Autocomplete

* Fragment autocomplete now does *not* provide suggestions within
comments (matching non-fragment autocomplete behavior).
* Autocomplete now respects iteration and recursion limits (some hangs
will now early exit with a "unification too complex error," some crashes
will now become internal complier exceptions).

# Runtime

* Add a limit to how many Luau codegen slot nodes addresses can be in
use at the same time (fixes #1605, fixes #1558).
* Added constant folding for vector arithmetic (fixes #1553).
* Added support for `buffer.readbits` and `buffer.writebits` (see:
https://github.com/luau-lang/rfcs/pull/18).

---

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: David Cope <dcope@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
Hunter Goldstein 2025-01-10 11:34:39 -08:00 committed by GitHub
parent 945c510b3c
commit c759cd5581
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
85 changed files with 1329 additions and 731 deletions

View file

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

View file

@ -16,6 +16,8 @@
#include <unordered_map>
#include <optional>
LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection)
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 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.
std::unordered_map<Name, TypeId> typeAliasTypeParameters;
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

View file

@ -626,7 +626,7 @@ struct TypeFunctionInstanceType
std::vector<TypeId> typeArguments;
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;
TypeFunctionInstanceType(

View file

@ -71,7 +71,7 @@ struct TypeFunctionContext
// The constraint being reduced in this run of the reduction
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);

View file

@ -269,8 +269,8 @@ bool isLiteral(const AstExpr* expr);
std::vector<TypeId> findBlockedTypesIn(AstExprTable* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes);
/**
* 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.
* 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.
* This is used as a precondition for bidirectional inference: be warned that
* the behavior of this algorithm is tightly coupled to that of bidirectional
* 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);
/**
* 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

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)

View file

@ -25,6 +25,7 @@ LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUseLimits)
static const std::unordered_set<std::string> kStatementStartingKeywords =
{"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.checkInhabited = false;
if (FFlag::LuauAutocompleteUseLimits)
{
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
}
return unifier.canUnify(subTy, superTy).empty();
}
}

View file

@ -611,7 +611,9 @@ static void dcrMagicFunctionTypeCheckFormat(MagicFunctionTypeCheckContext contex
if (!fmt)
{
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;
}

View file

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

View file

@ -31,15 +31,15 @@
LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTypestateBuiltins2)
LUAU_FASTFLAG(LuauUserTypeFunUpdateAllEnvs)
LUAU_FASTFLAGVARIABLE(LuauNewSolverVisitErrorExprLvalues)
LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunExportedAndLocal)
LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses)
LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunNoExtraConstraint)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(InferGlobalTypes)
@ -233,8 +233,17 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
Checkpoint end = checkpoint(this);
TypeId result = arena->addType(BlockedType{});
NotNull<Constraint> genConstraint =
addConstraint(scope, block->location, GeneralizationConstraint{result, moduleFnTy, std::move(interiorTypes.back())});
NotNull<Constraint> genConstraint = addConstraint(
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);
forEachConstraint(
start,
@ -303,9 +312,19 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat
}
}
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)
@ -2408,8 +2427,17 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
Checkpoint endCheckpoint = checkpoint(this);
TypeId generalizedTy = arena->addType(BlockedType{});
NotNull<Constraint> gc =
addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature, std::move(interiorTypes.back())});
NotNull<Constraint> gc = addConstraint(
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);
interiorTypes.pop_back();
@ -2975,11 +3003,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
ty,
expr,
toBlock
);
// The visitor we ran prior should ensure that there are no
// blocked types that we would encounter while matching on
// this expression.
LUAU_ASSERT(toBlock.empty());
);
// The visitor we ran prior should ensure that there are no
// blocked types that we would encounter while matching on
// this expression.
LUAU_ASSERT(toBlock.empty());
}
}
@ -3941,20 +3969,7 @@ TypeId ConstraintGenerator::createTypeFunctionInstance(
TypeId ConstraintGenerator::simplifyUnion(const ScopePtr& scope, Location location, TypeId left, TypeId right)
{
if (FFlag::DebugLuauEqSatSimplification)
{
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;
return ::Luau::simplifyUnion(builtinTypes, arena, left, right).result;
}
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints)

View file

@ -36,6 +36,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations)
LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer)
LUAU_FASTFLAG(LuauUserTypeFunNoExtraConstraint)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
namespace Luau
{
@ -724,8 +725,20 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
bind(constraint, c.generalizedType, builtinTypes->errorRecoveryType());
}
for (TypeId ty : c.interiorTypes)
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
{
// 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;
}
@ -801,6 +814,11 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
{
TypeId keyTy = 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{TableType::Props{}, TableIndexer{keyTy, valueTy}, TypeLevel{}, constraint->scope, TableState::Free});
@ -2062,6 +2080,8 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
// constitute any meaningful constraint, so we replace it
// with a free type.
TypeId f = freshType(arena, builtinTypes, constraint->scope);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(constraint->scope, f);
shiftReferences(resultTy, f);
emplaceType<BoundType>(asMutable(resultTy), f);
}
@ -2197,6 +2217,11 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
{
TypeId keyTy = 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});
getMutable<TableType>(tableTy)->indexer = TableIndexer{keyTy, valueTy};
@ -2453,6 +2478,8 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
if (ttv->state == TableState::Free)
{
TypeId result = freshType(arena, builtinTypes, ttv->scope);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(ttv->scope, result);
switch (context)
{
case ValueContext::RValue:
@ -2562,6 +2589,9 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
LUAU_ASSERT(tt);
TypeId propType = freshType(arena, builtinTypes, scope);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(scope, propType);
switch (context)
{
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
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauMathMap)
LUAU_FASTFLAGVARIABLE(LuauVectorDefinitions)
LUAU_FASTFLAGVARIABLE(LuauVectorDefinitionsExtra)
LUAU_FASTFLAG(LuauBufferBitMethods)
namespace Luau
{
// 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(
declare bit32: {
@ -422,7 +199,9 @@ declare utf8: {
-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype.
declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionBufferSrc_DEPRECATED = R"BUILTIN_SRC(
--- Buffer API
declare buffer: {
create: @checked (size: number) -> buffer,
@ -453,6 +232,39 @@ declare buffer: {
)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(
-- TODO: this will be replaced with a built-in primitive type
@ -511,11 +323,13 @@ declare vector: {
std::string getBuiltinDefinitionSource()
{
std::string result = FFlag::LuauMathMap ? kBuiltinDefinitionLuaSrcChecked : kBuiltinDefinitionLuaSrcChecked_DEPRECATED;
std::string result = kBuiltinDefinitionLuaSrcChecked;
result += FFlag::LuauBufferBitMethods ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED;
if (FFlag::LuauVectorDefinitionsExtra)
result += kBuiltinDefinitionVectorSrc;
else if (FFlag::LuauVectorDefinitions)
else
result += kBuiltinDefinitionVectorSrc_DEPRECATED;
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,
// TUnknown, or TError.
return !(
lhs.index() == rhs.index() ||
lhs.get<TUnknown>() || rhs.get<TUnknown>() || lhs.get<TAny>() || rhs.get<TAny>() || lhs.get<TNoRefine>() || rhs.get<TNoRefine>() ||
lhs.get<TError>() || rhs.get<TError>() || lhs.get<TOpaque>() || rhs.get<TOpaque>()
lhs.index() == rhs.index() || lhs.get<TUnknown>() || rhs.get<TUnknown>() || lhs.get<TAny>() || rhs.get<TAny>() || lhs.get<TNoRefine>() ||
rhs.get<TNoRefine>() || lhs.get<TError>() || rhs.get<TError>() || lhs.get<TOpaque>() || rhs.get<TOpaque>()
);
}
@ -694,7 +693,8 @@ TypeId flattenTableNode(
StringId propName = t->propNames[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 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 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);
}
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)
return "";
@ -1879,7 +1887,12 @@ void Simplifier::intersectWithNegatedClass(Id id)
isTag<SBoolean>(iNode) || isTag<SString>(iNode) || isTag<TFunction>(iNode) || isTag<TNever>(iNode))
{
// 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;
}
@ -1887,27 +1900,37 @@ void Simplifier::intersectWithNegatedClass(Id id)
{
switch (relateClasses(class_, negatedClass))
{
case LeftSuper:
// eg Instance & ~Part
// This cannot be meaningfully reduced.
continue;
case RightSuper:
subst(id, egraph.add(TNever{}), "intersectClassWithNegatedClass", {{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}});
return;
case Unrelated:
// Part & ~Folder == Part
case LeftSuper:
// eg Instance & ~Part
// This cannot be meaningfully reduced.
continue;
case RightSuper:
subst(
id,
egraph.add(TNever{}),
"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;
newParts.reserve(intersection->operands().size() - 1);
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}});
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}}
);
}
}
}
}

View file

@ -245,7 +245,6 @@ FragmentParseResult parseFragment(
opts.captureComments = true;
opts.parseFragment = FragmentParseResumeSettings{std::move(result.localMap), std::move(result.localStack), startPos};
ParseResult p = Luau::Parser::parse(srcStart, parseLength, *nameTbl, *fragmentResult.alloc.get(), opts);
std::vector<AstNode*> fabricatedAncestry = std::move(result.ancestry);
// Get the ancestry for the fragment at the offset cursor position.
@ -258,6 +257,7 @@ FragmentParseResult parseFragment(
fragmentResult.root = std::move(p.root);
fragmentResult.ancestry = std::move(fabricatedAncestry);
fragmentResult.nearestStatement = nearestStatement;
fragmentResult.commentLocations = std::move(p.commentLocations);
return fragmentResult;
}
@ -444,7 +444,7 @@ FragmentTypeCheckResult typecheckFragment_(
}
FragmentTypeCheckResult typecheckFragment(
std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
Frontend& frontend,
const ModuleName& moduleName,
const Position& cursorPos,
@ -469,12 +469,15 @@ FragmentTypeCheckResult typecheckFragment(
}
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);
const ScopePtr& closestScope = findClosestScope(module, parseResult.nearestStatement);
FragmentTypeCheckResult result =
typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions);
result.ancestry = std::move(parseResult.ancestry);
return result;
return {FragmentTypeCheckStatus::Success, result};
}
@ -498,7 +501,14 @@ FragmentAutocompleteResult fragmentAutocomplete(
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();
TypeArena arenaForFragmentAutocomplete;

View file

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

View file

@ -15,11 +15,12 @@
#include <algorithm>
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteCommentDetection)
namespace Luau
{
static bool contains(Position pos, Comment comment)
static bool contains_DEPRECATED(Position pos, Comment comment)
{
if (comment.location.contains(pos))
return true;
@ -32,7 +33,22 @@ static bool contains(Position pos, Comment comment)
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(
commentLocations.begin(),
@ -40,6 +56,11 @@ static bool isWithinComment(const std::vector<Comment>& commentLocations, Positi
Comment{Lexeme::Comment, Location{pos, pos}},
[](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;
}
);
@ -47,7 +68,7 @@ static bool isWithinComment(const std::vector<Comment>& commentLocations, Positi
if (iter == commentLocations.end())
return false;
if (contains(pos, *iter))
if (FFlag::LuauIncrementalAutocompleteCommentDetection ? contains(pos, *iter) : contains_DEPRECATED(pos, *iter))
return true;
// 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,
module->name,
InternalError{"Free type is escaping its module; please report this bug at "
"https://github.com/luau-lang/luau/issues"}
);
result = builtinTypes->errorRecoveryType();
"https://github.com/luau-lang/luau/issues"}
);
result = builtinTypes->errorRecoveryType();
}
else if (auto genericty = getMutable<GenericType>(result))
{
@ -173,8 +194,8 @@ struct ClonePublicInterface : Substitution
ftp->scope->location,
module->name,
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();
}
else if (auto gtp = getMutable<GenericTypePack>(clonedTp))

View file

@ -1809,7 +1809,8 @@ NormalizationResult Normalizer::unionNormalWithTy(
}
else if (get<UnknownType>(here.tops))
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)
return NormalizationResult::True;
@ -3162,7 +3163,8 @@ NormalizationResult Normalizer::intersectNormalWithTy(
}
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 topNorm{builtinTypes};

View file

@ -420,7 +420,8 @@ static std::optional<TypeId> selectOverload(
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);
if (status == OverloadResolver::Analysis::Ok)

View file

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

View file

@ -1020,7 +1020,8 @@ void TypeChecker2::visit(AstStatForIn* forInStatement)
{
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};

View file

@ -832,7 +832,12 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
{
if (FFlag::LuauUserTypeFunPrintToError)
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
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)
{
for (TypeId newTf : simplifyResult->newTypeFunctions)
ctx->solver->pushConstraint(ctx->scope, ctx->constraint->location, ReduceConstraint{newTf});
ctx->pushConstraint(ReduceConstraint{newTf});
}
return {simplifyResult->result, {}};

View file

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

View file

@ -2799,10 +2799,10 @@ TypeId TypeChecker::checkRelationalOperation(
reportError(
expr.location,
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;
}

View file

@ -11,6 +11,7 @@
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete);
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope);
namespace Luau
{
@ -318,6 +319,8 @@ TypePack extendTypePack(
{
FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType};
t = arena.addType(ft);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(ftp->scope, t);
}
else
t = arena.freshType(ftp->scope);
@ -533,7 +536,7 @@ std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMa
{
std::vector<TypeId> toBlock;
BlockedTypeInLiteralVisitor v{astTypes, NotNull{&toBlock}};
for (auto arg: expr->args)
for (auto arg : expr->args)
{
if (isLiteral(arg) || arg->is<AstExprGroup>())
{
@ -543,5 +546,21 @@ std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMa
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

View file

@ -45,4 +45,4 @@ private:
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;
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
{
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);
};
@ -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 overlaps(const Location& l) const;

View file

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

View file

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

View file

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

View file

@ -4,42 +4,6 @@
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)
{
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
{
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.
// See docs/SyntaxChanges.md for an explanation.
LUAU_FASTFLAGVARIABLE(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunParseExport)
LUAU_FASTFLAGVARIABLE(LuauAllowFragmentParsing)
LUAU_FASTFLAGVARIABLE(LuauAllowComplexTypesInGenericParams)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForTableTypes)
@ -936,12 +935,6 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported)
Lexeme matchFn = lexer.current();
nextLexeme();
if (!FFlag::LuauUserDefinedTypeFunParseExport)
{
if (exported)
report(start, "Type function cannot be exported");
}
// parse the name of the type function
std::optional<Name> fnName = parseNameOpt("type function name");
if (!fnName)
@ -2239,7 +2232,8 @@ std::optional<AstExprBinary::Op> Parser::checkBinaryConfusables(const BinaryOpPr
report(Location(start, next.location), "Unexpected '||'; did you mean '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();
report(Location(start, next.location), "Unexpected '!='; did you mean '~='?");
@ -2587,7 +2581,8 @@ AstExpr* Parser::parseSimpleExpr()
{
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();
}

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.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(
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 (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() != "..")
{

View file

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

View file

@ -848,7 +848,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[ra] = LBC_TYPE_NUMBER;
else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == 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));
bcType.result = regTags[ra];
@ -879,7 +880,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
if (bcType.b == LBC_TYPE_NUMBER || bcType.b == 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));
}
@ -901,7 +903,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
if (bcType.a == LBC_TYPE_NUMBER && bcType.b == 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));
bcType.result = regTags[ra];
@ -923,7 +926,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[ra] = LBC_TYPE_NUMBER;
else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == 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));
bcType.result = regTags[ra];
@ -954,7 +958,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
if (bcType.b == LBC_TYPE_NUMBER || bcType.b == 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));
}
@ -976,7 +981,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
if (bcType.a == LBC_TYPE_NUMBER && bcType.b == 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));
bcType.result = regTags[ra];
@ -997,7 +1003,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
regTags[ra] = LBC_TYPE_NUMBER;
else if (bcType.a == LBC_TYPE_VECTOR && bcType.b == 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));
bcType.result = regTags[ra];
@ -1026,7 +1033,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
if (bcType.b == LBC_TYPE_NUMBER || bcType.b == 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));
}

View file

@ -166,7 +166,7 @@ bool isSupported()
if (sizeof(LuaNode) != 32)
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 (!isUnwindSupported())
return false;

View file

@ -101,7 +101,7 @@ inline bool lowerImpl(
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
size_t textSize = build.text.length();

View file

@ -18,6 +18,8 @@
#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
// 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
@ -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
// this is because we're going to modify base/savedpc manually anyhow
// 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;
}
@ -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
// this is because we're going to modify base/savedpc manually anyhow
// 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);

View file

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

View file

@ -4,6 +4,8 @@
#include "Luau/IrUtils.h"
#include "lua.h"
#include "lobject.h"
#include "lstate.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");
const int kDetailsAlignColumn = 60;
const unsigned kMaxStringConstantPrintLength = 16;
LUAU_PRINTF_ATTR(2, 3)
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, ' ');
}
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)
{
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);
}
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)
{
switch (op.kind)
@ -458,6 +519,14 @@ void toString(IrToStringContext& ctx, IrOp op)
break;
case IrOpKind::VmConst:
append(ctx.result, "K%d", vmConstOp(op));
if (ctx.proto)
{
append(ctx.result, " (");
appendVmConstant(ctx.result, ctx.proto, vmConstOp(op));
append(ctx.result, ")");
}
break;
case IrOpKind::VmUpvalue:
append(ctx.result, "U%d", vmUpvalueOp(op));
@ -770,7 +839,7 @@ void toStringDetailed(
std::string toString(const IrFunction& function, IncludeUseInfo includeUseInfo)
{
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++)
{
@ -877,7 +946,7 @@ static void appendBlocks(IrToStringContext& ctx, const IrFunction& function, boo
std::string toDot(const IrFunction& function, bool includeInst)
{
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, "node[shape=record]\n");
@ -924,7 +993,7 @@ std::string toDot(const IrFunction& function, bool includeInst)
std::string toDotCfg(const IrFunction& function)
{
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, "node[shape=record]\n");
@ -947,7 +1016,7 @@ std::string toDotCfg(const IrFunction& function)
std::string toDotDjGraph(const IrFunction& function)
{
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");

View file

@ -11,6 +11,7 @@
#include <limits.h>
#include <math.h>
#include <algorithm>
#include <array>
#include <utility>
#include <vector>
@ -18,9 +19,11 @@
LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3)
LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64)
LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64)
LUAU_FASTINTVARIABLE(LuauCodeGenLiveSlotReuseLimit, 8)
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks)
LUAU_FASTFLAG(LuauVectorLibNativeDot);
LUAU_FASTFLAGVARIABLE(LuauCodeGenArithOpt);
LUAU_FASTFLAG(LuauVectorLibNativeDot)
LUAU_FASTFLAGVARIABLE(LuauCodeGenArithOpt)
LUAU_FASTFLAGVARIABLE(LuauCodeGenLimitLiveSlotReuse)
namespace Luau
{
@ -50,6 +53,14 @@ struct RegisterLink
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
struct ConstPropState
{
@ -190,7 +201,11 @@ struct ConstPropState
// Same goes for table array elements as well
void invalidateHeapTableData()
{
getSlotNodeCache.clear();
if (FFlag::LuauCodeGenLimitLiveSlotReuse)
getSlotNodeCache.clear();
else
getSlotNodeCache_DEPRECATED.clear();
checkSlotMatchCache.clear();
getArrAddrCache.clear();
@ -409,6 +424,64 @@ struct ConstPropState
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()
{
for (int i = 0; i <= maxReg; ++i)
@ -416,6 +489,9 @@ struct ConstPropState
maxReg = 0;
if (FFlag::LuauCodeGenLimitLiveSlotReuse)
instPos = 0u;
inSafeEnv = false;
checkedGc = false;
@ -436,6 +512,9 @@ struct ConstPropState
// For range/full invalidations, we only want to visit a limited number of data that we have recorded
int maxReg = 0;
// Number of the instruction being processed
uint32_t instPos = 0;
bool inSafeEnv = false;
bool checkedGc = false;
@ -447,7 +526,8 @@ struct ConstPropState
std::vector<uint32_t> tryNumToIndexCache; // Fallback block argument might be different
// 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> getArrAddrCache;
@ -457,6 +537,8 @@ struct ConstPropState
// 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> rangeEndTemp;
};
static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid, uint32_t firstReturnReg, int nresults)
@ -570,6 +652,9 @@ static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid
static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& function, IrBlock& block, IrInst& inst, uint32_t index)
{
if (FFlag::LuauCodeGenLimitLiveSlotReuse)
state.instPos++;
switch (inst.cmd)
{
case IrCmd::LOAD_TAG:
@ -1176,19 +1261,49 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
state.getArrAddrCache.push_back(index);
break;
case IrCmd::GET_SLOT_NODE_ADDR:
for (uint32_t prevIdx : state.getSlotNodeCache)
if (FFlag::LuauCodeGenLimitLiveSlotReuse)
{
const IrInst& prev = function.instructions[prevIdx];
if (prev.a == inst.a && prev.c == inst.c)
for (size_t i = 0; i < state.getSlotNodeCache.size(); i++)
{
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
return; // Break out from both the loop and the switch
}
}
auto&& [prevIdx, num, lastNum] = state.getSlotNodeCache[i];
if (int(state.getSlotNodeCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
state.getSlotNodeCache.push_back(index);
const IrInst& prev = function.instructions[prevIdx];
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;
case IrCmd::GET_HASH_NODE_ADDR:
case IrCmd::GET_CLOSURE_UPVAL_ADDR:

View file

@ -13,7 +13,7 @@ inline bool isFlagExperimental(const char* flag)
static const char* const kList[] = {
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"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",
// makes sure we always have at least one entry
nullptr,

View file

@ -7,7 +7,6 @@
#include <array>
LUAU_FASTFLAGVARIABLE(LuauVectorBuiltins)
LUAU_FASTFLAGVARIABLE(LuauCompileDisabledBuiltins)
LUAU_FASTFLAGVARIABLE(LuauCompileMathLerp)
@ -229,7 +228,7 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op
return LBF_BUFFER_WRITEF64;
}
if (FFlag::LuauVectorBuiltins && builtin.object == "vector")
if (builtin.object == "vector")
{
if (builtin.method == "create")
return LBF_VECTOR;

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
// (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)
// 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;
}
}
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)
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");
target->type = Compile::Constant::Type_String;
target->stringLength = l;
target->stringLength = unsigned(l);
target->valueString = s;
}

View file

@ -130,7 +130,8 @@ struct CostVisitor : AstVisitor
{
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);
}

View file

@ -3,7 +3,6 @@
#include "Luau/BytecodeBuilder.h"
LUAU_FASTFLAGVARIABLE(LuauCompileVectorTypeInfo)
LUAU_FASTFLAG(LuauCompileLibraryConstants)
namespace Luau
@ -32,7 +31,7 @@ static LuauBytecodeType getPrimitiveType(AstName name)
return LBC_TYPE_THREAD;
else if (name == "buffer")
return LBC_TYPE_BUFFER;
else if (FFlag::LuauCompileVectorTypeInfo && name == "vector")
else if (name == "vector")
return LBC_TYPE_VECTOR;
else if (name == "any" || name == "unknown")
return LBC_TYPE_ANY;

View file

@ -305,7 +305,8 @@ static Error parseJson(const std::string& contents, Action action)
arrayTop = (lexer.current().type == '[');
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(lexer.current().data, lexer.current().getLength())

View file

@ -198,7 +198,13 @@ private:
{
// An e-node 𝑛 is canonical iff 𝑛 = canonicalize(𝑛), where
// 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

View file

@ -244,7 +244,7 @@ private:
template<typename Phantom, typename T>
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);
template<typename... Args>
@ -302,7 +302,7 @@ struct Language final
template<typename T>
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);
template<typename T>
@ -388,7 +388,7 @@ private:
VariantTy v;
};
template <typename Node, typename Find>
template<typename Node, typename Find>
void canonicalize(Node& node, Find&& find)
{
// 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.
template <typename Phantom, typename T, typename Find>
template<typename Phantom, typename T, typename Find>
void canonicalize(NodeSet<Phantom, T>& node, Find&& find)
{
for (Id& id : node.vector)
@ -409,7 +409,7 @@ void canonicalize(NodeSet<Phantom, T>& node, Find&& find)
node.vector.erase(endIt, end(node.vector));
}
template <typename Find, typename... Vs>
template<typename Find, typename... Vs>
void canonicalize(Language<Vs...>& enode, Find&& find)
{
visit(

View file

@ -10,6 +10,8 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauBufferBitMethods)
// while C API returns 'size_t' for binary compatibility in case of future extensions,
// in the current implementation, length and offset are limited to 31 bits
// because offset is limited to an integer, a single 64bit comparison can be used and will not overflow
@ -247,7 +249,68 @@ static int buffer_fill(lua_State* L)
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) > uint64_t(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) > uint64_t(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},
{"fromstring", buffer_fromstring},
{"tostring", buffer_tostring},
@ -275,9 +338,39 @@ static const luaL_Reg bufferlib[] = {
{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)
{
luaL_register(L, LUA_BUFFERLIBNAME, bufferlib);
luaL_register(L, LUA_BUFFERLIBNAME, FFlag::LuauBufferBitMethods ? bufferlib : bufferlib_DEPRECATED);
return 1;
}

View file

@ -18,6 +18,7 @@
#include <string.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauStackLimit, false)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauPopIncompleteCi, false)
// keep max stack allocation request under 1GB
#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;
}
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
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);
}
TValue* oldstack = L->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)
{
if (n <= L->stacksize) // double size is enough?
luaD_reallocstack(L, 2 * L->stacksize);
if (DFFlag::LuauPopIncompleteCi)
{
luaD_reallocstack(L, getgrownstacksize(L, n), 0);
}
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)

View file

@ -7,11 +7,21 @@
#include "luaconf.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) \
if ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue)) \
luaD_growstack(L, n); \
else \
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK));
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK, 0));
#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 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_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_checkCstack(lua_State* L);

View file

@ -442,7 +442,7 @@ static void shrinkstack(lua_State* L)
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)
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));
}

View file

@ -463,6 +463,7 @@ static const luaL_Reg mathlib[] = {
{"clamp", math_clamp},
{"sign", math_sign},
{"round", math_round},
{"map", math_map},
{NULL, NULL},
};
@ -483,10 +484,10 @@ int luaopen_math(lua_State* L)
lua_pushnumber(L, HUGE_VAL);
lua_setfield(L, -2, "huge");
if (FFlag::LuauMathMap)
if (FFlag::LuauMathLerp)
{
lua_pushcfunction(L, math_map, "map");
lua_setfield(L, -2, "map");
lua_pushcfunction(L, math_lerp, "lerp");
lua_setfield(L, -2, "lerp");
}
if (FFlag::LuauMathLerp)

View file

@ -192,7 +192,7 @@ struct SizeClassConfig
const SizeClassConfig kSizeClassConfig;
// 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
#define metadata(block) (*(void**)(block))

View file

@ -149,7 +149,7 @@ void lua_resetthread(lua_State* L)
L->nCcalls = L->baseCcalls = 0;
// clear thread 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++)
setnilvalue(L->stack + i);
}

View file

@ -16,6 +16,8 @@
#include <string.h>
LUAU_DYNAMIC_FASTFLAG(LuauPopIncompleteCi)
// Disable c99-designator to avoid the warning in CGOTO dispatch table
#ifdef __clang__
#if __has_warning("-Wc99-designator")
@ -935,7 +937,14 @@ reentry:
// 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
// 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);
@ -3071,7 +3080,14 @@ int luau_precall(lua_State* L, StkId func, int nresults)
L->base = ci->base;
// 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);
if (!ccl->isC)

View file

@ -18,6 +18,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(StudioReportLuauAny2)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
struct ATSFixture : BuiltinsFixture
@ -97,7 +98,10 @@ end
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
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")
@ -581,6 +585,9 @@ TEST_CASE_FIXTURE(ATSFixture, "racing_spawning_1")
ScopedFastFlag sff[] = {
{FFlag::LuauSolverV2, 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"(
@ -632,7 +639,7 @@ initialize()
)";
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");

View file

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

View file

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

View file

@ -31,17 +31,16 @@ extern int optimizationLevel;
void luaC_fullgc(lua_State* L);
void luaC_validate(lua_State* L);
LUAU_FASTFLAG(LuauMathMap)
LUAU_FASTFLAG(LuauMathLerp)
LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_DYNAMIC_FASTFLAG(LuauStackLimit)
LUAU_FASTFLAG(LuauVectorDefinitions)
LUAU_DYNAMIC_FASTFLAG(LuauDebugInfoInvArgLeftovers)
LUAU_FASTFLAG(LuauVectorLibNativeCodegen)
LUAU_FASTFLAG(LuauVectorLibNativeDot)
LUAU_FASTFLAG(LuauVectorBuiltins)
LUAU_FASTFLAG(LuauVectorMetatable)
LUAU_FASTFLAG(LuauBufferBitMethods)
LUAU_FASTFLAG(LuauCodeGenLimitLiveSlotReuse)
static lua_CompileOptions defaultOptions()
{
@ -655,12 +654,13 @@ TEST_CASE("Basic")
TEST_CASE("Buffers")
{
ScopedFastFlag luauBufferBitMethods{FFlag::LuauBufferBitMethods, true};
runConformance("buffers.lua");
}
TEST_CASE("Math")
{
ScopedFastFlag LuauMathMap{FFlag::LuauMathMap, true};
ScopedFastFlag LuauMathLerp{FFlag::LuauMathLerp, true};
runConformance("math.lua");
@ -893,7 +893,6 @@ TEST_CASE("Vector")
TEST_CASE("VectorLibrary")
{
ScopedFastFlag luauVectorBuiltins{FFlag::LuauVectorBuiltins, true};
ScopedFastFlag luauVectorLibNativeCodegen{FFlag::LuauVectorLibNativeCodegen, true};
ScopedFastFlag luauVectorLibNativeDot{FFlag::LuauVectorLibNativeDot, true};
ScopedFastFlag luauVectorMetatable{FFlag::LuauVectorMetatable, true};
@ -987,7 +986,6 @@ static void populateRTTI(lua_State* L, Luau::TypeId type)
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(
@ -2579,6 +2577,8 @@ TEST_CASE("SafeEnv")
TEST_CASE("Native")
{
ScopedFastFlag luauCodeGenLimitLiveSlotReuse{FFlag::LuauCodeGenLimitLiveSlotReuse, true};
// This tests requires code to run natively, otherwise all 'is_native' checks will fail
if (!codegen || !luau_codegen_supported())
return;

View file

@ -23,15 +23,11 @@ struct ESFixture : Fixture
TypeId genericT = arena_.addType(GenericType{"T"});
TypeId genericU = arena_.addType(GenericType{"U"});
TypeId numberToString = arena_.addType(FunctionType{
arena_.addTypePack({builtinTypes->numberType}),
arena_.addTypePack({builtinTypes->stringType})
});
TypeId numberToString =
arena_.addType(FunctionType{arena_.addTypePack({builtinTypes->numberType}), arena_.addTypePack({builtinTypes->stringType})});
TypeId stringToNumber = arena_.addType(FunctionType{
arena_.addTypePack({builtinTypes->stringType}),
arena_.addTypePack({builtinTypes->numberType})
});
TypeId stringToNumber =
arena_.addType(FunctionType{arena_.addTypePack({builtinTypes->stringType}), arena_.addTypePack({builtinTypes->numberType})});
ESFixture()
: simplifier(newSimplifier(arena, builtinTypes))
@ -163,10 +159,11 @@ TEST_CASE_FIXTURE(ESFixture, "never & string")
TEST_CASE_FIXTURE(ESFixture, "string & (unknown | never)")
{
CHECK("string" == simplifyStr(arena->addType(IntersectionType{{
builtinTypes->stringType,
arena->addType(UnionType{{builtinTypes->unknownType, builtinTypes->neverType}})
}})));
CHECK(
"string" == simplifyStr(arena->addType(
IntersectionType{{builtinTypes->stringType, arena->addType(UnionType{{builtinTypes->unknownType, builtinTypes->neverType}})}}
))
);
}
TEST_CASE_FIXTURE(ESFixture, "true | false")
@ -211,112 +208,97 @@ TEST_CASE_FIXTURE(ESFixture, "error | unknown")
TEST_CASE_FIXTURE(ESFixture, "\"hello\" | string")
{
CHECK("string" == simplifyStr(arena->addType(UnionType{{
arena->addType(SingletonType{StringSingleton{"hello"}}), builtinTypes->stringType
}})));
CHECK("string" == simplifyStr(arena->addType(UnionType{{arena->addType(SingletonType{StringSingleton{"hello"}}), builtinTypes->stringType}})));
}
TEST_CASE_FIXTURE(ESFixture, "\"hello\" | \"world\" | \"hello\"")
{
CHECK("\"hello\" | \"world\"" == simplifyStr(arena->addType(UnionType{{
arena->addType(SingletonType{StringSingleton{"hello"}}),
arena->addType(SingletonType{StringSingleton{"world"}}),
arena->addType(SingletonType{StringSingleton{"hello"}}),
}})));
CHECK(
"\"hello\" | \"world\"" == simplifyStr(arena->addType(UnionType{{
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")
{
CHECK("unknown" == simplifyStr(arena->addType(UnionType{{
builtinTypes->nilType,
builtinTypes->booleanType,
builtinTypes->numberType,
builtinTypes->stringType,
builtinTypes->threadType,
builtinTypes->functionType,
builtinTypes->tableType,
builtinTypes->classType,
builtinTypes->bufferType,
}})));
CHECK(
"unknown" == simplifyStr(arena->addType(UnionType{{
builtinTypes->nilType,
builtinTypes->booleanType,
builtinTypes->numberType,
builtinTypes->stringType,
builtinTypes->threadType,
builtinTypes->functionType,
builtinTypes->tableType,
builtinTypes->classType,
builtinTypes->bufferType,
}}))
);
}
TEST_CASE_FIXTURE(ESFixture, "Parent & number")
{
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{
parentClass, builtinTypes->numberType
}})));
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{parentClass, builtinTypes->numberType}})));
}
TEST_CASE_FIXTURE(ESFixture, "Child & Parent")
{
CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{
childClass, parentClass
}})));
CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{childClass, parentClass}})));
}
TEST_CASE_FIXTURE(ESFixture, "Child & Unrelated")
{
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{
childClass, unrelatedClass
}})));
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{childClass, unrelatedClass}})));
}
TEST_CASE_FIXTURE(ESFixture, "Child | Parent")
{
CHECK("Parent" == simplifyStr(arena->addType(UnionType{{
childClass, parentClass
}})));
CHECK("Parent" == simplifyStr(arena->addType(UnionType{{childClass, parentClass}})));
}
TEST_CASE_FIXTURE(ESFixture, "class | Child")
{
CHECK("class" == simplifyStr(arena->addType(UnionType{{
builtinTypes->classType, childClass
}})));
CHECK("class" == simplifyStr(arena->addType(UnionType{{builtinTypes->classType, childClass}})));
}
TEST_CASE_FIXTURE(ESFixture, "Parent | class | Child")
{
CHECK("class" == simplifyStr(arena->addType(UnionType{{
parentClass, builtinTypes->classType, childClass
}})));
CHECK("class" == simplifyStr(arena->addType(UnionType{{parentClass, builtinTypes->classType, childClass}})));
}
TEST_CASE_FIXTURE(ESFixture, "Parent | Unrelated")
{
CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{
parentClass, unrelatedClass
}})));
CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{parentClass, unrelatedClass}})));
}
TEST_CASE_FIXTURE(ESFixture, "never | Parent | Unrelated")
{
CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{
builtinTypes->neverType, parentClass, unrelatedClass
}})));
CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{builtinTypes->neverType, parentClass, unrelatedClass}})));
}
TEST_CASE_FIXTURE(ESFixture, "never | Parent | (number & string) | Unrelated")
{
CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{
builtinTypes->neverType, parentClass,
arena->addType(IntersectionType{{builtinTypes->numberType, builtinTypes->stringType}}),
unrelatedClass
}})));
CHECK(
"Parent | Unrelated" == simplifyStr(arena->addType(UnionType{
{builtinTypes->neverType,
parentClass,
arena->addType(IntersectionType{{builtinTypes->numberType, builtinTypes->stringType}}),
unrelatedClass}
}))
);
}
TEST_CASE_FIXTURE(ESFixture, "T & U")
{
CHECK("T & U" == simplifyStr(arena->addType(IntersectionType{{
genericT, genericU
}})));
CHECK("T & U" == simplifyStr(arena->addType(IntersectionType{{genericT, genericU}})));
}
TEST_CASE_FIXTURE(ESFixture, "boolean & true")
{
CHECK("true" == simplifyStr(arena->addType(IntersectionType{{
builtinTypes->booleanType, builtinTypes->trueType
}})));
CHECK("true" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->booleanType, builtinTypes->trueType}})));
}
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,
}});
CHECK("true" == simplifyStr(arena->addType(IntersectionType{{
builtinTypes->booleanType, truthy
}})));
CHECK("true" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->booleanType, truthy}})));
}
TEST_CASE_FIXTURE(ESFixture, "boolean & ~(false?)")
{
CHECK("true" == simplifyStr(arena->addType(IntersectionType{{
builtinTypes->booleanType, builtinTypes->truthyType
}})));
CHECK("true" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->booleanType, builtinTypes->truthyType}})));
}
TEST_CASE_FIXTURE(ESFixture, "false & ~(false?)")
{
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{
builtinTypes->falseType, builtinTypes->truthyType
}})));
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->falseType, builtinTypes->truthyType}})));
}
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>")
{
CHECK("number" == simplifyStr(arena->addType(
TypeFunctionInstanceType{builtinTypeFunctions().addFunc, {
builtinTypes->numberType, builtinTypes->numberType
}}
)));
CHECK(
"number" ==
simplifyStr(arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().addFunc, {builtinTypes->numberType, builtinTypes->numberType}}))
);
}
TEST_CASE_FIXTURE(ESFixture, "union<number, number>")
{
CHECK("number" == simplifyStr(arena->addType(
TypeFunctionInstanceType{builtinTypeFunctions().unionFunc, {
builtinTypes->numberType, builtinTypes->numberType
}}
)));
CHECK(
"number" ==
simplifyStr(arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().unionFunc, {builtinTypes->numberType, builtinTypes->numberType}}))
);
}
TEST_CASE_FIXTURE(ESFixture, "never & ~string")
{
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{
builtinTypes->neverType,
arena->addType(NegationType{builtinTypes->stringType})
}})));
CHECK(
"never" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->neverType, arena->addType(NegationType{builtinTypes->stringType})}}))
);
}
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)")
{
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}})));
}
@ -493,26 +468,17 @@ TEST_CASE_FIXTURE(ESFixture, "(blocked & number) | (blocked & number)")
TEST_CASE_FIXTURE(ESFixture, "{} & unknown")
{
CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{
tbl({}),
builtinTypes->unknownType
}})));
CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{tbl({}), builtinTypes->unknownType}})));
}
TEST_CASE_FIXTURE(ESFixture, "{} & table")
{
CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{
tbl({}),
builtinTypes->tableType
}})));
CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{tbl({}), builtinTypes->tableType}})));
}
TEST_CASE_FIXTURE(ESFixture, "{} & ~(false?)")
{
CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{
tbl({}),
builtinTypes->truthyType
}})));
CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{tbl({}), builtinTypes->truthyType}})));
}
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 ty = arena->addType(IntersectionType{{
tblTy,
arena->addType(NegationType{builtinTypes->booleanType})
}});
const TypeId ty = arena->addType(IntersectionType{{tblTy, arena->addType(NegationType{builtinTypes->booleanType})}});
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 bye = arena->addType(SingletonType{StringSingleton{"bye"}});
CHECK("\"bye\" | \"hi\"" == simplifyStr(arena->addType(IntersectionType{{
builtinTypes->stringType,
arena->addType(UnionType{{hi, bye}})
}})));
CHECK("\"bye\" | \"hi\"" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->stringType, arena->addType(UnionType{{hi, bye}})}})));
}
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 ok2 = arena->addType(SingletonType{StringSingleton{"ok"}});
TypeId ty = arena->addType(IntersectionType{{
arena->addType(UnionType{{err, ok1}}),
arena->addType(NegationType{ok2})
}});
TypeId ty = arena->addType(IntersectionType{{arena->addType(UnionType{{err, ok1}}), arena->addType(NegationType{ok2})}});
CHECK("\"err\"" == simplifyStr(ty));
}
TEST_CASE_FIXTURE(ESFixture, "(Child | Unrelated) & ~Child")
{
const TypeId ty = arena->addType(IntersectionType{{
arena->addType(UnionType{{childClass, unrelatedClass}}),
arena->addType(NegationType{childClass})
}});
const TypeId ty =
arena->addType(IntersectionType{{arena->addType(UnionType{{childClass, unrelatedClass}}), arena->addType(NegationType{childClass})}});
CHECK("Unrelated" == simplifyStr(ty));
}
TEST_CASE_FIXTURE(ESFixture, "string & ~Child")
{
CHECK("string" == simplifyStr(arena->addType(IntersectionType{{
builtinTypes->stringType,
arena->addType(NegationType{childClass})
}})));
CHECK("string" == simplifyStr(arena->addType(IntersectionType{{builtinTypes->stringType, arena->addType(NegationType{childClass})}})));
}
TEST_CASE_FIXTURE(ESFixture, "(Child | Unrelated) & Child")
{
CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{
arena->addType(UnionType{{childClass, unrelatedClass}}),
childClass
}})));
CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{arena->addType(UnionType{{childClass, unrelatedClass}}), childClass}})));
}
TEST_CASE_FIXTURE(ESFixture, "(Child | AnotherChild) & ~Child")
{
CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{
arena->addType(UnionType{{childClass, anotherChild}}),
childClass
}})));
CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{arena->addType(UnionType{{childClass, anotherChild}}), childClass}})));
}
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>")
{
const TypeId u = arena->addType(UnionType{{childClass, anotherChild, builtinTypes->stringType}});
const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{
builtinTypeFunctions().addFunc,
{u, parentClass},
{}
});
const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().addFunc, {u, parentClass}, {}});
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>")
{
const TypeId u = arena->addType(UnionType{{childClass, anotherChild, builtinTypes->stringType}});
const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{
builtinTypeFunctions().intersectFunc,
{u, parentClass},
{}
});
const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().intersectFunc, {u, parentClass}, {}});
const TypeId intersection = arena->addType(IntersectionType{{childClass, intersectTf}});
@ -740,7 +678,8 @@ TEST_CASE_FIXTURE(ESFixture, "lt<number, _> == boolean")
{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}});
CHECK("boolean" == simplifyStr(tfun));
}

View file

@ -29,8 +29,7 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests)
LUAU_FASTFLAG(LuauVectorDefinitionsExtra)
#define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) \
ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests};
#define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests};
#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(LuauStoreSolverTypeOnModule);
LUAU_FASTFLAG(LexerResumesFromPosition2)
LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection)
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
)
{
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(
@ -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();

View file

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

View file

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

View file

@ -16,7 +16,6 @@ LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTINT(LuauTypeLengthLimit)
LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauUserDefinedTypeFunParseExport)
LUAU_FASTFLAG(LuauAllowComplexTypesInGenericParams)
LUAU_FASTFLAG(LuauErrorRecoveryForTableTypes)
LUAU_FASTFLAG(LuauErrorRecoveryForClassNames)
@ -447,38 +446,62 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_span_is_correct")
TEST_CASE_FIXTURE(Fixture, "parse_error_messages")
{
matchParseError(R"(
matchParseError(
R"(
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) -> (
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)
)", "Expected '->' when parsing function type, got <eof>");
)",
"Expected '->' when parsing function type, got <eof>"
);
matchParseError(R"(
matchParseError(
R"(
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,
)", "Expected identifier when parsing table field, got <eof>");
)",
"Expected identifier when parsing table field, got <eof>"
);
matchParseError(R"(
matchParseError(
R"(
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 }
)", "Cannot have more than one table indexer");
)",
"Cannot have more than one table indexer"
);
matchParseError(R"(
matchParseError(
R"(
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")
@ -613,9 +636,12 @@ TEST_CASE_FIXTURE(Fixture, "vertical_space")
TEST_CASE_FIXTURE(Fixture, "parse_error_type_name")
{
matchParseError(R"(
matchParseError(
R"(
local a: Foo.=
)", "Expected identifier when parsing field name, got '='");
)",
"Expected identifier when parsing field name, got '='"
);
}
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")
{
matchParseError(R"(
matchParseError(
R"(
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")
@ -690,9 +719,12 @@ TEST_CASE_FIXTURE(Fixture, "allow_unicode_in_string")
TEST_CASE_FIXTURE(Fixture, "error_on_confusable")
{
matchParseError(R"(
matchParseError(
R"(
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")
@ -2341,8 +2373,6 @@ TEST_CASE_FIXTURE(Fixture, "invalid_type_forms")
TEST_CASE_FIXTURE(Fixture, "parse_user_defined_type_functions")
{
ScopedFastFlag sff2{FFlag::LuauUserDefinedTypeFunParseExport, true};
AstStat* stat = parse(R"(
type function foo()
return types.number
@ -3656,7 +3686,7 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
auto unionTy = paramTy.type->as<AstTypeUnion>();
LUAU_ASSERT(unionTy);
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
}
@ -3702,11 +3732,14 @@ TEST_CASE_FIXTURE(Fixture, "recover_from_bad_table_type")
ScopedFastFlag _{FFlag::LuauErrorRecoveryForTableTypes, true};
ParseOptions opts;
opts.allowDeclarationSyntax = true;
const auto result = tryParse(R"(
const auto result = tryParse(
R"(
declare class Widget
state: {string: function(string, Widget)}
end
)", opts);
)",
opts
);
CHECK_EQ(result.errors.size(), 2);
}

View file

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

View file

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

View file

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

View file

@ -665,12 +665,11 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes")
)");
if (FFlag::LuauSolverV2)
CHECK(
"Type 'boolean' could not be converted into 'number | string'" == toString(result.errors.at(0))
);
CHECK("Type 'boolean' could not be converted into 'number | string'" == toString(result.errors.at(0)));
else
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)
CHECK(
"Type 'boolean' could not be converted into 'number | string'" == toString(result.errors.at(0))
);
CHECK("Type 'boolean' could not be converted into 'number | string'" == toString(result.errors.at(0)));
else
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

@ -2566,10 +2566,7 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_return_type")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauDontRefCountTypesInTypeFunctions, true}
};
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDontRefCountTypesInTypeFunctions, true}};
// CLI-114134: This test:
// 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",
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
{

View file

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

View file

@ -69,9 +69,9 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
const std::string expectedWithEqSat = R"(
function f(a:{fn:()->(unknown,...unknown)}): ()
if type(a) == 'boolean'then
local a1:never=a
local a1:{fn:()->(unknown,...unknown)}&boolean=a
elseif a.fn()then
local a2:{fn:()->(unknown,...unknown)}=a
local a2:{fn:()->(unknown,...unknown)}&negate<boolean>=a
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);
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(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")
@ -742,11 +742,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_
if (FFlag::LuauSolverV2)
{
// CLI-115281 Types produced by refinements do not consistently get simplified
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("(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("(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("(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"
}
else
{

View file

@ -21,6 +21,7 @@ LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAG(LuauRetrySubtypingWithoutHiddenPack)
LUAU_FASTFLAG(LuauTableKeysAreRValues)
LUAU_FASTFLAG(LuauAllowNilAssignmentToIndexer)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
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")
{
ScopedFastFlag _{FFlag::LuauTrackInteriorFreeTypesOnScope, true};
CheckResult result = check(R"(
local function f(s): string
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)
{
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.");
// FIXME: These free types should have been generalized by now.
CHECK(
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(get<CannotCallNonFunction>(result.errors[3]));
CHECK_EQ("(never) -> string", toString(requireType("f")));
}
else
@ -5002,7 +5002,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
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])
);
}

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")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauNewSolverVisitErrorExprLvalues, true}
};
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauNewSolverVisitErrorExprLvalues, true}};
// 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
@ -1734,10 +1731,7 @@ TEST_CASE_FIXTURE(Fixture, "visit_error_nodes_in_lvalue")
TEST_CASE_FIXTURE(Fixture, "avoid_blocking_type_function")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauDontRefCountTypesInTypeFunctions, true}
};
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDontRefCountTypesInTypeFunctions, true}};
LUAU_CHECK_NO_ERRORS(check(R"(
--!strict
@ -1750,10 +1744,7 @@ TEST_CASE_FIXTURE(Fixture, "avoid_blocking_type_function")
TEST_CASE_FIXTURE(Fixture, "avoid_double_reference_to_free_type")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauDontRefCountTypesInTypeFunctions, true}
};
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDontRefCountTypesInTypeFunctions, true}};
LUAU_CHECK_NO_ERRORS(check(R"(
--!strict

View file

@ -953,11 +953,10 @@ a = b
if (FFlag::LuauSolverV2)
{
const std::string expected =
"Type\n"
" '() -> (number, ...boolean)'\n"
"could not be converted into\n"
" '() -> (number, ...string)'; at returns().tail().variadic(), boolean is not a subtype of string";
const std::string expected = "Type\n"
" '() -> (number, ...boolean)'\n"
"could not be converted into\n"
" '() -> (number, ...string)'; at returns().tail().variadic(), boolean is not a subtype of string";
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);
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")

View file

@ -599,6 +599,90 @@ end
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()
getfenv()
@ -619,6 +703,7 @@ local function testslowcalls()
fromtostring()
fill()
misc(table.create(16, 0))
bitops(16, 0)
end
testslowcalls()

View file

@ -237,7 +237,7 @@ if not limitedstack then
end
-- 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
local ok, msg = pcall(recurse, 19000)

View file

@ -513,4 +513,68 @@ end
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')