mirror of
https://github.com/luau-lang/luau.git
synced 2025-01-19 17:28:06 +00:00
Merge branch 'upstream' into merge
Note: Fixed conflicts by hand in: - Analysis/src/ConstraintGenerator.cpp - CodeGen/src/OptimizeConstProp.cpp - VM/src/lmathlib.cpp - tests/Conformance.test.cpp
This commit is contained in:
commit
a0ed331da0
85 changed files with 1329 additions and 731 deletions
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -62,7 +62,6 @@ struct ReferenceCountInitializer : TypeOnceVisitor
|
|||
// of this type, hence:
|
||||
return !FFlag::LuauDontRefCountTypesInTypeFunctions;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
bool isReferenceCountedType(const TypeId typ)
|
||||
|
|
|
@ -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,10 +312,20 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
TypeId ConstraintGenerator::freshType(const ScopePtr& scope)
|
||||
{
|
||||
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();
|
||||
|
||||
|
@ -3941,19 +3969,6 @@ 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -942,7 +942,15 @@ std::string mkDesc(
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -1892,7 +1905,12 @@ void Simplifier::intersectWithNegatedClass(Id id)
|
|||
// This cannot be meaningfully reduced.
|
||||
continue;
|
||||
case RightSuper:
|
||||
subst(id, egraph.add(TNever{}), "intersectClassWithNegatedClass", {{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}});
|
||||
subst(
|
||||
id,
|
||||
egraph.add(TNever{}),
|
||||
"intersectClassWithNegatedClass",
|
||||
{{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}}
|
||||
);
|
||||
return;
|
||||
case Unrelated:
|
||||
// Part & ~Folder == Part
|
||||
|
@ -1906,7 +1924,12 @@ void Simplifier::intersectWithNegatedClass(Id id)
|
|||
}
|
||||
|
||||
Id substId = egraph.add(Intersection{newParts.begin(), newParts.end()});
|
||||
subst(id, substId, "intersectClassWithNegatedClass", {{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}});
|
||||
subst(
|
||||
id,
|
||||
substId,
|
||||
"intersectClassWithNegatedClass",
|
||||
{{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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, {}};
|
||||
|
|
|
@ -464,7 +464,9 @@ public:
|
|||
, typeFunctionRuntime(state->ctx->typeFunctionRuntime)
|
||||
, queue({})
|
||||
, types({})
|
||||
, packs({}){};
|
||||
, packs({})
|
||||
{
|
||||
}
|
||||
|
||||
TypeId deserialize(TypeFunctionTypeId ty)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
@ -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
|
||||
|
|
|
@ -45,4 +45,4 @@ private:
|
|||
size_t offset;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace Luau
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -63,4 +63,4 @@ void* Allocator::allocate(size_t size)
|
|||
return page->data;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace Luau
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 '=':
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
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
|
||||
if (DFFlag::LuauPopIncompleteCi)
|
||||
{
|
||||
luaD_checkstackfornewci(L, ccl->stacksize);
|
||||
}
|
||||
else
|
||||
{
|
||||
luaD_checkstack(L, ccl->stacksize);
|
||||
}
|
||||
|
||||
LUAU_ASSERT(ci->top <= L->stack_last);
|
||||
|
||||
|
|
|
@ -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++)
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
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,7 +1261,36 @@ 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)
|
||||
{
|
||||
for (size_t i = 0; i < state.getSlotNodeCache.size(); i++)
|
||||
{
|
||||
auto&& [prevIdx, num, lastNum] = state.getSlotNodeCache[i];
|
||||
|
||||
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];
|
||||
|
||||
|
@ -1187,8 +1301,9 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
|||
}
|
||||
}
|
||||
|
||||
if (int(state.getSlotNodeCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
|
||||
state.getSlotNodeCache.push_back(index);
|
||||
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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) > len * 8)
|
||||
luaL_error(L, "buffer access out of bounds");
|
||||
|
||||
unsigned startbyte = unsigned(bitoffset / 8);
|
||||
unsigned endbyte = unsigned((bitoffset + bitcount + 7) / 8);
|
||||
|
||||
uint64_t data = 0;
|
||||
memcpy(&data, (char*)buf + startbyte, endbyte - startbyte);
|
||||
|
||||
uint64_t subbyteoffset = bitoffset & 0x7;
|
||||
uint64_t mask = (1ull << bitcount) - 1;
|
||||
|
||||
lua_pushunsigned(L, unsigned((data >> subbyteoffset) & mask));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int buffer_writebits(lua_State* L)
|
||||
{
|
||||
size_t len = 0;
|
||||
void* buf = luaL_checkbuffer(L, 1, &len);
|
||||
int64_t bitoffset = (int64_t)luaL_checknumber(L, 2);
|
||||
int bitcount = luaL_checkinteger(L, 3);
|
||||
unsigned value = luaL_checkunsigned(L, 4);
|
||||
|
||||
if (bitoffset < 0)
|
||||
luaL_error(L, "buffer access out of bounds");
|
||||
|
||||
if (unsigned(bitcount) > 32)
|
||||
luaL_error(L, "bit count is out of range of [0; 32]");
|
||||
|
||||
if (uint64_t(bitoffset + bitcount) > len * 8)
|
||||
luaL_error(L, "buffer access out of bounds");
|
||||
|
||||
unsigned startbyte = unsigned(bitoffset / 8);
|
||||
unsigned endbyte = unsigned((bitoffset + bitcount + 7) / 8);
|
||||
|
||||
uint64_t data = 0;
|
||||
memcpy(&data, (char*)buf + startbyte, endbyte - startbyte);
|
||||
|
||||
uint64_t subbyteoffset = bitoffset & 0x7;
|
||||
uint64_t mask = ((1ull << bitcount) - 1) << subbyteoffset;
|
||||
|
||||
data = (data & ~mask) | ((uint64_t(value) << subbyteoffset) & mask);
|
||||
|
||||
memcpy((char*)buf + startbyte, &data, endbyte - startbyte);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const luaL_Reg bufferlib_DEPRECATED[] = {
|
||||
{"create", buffer_create},
|
||||
{"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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
14
VM/src/ldo.h
14
VM/src/ldo.h
|
@ -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);
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
||||
if (DFFlag::LuauPopIncompleteCi)
|
||||
{
|
||||
luaD_checkstackfornewci(L, ccl->stacksize);
|
||||
}
|
||||
else
|
||||
{
|
||||
luaD_checkstack(L, ccl->stacksize);
|
||||
}
|
||||
LUAU_ASSERT(ci->top <= L->stack_last);
|
||||
|
||||
if (!ccl->isC)
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,23 +208,24 @@ 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{{
|
||||
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{{
|
||||
CHECK(
|
||||
"unknown" == simplifyStr(arena->addType(UnionType{{
|
||||
builtinTypes->nilType,
|
||||
builtinTypes->booleanType,
|
||||
builtinTypes->numberType,
|
||||
|
@ -237,86 +235,70 @@ TEST_CASE_FIXTURE(ESFixture, "nil | boolean | number | string | thread | functio
|
|||
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,
|
||||
CHECK(
|
||||
"Parent | Unrelated" == simplifyStr(arena->addType(UnionType{
|
||||
{builtinTypes->neverType,
|
||||
parentClass,
|
||||
arena->addType(IntersectionType{{builtinTypes->numberType, builtinTypes->stringType}}),
|
||||
unrelatedClass
|
||||
}})));
|
||||
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));
|
||||
}
|
||||
|
|
|
@ -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__)
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 = 3․13
|
||||
)", "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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauVectorDefinitions)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferPrimitives");
|
||||
|
|
|
@ -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
|
||||
)";
|
||||
|
|
|
@ -743,10 +743,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_
|
|||
{
|
||||
// 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(
|
||||
"(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(
|
||||
"(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({12, 24}))
|
||||
); // equivalent to type(v) ~= "nil"
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -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])
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -953,8 +953,7 @@ a = b
|
|||
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
const std::string expected =
|
||||
"Type\n"
|
||||
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";
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in a new issue