mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
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:
parent
d323237b6c
commit
d50b079325
18
.gitignore
vendored
18
.gitignore
vendored
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
LUAU_FASTFLAG(LuauTypedAllocatorZeroStart)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -20,6 +22,9 @@ class TypedAllocator
|
||||
public:
|
||||
TypedAllocator()
|
||||
{
|
||||
if (FFlag::LuauTypedAllocatorZeroStart)
|
||||
currentBlockSize = kBlockSize;
|
||||
else
|
||||
appendBlock();
|
||||
}
|
||||
|
||||
@ -59,11 +64,17 @@ public:
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
if (FFlag::LuauTypedAllocatorZeroStart)
|
||||
return stuff.empty();
|
||||
else
|
||||
return stuff.size() == 1 && currentBlockSize == 0;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
if (FFlag::LuauTypedAllocatorZeroStart)
|
||||
return stuff.empty() ? 0 : kBlockSize * (stuff.size() - 1) + currentBlockSize;
|
||||
else
|
||||
return kBlockSize * (stuff.size() - 1) + currentBlockSize;
|
||||
}
|
||||
|
||||
@ -72,6 +83,10 @@ public:
|
||||
if (frozen)
|
||||
unfreeze();
|
||||
free();
|
||||
|
||||
if (FFlag::LuauTypedAllocatorZeroStart)
|
||||
currentBlockSize = kBlockSize;
|
||||
else
|
||||
appendBlock();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 0)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
|
@ -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])
|
||||
{
|
||||
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,8 +1138,6 @@ 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())
|
||||
{
|
||||
@ -1163,26 +1148,9 @@ std::string toStringNamedFunction(const std::string& prefix, const FunctionTypeV
|
||||
{
|
||||
s += "_: ";
|
||||
}
|
||||
}
|
||||
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 += toString_(*argPackIter);
|
||||
++argPackIter;
|
||||
|
||||
if (!FFlag::LuauFunctionArgumentNameSize)
|
||||
{
|
||||
if (!ftv.argNames.empty())
|
||||
{
|
||||
LUAU_ASSERT(argNameIter != ftv.argNames.end());
|
||||
++argNameIter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (argPackIter.tail())
|
||||
|
@ -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
@ -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,16 +39,30 @@ 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())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TypePackIterator& TypePackIterator::operator++()
|
||||
{
|
||||
@ -52,9 +70,18 @@ TypePackIterator& TypePackIterator::operator++()
|
||||
|
||||
++currentIndex;
|
||||
while (tp && currentIndex >= tp->head.size())
|
||||
{
|
||||
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;
|
||||
|
@ -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
|
||||
|
@ -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
@ -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))
|
||||
|
@ -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;
|
||||
|
38
CLI/Repl.cpp
38
CLI/Repl.cpp
@ -234,12 +234,15 @@ static void completeIndexer(lua_State* L, const char* editBuffer, size_t start,
|
||||
lua_pushnil(L);
|
||||
|
||||
while (lua_next(L, -2) != 0)
|
||||
{
|
||||
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())));
|
||||
}
|
||||
|
||||
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;
|
||||
|
24
CLI/Web.cpp
24
CLI/Web.cpp
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
adjustresults(L, nresults);
|
||||
return status;
|
||||
|
@ -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:
|
||||
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]);
|
||||
#else
|
||||
lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2]);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -19,7 +19,6 @@
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCcallRestoreFix, false)
|
||||
LUAU_FASTFLAG(LuauCoroutineClose)
|
||||
LUAU_FASTFLAGVARIABLE(LuauActivateBeforeExec, true)
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
@ -228,8 +227,6 @@ 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);
|
||||
@ -239,11 +236,6 @@ void luaD_call(lua_State* L, StkId func, int nResults)
|
||||
if (!oldactive)
|
||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
luau_execute(L); /* call it */
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
if (FFlag::LuauCcallRestoreFix)
|
||||
{
|
||||
|
@ -12,8 +12,6 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauArrayBoundary)
|
||||
|
||||
#define GC_SWEEPMAX 40
|
||||
#define GC_SWEEPCOST 10
|
||||
|
||||
|
@ -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);
|
||||
|
||||
if (h->metatable)
|
||||
validateobjref(g, obj2gco(h), obj2gco(h->metatable));
|
||||
|
375
VM/src/lnumprint.cpp
Normal file
375
VM/src/lnumprint.cpp
Normal 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);
|
||||
}
|
||||
}
|
@ -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))
|
||||
|
@ -289,6 +289,7 @@ typedef struct Proto
|
||||
int sizek;
|
||||
int sizelineinfo;
|
||||
int linegaplog2;
|
||||
int linedefined;
|
||||
|
||||
|
||||
uint8_t nups; /* number of upvalues */
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
|
||||
if (version == 0)
|
||||
lua_pushfstring(L, "%s%.*s", chunkid, int(size - offset), data + offset);
|
||||
else
|
||||
lua_pushfstring(L, "%s: bytecode version mismatch", chunkid);
|
||||
return 1;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
35
fuzz/number.cpp
Normal 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;
|
||||
}
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
)";
|
||||
|
@ -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();
|
||||
|
@ -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")
|
||||
|
@ -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);
|
||||
|
@ -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")
|
||||
|
@ -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'
|
||||
|
51
tests/conformance/strconv.lua
Normal file
51
tests/conformance/strconv.lua
Normal 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"
|
@ -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
82
tools/numprint.py
Normal 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("}")
|
Loading…
Reference in New Issue
Block a user