Sync to upstream/release/509 (#303)

- Rework transaction log used for type checking which should result in more robust type checking internals with fewer bugs
- Reduce the amount of memory consumed by type checker on large module graphs
- Type checker now errors on attempts to change the type of imported module fields
- The return type of newproxy is now any (fixes #296)
- Implement new number printing algorithm (Schubfach) which makes tostring() produce precise (round-trippable) and short decimal output up to 10x faster
- Fix lua_Debug::linedefined to point to the line with the function definition instead of the first statement (fixes #265)
- Fix minor bugs in Tab completion in Repl
- Repl now saves/restores command history in ~/.luau_history
This commit is contained in:
Arseny Kapoulkine 2022-01-06 17:46:53 -08:00 committed by GitHub
parent d323237b6c
commit d50b079325
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
62 changed files with 3901 additions and 1375 deletions

18
.gitignore vendored
View file

@ -1,8 +1,10 @@
^build/
^coverage/
^fuzz/luau.pb.*
^crash-*
^default.prof*
^fuzz-*
^luau$
/.vs
/build/
/build[.-]*/
/coverage/
/.vs/
/.vscode/
/fuzz/luau.pb.*
/crash-*
/default.prof*
/fuzz-*
/luau

View file

@ -68,7 +68,7 @@ struct FrontendOptions
// is complete.
bool retainFullTypeGraphs = false;
// When true, we run typechecking twice, one in the regular mode, ond once in strict mode
// When true, we run typechecking twice, once in the regular mode, and once in strict mode
// in order to get more precise type information (e.g. for autocomplete).
bool typecheckTwice = false;
};
@ -109,18 +109,18 @@ struct Frontend
Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options = {});
CheckResult check(const ModuleName& name); // new shininess
LintResult lint(const ModuleName& name, std::optional<Luau::LintOptions> enabledLintWarnings = {});
CheckResult check(const ModuleName& name, std::optional<FrontendOptions> optionOverride = {}); // new shininess
LintResult lint(const ModuleName& name, std::optional<LintOptions> enabledLintWarnings = {});
/** Lint some code that has no associated DataModel object
*
* Since this source fragment has no name, we cannot cache its AST. Instead,
* we return it to the caller to use as they wish.
*/
std::pair<SourceModule, LintResult> lintFragment(std::string_view source, std::optional<Luau::LintOptions> enabledLintWarnings = {});
std::pair<SourceModule, LintResult> lintFragment(std::string_view source, std::optional<LintOptions> enabledLintWarnings = {});
CheckResult check(const SourceModule& module); // OLD. TODO KILL
LintResult lint(const SourceModule& module, std::optional<Luau::LintOptions> enabledLintWarnings = {});
LintResult lint(const SourceModule& module, std::optional<LintOptions> enabledLintWarnings = {});
bool isDirty(const ModuleName& name) const;
void markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty = nullptr);

View file

@ -1,7 +1,11 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <memory>
#include <unordered_map>
#include "Luau/TypeVar.h"
#include "Luau/TypePack.h"
LUAU_FASTFLAG(LuauShareTxnSeen);
@ -9,27 +13,28 @@ namespace Luau
{
// Log of where what TypeIds we are rebinding and what they used to be
struct TxnLog
// Remove with LuauUseCommitTxnLog
struct DEPRECATED_TxnLog
{
TxnLog()
DEPRECATED_TxnLog()
: originalSeenSize(0)
, ownedSeen()
, sharedSeen(&ownedSeen)
{
}
explicit TxnLog(std::vector<std::pair<TypeId, TypeId>>* sharedSeen)
explicit DEPRECATED_TxnLog(std::vector<std::pair<TypeId, TypeId>>* sharedSeen)
: originalSeenSize(sharedSeen->size())
, ownedSeen()
, sharedSeen(sharedSeen)
{
}
TxnLog(const TxnLog&) = delete;
TxnLog& operator=(const TxnLog&) = delete;
DEPRECATED_TxnLog(const DEPRECATED_TxnLog&) = delete;
DEPRECATED_TxnLog& operator=(const DEPRECATED_TxnLog&) = delete;
TxnLog(TxnLog&&) = default;
TxnLog& operator=(TxnLog&&) = default;
DEPRECATED_TxnLog(DEPRECATED_TxnLog&&) = default;
DEPRECATED_TxnLog& operator=(DEPRECATED_TxnLog&&) = default;
void operator()(TypeId a);
void operator()(TypePackId a);
@ -37,7 +42,7 @@ struct TxnLog
void rollback();
void concat(TxnLog rhs);
void concat(DEPRECATED_TxnLog rhs);
bool haveSeen(TypeId lhs, TypeId rhs);
void pushSeen(TypeId lhs, TypeId rhs);
@ -54,4 +59,263 @@ public:
std::vector<std::pair<TypeId, TypeId>>* sharedSeen; // shared with all the descendent logs
};
// Pending state for a TypeVar. Generated by a TxnLog and committed via
// TxnLog::commit.
struct PendingType
{
// The pending TypeVar state.
TypeVar pending;
explicit PendingType(TypeVar state)
: pending(std::move(state))
{
}
};
// Pending state for a TypePackVar. Generated by a TxnLog and committed via
// TxnLog::commit.
struct PendingTypePack
{
// The pending TypePackVar state.
TypePackVar pending;
explicit PendingTypePack(TypePackVar state)
: pending(std::move(state))
{
}
};
template<typename T>
T* getMutable(PendingType* pending)
{
// We use getMutable here because this state is intended to be mutated freely.
return getMutable<T>(&pending->pending);
}
template<typename T>
T* getMutable(PendingTypePack* pending)
{
// We use getMutable here because this state is intended to be mutated freely.
return getMutable<T>(&pending->pending);
}
// Log of what TypeIds we are rebinding, to be committed later.
struct TxnLog
{
TxnLog()
: ownedSeen()
, sharedSeen(&ownedSeen)
{
}
explicit TxnLog(TxnLog* parent)
: parent(parent)
{
if (parent)
{
sharedSeen = parent->sharedSeen;
}
else
{
sharedSeen = &ownedSeen;
}
}
explicit TxnLog(std::vector<std::pair<TypeId, TypeId>>* sharedSeen)
: sharedSeen(sharedSeen)
{
}
TxnLog(TxnLog* parent, std::vector<std::pair<TypeId, TypeId>>* sharedSeen)
: parent(parent)
, sharedSeen(sharedSeen)
{
}
TxnLog(const TxnLog&) = delete;
TxnLog& operator=(const TxnLog&) = delete;
TxnLog(TxnLog&&) = default;
TxnLog& operator=(TxnLog&&) = default;
// Gets an empty TxnLog pointer. This is useful for constructs that
// take a TxnLog, like TypePackIterator - use the empty log if you
// don't have a TxnLog to give it.
static const TxnLog* empty();
// Joins another TxnLog onto this one. You should use std::move to avoid
// copying the rhs TxnLog.
//
// If both logs talk about the same type, pack, or table, the rhs takes
// priority.
void concat(TxnLog rhs);
// Commits the TxnLog, rebinding all type pointers to their pending states.
// Clears the TxnLog afterwards.
void commit();
// Clears the TxnLog without committing any pending changes.
void clear();
// Computes an inverse of this TxnLog at the current time.
// This method should be called before commit is called in order to give an
// accurate result. Committing the inverse of a TxnLog will undo the changes
// made by commit, assuming the inverse log is accurate.
TxnLog inverse();
bool haveSeen(TypeId lhs, TypeId rhs) const;
void pushSeen(TypeId lhs, TypeId rhs);
void popSeen(TypeId lhs, TypeId rhs);
// Queues a type for modification. The original type will not change until commit
// is called. Use pending to get the pending state.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* queue(TypeId ty);
// Queues a type pack for modification. The original type pack will not change
// until commit is called. Use pending to get the pending state.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* queue(TypePackId tp);
// Returns the pending state of a type, or nullptr if there isn't any. It is important
// to note that this pending state is not transitive: the pending state may reference
// non-pending types freely, so you may need to call pending multiple times to view the
// entire pending state of a type graph.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* pending(TypeId ty) const;
// Returns the pending state of a type pack, or nullptr if there isn't any. It is
// important to note that this pending state is not transitive: the pending state may
// reference non-pending types freely, so you may need to call pending multiple times
// to view the entire pending state of a type graph.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* pending(TypePackId tp) const;
// Queues a replacement of a type with another type.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* replace(TypeId ty, TypeVar replacement);
// Queues a replacement of a type pack with another type pack.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* replace(TypePackId tp, TypePackVar replacement);
// Queues a replacement of a table type with another table type that is bound
// to a specific value.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* bindTable(TypeId ty, std::optional<TypeId> newBoundTo);
// Queues a replacement of a type with a level with a duplicate of that type
// with a new type level.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* changeLevel(TypeId ty, TypeLevel newLevel);
// Queues a replacement of a type pack with a level with a duplicate of that
// type pack with a new type level.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel);
// Queues a replacement of a table type with another table type with a new
// indexer.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* changeIndexer(TypeId ty, std::optional<TableIndexer> indexer);
// Returns the type level of the pending state of the type, or the level of that
// type, if no pending state exists. If the type doesn't have a notion of a level,
// returns nullopt. If the pending state doesn't have a notion of a level, but the
// original state does, returns nullopt.
std::optional<TypeLevel> getLevel(TypeId ty) const;
// Follows a type, accounting for pending type states. The returned type may have
// pending state; you should use `pending` or `get` to find out.
TypeId follow(TypeId ty);
// Follows a type pack, accounting for pending type states. The returned type pack
// may have pending state; you should use `pending` or `get` to find out.
TypePackId follow(TypePackId tp) const;
// Replaces a given type's state with a new variant. Returns the new pending state
// of that type.
//
// The pointer returned lives until `commit` or `clear` is called.
template<typename T>
PendingType* replace(TypeId ty, T replacement)
{
return replace(ty, TypeVar(replacement));
}
// Replaces a given type pack's state with a new variant. Returns the new
// pending state of that type pack.
//
// The pointer returned lives until `commit` or `clear` is called.
template<typename T>
PendingTypePack* replace(TypePackId tp, T replacement)
{
return replace(tp, TypePackVar(replacement));
}
// Returns T if a given type or type pack is this variant, respecting the
// log's pending state.
//
// Do not retain this pointer; it has the potential to be invalidated when
// commit or clear is called.
template<typename T, typename TID>
T* getMutable(TID ty) const
{
auto* pendingTy = pending(ty);
if (pendingTy)
return Luau::getMutable<T>(pendingTy);
return Luau::getMutable<T>(ty);
}
// Returns whether a given type or type pack is a given state, respecting the
// log's pending state.
//
// This method will not assert if called on a BoundTypeVar or BoundTypePack.
template<typename T, typename TID>
bool is(TID ty) const
{
// We do not use getMutable here because this method can be called on
// BoundTypeVars, which triggers an assertion.
auto* pendingTy = pending(ty);
if (pendingTy)
return Luau::get_if<T>(&pendingTy->pending.ty) != nullptr;
return Luau::get_if<T>(&ty->ty) != nullptr;
}
private:
// unique_ptr is used to give us stable pointers across insertions into the
// map. Otherwise, it would be really easy to accidentally invalidate the
// pointers returned from queue/pending.
//
// We can't use a DenseHashMap here because we need a non-const iterator
// over the map when we concatenate.
std::unordered_map<TypeId, std::unique_ptr<PendingType>> typeVarChanges;
std::unordered_map<TypePackId, std::unique_ptr<PendingTypePack>> typePackChanges;
TxnLog* parent = nullptr;
// Owned version of sharedSeen. This should not be accessed directly in
// TxnLogs; use sharedSeen instead. This field exists because in the tree
// of TxnLogs, the root must own its seen set. In all descendant TxnLogs,
// this is an empty vector.
std::vector<std::pair<TypeId, TypeId>> ownedSeen;
public:
// Used to avoid infinite recursion when types are cyclic.
// Shared with all the descendent TxnLogs.
std::vector<std::pair<TypeId, TypeId>>* sharedSeen;
};
} // namespace Luau

View file

@ -198,32 +198,32 @@ struct TypeChecker
*/
TypeId anyIfNonstrict(TypeId ty) const;
/** Attempt to unify the types left and right. Treat any failures as type errors
* in the final typecheck report.
/** Attempt to unify the types.
* Treat any failures as type errors in the final typecheck report.
*/
bool unify(TypeId left, TypeId right, const Location& location);
bool unify(TypePackId left, TypePackId right, const Location& location, CountMismatch::Context ctx = CountMismatch::Context::Arg);
bool unify(TypeId subTy, TypeId superTy, const Location& location);
bool unify(TypePackId subTy, TypePackId superTy, const Location& location, CountMismatch::Context ctx = CountMismatch::Context::Arg);
/** Attempt to unify the types left and right.
* If this fails, and the right type can be instantiated, do so and try unification again.
/** Attempt to unify the types.
* If this fails, and the subTy type can be instantiated, do so and try unification again.
*/
bool unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId left, TypeId right, const Location& location);
void unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId left, TypeId right, Unifier& state);
bool unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId subTy, TypeId superTy, const Location& location);
void unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId subTy, TypeId superTy, Unifier& state);
/** Attempt to unify left with right.
/** Attempt to unify.
* If there are errors, undo everything and return the errors.
* If there are no errors, commit and return an empty error vector.
*/
ErrorVec tryUnify(TypeId left, TypeId right, const Location& location);
ErrorVec tryUnify(TypePackId left, TypePackId right, const Location& location);
template<typename Id>
ErrorVec tryUnify_(Id subTy, Id superTy, const Location& location);
ErrorVec tryUnify(TypeId subTy, TypeId superTy, const Location& location);
ErrorVec tryUnify(TypePackId subTy, TypePackId superTy, const Location& location);
// Test whether the two type vars unify. Never commits the result.
ErrorVec canUnify(TypeId superTy, TypeId subTy, const Location& location);
ErrorVec canUnify(TypePackId superTy, TypePackId subTy, const Location& location);
// Variant that takes a preexisting 'seen' set. We need this in certain cases to avoid infinitely recursing
// into cyclic types.
ErrorVec canUnify(const std::vector<std::pair<TypeId, TypeId>>& seen, TypeId left, TypeId right, const Location& location);
template<typename Id>
ErrorVec canUnify_(Id subTy, Id superTy, const Location& location);
ErrorVec canUnify(TypeId subTy, TypeId superTy, const Location& location);
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, const Location& location);
std::optional<TypeId> findMetatableEntry(TypeId type, std::string entry, const Location& location);
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location);
@ -237,12 +237,6 @@ struct TypeChecker
std::optional<TypeId> tryStripUnionFromNil(TypeId ty);
TypeId stripFromNilAndReport(TypeId ty, const Location& location);
template<typename Id>
ErrorVec tryUnify_(Id left, Id right, const Location& location);
template<typename Id>
ErrorVec canUnify_(Id left, Id right, const Location& location);
public:
/*
* Convert monotype into a a polytype, by replacing any metavariables in descendant scopes

View file

@ -18,6 +18,8 @@ struct VariadicTypePack;
struct TypePackVar;
struct TxnLog;
using TypePackId = const TypePackVar*;
using FreeTypePack = Unifiable::Free;
using BoundTypePack = Unifiable::Bound<TypePackId>;
@ -84,6 +86,7 @@ struct TypePackIterator
TypePackIterator() = default;
explicit TypePackIterator(TypePackId tp);
TypePackIterator(TypePackId tp, const TxnLog* log);
TypePackIterator& operator++();
TypePackIterator operator++(int);
@ -104,9 +107,13 @@ private:
TypePackId currentTypePack = nullptr;
const TypePack* tp = nullptr;
size_t currentIndex = 0;
// Only used if LuauUseCommittingTxnLog is true.
const TxnLog* log;
};
TypePackIterator begin(TypePackId tp);
TypePackIterator begin(TypePackId tp, TxnLog* log);
TypePackIterator end(TypePackId tp);
using SeenSet = std::set<std::pair<void*, void*>>;
@ -114,6 +121,7 @@ using SeenSet = std::set<std::pair<void*, void*>>;
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);
TypePackId follow(TypePackId tp);
TypePackId follow(TypePackId tp, std::function<TypePackId(TypePackId)> mapper);
size_t size(TypePackId tp);
bool finite(TypePackId tp);

View file

@ -453,6 +453,7 @@ bool areEqual(SeenSet& seen, const TypeVar& lhs, const TypeVar& rhs);
// Follow BoundTypeVars until we get to something real
TypeId follow(TypeId t);
TypeId follow(TypeId t, std::function<TypeId(TypeId)> mapper);
std::vector<TypeId> flattenIntersection(TypeId ty);

View file

@ -6,6 +6,8 @@
#include <vector>
#include <memory>
LUAU_FASTFLAG(LuauTypedAllocatorZeroStart)
namespace Luau
{
@ -20,7 +22,10 @@ class TypedAllocator
public:
TypedAllocator()
{
appendBlock();
if (FFlag::LuauTypedAllocatorZeroStart)
currentBlockSize = kBlockSize;
else
appendBlock();
}
~TypedAllocator()
@ -59,12 +64,18 @@ public:
bool empty() const
{
return stuff.size() == 1 && currentBlockSize == 0;
if (FFlag::LuauTypedAllocatorZeroStart)
return stuff.empty();
else
return stuff.size() == 1 && currentBlockSize == 0;
}
size_t size() const
{
return kBlockSize * (stuff.size() - 1) + currentBlockSize;
if (FFlag::LuauTypedAllocatorZeroStart)
return stuff.empty() ? 0 : kBlockSize * (stuff.size() - 1) + currentBlockSize;
else
return kBlockSize * (stuff.size() - 1) + currentBlockSize;
}
void clear()
@ -72,7 +83,11 @@ public:
if (frozen)
unfreeze();
free();
appendBlock();
if (FFlag::LuauTypedAllocatorZeroStart)
currentBlockSize = kBlockSize;
else
appendBlock();
}
void freeze()

View file

@ -25,6 +25,7 @@ struct Unifier
Mode mode;
ScopePtr globalScope; // sigh. Needed solely to get at string's metatable.
DEPRECATED_TxnLog DEPRECATED_log;
TxnLog log;
ErrorVec errors;
Location location;
@ -33,44 +34,45 @@ struct Unifier
UnifierSharedState& sharedState;
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, UnifierSharedState& sharedState);
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, UnifierSharedState& sharedState,
TxnLog* parentLog = nullptr);
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
Variance variance, UnifierSharedState& sharedState);
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
// Test whether the two type vars unify. Never commits the result.
ErrorVec canUnify(TypeId superTy, TypeId subTy);
ErrorVec canUnify(TypePackId superTy, TypePackId subTy, bool isFunctionCall = false);
ErrorVec canUnify(TypeId subTy, TypeId superTy);
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
/** Attempt to unify left with right.
/** Attempt to unify.
* Populate the vector errors with any type errors that may arise.
* Populate the transaction log with the set of TypeIds that need to be reset to undo the unification attempt.
*/
void tryUnify(TypeId superTy, TypeId subTy, bool isFunctionCall = false, bool isIntersection = false);
void tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false);
private:
void tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall = false, bool isIntersection = false);
void tryUnifyPrimitives(TypeId superTy, TypeId subTy);
void tryUnifySingletons(TypeId superTy, TypeId subTy);
void tryUnifyFunctions(TypeId superTy, TypeId subTy, bool isFunctionCall = false);
void tryUnifyTables(TypeId left, TypeId right, bool isIntersection = false);
void DEPRECATED_tryUnifyTables(TypeId left, TypeId right, bool isIntersection = false);
void tryUnifyFreeTable(TypeId free, TypeId other);
void tryUnifySealedTables(TypeId left, TypeId right, bool isIntersection);
void tryUnifyWithMetatable(TypeId metatable, TypeId other, bool reversed);
void tryUnifyWithClass(TypeId superTy, TypeId subTy, bool reversed);
void tryUnify(const TableIndexer& superIndexer, const TableIndexer& subIndexer);
void tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false);
void tryUnifyPrimitives(TypeId subTy, TypeId superTy);
void tryUnifySingletons(TypeId subTy, TypeId superTy);
void tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCall = false);
void tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false);
void DEPRECATED_tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false);
void tryUnifyFreeTable(TypeId subTy, TypeId superTy);
void tryUnifySealedTables(TypeId subTy, TypeId superTy, bool isIntersection);
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyIndexer(const TableIndexer& subIndexer, const TableIndexer& superIndexer);
TypeId deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen = {});
void cacheResult(TypeId superTy, TypeId subTy);
void cacheResult(TypeId subTy, TypeId superTy);
public:
void tryUnify(TypePackId superTy, TypePackId subTy, bool isFunctionCall = false);
void tryUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
private:
void tryUnify_(TypePackId superTy, TypePackId subTy, bool isFunctionCall = false);
void tryUnifyVariadics(TypePackId superTy, TypePackId subTy, bool reversed, int subOffset = 0);
void tryUnify_(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
void tryUnifyVariadics(TypePackId subTy, TypePackId superTy, bool reversed, int subOffset = 0);
void tryUnifyWithAny(TypeId any, TypeId ty);
void tryUnifyWithAny(TypePackId any, TypePackId ty);
void tryUnifyWithAny(TypeId subTy, TypeId anyTy);
void tryUnifyWithAny(TypePackId subTy, TypePackId anyTp);
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name);

View file

@ -12,10 +12,12 @@
#include <unordered_set>
#include <utility>
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
LUAU_FASTFLAG(LuauIfElseExpressionAnalysisSupport)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false);
LUAU_FASTFLAGVARIABLE(LuauAutocompletePreferToCallFunctions, false);
LUAU_FASTFLAGVARIABLE(LuauAutocompleteFirstArg, false);
LUAU_FASTFLAGVARIABLE(LuauCompleteBrokenStringParams, false);
static const std::unordered_set<std::string> kStatementStartingKeywords = {
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -236,28 +238,31 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
{
ty = follow(ty);
auto canUnify = [&typeArena, &module](TypeId expectedType, TypeId actualType) {
auto canUnify = [&typeArena, &module](TypeId subTy, TypeId superTy) {
InternalErrorReporter iceReporter;
UnifierSharedState unifierState(&iceReporter);
Unifier unifier(typeArena, Mode::Strict, module.getModuleScope(), Location(), Variance::Covariant, unifierState);
if (FFlag::LuauAutocompleteAvoidMutation)
if (FFlag::LuauAutocompleteAvoidMutation && !FFlag::LuauUseCommittingTxnLog)
{
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
expectedType = clone(expectedType, *typeArena, seenTypes, seenTypePacks, cloneState);
actualType = clone(actualType, *typeArena, seenTypes, seenTypePacks, cloneState);
superTy = clone(superTy, *typeArena, seenTypes, seenTypePacks, cloneState);
subTy = clone(subTy, *typeArena, seenTypes, seenTypePacks, cloneState);
auto errors = unifier.canUnify(expectedType, actualType);
auto errors = unifier.canUnify(subTy, superTy);
return errors.empty();
}
else
{
unifier.tryUnify(expectedType, actualType);
unifier.tryUnify(subTy, superTy);
bool ok = unifier.errors.empty();
unifier.log.rollback();
if (!FFlag::LuauUseCommittingTxnLog)
unifier.DEPRECATED_log.rollback();
return ok;
}
};
@ -293,22 +298,22 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
{
auto [retHead, retTail] = flatten(ftv->retType);
if (!retHead.empty() && canUnify(expectedType, retHead.front()))
if (!retHead.empty() && canUnify(retHead.front(), expectedType))
return TypeCorrectKind::CorrectFunctionResult;
// We might only have a variadic tail pack, check if the element is compatible
if (retTail)
{
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail)); vtp && canUnify(expectedType, vtp->ty))
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail)); vtp && canUnify(vtp->ty, expectedType))
return TypeCorrectKind::CorrectFunctionResult;
}
}
return canUnify(expectedType, ty) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
return canUnify(ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
}
else
{
if (canUnify(expectedType, ty))
if (canUnify(ty, expectedType))
return TypeCorrectKind::Correct;
// We also want to suggest functions that return compatible result
@ -320,13 +325,13 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
auto [retHead, retTail] = flatten(ftv->retType);
if (!retHead.empty())
return canUnify(expectedType, retHead.front()) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
return canUnify(retHead.front(), expectedType) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
// We might only have a variadic tail pack, check if the element is compatible
if (retTail)
{
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*retTail)))
return canUnify(expectedType, vtp->ty) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
return canUnify(vtp->ty, expectedType) ? TypeCorrectKind::CorrectFunctionResult : TypeCorrectKind::None;
}
return TypeCorrectKind::None;
@ -1319,7 +1324,7 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(const Source
return std::nullopt;
}
if (!nodes.back()->is<AstExprConstantString>())
if (!nodes.back()->is<AstExprConstantString>() && (!FFlag::LuauCompleteBrokenStringParams || !nodes.back()->is<AstExprError>()))
{
return std::nullopt;
}

View file

@ -138,12 +138,7 @@ declare function gcinfo(): number
-- (nil, string).
declare function loadstring<A...>(src: string, chunkname: string?): (((A...) -> any)?, string?)
-- a userdata object is "roughly" the same as a sealed empty table
-- except `type(newproxy(false))` evaluates to "userdata" so we may need another special type here too.
-- another important thing to note: the value passed in conditionally creates an empty metatable, and you have to use getmetatable, NOT
-- setmetatable.
-- FIXME: change this to something Luau can understand how to reject `setmetatable(newproxy(false or true), {})`.
declare function newproxy(mt: boolean?): {}
declare function newproxy(mt: boolean?): any
declare coroutine: {
create: <A..., R...>((A...) -> R...) -> thread,

View file

@ -351,7 +351,7 @@ FrontendModuleResolver::FrontendModuleResolver(Frontend* frontend)
{
}
CheckResult Frontend::check(const ModuleName& name)
CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOptions> optionOverride)
{
LUAU_TIMETRACE_SCOPE("Frontend::check", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
@ -372,6 +372,8 @@ CheckResult Frontend::check(const ModuleName& name)
std::vector<ModuleName> buildQueue;
bool cycleDetected = parseGraph(buildQueue, checkResult, name);
FrontendOptions frontendOptions = optionOverride.value_or(options);
// Keep track of which AST nodes we've reported cycles in
std::unordered_set<AstNode*> reportedCycles;
@ -411,31 +413,11 @@ CheckResult Frontend::check(const ModuleName& name)
// If we're typechecking twice, we do so.
// The second typecheck is always in strict mode with DM awareness
// to provide better typen information for IDE features.
if (options.typecheckTwice)
if (frontendOptions.typecheckTwice)
{
ModulePtr moduleForAutocomplete = typeCheckerForAutocomplete.check(sourceModule, Mode::Strict);
moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete;
}
else if (options.retainFullTypeGraphs && options.typecheckTwice && mode != Mode::Strict)
{
ModulePtr strictModule = typeChecker.check(sourceModule, Mode::Strict, environmentScope);
module->astTypes.clear();
module->astOriginalCallTypes.clear();
module->astExpectedTypes.clear();
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
for (const auto& [expr, strictTy] : strictModule->astTypes)
module->astTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState);
for (const auto& [expr, strictTy] : strictModule->astOriginalCallTypes)
module->astOriginalCallTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState);
for (const auto& [expr, strictTy] : strictModule->astExpectedTypes)
module->astExpectedTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks, cloneState);
}
stats.timeCheck += getTimestamp() - timestamp;
stats.filesStrict += mode == Mode::Strict;
@ -444,7 +426,7 @@ CheckResult Frontend::check(const ModuleName& name)
if (module == nullptr)
throw std::runtime_error("Frontend::check produced a nullptr module for " + moduleName);
if (!options.retainFullTypeGraphs)
if (!frontendOptions.retainFullTypeGraphs)
{
// copyErrors needs to allocate into interfaceTypes as it copies
// types out of internalTypes, so we unfreeze it here.

View file

@ -13,7 +13,7 @@
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false)
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 0)
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
namespace Luau
{

View file

@ -4,8 +4,6 @@
#include "Luau/VisitTypeVar.h"
LUAU_FASTFLAGVARIABLE(LuauQuantifyVisitOnce, false)
namespace Luau
{
@ -81,16 +79,8 @@ struct Quantifier
void quantify(ModulePtr module, TypeId ty, TypeLevel level)
{
Quantifier q{std::move(module), level};
if (FFlag::LuauQuantifyVisitOnce)
{
DenseHashSet<void*> seen{nullptr};
visitTypeVarOnce(ty, q, seen);
}
else
{
visitTypeVar(ty, q);
}
DenseHashSet<void*> seen{nullptr};
visitTypeVarOnce(ty, q, seen);
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
LUAU_ASSERT(ftv);

View file

@ -11,7 +11,6 @@
#include <stdexcept>
LUAU_FASTFLAG(LuauOccursCheckOkWithRecursiveFunctions)
LUAU_FASTFLAGVARIABLE(LuauFunctionArgumentNameSize, false)
/*
* Prefix generic typenames with gen-
@ -766,24 +765,12 @@ struct TypePackStringifier
else
state.emit(", ");
if (FFlag::LuauFunctionArgumentNameSize)
if (elemIndex < elemNames.size() && elemNames[elemIndex])
{
if (elemIndex < elemNames.size() && elemNames[elemIndex])
{
state.emit(elemNames[elemIndex]->name);
state.emit(": ");
}
state.emit(elemNames[elemIndex]->name);
state.emit(": ");
}
else
{
LUAU_ASSERT(elemNames.empty() || elemIndex < elemNames.size());
if (!elemNames.empty() && elemNames[elemIndex])
{
state.emit(elemNames[elemIndex]->name);
state.emit(": ");
}
}
elemIndex++;
stringify(typeId);
@ -1151,38 +1138,19 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV
s += ", ";
first = false;
if (FFlag::LuauFunctionArgumentNameSize)
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
if (argNameIter != ftv.argNames.end())
{
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
if (argNameIter != ftv.argNames.end())
{
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
++argNameIter;
}
else
{
s += "_: ";
}
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
++argNameIter;
}
else
{
// argNames is guaranteed to be equal to argTypes iff argNames is not empty.
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
if (!ftv.argNames.empty())
s += (*argNameIter ? (*argNameIter)->name : "_") + ": ";
s += "_: ";
}
s += toString_(*argPackIter);
++argPackIter;
if (!FFlag::LuauFunctionArgumentNameSize)
{
if (!ftv.argNames.empty())
{
LUAU_ASSERT(argNameIter != ftv.argNames.end());
++argNameIter;
}
}
}
if (argPackIter.tail())

View file

@ -4,27 +4,34 @@
#include "Luau/TypePack.h"
#include <algorithm>
#include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauUseCommittingTxnLog, false)
namespace Luau
{
void TxnLog::operator()(TypeId a)
void DEPRECATED_TxnLog::operator()(TypeId a)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
typeVarChanges.emplace_back(a, *a);
}
void TxnLog::operator()(TypePackId a)
void DEPRECATED_TxnLog::operator()(TypePackId a)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
typePackChanges.emplace_back(a, *a);
}
void TxnLog::operator()(TableTypeVar* a)
void DEPRECATED_TxnLog::operator()(TableTypeVar* a)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
tableChanges.emplace_back(a, a->boundTo);
}
void TxnLog::rollback()
void DEPRECATED_TxnLog::rollback()
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
for (auto it = typeVarChanges.rbegin(); it != typeVarChanges.rend(); ++it)
std::swap(*asMutable(it->first), it->second);
@ -38,8 +45,9 @@ void TxnLog::rollback()
sharedSeen->resize(originalSeenSize);
}
void TxnLog::concat(TxnLog rhs)
void DEPRECATED_TxnLog::concat(DEPRECATED_TxnLog rhs)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
typeVarChanges.insert(typeVarChanges.end(), rhs.typeVarChanges.begin(), rhs.typeVarChanges.end());
rhs.typeVarChanges.clear();
@ -50,23 +58,298 @@ void TxnLog::concat(TxnLog rhs)
rhs.tableChanges.clear();
}
bool TxnLog::haveSeen(TypeId lhs, TypeId rhs)
bool DEPRECATED_TxnLog::haveSeen(TypeId lhs, TypeId rhs)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
return (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair));
}
void DEPRECATED_TxnLog::pushSeen(TypeId lhs, TypeId rhs)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
sharedSeen->push_back(sortedPair);
}
void DEPRECATED_TxnLog::popSeen(TypeId lhs, TypeId rhs)
{
LUAU_ASSERT(!FFlag::LuauUseCommittingTxnLog);
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
LUAU_ASSERT(sortedPair == sharedSeen->back());
sharedSeen->pop_back();
}
static const TxnLog emptyLog;
const TxnLog* TxnLog::empty()
{
return &emptyLog;
}
void TxnLog::concat(TxnLog rhs)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
for (auto& [ty, rep] : rhs.typeVarChanges)
typeVarChanges[ty] = std::move(rep);
for (auto& [tp, rep] : rhs.typePackChanges)
typePackChanges[tp] = std::move(rep);
}
void TxnLog::commit()
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
for (auto& [ty, rep] : typeVarChanges)
*asMutable(ty) = rep.get()->pending;
for (auto& [tp, rep] : typePackChanges)
*asMutable(tp) = rep.get()->pending;
clear();
}
void TxnLog::clear()
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
typeVarChanges.clear();
typePackChanges.clear();
}
TxnLog TxnLog::inverse()
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
TxnLog inversed(sharedSeen);
for (auto& [ty, _rep] : typeVarChanges)
inversed.typeVarChanges[ty] = std::make_unique<PendingType>(*ty);
for (auto& [tp, _rep] : typePackChanges)
inversed.typePackChanges[tp] = std::make_unique<PendingTypePack>(*tp);
return inversed;
}
bool TxnLog::haveSeen(TypeId lhs, TypeId rhs) const
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair))
{
return true;
}
if (parent)
{
return parent->haveSeen(lhs, rhs);
}
return false;
}
void TxnLog::pushSeen(TypeId lhs, TypeId rhs)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
sharedSeen->push_back(sortedPair);
}
void TxnLog::popSeen(TypeId lhs, TypeId rhs)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
LUAU_ASSERT(sortedPair == sharedSeen->back());
sharedSeen->pop_back();
}
PendingType* TxnLog::queue(TypeId ty)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
LUAU_ASSERT(!ty->persistent);
// Explicitly don't look in ancestors. If we have discovered something new
// about this type, we don't want to mutate the parent's state.
auto& pending = typeVarChanges[ty];
if (!pending)
pending = std::make_unique<PendingType>(*ty);
return pending.get();
}
PendingTypePack* TxnLog::queue(TypePackId tp)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
LUAU_ASSERT(!tp->persistent);
// Explicitly don't look in ancestors. If we have discovered something new
// about this type, we don't want to mutate the parent's state.
auto& pending = typePackChanges[tp];
if (!pending)
pending = std::make_unique<PendingTypePack>(*tp);
return pending.get();
}
PendingType* TxnLog::pending(TypeId ty) const
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
for (const TxnLog* current = this; current; current = current->parent)
{
if (auto it = current->typeVarChanges.find(ty); it != current->typeVarChanges.end())
return it->second.get();
}
return nullptr;
}
PendingTypePack* TxnLog::pending(TypePackId tp) const
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
for (const TxnLog* current = this; current; current = current->parent)
{
if (auto it = current->typePackChanges.find(tp); it != current->typePackChanges.end())
return it->second.get();
}
return nullptr;
}
PendingType* TxnLog::replace(TypeId ty, TypeVar replacement)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
PendingType* newTy = queue(ty);
newTy->pending = replacement;
return newTy;
}
PendingTypePack* TxnLog::replace(TypePackId tp, TypePackVar replacement)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
PendingTypePack* newTp = queue(tp);
newTp->pending = replacement;
return newTp;
}
PendingType* TxnLog::bindTable(TypeId ty, std::optional<TypeId> newBoundTo)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
LUAU_ASSERT(get<TableTypeVar>(ty));
PendingType* newTy = queue(ty);
if (TableTypeVar* ttv = Luau::getMutable<TableTypeVar>(newTy))
ttv->boundTo = newBoundTo;
return newTy;
}
PendingType* TxnLog::changeLevel(TypeId ty, TypeLevel newLevel)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
LUAU_ASSERT(get<FreeTypeVar>(ty) || get<TableTypeVar>(ty) || get<FunctionTypeVar>(ty));
PendingType* newTy = queue(ty);
if (FreeTypeVar* ftv = Luau::getMutable<FreeTypeVar>(newTy))
{
ftv->level = newLevel;
}
else if (TableTypeVar* ttv = Luau::getMutable<TableTypeVar>(newTy))
{
LUAU_ASSERT(ttv->state == TableState::Free || ttv->state == TableState::Generic);
ttv->level = newLevel;
}
else if (FunctionTypeVar* ftv = Luau::getMutable<FunctionTypeVar>(newTy))
{
ftv->level = newLevel;
}
return newTy;
}
PendingTypePack* TxnLog::changeLevel(TypePackId tp, TypeLevel newLevel)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
LUAU_ASSERT(get<FreeTypePack>(tp));
PendingTypePack* newTp = queue(tp);
if (FreeTypePack* ftp = Luau::getMutable<FreeTypePack>(newTp))
{
ftp->level = newLevel;
}
return newTp;
}
PendingType* TxnLog::changeIndexer(TypeId ty, std::optional<TableIndexer> indexer)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
LUAU_ASSERT(get<TableTypeVar>(ty));
PendingType* newTy = queue(ty);
if (TableTypeVar* ttv = Luau::getMutable<TableTypeVar>(newTy))
{
ttv->indexer = indexer;
}
return newTy;
}
std::optional<TypeLevel> TxnLog::getLevel(TypeId ty) const
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
if (FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty))
return ftv->level;
else if (TableTypeVar* ttv = getMutable<TableTypeVar>(ty); ttv && (ttv->state == TableState::Free || ttv->state == TableState::Generic))
return ttv->level;
else if (FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty))
return ftv->level;
return std::nullopt;
}
TypeId TxnLog::follow(TypeId ty)
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
return Luau::follow(ty, [this](TypeId ty) {
PendingType* state = this->pending(ty);
if (state == nullptr)
return ty;
// Ugly: Fabricate a TypeId that doesn't adhere to most of the invariants
// that normally apply. This is safe because follow will only call get<>
// on the returned pointer.
return const_cast<const TypeVar*>(&state->pending);
});
}
TypePackId TxnLog::follow(TypePackId tp) const
{
LUAU_ASSERT(FFlag::LuauUseCommittingTxnLog);
return Luau::follow(tp, [this](TypePackId tp) {
PendingTypePack* state = this->pending(tp);
if (state == nullptr)
return tp;
// Ugly: Fabricate a TypePackId that doesn't adhere to most of the
// invariants that normally apply. This is safe because follow will
// only call get<> on the returned pointer.
return const_cast<const TypePackVar*>(&state->pending);
});
}
} // namespace Luau

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,12 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TypePack.h"
#include "Luau/TxnLog.h"
#include <stdexcept>
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
namespace Luau
{
@ -35,14 +39,28 @@ TypePackVar& TypePackVar::operator=(TypePackVariant&& tp)
}
TypePackIterator::TypePackIterator(TypePackId typePack)
: TypePackIterator(typePack, TxnLog::empty())
{
}
TypePackIterator::TypePackIterator(TypePackId typePack, const TxnLog* log)
: currentTypePack(follow(typePack))
, tp(get<TypePack>(currentTypePack))
, currentIndex(0)
, log(log)
{
while (tp && tp->head.empty())
{
currentTypePack = tp->tail ? follow(*tp->tail) : nullptr;
tp = currentTypePack ? get<TypePack>(currentTypePack) : nullptr;
if (FFlag::LuauUseCommittingTxnLog)
{
currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr;
tp = currentTypePack ? log->getMutable<TypePack>(currentTypePack) : nullptr;
}
else
{
currentTypePack = tp->tail ? follow(*tp->tail) : nullptr;
tp = currentTypePack ? get<TypePack>(currentTypePack) : nullptr;
}
}
}
@ -53,8 +71,17 @@ TypePackIterator& TypePackIterator::operator++()
++currentIndex;
while (tp && currentIndex >= tp->head.size())
{
currentTypePack = tp->tail ? follow(*tp->tail) : nullptr;
tp = currentTypePack ? get<TypePack>(currentTypePack) : nullptr;
if (FFlag::LuauUseCommittingTxnLog)
{
currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr;
tp = currentTypePack ? log->getMutable<TypePack>(currentTypePack) : nullptr;
}
else
{
currentTypePack = tp->tail ? follow(*tp->tail) : nullptr;
tp = currentTypePack ? get<TypePack>(currentTypePack) : nullptr;
}
currentIndex = 0;
}
@ -95,6 +122,11 @@ TypePackIterator begin(TypePackId tp)
return TypePackIterator{tp};
}
TypePackIterator begin(TypePackId tp, TxnLog* log)
{
return TypePackIterator{tp, log};
}
TypePackIterator end(TypePackId tp)
{
return TypePackIterator{};
@ -160,8 +192,15 @@ bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs)
TypePackId follow(TypePackId tp)
{
auto advance = [](TypePackId ty) -> std::optional<TypePackId> {
if (const Unifiable::Bound<TypePackId>* btv = get<Unifiable::Bound<TypePackId>>(ty))
return follow(tp, [](TypePackId t) {
return t;
});
}
TypePackId follow(TypePackId tp, std::function<TypePackId(TypePackId)> mapper)
{
auto advance = [&mapper](TypePackId ty) -> std::optional<TypePackId> {
if (const Unifiable::Bound<TypePackId>* btv = get<Unifiable::Bound<TypePackId>>(mapper(ty)))
return btv->boundTo;
else
return std::nullopt;

View file

@ -31,17 +31,24 @@ std::optional<ExprResult<TypePackId>> magicFunctionFormat(
TypeId follow(TypeId t)
{
auto advance = [](TypeId ty) -> std::optional<TypeId> {
if (auto btv = get<Unifiable::Bound<TypeId>>(ty))
return follow(t, [](TypeId t) {
return t;
});
}
TypeId follow(TypeId t, std::function<TypeId(TypeId)> mapper)
{
auto advance = [&mapper](TypeId ty) -> std::optional<TypeId> {
if (auto btv = get<Unifiable::Bound<TypeId>>(mapper(ty)))
return btv->boundTo;
else if (auto ttv = get<TableTypeVar>(ty))
else if (auto ttv = get<TableTypeVar>(mapper(ty)))
return ttv->boundTo;
else
return std::nullopt;
};
auto force = [](TypeId ty) {
if (auto ltv = get_if<LazyTypeVar>(&ty->ty))
auto force = [&mapper](TypeId ty) {
if (auto ltv = get_if<LazyTypeVar>(&mapper(ty)->ty))
{
TypeId res = ltv->thunk();
if (get<LazyTypeVar>(res))
@ -1004,7 +1011,7 @@ std::optional<ExprResult<TypePackId>> magicFunctionFormat(
{
Location location = expr.args.data[std::min(i + dataOffset, expr.args.size - 1)]->location;
typechecker.unify(expected[i], params[i + paramOffset], location);
typechecker.unify(params[i + paramOffset], expected[i], location);
}
// if we know the argument count or if we have too many arguments for sure, we can issue an error

View file

@ -20,6 +20,7 @@ const size_t kPageSize = sysconf(_SC_PAGESIZE);
#include <stdlib.h>
LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAGVARIABLE(LuauTypedAllocatorZeroStart, false)
namespace Luau
{

File diff suppressed because it is too large Load diff

View file

@ -29,7 +29,7 @@
namespace Luau
{
using AssertHandler = int (*)(const char* expression, const char* file, int line);
using AssertHandler = int (*)(const char* expression, const char* file, int line, const char* function);
inline AssertHandler& assertHandler()
{
@ -37,10 +37,10 @@ inline AssertHandler& assertHandler()
return handler;
}
inline int assertCallHandler(const char* expression, const char* file, int line)
inline int assertCallHandler(const char* expression, const char* file, int line, const char* function)
{
if (AssertHandler handler = assertHandler())
return handler(expression, file, line);
return handler(expression, file, line, function);
return 1;
}
@ -48,7 +48,7 @@ inline int assertCallHandler(const char* expression, const char* file, int line)
} // namespace Luau
#if !defined(NDEBUG) || defined(LUAU_ENABLE_ASSERT)
#define LUAU_ASSERT(expr) ((void)(!!(expr) || (Luau::assertCallHandler(#expr, __FILE__, __LINE__) && (LUAU_DEBUGBREAK(), 0))))
#define LUAU_ASSERT(expr) ((void)(!!(expr) || (Luau::assertCallHandler(#expr, __FILE__, __LINE__, __FUNCTION__) && (LUAU_DEBUGBREAK(), 0))))
#define LUAU_ASSERTENABLED
#else
#define LUAU_ASSERT(expr) (void)sizeof(!!(expr))

View file

@ -107,7 +107,7 @@ static void displayHelp(const char* argv0)
printf(" --formatter=gnu: report analysis errors in GNU-compatible format\n");
}
static int assertionHandler(const char* expr, const char* file, int line)
static int assertionHandler(const char* expr, const char* file, int line, const char* function)
{
printf("%s(%d): ASSERTION FAILED: %s\n", file, line, expr);
return 1;

View file

@ -235,11 +235,14 @@ static void completeIndexer(lua_State* L, const char* editBuffer, size_t start,
while (lua_next(L, -2) != 0)
{
// table, key, value
std::string_view key = lua_tostring(L, -2);
if (lua_type(L, -2) == LUA_TSTRING)
{
// table, key, value
std::string_view key = lua_tostring(L, -2);
if (!key.empty() && Luau::startsWith(key, prefix))
completions.push_back(editBuffer + std::string(key.substr(prefix.size())));
if (!key.empty() && Luau::startsWith(key, prefix))
completions.push_back(editBuffer + std::string(key.substr(prefix.size())));
}
lua_pop(L, 1);
}
@ -253,7 +256,7 @@ static void completeIndexer(lua_State* L, const char* editBuffer, size_t start,
lua_rawget(L, -2);
lua_remove(L, -2);
if (lua_isnil(L, -1))
if (!lua_istable(L, -1))
break;
lookup.remove_prefix(dot + 1);
@ -266,7 +269,7 @@ static void completeIndexer(lua_State* L, const char* editBuffer, size_t start,
static void completeRepl(lua_State* L, const char* editBuffer, std::vector<std::string>& completions)
{
size_t start = strlen(editBuffer);
while (start > 0 && (isalnum(editBuffer[start - 1]) || editBuffer[start - 1] == '.'))
while (start > 0 && (isalnum(editBuffer[start - 1]) || editBuffer[start - 1] == '.' || editBuffer[start - 1] == '_'))
start--;
// look the value up in current global table first
@ -278,6 +281,34 @@ static void completeRepl(lua_State* L, const char* editBuffer, std::vector<std::
completeIndexer(L, editBuffer, start, completions);
}
struct LinenoiseScopedHistory
{
LinenoiseScopedHistory()
{
const std::string name(".luau_history");
if (const char* home = getenv("HOME"))
{
historyFilepath = joinPaths(home, name);
}
else if (const char* userProfile = getenv("USERPROFILE"))
{
historyFilepath = joinPaths(userProfile, name);
}
if (!historyFilepath.empty())
linenoise::LoadHistory(historyFilepath.c_str());
}
~LinenoiseScopedHistory()
{
if (!historyFilepath.empty())
linenoise::SaveHistory(historyFilepath.c_str());
}
std::string historyFilepath;
};
static void runRepl()
{
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
@ -292,6 +323,7 @@ static void runRepl()
});
std::string buffer;
LinenoiseScopedHistory scopedHistory;
for (;;)
{
@ -457,7 +489,7 @@ static void displayHelp(const char* argv0)
printf(" --coverage: collect code coverage while running the code and output results to coverage.out\n");
}
static int assertionHandler(const char* expr, const char* file, int line)
static int assertionHandler(const char* expr, const char* file, int line, const char* function)
{
printf("%s(%d): ASSERTION FAILED: %s\n", file, line, expr);
return 1;

View file

@ -53,30 +53,38 @@ static std::string runCode(lua_State* L, const std::string& source)
lua_insert(T, 1);
lua_pcall(T, n, 0, 0);
}
lua_pop(L, 1); // pop T
return std::string();
}
else
{
std::string error;
lua_Debug ar;
if (lua_getinfo(L, 0, "sln", &ar))
{
error += ar.short_src;
error += ':';
error += std::to_string(ar.currentline);
error += ": ";
}
if (status == LUA_YIELD)
{
error = "thread yielded unexpectedly";
error += "thread yielded unexpectedly";
}
else if (const char* str = lua_tostring(T, -1))
{
error = str;
error += str;
}
error += "\nstack backtrace:\n";
error += lua_debugtrace(T);
error = "Error:" + error;
fprintf(stdout, "%s", error.c_str());
lua_pop(L, 1); // pop T
return error;
}
lua_pop(L, 1);
return std::string();
}
extern "C" const char* executeScript(const char* source)

View file

@ -377,6 +377,7 @@ enum LuauBytecodeTag
{
// Bytecode version
LBC_VERSION = 1,
LBC_VERSION_FUTURE = 2, // TODO: This will be removed in favor of LBC_VERSION with LuauBytecodeV2Force
// Types of constant table entries
LBC_CONSTANT_NIL = 0,
LBC_CONSTANT_BOOLEAN,

View file

@ -74,6 +74,7 @@ public:
void expandJumps();
void setDebugFunctionName(StringRef name);
void setDebugFunctionLineDefined(int line);
void setDebugLine(int line);
void pushDebugLocal(StringRef name, uint8_t reg, uint32_t startpc, uint32_t endpc);
void pushDebugUpval(StringRef name);
@ -162,6 +163,7 @@ private:
bool isvararg = false;
unsigned int debugname = 0;
int debuglinedefined = 0;
std::string dump;
std::string dumpname;

View file

@ -6,6 +6,8 @@
#include <algorithm>
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauBytecodeV2Write, false)
namespace Luau
{
@ -81,6 +83,52 @@ static int getOpLength(LuauOpcode op)
}
}
inline bool isJumpD(LuauOpcode op)
{
switch (op)
{
case LOP_JUMP:
case LOP_JUMPIF:
case LOP_JUMPIFNOT:
case LOP_JUMPIFEQ:
case LOP_JUMPIFLE:
case LOP_JUMPIFLT:
case LOP_JUMPIFNOTEQ:
case LOP_JUMPIFNOTLE:
case LOP_JUMPIFNOTLT:
case LOP_FORNPREP:
case LOP_FORNLOOP:
case LOP_FORGLOOP:
case LOP_FORGPREP_INEXT:
case LOP_FORGLOOP_INEXT:
case LOP_FORGPREP_NEXT:
case LOP_FORGLOOP_NEXT:
case LOP_JUMPBACK:
case LOP_JUMPIFEQK:
case LOP_JUMPIFNOTEQK:
return true;
default:
return false;
}
}
inline bool isSkipC(LuauOpcode op)
{
switch (op)
{
case LOP_LOADB:
case LOP_FASTCALL:
case LOP_FASTCALL1:
case LOP_FASTCALL2:
case LOP_FASTCALL2K:
return true;
default:
return false;
}
}
bool BytecodeBuilder::StringRef::operator==(const StringRef& other) const
{
return (data && other.data) ? (length == other.length && memcmp(data, other.data, length) == 0) : (data == other.data);
@ -365,13 +413,7 @@ bool BytecodeBuilder::patchJumpD(size_t jumpLabel, size_t targetLabel)
unsigned int jumpInsn = insns[jumpLabel];
(void)jumpInsn;
LUAU_ASSERT(LUAU_INSN_OP(jumpInsn) == LOP_JUMP || LUAU_INSN_OP(jumpInsn) == LOP_JUMPIF || LUAU_INSN_OP(jumpInsn) == LOP_JUMPIFNOT ||
LUAU_INSN_OP(jumpInsn) == LOP_JUMPIFEQ || LUAU_INSN_OP(jumpInsn) == LOP_JUMPIFLE || LUAU_INSN_OP(jumpInsn) == LOP_JUMPIFLT ||
LUAU_INSN_OP(jumpInsn) == LOP_JUMPIFNOTEQ || LUAU_INSN_OP(jumpInsn) == LOP_JUMPIFNOTLE || LUAU_INSN_OP(jumpInsn) == LOP_JUMPIFNOTLT ||
LUAU_INSN_OP(jumpInsn) == LOP_FORNPREP || LUAU_INSN_OP(jumpInsn) == LOP_FORNLOOP || LUAU_INSN_OP(jumpInsn) == LOP_FORGLOOP ||
LUAU_INSN_OP(jumpInsn) == LOP_FORGPREP_INEXT || LUAU_INSN_OP(jumpInsn) == LOP_FORGLOOP_INEXT ||
LUAU_INSN_OP(jumpInsn) == LOP_FORGPREP_NEXT || LUAU_INSN_OP(jumpInsn) == LOP_FORGLOOP_NEXT ||
LUAU_INSN_OP(jumpInsn) == LOP_JUMPBACK || LUAU_INSN_OP(jumpInsn) == LOP_JUMPIFEQK || LUAU_INSN_OP(jumpInsn) == LOP_JUMPIFNOTEQK);
LUAU_ASSERT(isJumpD(LuauOpcode(LUAU_INSN_OP(jumpInsn))));
LUAU_ASSERT(LUAU_INSN_D(jumpInsn) == 0);
LUAU_ASSERT(targetLabel <= insns.size());
@ -403,8 +445,7 @@ bool BytecodeBuilder::patchSkipC(size_t jumpLabel, size_t targetLabel)
unsigned int jumpInsn = insns[jumpLabel];
(void)jumpInsn;
LUAU_ASSERT(LUAU_INSN_OP(jumpInsn) == LOP_FASTCALL || LUAU_INSN_OP(jumpInsn) == LOP_FASTCALL1 || LUAU_INSN_OP(jumpInsn) == LOP_FASTCALL2 ||
LUAU_INSN_OP(jumpInsn) == LOP_FASTCALL2K);
LUAU_ASSERT(isSkipC(LuauOpcode(LUAU_INSN_OP(jumpInsn))));
LUAU_ASSERT(LUAU_INSN_C(jumpInsn) == 0);
int offset = int(targetLabel) - int(jumpLabel) - 1;
@ -428,6 +469,11 @@ void BytecodeBuilder::setDebugFunctionName(StringRef name)
functions[currentFunction].dumpname = std::string(name.data, name.length);
}
void BytecodeBuilder::setDebugFunctionLineDefined(int line)
{
functions[currentFunction].debuglinedefined = line;
}
void BytecodeBuilder::setDebugLine(int line)
{
debugLine = line;
@ -464,7 +510,7 @@ uint32_t BytecodeBuilder::getDebugPC() const
void BytecodeBuilder::finalize()
{
LUAU_ASSERT(bytecode.empty());
bytecode = char(LBC_VERSION);
bytecode = char(FFlag::LuauBytecodeV2Write ? LBC_VERSION_FUTURE : LBC_VERSION);
writeStringTable(bytecode);
@ -565,6 +611,9 @@ void BytecodeBuilder::writeFunction(std::string& ss, uint32_t id) const
writeVarInt(ss, child);
// debug info
if (FFlag::LuauBytecodeV2Write)
writeVarInt(ss, func.debuglinedefined);
writeVarInt(ss, func.debugname);
bool hasLines = true;

View file

@ -11,7 +11,6 @@
#include <math.h>
LUAU_FASTFLAG(LuauIfElseExpressionBaseSupport)
LUAU_FASTFLAGVARIABLE(LuauBit32CountBuiltin, false)
namespace Luau
{
@ -179,6 +178,8 @@ struct Compiler
if (options.optimizationLevel >= 1 && options.debugLevel >= 2)
gatherConstUpvals(func);
bytecode.setDebugFunctionLineDefined(func->location.begin.line + 1);
if (options.debugLevel >= 1 && func->debugname.value)
bytecode.setDebugFunctionName(sref(func->debugname));
@ -3626,9 +3627,9 @@ struct Compiler
return LBF_BIT32_RROTATE;
if (builtin.method == "rshift")
return LBF_BIT32_RSHIFT;
if (builtin.method == "countlz" && FFlag::LuauBit32CountBuiltin)
if (builtin.method == "countlz")
return LBF_BIT32_COUNTLZ;
if (builtin.method == "countrz" && FFlag::LuauBit32CountBuiltin)
if (builtin.method == "countrz")
return LBF_BIT32_COUNTRZ;
}

View file

@ -125,6 +125,7 @@ target_sources(Luau.VM PRIVATE
VM/src/linit.cpp
VM/src/lmathlib.cpp
VM/src/lmem.cpp
VM/src/lnumprint.cpp
VM/src/lobject.cpp
VM/src/loslib.cpp
VM/src/lperf.cpp

View file

@ -138,10 +138,6 @@
/* }================================================================== */
/* Default number printing format and the string length limit */
#define LUA_NUMBER_FMT "%.14g"
#define LUAI_MAXNUMBER2STR 32 /* 16 digits, sign, point, and \0 */
/*
@@ LUAI_USER_ALIGNMENT_T is a type that requires maximum alignment.
** CHANGE it if your system requires alignments larger than double. (For

View file

@ -14,8 +14,6 @@
#include <string.h>
LUAU_FASTFLAG(LuauActivateBeforeExec)
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n"
"$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n"
"$URL: www.lua.org $\n";
@ -939,21 +937,7 @@ void lua_call(lua_State* L, int nargs, int nresults)
checkresults(L, nargs, nresults);
func = L->top - (nargs + 1);
if (FFlag::LuauActivateBeforeExec)
{
luaD_call(L, func, nresults);
}
else
{
int oldactive = luaC_threadactive(L);
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
luaC_checkthreadsleep(L);
luaD_call(L, func, nresults);
if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
}
luaD_call(L, func, nresults);
adjustresults(L, nresults);
return;
@ -994,21 +978,7 @@ int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc)
c.func = L->top - (nargs + 1); /* function to be called */
c.nresults = nresults;
if (FFlag::LuauActivateBeforeExec)
{
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
}
else
{
int oldactive = luaC_threadactive(L);
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
luaC_checkthreadsleep(L);
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
}
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
adjustresults(L, nresults);
return status;

View file

@ -7,9 +7,12 @@
#include "lstring.h"
#include "lapi.h"
#include "lgc.h"
#include "lnumutils.h"
#include <string.h>
LUAU_FASTFLAG(LuauSchubfach)
/* convert a stack index to positive */
#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1)
@ -477,7 +480,17 @@ const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
switch (lua_type(L, idx))
{
case LUA_TNUMBER:
lua_pushstring(L, lua_tostring(L, idx));
if (FFlag::LuauSchubfach)
{
double n = lua_tonumber(L, idx);
char s[LUAI_MAXNUM2STR];
char* e = luai_num2str(s, n);
lua_pushlstring(L, s, e - s);
}
else
{
lua_pushstring(L, lua_tostring(L, idx));
}
break;
case LUA_TSTRING:
lua_pushvalue(L, idx);
@ -491,11 +504,30 @@ const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
case LUA_TVECTOR:
{
const float* v = lua_tovector(L, idx);
if (FFlag::LuauSchubfach)
{
char s[LUAI_MAXNUM2STR * LUA_VECTOR_SIZE];
char* e = s;
for (int i = 0; i < LUA_VECTOR_SIZE; ++i)
{
if (i != 0)
{
*e++ = ',';
*e++ = ' ';
}
e = luai_num2str(e, v[i]);
}
lua_pushlstring(L, s, e - s);
}
else
{
#if LUA_VECTOR_SIZE == 4
lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2], v[3]);
lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2], v[3]);
#else
lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2]);
lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2]);
#endif
}
break;
}
default:

View file

@ -5,8 +5,6 @@
#include "lcommon.h"
#include "lnumutils.h"
LUAU_FASTFLAGVARIABLE(LuauBit32Count, false)
#define ALLONES ~0u
#define NBITS int(8 * sizeof(unsigned))
@ -182,9 +180,6 @@ static int b_replace(lua_State* L)
static int b_countlz(lua_State* L)
{
if (!FFlag::LuauBit32Count)
luaL_error(L, "bit32.countlz isn't enabled");
b_uint v = luaL_checkunsigned(L, 1);
b_uint r = NBITS;
@ -201,9 +196,6 @@ static int b_countlz(lua_State* L)
static int b_countrz(lua_State* L)
{
if (!FFlag::LuauBit32Count)
luaL_error(L, "bit32.countrz isn't enabled");
b_uint v = luaL_checkunsigned(L, 1);
b_uint r = NBITS;

View file

@ -12,6 +12,9 @@
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAG(LuauBytecodeV2Read)
LUAU_FASTFLAG(LuauBytecodeV2Force)
static const char* getfuncname(Closure* f);
static int currentpc(lua_State* L, CallInfo* ci)
@ -89,6 +92,16 @@ const char* lua_setlocal(lua_State* L, int level, int n)
return name;
}
static int getlinedefined(Proto* p)
{
if (FFlag::LuauBytecodeV2Force)
return p->linedefined;
else if (FFlag::LuauBytecodeV2Read && p->linedefined >= 0)
return p->linedefined;
else
return luaG_getline(p, 0);
}
static int auxgetinfo(lua_State* L, const char* what, lua_Debug* ar, Closure* f, CallInfo* ci)
{
int status = 1;
@ -108,7 +121,7 @@ static int auxgetinfo(lua_State* L, const char* what, lua_Debug* ar, Closure* f,
{
ar->source = getstr(f->l.p->source);
ar->what = "Lua";
ar->linedefined = luaG_getline(f->l.p, 0);
ar->linedefined = getlinedefined(f->l.p);
}
luaO_chunkid(ar->short_src, ar->source, LUA_IDSIZE);
break;
@ -121,7 +134,7 @@ static int auxgetinfo(lua_State* L, const char* what, lua_Debug* ar, Closure* f,
}
else
{
ar->currentline = f->isC ? -1 : luaG_getline(f->l.p, 0);
ar->currentline = f->isC ? -1 : getlinedefined(f->l.p);
}
break;

View file

@ -19,7 +19,6 @@
LUAU_FASTFLAGVARIABLE(LuauCcallRestoreFix, false)
LUAU_FASTFLAG(LuauCoroutineClose)
LUAU_FASTFLAGVARIABLE(LuauActivateBeforeExec, true)
/*
** {======================================================
@ -228,21 +227,14 @@ void luaD_call(lua_State* L, StkId func, int nResults)
{ /* is a Lua function? */
L->ci->flags |= LUA_CALLINFO_RETURN; /* luau_execute will stop after returning from the stack frame */
if (FFlag::LuauActivateBeforeExec)
{
int oldactive = luaC_threadactive(L);
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
luaC_checkthreadsleep(L);
int oldactive = luaC_threadactive(L);
l_setbit(L->stackstate, THREAD_ACTIVEBIT);
luaC_checkthreadsleep(L);
luau_execute(L); /* call it */
luau_execute(L); /* call it */
if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
}
else
{
luau_execute(L); /* call it */
}
if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
}
L->nCcalls--;
luaC_checkGC(L);
@ -549,12 +541,9 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e
status = LUA_ERRERR;
}
if (FFlag::LuauActivateBeforeExec)
{
// since the call failed with an error, we might have to reset the 'active' thread state
if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
}
// since the call failed with an error, we might have to reset the 'active' thread state
if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
if (FFlag::LuauCcallRestoreFix)
{

View file

@ -12,8 +12,6 @@
#include <string.h>
LUAU_FASTFLAG(LuauArrayBoundary)
#define GC_SWEEPMAX 40
#define GC_SWEEPCOST 10

View file

@ -12,8 +12,6 @@
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAG(LuauArrayBoundary)
static void validateobjref(global_State* g, GCObject* f, GCObject* t)
{
LUAU_ASSERT(!isdead(g, t));
@ -38,10 +36,7 @@ static void validatetable(global_State* g, Table* h)
{
int sizenode = 1 << h->lsizenode;
if (FFlag::LuauArrayBoundary)
LUAU_ASSERT(h->lastfree <= sizenode);
else
LUAU_ASSERT(h->lastfree >= 0 && h->lastfree <= sizenode);
LUAU_ASSERT(h->lastfree <= sizenode);
if (h->metatable)
validateobjref(g, obj2gco(h), obj2gco(h->metatable));

375
VM/src/lnumprint.cpp Normal file
View file

@ -0,0 +1,375 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
#include "luaconf.h"
#include "lnumutils.h"
#include "lcommon.h"
#include <string.h>
#include <stdio.h> // TODO: Remove with LuauSchubfach
#ifdef _MSC_VER
#include <intrin.h>
#endif
// This work is based on:
// Raffaello Giulietti. The Schubfach way to render doubles. 2021
// https://drive.google.com/file/d/1IEeATSVnEE6TkrHlCYNY2GjaraBjOT4f/edit
// The code uses the notation from the paper for local variables where appropriate, and refers to paper sections/figures/results.
LUAU_FASTFLAGVARIABLE(LuauSchubfach, false)
// 9.8.2. Precomputed table for 128-bit overestimates of powers of 10 (see figure 3 for table bounds)
// To avoid storing 616 128-bit numbers directly we use a technique inspired by Dragonbox implementation and store 16 consecutive
// powers using a 128-bit baseline and a bitvector with 1-bit scale and 3-bit offset for the delta between each entry and base*5^k
static const int kPow10TableMin = -292;
static const int kPow10TableMax = 324;
// clang-format off
static const uint64_t kPow5Table[16] = {
0x8000000000000000, 0xa000000000000000, 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000, 0xc350000000000000,
0xf424000000000000, 0x9896800000000000, 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000, 0xba43b74000000000,
0xe8d4a51000000000, 0x9184e72a00000000, 0xb5e620f480000000, 0xe35fa931a0000000,
};
static const uint64_t kPow10Table[(kPow10TableMax - kPow10TableMin + 1 + 15) / 16][3] = {
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b, 0x333443443333443b}, {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4, 0xbbb3ab3cb3ba3cbc},
{0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa, 0x4ba4bc4bb4bb4bcc}, {0xaecc49914078536d, 0x58fae9f773886e19, 0x3ba3bc33b43b43bb},
{0xc21094364dfb5636, 0x985915fc12f542e5, 0x33b43b43a33b33cb}, {0xd77485cb25823ac7, 0x7d633293366b828c, 0x34b44c444343443c},
{0xef340a98172aace4, 0x86fb897116c87c35, 0x333343333343334b}, {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074, 0xccaccbbcbcbb4bbc},
{0x936b9fcebb25c995, 0xcab10dd900beec35, 0x3ab3ab3ab3bb3bbb}, {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb, 0x4cc3dc4db4db4dbb},
{0xb5b5ada8aaff80b8, 0x0d819992132456bb, 0x33b33a34c33b34ab}, {0xc9bcff6034c13052, 0xfc89b393dd02f0b6, 0x33c33b44b43c34bc},
{0xdff9772470297ebd, 0x59787e2b93bc56f8, 0x43b444444443434c}, {0xf8a95fcf88747d94, 0x75a44c6397ce912b, 0x443334343443343b},
{0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900, 0xbbabab3aa3ab4ccc}, {0x993fe2c6d07b7fab, 0xe546a8038efe402a, 0x4cb4bc4db4db4bcc},
{0xaa242499697392d2, 0xdde50bd1d5d0b9ea, 0x3ba3ba3bb33b33bc}, {0xbce5086492111aea, 0x88f4bb1ca6bcf585, 0x44b44c44c44c43cb},
{0xd1b71758e219652b, 0xd3c36113404ea4a9, 0x44c44c44c444443b}, {0xe8d4a51000000000, 0x0000000000000000, 0x444444444444444c},
{0x813f3978f8940984, 0x4000000000000000, 0xcccccccccccccccc}, {0x8f7e32ce7bea5c6f, 0xe4820023a2000000, 0xbba3bc4cc4cc4ccc},
{0x9f4f2726179a2245, 0x01d762422c946591, 0x4aa3bb3aa3ba3bab}, {0xb0de65388cc8ada8, 0x3b25a55f43294bcc, 0x3ca33b33b44b43bc},
{0xc45d1df942711d9a, 0x3ba5d0bd324f8395, 0x44c44c34c44b44cb}, {0xda01ee641a708de9, 0xe80e6f4820cc9496, 0x33b33b343333333c},
{0xf209787bb47d6b84, 0xc0678c5dbd23a49b, 0x443444444443443b}, {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3, 0xdbccbcccb4cb3bbb},
{0x952ab45cfa97a0b2, 0xdd945a747bf26184, 0x3bc4bb4ab3ca3cbc}, {0xa59bc234db398c25, 0x43fab9837e699096, 0x3bb3ac3ab3bb33ac},
{0xb7dcbf5354e9bece, 0x0c11ed6d538aeb30, 0x33b43b43b34c34dc}, {0xcc20ce9bd35c78a5, 0x31ec038df7b441f5, 0x34c44c43c44b44cb},
{0xe2a0b5dc971f303a, 0x2e44ae64840fd61e, 0x333333333333333c}, {0xfb9b7cd9a4a7443c, 0x169840ef017da3b2, 0x433344443333344c},
{0x8bab8eefb6409c1a, 0x1ad089b6c2f7548f, 0xdcbdcc3cc4cc4bcb}, {0x9b10a4e5e9913128, 0xca7cf2b4191c8327, 0x3ab3cb3bc3bb4bbb},
{0xac2820d9623bf429, 0x546345fa9fbdcd45, 0x3bb3cc43c43c43cb}, {0xbf21e44003acdd2c, 0xe0470a63e6bd56c4, 0x44b34a43b44c44bc},
{0xd433179d9c8cb841, 0x5fa60692a46151ec, 0x43a33a33a333333c},
};
// clang-format on
static const char kDigitTable[] = "0001020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546474849"
"5051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899";
// x*y => 128-bit product (lo+hi)
inline uint64_t mul128(uint64_t x, uint64_t y, uint64_t* hi)
{
#if defined(_MSC_VER) && defined(_M_X64)
return _umul128(x, y, hi);
#elif defined(__SIZEOF_INT128__)
unsigned __int128 r = x;
r *= y;
*hi = uint64_t(r >> 64);
return uint64_t(r);
#else
uint32_t x0 = uint32_t(x), x1 = uint32_t(x >> 32);
uint32_t y0 = uint32_t(y), y1 = uint32_t(y >> 32);
uint64_t p11 = uint64_t(x1) * y1, p01 = uint64_t(x0) * y1;
uint64_t p10 = uint64_t(x1) * y0, p00 = uint64_t(x0) * y0;
uint64_t mid = p10 + (p00 >> 32) + uint32_t(p01);
uint64_t r0 = (mid << 32) | uint32_t(p00);
uint64_t r1 = p11 + (mid >> 32) + (p01 >> 32);
*hi = r1;
return r0;
#endif
}
// (x*y)>>64 => 128-bit product (lo+hi)
inline uint64_t mul192hi(uint64_t xhi, uint64_t xlo, uint64_t y, uint64_t* hi)
{
uint64_t z2;
uint64_t z1 = mul128(xhi, y, &z2);
uint64_t z1c;
uint64_t z0 = mul128(xlo, y, &z1c);
(void)z0;
z1 += z1c;
z2 += (z1 < z1c);
*hi = z2;
return z1;
}
// 9.3. Rounding to odd (+ figure 8 + result 23)
inline uint64_t roundodd(uint64_t ghi, uint64_t glo, uint64_t cp)
{
uint64_t xhi;
uint64_t xlo = mul128(glo, cp, &xhi);
(void)xlo;
uint64_t yhi;
uint64_t ylo = mul128(ghi, cp, &yhi);
uint64_t z = ylo + xhi;
return (yhi + (z < xhi)) | (z > 1);
}
struct Decimal
{
uint64_t s;
int k;
};
static Decimal schubfach(int exponent, uint64_t fraction)
{
// Extract c & q such that c*2^q == |v|
uint64_t c = fraction;
int q = exponent - 1023 - 51;
if (exponent != 0) // normal numbers have implicit leading 1
{
c |= (1ull << 52);
q--;
}
// 8.3. Fast path for integers
if (unsigned(-q) < 53 && (c & ((1ull << (-q)) - 1)) == 0)
return {c >> (-q), 0};
// 5. Rounding interval
int irr = (c == (1ull << 52) && q != -1074); // Qmin
int out = int(c & 1);
// 9.8.1. Boundaries for c
uint64_t cbl = 4 * c - 2 + irr;
uint64_t cb = 4 * c;
uint64_t cbr = 4 * c + 2;
// 9.1. Computing k and h
const int Q = 20;
const int C = 315652; // floor(2^Q * log10(2))
const int A = -131008; // floor(2^Q * log10(3/4))
const int C2 = 3483294; // floor(2^Q * log2(10))
int k = (q * C + (irr ? A : 0)) >> Q;
int h = q + ((-k * C2) >> Q) + 1; // see (9) in 9.9
// 9.8.2. Overestimates of powers of 10
// Recover 10^-k fraction using compact tables generated by tools/numutils.py
// The 128-bit fraction is encoded as 128-bit baseline * power-of-5 * scale + offset
LUAU_ASSERT(-k >= kPow10TableMin && -k <= kPow10TableMax);
int gtoff = -k - kPow10TableMin;
const uint64_t* gt = kPow10Table[gtoff >> 4];
uint64_t ghi;
uint64_t glo = mul192hi(gt[0], gt[1], kPow5Table[gtoff & 15], &ghi);
// Apply 1-bit scale + 3-bit offset; note, offset is intentionally applied without carry, numutils.py validates that this is sufficient
int gterr = (gt[2] >> ((gtoff & 15) * 4)) & 15;
int gtscale = gterr >> 3;
ghi <<= gtscale;
ghi += (glo >> 63) & gtscale;
glo <<= gtscale;
glo -= (gterr & 7) - 4;
// 9.9. Boundaries for v
uint64_t vbl = roundodd(ghi, glo, cbl << h);
uint64_t vb = roundodd(ghi, glo, cb << h);
uint64_t vbr = roundodd(ghi, glo, cbr << h);
// Main algorithm; see figure 7 + figure 9
uint64_t s = vb / 4;
if (s >= 10)
{
uint64_t sp = s / 10;
bool upin = vbl + out <= 40 * sp;
bool wpin = vbr >= 40 * sp + 40 + out;
if (upin != wpin)
return {sp + wpin, k + 1};
}
// Figure 7 contains the algorithm to select between u (s) and w (s+1)
// rup computes the last 4 conditions in that algorithm
// rup is only used when uin == win, but since these branches predict poorly we use branchless selects
bool uin = vbl + out <= 4 * s;
bool win = 4 * s + 4 + out <= vbr;
bool rup = vb >= 4 * s + 2 + 1 - (s & 1);
return {s + (uin != win ? win : rup), k};
}
static char* printspecial(char* buf, int sign, uint64_t fraction)
{
if (fraction == 0)
{
memcpy(buf, ("-inf") + (1 - sign), 4);
return buf + 3 + sign;
}
else
{
memcpy(buf, "nan", 4);
return buf + 3;
}
}
static char* printunsignedrev(char* end, uint64_t num)
{
while (num >= 10000)
{
unsigned int tail = unsigned(num % 10000);
memcpy(end - 4, &kDigitTable[int(tail / 100) * 2], 2);
memcpy(end - 2, &kDigitTable[int(tail % 100) * 2], 2);
num /= 10000;
end -= 4;
}
unsigned int rest = unsigned(num);
while (rest >= 10)
{
memcpy(end - 2, &kDigitTable[int(rest % 100) * 2], 2);
rest /= 100;
end -= 2;
}
if (rest)
{
end[-1] = '0' + int(rest);
end -= 1;
}
return end;
}
static char* printexp(char* buf, int num)
{
*buf++ = 'e';
*buf++ = num < 0 ? '-' : '+';
int v = num < 0 ? -num : num;
if (v >= 100)
{
*buf++ = '0' + (v / 100);
v %= 100;
}
memcpy(buf, &kDigitTable[v * 2], 2);
return buf + 2;
}
inline char* trimzero(char* end)
{
while (end[-1] == '0')
end--;
return end;
}
// We use fixed-length memcpy/memset since they lower to fast SIMD+scalar writes; the target buffers should have padding space
#define fastmemcpy(dst, src, size, sizefast) check_exp((size) <= sizefast, memcpy(dst, src, sizefast))
#define fastmemset(dst, val, size, sizefast) check_exp((size) <= sizefast, memset(dst, val, sizefast))
char* luai_num2str(char* buf, double n)
{
if (!FFlag::LuauSchubfach)
{
snprintf(buf, LUAI_MAXNUM2STR, LUA_NUMBER_FMT, n);
return buf + strlen(buf);
}
// IEEE-754
union
{
double v;
uint64_t bits;
} v = {n};
int sign = int(v.bits >> 63);
int exponent = int(v.bits >> 52) & 2047;
uint64_t fraction = v.bits & ((1ull << 52) - 1);
// specials
if (LUAU_UNLIKELY(exponent == 0x7ff))
return printspecial(buf, sign, fraction);
// sign bit
*buf = '-';
buf += sign;
// zero
if (exponent == 0 && fraction == 0)
{
buf[0] = '0';
return buf + 1;
}
// convert binary to decimal using Schubfach
Decimal d = schubfach(exponent, fraction);
LUAU_ASSERT(d.s < uint64_t(1e17));
// print the decimal to a temporary buffer; we'll need to insert the decimal point and figure out the format
char decbuf[40];
char* decend = decbuf + 20; // significand needs at most 17 digits; the rest of the buffer may be copied using fixed length memcpy
char* dec = printunsignedrev(decend, d.s);
int declen = int(decend - dec);
LUAU_ASSERT(declen <= 17);
int dot = declen + d.k;
// the limits are somewhat arbitrary but changing them may require changing fastmemset/fastmemcpy sizes below
if (dot >= -5 && dot <= 21)
{
// fixed point format
if (dot <= 0)
{
buf[0] = '0';
buf[1] = '.';
fastmemset(buf + 2, '0', -dot, 5);
fastmemcpy(buf + 2 + (-dot), dec, declen, 17);
return trimzero(buf + 2 + (-dot) + declen);
}
else if (dot == declen)
{
// no dot
fastmemcpy(buf, dec, dot, 17);
return buf + dot;
}
else if (dot < declen)
{
// dot in the middle
fastmemcpy(buf, dec, dot, 16);
buf[dot] = '.';
fastmemcpy(buf + dot + 1, dec + dot, declen - dot, 16);
return trimzero(buf + declen + 1);
}
else
{
// no dot, zero padding
fastmemcpy(buf, dec, declen, 17);
fastmemset(buf + declen, '0', dot - declen, 8);
return buf + dot;
}
}
else
{
// scientific format
buf[0] = dec[0];
buf[1] = '.';
fastmemcpy(buf + 2, dec + 1, declen - 1, 16);
char* exp = trimzero(buf + declen + 1);
return printexp(exp, dot - 1);
}
}

View file

@ -3,7 +3,6 @@
#pragma once
#include <math.h>
#include <stdio.h>
#define luai_numadd(a, b) ((a) + (b))
#define luai_numsub(a, b) ((a) - (b))
@ -56,5 +55,9 @@ LUAU_FASTMATH_END
#define luai_num2unsigned(i, n) ((i) = (unsigned)(long long)(n))
#endif
#define luai_num2str(s, n) snprintf((s), sizeof(s), LUA_NUMBER_FMT, (n))
#define LUA_NUMBER_FMT "%.14g" /* TODO: Remove with LuauSchubfach */
#define LUAI_MAXNUM2STR 48
LUAI_FUNC char* luai_num2str(char* buf, double n);
#define luai_str2num(s, p) strtod((s), (p))

View file

@ -289,6 +289,7 @@ typedef struct Proto
int sizek;
int sizelineinfo;
int linegaplog2;
int linedefined;
uint8_t nups; /* number of upvalues */

View file

@ -24,8 +24,6 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauArrayBoundary, false)
// max size of both array and hash part is 2^MAXBITS
#define MAXBITS 26
#define MAXSIZE (1 << MAXBITS)
@ -222,7 +220,7 @@ int luaH_next(lua_State* L, Table* t, StkId key)
#define maybesetaboundary(t, boundary) \
{ \
if (FFlag::LuauArrayBoundary && t->aboundary <= 0) \
if (t->aboundary <= 0) \
t->aboundary = -int(boundary); \
}
@ -705,7 +703,7 @@ int luaH_getn(Table* t)
{
int boundary = getaboundary(t);
if (FFlag::LuauArrayBoundary && boundary > 0)
if (boundary > 0)
{
if (!ttisnil(&t->array[t->sizearray - 1]) && t->node == dummynode)
return t->sizearray; /* fast-path: the end of the array in `t' already refers to a boundary */

View file

@ -13,6 +13,9 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauBytecodeV2Read, true)
LUAU_FASTFLAGVARIABLE(LuauBytecodeV2Force, false)
// TODO: RAII deallocation doesn't work for longjmp builds if a memory error happens
template<typename T>
struct TempBuffer
@ -146,15 +149,19 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
uint8_t version = read<uint8_t>(data, size, offset);
// 0 means the rest of the bytecode is the error message
if (version == 0 || version != LBC_VERSION)
if (version == 0)
{
char chunkid[LUA_IDSIZE];
luaO_chunkid(chunkid, chunkname, LUA_IDSIZE);
lua_pushfstring(L, "%s%.*s", chunkid, int(size - offset), data + offset);
return 1;
}
if (version == 0)
lua_pushfstring(L, "%s%.*s", chunkid, int(size - offset), data + offset);
else
lua_pushfstring(L, "%s: bytecode version mismatch", chunkid);
if (FFlag::LuauBytecodeV2Force ? (version != LBC_VERSION_FUTURE) : FFlag::LuauBytecodeV2Read ? (version != LBC_VERSION && version != LBC_VERSION_FUTURE) : (version != LBC_VERSION))
{
char chunkid[LUA_IDSIZE];
luaO_chunkid(chunkid, chunkname, LUA_IDSIZE);
lua_pushfstring(L, "%s: bytecode version mismatch (expected %d, got %d)", chunkid, FFlag::LuauBytecodeV2Force ? LBC_VERSION_FUTURE : LBC_VERSION, version);
return 1;
}
@ -285,6 +292,11 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
p->p[j] = protos[fid];
}
if (FFlag::LuauBytecodeV2Force || (FFlag::LuauBytecodeV2Read && version == LBC_VERSION_FUTURE))
p->linedefined = readVarInt(data, size, offset);
else
p->linedefined = -1;
p->debugname = readString(strings, data, size, offset);
uint8_t lineinfo = read<uint8_t>(data, size, offset);
@ -307,11 +319,11 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
p->lineinfo[j] = lastoffset;
}
int lastLine = 0;
int lastline = 0;
for (int j = 0; j < intervals; ++j)
{
lastLine += read<int32_t>(data, size, offset);
p->abslineinfo[j] = lastLine;
lastline += read<int32_t>(data, size, offset);
p->abslineinfo[j] = lastline;
}
}

View file

@ -34,10 +34,11 @@ int luaV_tostring(lua_State* L, StkId obj)
return 0;
else
{
char s[LUAI_MAXNUMBER2STR];
char s[LUAI_MAXNUM2STR];
double n = nvalue(obj);
luai_num2str(s, n);
setsvalue2s(L, obj, luaS_new(L, s));
char* e = luai_num2str(s, n);
LUAU_ASSERT(e < s + sizeof(s));
setsvalue2s(L, obj, luaS_newlstr(L, s, e - s));
return 1;
}
}

35
fuzz/number.cpp Normal file
View file

@ -0,0 +1,35 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Common.h"
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
LUAU_FASTFLAG(LuauSchubfach);
#define LUAI_MAXNUM2STR 48
char* luai_num2str(char* buf, double n);
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size)
{
if (Size < 8)
return 0;
FFlag::LuauSchubfach.value = true;
double num;
memcpy(&num, Data, 8);
char buf[LUAI_MAXNUM2STR];
char* end = luai_num2str(buf, num);
LUAU_ASSERT(end < buf + sizeof(buf));
*end = 0;
double rec = strtod(buf, nullptr);
LUAU_ASSERT(rec == num || (rec != rec && num != num));
return 0;
}

View file

@ -83,8 +83,6 @@ TEST_SUITE_BEGIN("AstQuery");
TEST_CASE_FIXTURE(Fixture, "last_argument_function_call_type")
{
ScopedFastFlag luauTailArgumentTypeInfo{"LuauTailArgumentTypeInfo", true};
check(R"(
local function foo() return 2 end
local function bar(a: number) return -a end

View file

@ -15,6 +15,7 @@
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
using namespace Luau;
@ -1911,11 +1912,14 @@ local bar: @1= foo
CHECK(!ac.entryMap.count("foo"));
}
TEST_CASE_FIXTURE(ACFixture, "type_correct_function_no_parenthesis")
// Switch back to TEST_CASE_FIXTURE with regular ACFixture when removing the
// LuauUseCommittingTxnLog flag.
TEST_CASE("type_correct_function_no_parenthesis")
{
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
ScopedFastFlag sff_LuauUseCommittingTxnLog = ScopedFastFlag("LuauUseCommittingTxnLog", true);
ACFixture fix;
check(R"(
fix.check(R"(
local function target(a: (number) -> number) return a(4) end
local function bar1(a: number) return -a end
local function bar2(a: string) return a .. 'x' end
@ -1923,7 +1927,7 @@ local function bar2(a: string) return a .. 'x' end
return target(b@1
)");
auto ac = autocomplete('1');
auto ac = fix.autocomplete('1');
CHECK(ac.entryMap.count("bar1"));
CHECK(ac.entryMap["bar1"].typeCorrect == TypeCorrectKind::Correct);
@ -1976,11 +1980,14 @@ local fp: @1= f
CHECK(ac.entryMap.count("({ x: number, y: number }) -> number"));
}
TEST_CASE_FIXTURE(ACFixture, "type_correct_keywords")
// Switch back to TEST_CASE_FIXTURE with regular ACFixture when removing the
// LuauUseCommittingTxnLog flag.
TEST_CASE("type_correct_keywords")
{
ScopedFastFlag luauAutocompleteAvoidMutation("LuauAutocompleteAvoidMutation", true);
ScopedFastFlag sff_LuauUseCommittingTxnLog = ScopedFastFlag("LuauUseCommittingTxnLog", true);
ACFixture fix;
check(R"(
fix.check(R"(
local function a(x: boolean) end
local function b(x: number?) end
local function c(x: (number) -> string) end
@ -1997,26 +2004,26 @@ local dc = d(f@4)
local ec = e(f@5)
)");
auto ac = autocomplete('1');
auto ac = fix.autocomplete('1');
CHECK(ac.entryMap.count("tru"));
CHECK(ac.entryMap["tru"].typeCorrect == TypeCorrectKind::None);
CHECK(ac.entryMap["true"].typeCorrect == TypeCorrectKind::Correct);
CHECK(ac.entryMap["false"].typeCorrect == TypeCorrectKind::Correct);
ac = autocomplete('2');
ac = fix.autocomplete('2');
CHECK(ac.entryMap.count("ni"));
CHECK(ac.entryMap["ni"].typeCorrect == TypeCorrectKind::None);
CHECK(ac.entryMap["nil"].typeCorrect == TypeCorrectKind::Correct);
ac = autocomplete('3');
ac = fix.autocomplete('3');
CHECK(ac.entryMap.count("false"));
CHECK(ac.entryMap["false"].typeCorrect == TypeCorrectKind::None);
CHECK(ac.entryMap["function"].typeCorrect == TypeCorrectKind::Correct);
ac = autocomplete('4');
ac = fix.autocomplete('4');
CHECK(ac.entryMap["function"].typeCorrect == TypeCorrectKind::Correct);
ac = autocomplete('5');
ac = fix.autocomplete('5');
CHECK(ac.entryMap["function"].typeCorrect == TypeCorrectKind::Correct);
}
@ -2507,21 +2514,23 @@ local t = {
CHECK(ac.entryMap.count("second"));
}
TEST_CASE_FIXTURE(UnfrozenFixture, "autocomplete_documentation_symbols")
TEST_CASE("autocomplete_documentation_symbols")
{
loadDefinition(R"(
Fixture fix(FFlag::LuauUseCommittingTxnLog);
fix.loadDefinition(R"(
declare y: {
x: number,
}
)");
fileResolver.source["Module/A"] = R"(
fix.fileResolver.source["Module/A"] = R"(
local a = y.
)";
frontend.check("Module/A");
fix.frontend.check("Module/A");
auto ac = autocomplete(frontend, "Module/A", Position{1, 21}, nullCallback);
auto ac = autocomplete(fix.frontend, "Module/A", Position{1, 21}, nullCallback);
REQUIRE(ac.entryMap.count("x"));
CHECK_EQ(ac.entryMap["x"].documentationSymbol, "@test/global/y.x");

View file

@ -13,8 +13,11 @@
#include "ScopedFlags.h"
#include <fstream>
#include <vector>
#include <math.h>
extern bool verbose;
static int lua_collectgarbage(lua_State* L)
{
static const char* const opts[] = {"stop", "restart", "collect", "count", "isrunning", "step", "setgoal", "setstepmul", "setstepsize", nullptr};
@ -146,15 +149,21 @@ static StateRef runConformance(const char* name, void (*setup)(lua_State* L) = n
luaL_openlibs(L);
// Register a few global functions for conformance tests
static const luaL_Reg funcs[] = {
std::vector<luaL_Reg> funcs = {
{"collectgarbage", lua_collectgarbage},
{"loadstring", lua_loadstring},
{"print", lua_silence}, // Disable print() by default; comment this out to enable debug prints in tests
{nullptr, nullptr},
};
if (!verbose)
{
funcs.push_back({"print", lua_silence});
}
// "null" terminate the list of functions to register
funcs.push_back({nullptr, nullptr});
lua_pushvalue(L, LUA_GLOBALSINDEX);
luaL_register(L, nullptr, funcs);
luaL_register(L, nullptr, funcs.data());
lua_pop(L, 1);
// In some configurations we have a larger C stack consumption which trips some conformance tests
@ -312,8 +321,6 @@ TEST_CASE("GC")
TEST_CASE("Bitwise")
{
ScopedFastFlag sff("LuauBit32Count", true);
runConformance("bitwise.lua");
}
@ -491,6 +498,9 @@ TEST_CASE("DateTime")
TEST_CASE("Debug")
{
ScopedFastFlag sffr("LuauBytecodeV2Read", true);
ScopedFastFlag sffw("LuauBytecodeV2Write", true);
runConformance("debug.lua");
}
@ -738,8 +748,6 @@ TEST_CASE("ApiFunctionCalls")
// lua_equal with a sleeping thread wake up
{
ScopedFastFlag luauActivateBeforeExec("LuauActivateBeforeExec", true);
lua_State* L2 = lua_newthread(L);
lua_getfield(L2, LUA_GLOBALSINDEX, "create_with_tm");
@ -913,4 +921,11 @@ TEST_CASE("Coverage")
nullptr, nullptr, &copts);
}
TEST_CASE("StringConversion")
{
ScopedFastFlag sff{"LuauSchubfach", true};
runConformance("strconv.lua");
}
TEST_SUITE_END();

View file

@ -103,11 +103,6 @@ Fixture::~Fixture()
Luau::resetPrintLine();
}
UnfrozenFixture::UnfrozenFixture()
: Fixture(false)
{
}
AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& parseOptions)
{
sourceModule.reset(new SourceModule);

View file

@ -152,15 +152,6 @@ struct Fixture
LoadDefinitionFileResult loadDefinition(const std::string& source);
};
// Disables arena freezing for a given test case.
// Do not use this in new tests. If you are running into access violations, you
// are violating Luau's memory model - the fix is not to use UnfrozenFixture.
// Related: CLI-45692
struct UnfrozenFixture : Fixture
{
UnfrozenFixture();
};
ModuleName fromString(std::string_view name);
template<typename T>

View file

@ -914,6 +914,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "typecheck_twice_for_ast_types")
TEST_CASE_FIXTURE(FrontendFixture, "imported_table_modification_2")
{
ScopedFastFlag sffs("LuauSealExports", true);
frontend.options.retainFullTypeGraphs = false;
fileResolver.source["Module/A"] = R"(
@ -927,7 +929,7 @@ return a;
--!nonstrict
local a = require(script.Parent.A)
local b = {}
function a:b() end -- this should error, but doesn't
function a:b() end -- this should error, since A doesn't define a:b()
return b
)";
@ -942,8 +944,7 @@ a:b() -- this should error, since A doesn't define a:b()
LUAU_REQUIRE_NO_ERRORS(resultA);
CheckResult resultB = frontend.check("Module/B");
// TODO (CLI-45592): this should error, since we shouldn't be adding properties to objects from other modules
LUAU_REQUIRE_NO_ERRORS(resultB);
LUAU_REQUIRE_ERRORS(resultB);
CheckResult resultC = frontend.check("Module/C");
LUAU_REQUIRE_ERRORS(resultC);

View file

@ -620,7 +620,7 @@ struct AssertionCatcher
{
tripped = 0;
oldhook = Luau::assertHandler();
Luau::assertHandler() = [](const char* expr, const char* file, int line) -> int {
Luau::assertHandler() = [](const char* expr, const char* file, int line, const char* function) -> int {
++tripped;
return 0;
};

View file

@ -11,6 +11,8 @@ LUAU_FASTFLAG(LuauFixTonumberReturnType)
using namespace Luau;
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
TEST_SUITE_BEGIN("BuiltinTests");
TEST_CASE_FIXTURE(Fixture, "math_things_are_defined")
@ -444,19 +446,28 @@ TEST_CASE_FIXTURE(Fixture, "os_time_takes_optional_date_table")
CHECK_EQ(*typeChecker.numberType, *requireType("n3"));
}
TEST_CASE_FIXTURE(Fixture, "thread_is_a_type")
// Switch back to TEST_CASE_FIXTURE with regular Fixture when removing the
// LuauUseCommittingTxnLog flag.
TEST_CASE("thread_is_a_type")
{
CheckResult result = check(R"(
Fixture fix(FFlag::LuauUseCommittingTxnLog);
CheckResult result = fix.check(R"(
local co = coroutine.create(function() end)
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*typeChecker.threadType, *requireType("co"));
// Replace with LUAU_REQUIRE_NO_ERRORS(result) when using TEST_CASE_FIXTURE.
CHECK(result.errors.size() == 0);
CHECK_EQ(*fix.typeChecker.threadType, *fix.requireType("co"));
}
TEST_CASE_FIXTURE(Fixture, "coroutine_resume_anything_goes")
// Switch back to TEST_CASE_FIXTURE with regular Fixture when removing the
// LuauUseCommittingTxnLog flag.
TEST_CASE("coroutine_resume_anything_goes")
{
CheckResult result = check(R"(
Fixture fix(FFlag::LuauUseCommittingTxnLog);
CheckResult result = fix.check(R"(
local function nifty(x, y)
print(x, y)
local z = coroutine.yield(1, 2)
@ -469,12 +480,17 @@ TEST_CASE_FIXTURE(Fixture, "coroutine_resume_anything_goes")
local answer = coroutine.resume(co, 3)
)");
LUAU_REQUIRE_NO_ERRORS(result);
// Replace with LUAU_REQUIRE_NO_ERRORS(result) when using TEST_CASE_FIXTURE.
CHECK(result.errors.size() == 0);
}
TEST_CASE_FIXTURE(Fixture, "coroutine_wrap_anything_goes")
// Switch back to TEST_CASE_FIXTURE with regular Fixture when removing the
// LuauUseCommittingTxnLog flag.
TEST_CASE("coroutine_wrap_anything_goes")
{
CheckResult result = check(R"(
Fixture fix(FFlag::LuauUseCommittingTxnLog);
CheckResult result = fix.check(R"(
--!nonstrict
local function nifty(x, y)
print(x, y)
@ -488,7 +504,8 @@ TEST_CASE_FIXTURE(Fixture, "coroutine_wrap_anything_goes")
local answer = f(3)
)");
LUAU_REQUIRE_NO_ERRORS(result);
// Replace with LUAU_REQUIRE_NO_ERRORS(result) when using TEST_CASE_FIXTURE.
CHECK(result.errors.size() == 0);
}
TEST_CASE_FIXTURE(Fixture, "setmetatable_should_not_mutate_persisted_types")

View file

@ -629,8 +629,6 @@ return exports
TEST_CASE_FIXTURE(Fixture, "instantiated_function_argument_names")
{
ScopedFastFlag luauFunctionArgumentNameSize{"LuauFunctionArgumentNameSize", true};
CheckResult result = check(R"(
local function f<T, U...>(a: T, ...: U...) end

View file

@ -4803,8 +4803,6 @@ local bar = foo.nutrition + 100
TEST_CASE_FIXTURE(Fixture, "require_failed_module")
{
ScopedFastFlag luauModuleRequireErrorPack{"LuauModuleRequireErrorPack", true};
fileResolver.source["game/A"] = R"(
return unfortunately()
)";

View file

@ -12,6 +12,8 @@ LUAU_FASTFLAG(LuauQuantifyInPlace2);
using namespace Luau;
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
struct TryUnifyFixture : Fixture
{
TypeArena arena;
@ -28,7 +30,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "primitives_unify")
TypeVar numberOne{TypeVariant{PrimitiveTypeVar{PrimitiveTypeVar::Number}}};
TypeVar numberTwo = numberOne;
state.tryUnify(&numberOne, &numberTwo);
state.tryUnify(&numberTwo, &numberOne);
CHECK(state.errors.empty());
}
@ -41,9 +43,12 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "compatible_functions_are_unified")
TypeVar functionTwo{TypeVariant{
FunctionTypeVar(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({arena.freshType(globalScope->level)}))}};
state.tryUnify(&functionOne, &functionTwo);
state.tryUnify(&functionTwo, &functionOne);
CHECK(state.errors.empty());
if (FFlag::LuauUseCommittingTxnLog)
state.log.commit();
CHECK_EQ(functionOne, functionTwo);
}
@ -61,7 +66,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_functions_are_preserved")
TypeVar functionTwoSaved = functionTwo;
state.tryUnify(&functionOne, &functionTwo);
state.tryUnify(&functionTwo, &functionOne);
CHECK(!state.errors.empty());
CHECK_EQ(functionOne, functionOneSaved);
@ -80,10 +85,13 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "tables_can_be_unified")
CHECK_NE(*getMutable<TableTypeVar>(&tableOne)->props["foo"].type, *getMutable<TableTypeVar>(&tableTwo)->props["foo"].type);
state.tryUnify(&tableOne, &tableTwo);
state.tryUnify(&tableTwo, &tableOne);
CHECK(state.errors.empty());
if (FFlag::LuauUseCommittingTxnLog)
state.log.commit();
CHECK_EQ(*getMutable<TableTypeVar>(&tableOne)->props["foo"].type, *getMutable<TableTypeVar>(&tableTwo)->props["foo"].type);
}
@ -101,11 +109,12 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved")
CHECK_NE(*getMutable<TableTypeVar>(&tableOne)->props["foo"].type, *getMutable<TableTypeVar>(&tableTwo)->props["foo"].type);
state.tryUnify(&tableOne, &tableTwo);
state.tryUnify(&tableTwo, &tableOne);
CHECK_EQ(1, state.errors.size());
state.log.rollback();
if (!FFlag::LuauUseCommittingTxnLog)
state.DEPRECATED_log.rollback();
CHECK_NE(*getMutable<TableTypeVar>(&tableOne)->props["foo"].type, *getMutable<TableTypeVar>(&tableTwo)->props["foo"].type);
}
@ -170,7 +179,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "variadic_type_pack_unification")
TypePackVar testPack{TypePack{{typeChecker.numberType, typeChecker.stringType}, std::nullopt}};
TypePackVar variadicPack{VariadicTypePack{typeChecker.numberType}};
state.tryUnify(&variadicPack, &testPack);
state.tryUnify(&testPack, &variadicPack);
CHECK(!state.errors.empty());
}
@ -180,7 +189,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "variadic_tails_respect_progress")
TypePackVar a{TypePack{{typeChecker.numberType, typeChecker.stringType, typeChecker.booleanType, typeChecker.booleanType}}};
TypePackVar b{TypePack{{typeChecker.numberType, typeChecker.stringType}, &variadicPack}};
state.tryUnify(&a, &b);
state.tryUnify(&b, &a);
CHECK(state.errors.empty());
}
@ -214,32 +223,41 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "cli_41095_concat_log_in_sealed_table_unifica
CHECK_EQ(toString(result.errors[1]), "Available overloads: ({a}, a) -> (); and ({a}, number, a) -> ()");
}
TEST_CASE_FIXTURE(TryUnifyFixture, "undo_new_prop_on_unsealed_table")
TEST_CASE("undo_new_prop_on_unsealed_table")
{
ScopedFastFlag flags[] = {
{"LuauTableSubtypingVariance2", true},
// This test makes no sense with a committing TxnLog.
{"LuauUseCommittingTxnLog", false},
};
// I am not sure how to make this happen in Luau code.
TypeId unsealedTable = arena.addType(TableTypeVar{TableState::Unsealed, TypeLevel{}});
TypeId sealedTable = arena.addType(TableTypeVar{
{{"prop", Property{getSingletonTypes().numberType}}},
std::nullopt,
TypeLevel{},
TableState::Sealed
});
TryUnifyFixture fix;
TypeId unsealedTable = fix.arena.addType(TableTypeVar{TableState::Unsealed, TypeLevel{}});
TypeId sealedTable =
fix.arena.addType(TableTypeVar{{{"prop", Property{getSingletonTypes().numberType}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
const TableTypeVar* ttv = get<TableTypeVar>(unsealedTable);
REQUIRE(ttv);
state.tryUnify(unsealedTable, sealedTable);
fix.state.tryUnify(sealedTable, unsealedTable);
// To be honest, it's really quite spooky here that we're amending an unsealed table in this case.
CHECK(!ttv->props.empty());
state.log.rollback();
fix.state.DEPRECATED_log.rollback();
CHECK(ttv->props.empty());
}
TEST_CASE_FIXTURE(TryUnifyFixture, "free_tail_is_grown_properly")
{
TypePackId threeNumbers = arena.addTypePack(TypePack{{typeChecker.numberType, typeChecker.numberType, typeChecker.numberType}, std::nullopt});
TypePackId numberAndFreeTail = arena.addTypePack(TypePack{{typeChecker.numberType}, arena.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})});
ErrorVec unifyErrors = state.canUnify(numberAndFreeTail, threeNumbers);
CHECK(unifyErrors.size() == 0);
}
TEST_SUITE_END();

View file

@ -10,6 +10,8 @@
using namespace Luau;
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
TEST_SUITE_BEGIN("TypePackTests");
TEST_CASE_FIXTURE(Fixture, "infer_multi_return")
@ -263,10 +265,13 @@ TEST_CASE_FIXTURE(Fixture, "variadic_pack_syntax")
CHECK_EQ(toString(requireType("foo")), "(...number) -> ()");
}
// CLI-45791
TEST_CASE_FIXTURE(UnfrozenFixture, "type_pack_hidden_free_tail_infinite_growth")
// Switch back to TEST_CASE_FIXTURE with regular Fixture when removing the
// LuauUseCommittingTxnLog flag.
TEST_CASE("type_pack_hidden_free_tail_infinite_growth")
{
CheckResult result = check(R"(
Fixture fix(FFlag::LuauUseCommittingTxnLog);
CheckResult result = fix.check(R"(
--!nonstrict
if _ then
_[function(l0)end],l0 = _
@ -278,7 +283,8 @@ elseif _ then
end
)");
LUAU_REQUIRE_ERRORS(result);
// Switch back to LUAU_REQUIRE_ERRORS(result) when using TEST_CASE_FIXTURE.
CHECK(result.errors.size() > 0);
}
TEST_CASE_FIXTURE(Fixture, "variadic_argument_tail")

View file

@ -8,6 +8,7 @@
#include "doctest.h"
LUAU_FASTFLAG(LuauEqConstraint)
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
using namespace Luau;
@ -282,16 +283,19 @@ local c = b:foo(1, 2)
CHECK_EQ("Value of type 'A?' could be nil", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(UnfrozenFixture, "optional_union_follow")
TEST_CASE("optional_union_follow")
{
CheckResult result = check(R"(
Fixture fix(FFlag::LuauUseCommittingTxnLog);
CheckResult result = fix.check(R"(
local y: number? = 2
local x = y
local function f(a: number, b: typeof(x), c: typeof(x)) return -a end
return f()
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
REQUIRE_EQ(result.errors.size(), 1);
// LUAU_REQUIRE_ERROR_COUNT(1, result);
auto acm = get<CountMismatch>(result.errors[0]);
REQUIRE(acm);

View file

@ -185,6 +185,8 @@ TEST_CASE_FIXTURE(Fixture, "UnionTypeVarIterator_with_empty_union")
TEST_CASE_FIXTURE(Fixture, "substitution_skip_failure")
{
ScopedFastFlag sff{"LuauSealExports", true};
TypeVar ftv11{FreeTypeVar{TypeLevel{}}};
TypePackVar tp24{TypePack{{&ftv11}}};
@ -261,7 +263,7 @@ TEST_CASE_FIXTURE(Fixture, "substitution_skip_failure")
TypeId result = typeChecker.anyify(typeChecker.globalScope, root, Location{});
CHECK_EQ("{ f: t1 } where t1 = () -> { f: () -> { f: ({ f: t1 }) -> (), signal: { f: (any) -> () } } }", toString(result));
CHECK_EQ("{| f: t1 |} where t1 = () -> {| f: () -> {| f: ({| f: t1 |}) -> (), signal: {| f: (any) -> () |} |} |}", toString(result));
}
TEST_CASE("tagging_tables")

View file

@ -98,4 +98,13 @@ assert(quuz(function(...) end) == "0 true")
assert(quuz(function(a, b) end) == "2 false")
assert(quuz(function(a, b, ...) end) == "2 true")
-- info linedefined & line
function testlinedefined()
local line = debug.info(1, "l")
local linedefined = debug.info(testlinedefined, "l")
assert(linedefined + 1 == line)
end
testlinedefined()
return 'OK'

View file

@ -0,0 +1,51 @@
-- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
-- This file is based on Lua 5.x tests -- https://github.com/lua/lua/tree/master/testes
print("testing string-number conversion")
-- zero
assert(tostring(0) == "0")
assert(tostring(0/-1) == "-0")
-- specials
assert(tostring(1/0) == "inf")
assert(tostring(-1/0) == "-inf")
assert(tostring(0/0) == "nan")
-- integers
assert(tostring(1) == "1")
assert(tostring(42) == "42")
assert(tostring(-4294967296) == "-4294967296")
assert(tostring(9007199254740991) == "9007199254740991")
-- decimals
assert(tostring(0.5) == "0.5")
assert(tostring(0.1) == "0.1")
assert(tostring(-0.17) == "-0.17")
assert(tostring(math.pi) == "3.141592653589793")
-- fuzzing corpus
assert(tostring(5.4536123983019448e-311) == "5.453612398302e-311")
assert(tostring(5.4834368411298348e-311) == "5.48343684113e-311")
assert(tostring(4.4154895841930002e-305) == "4.415489584193e-305")
assert(tostring(1125968630513728) == "1125968630513728")
assert(tostring(3.3951932655938423e-313) == "3.3951932656e-313")
assert(tostring(1.625) == "1.625")
assert(tostring(4.9406564584124654e-324) == "5.e-324")
assert(tostring(2.0049288280105384) == "2.0049288280105384")
assert(tostring(3.0517578125e-05) == "0.000030517578125")
assert(tostring(1.383544921875) == "1.383544921875")
assert(tostring(3.0053350932691001) == "3.0053350932691")
assert(tostring(0.0001373291015625) == "0.0001373291015625")
assert(tostring(-1.9490628022799998e+289) == "-1.94906280228e+289")
assert(tostring(-0.00610404721867928) == "-0.00610404721867928")
assert(tostring(0.00014495849609375) == "0.00014495849609375")
assert(tostring(0.453125) == "0.453125")
assert(tostring(-4.2375343999999997e+73) == "-4.2375344e+73")
assert(tostring(1.3202313930270133e-192) == "1.3202313930270133e-192")
assert(tostring(3.6984408976312836e+19) == "36984408976312840000")
assert(tostring(2.0563000527063302) == "2.05630005270633")
assert(tostring(4.8970527433648997e-260) == "4.8970527433649e-260")
assert(tostring(1.62890625) == "1.62890625")
assert(tostring(1.1295093211933533e+65) == "1.1295093211933533e+65")
return "OK"

View file

@ -2,6 +2,8 @@
#include "Luau/Common.h"
#define DOCTEST_CONFIG_IMPLEMENT
// Our calls to parseOption/parseFlag don't provide a prefix so set the prefix to the empty string.
#define DOCTEST_CONFIG_OPTIONS_PREFIX ""
#include "doctest.h"
#ifdef _WIN32
@ -18,6 +20,10 @@
#include <optional>
// Indicates if verbose output is enabled.
// Currently, this enables output from lua's 'print', but other verbose output could be enabled eventually.
bool verbose = false;
static bool skipFastFlag(const char* flagName)
{
if (strncmp(flagName, "Test", 4) == 0)
@ -46,7 +52,7 @@ static bool debuggerPresent()
#endif
}
static int assertionHandler(const char* expr, const char* file, int line)
static int assertionHandler(const char* expr, const char* file, int line, const char* function)
{
if (debuggerPresent())
LUAU_DEBUGBREAK();
@ -235,6 +241,11 @@ int main(int argc, char** argv)
return 0;
}
if (doctest::parseFlag(argc, argv, "--verbose"))
{
verbose = true;
}
if (std::vector<doctest::String> flags; doctest::parseCommaSepArgs(argc, argv, "--fflags=", flags))
setFastFlags(flags);
@ -261,7 +272,15 @@ int main(int argc, char** argv)
}
}
return context.run();
int result = context.run();
if (doctest::parseFlag(argc, argv, "--help") || doctest::parseFlag(argc, argv, "-h"))
{
printf("Additional command line options:\n");
printf(" --verbose Enables verbose output (e.g. lua 'print' statements)\n");
printf(" --fflags= Sets specified fast flags\n");
printf(" --list-fflags List all fast flags\n");
}
return result;
}

82
tools/numprint.py Normal file
View file

@ -0,0 +1,82 @@
#!/usr/bin/python
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
# This code can be used to generate power tables for Schubfach algorithm (see lnumprint.cpp)
import math
import sys
(_, pow10min, pow10max, compact) = sys.argv
pow10min = int(pow10min)
pow10max = int(pow10max)
compact = compact == "True"
# extract high 128 bits of the value
def high128(n, roundup):
L = math.ceil(math.log2(n))
r = 0
for i in range(L - 128, L):
if i >= 0 and (n & (1 << i)) != 0:
r |= (1 << (i - L + 128))
return r + (1 if roundup else 0)
def pow10approx(n):
if n == 0:
return 1 << 127
elif n > 0:
return high128(10**n, 5**n >= 2**128)
else:
# 10^-n is a binary fraction that can't be represented in floating point
# we need to extract top 128 bits of the fraction starting from the first 1
# to get there, we need to divide 2^k by 10^n for a sufficiently large k and repeat the extraction process
p = 10**-n
k = 2**128 * 16**-n # this guarantees that the fraction has more than 128 extra bits
return high128(k // p, True)
def pow5_64(n):
assert(n >= 0)
if n == 0:
return 1 << 63
else:
return high128(5**n, False) >> 64
if not compact:
print("// kPow10Table", pow10min, "..", pow10max)
print("{")
for p in range(pow10min, pow10max + 1):
h = hex(pow10approx(p))[2:]
assert(len(h) == 32)
print(" {0x%s, 0x%s}," % (h[0:16].upper(), h[16:32].upper()))
print("}")
else:
print("// kPow5Table")
print("{")
for i in range(16):
print(" " + hex(pow5_64(i)) + ",")
print("}")
print("// kPow10Table", pow10min, "..", pow10max)
print("{")
for p in range(pow10min, pow10max + 1, 16):
base = pow10approx(p)
errw = 0
for i in range(16):
real = pow10approx(p + i)
appr = (base * pow5_64(i)) >> 64
scale = 1 if appr < (1 << 127) else 0 # 1-bit scale
offset = (appr << scale) - real
assert(offset >= -4 and offset <= 3) # 3-bit offset
assert((appr << scale) >> 64 == real >> 64) # offset only affects low half
assert((appr << scale) - offset == real) # validate full reconstruction
err = (scale << 3) | (offset + 4)
errw |= err << (i * 4)
hbase = hex(base)[2:]
assert(len(hbase) == 32)
assert(errw < 1 << 64)
print(" {0x%s, 0x%s, 0x%16x}," % (hbase[0:16], hbase[16:32], errw))
print("}")