Merge branch 'upstream' into merge

This commit is contained in:
Arseny Kapoulkine 2022-02-17 16:42:11 -08:00
commit 1f679b08b8
84 changed files with 1497 additions and 579 deletions

View file

@ -21,6 +21,7 @@ struct BasicDocumentation
{ {
std::string documentation; std::string documentation;
std::string learnMoreLink; std::string learnMoreLink;
std::string codeSample;
}; };
struct FunctionParameterDocumentation struct FunctionParameterDocumentation
@ -37,6 +38,7 @@ struct FunctionDocumentation
std::vector<FunctionParameterDocumentation> parameters; std::vector<FunctionParameterDocumentation> parameters;
std::vector<DocumentationSymbol> returns; std::vector<DocumentationSymbol> returns;
std::string learnMoreLink; std::string learnMoreLink;
std::string codeSample;
}; };
struct OverloadedFunctionDocumentation struct OverloadedFunctionDocumentation
@ -52,6 +54,7 @@ struct TableDocumentation
std::string documentation; std::string documentation;
Luau::DenseHashMap<std::string, DocumentationSymbol> keys; Luau::DenseHashMap<std::string, DocumentationSymbol> keys;
std::string learnMoreLink; std::string learnMoreLink;
std::string codeSample;
}; };
using DocumentationDatabase = Luau::DenseHashMap<DocumentationSymbol, Documentation>; using DocumentationDatabase = Luau::DenseHashMap<DocumentationSymbol, Documentation>;

View file

@ -24,6 +24,7 @@ struct TypeChecker;
struct FileResolver; struct FileResolver;
struct ModuleResolver; struct ModuleResolver;
struct ParseResult; struct ParseResult;
struct HotComment;
struct LoadDefinitionFileResult struct LoadDefinitionFileResult
{ {
@ -35,7 +36,7 @@ struct LoadDefinitionFileResult
LoadDefinitionFileResult loadDefinitionFile( LoadDefinitionFileResult loadDefinitionFile(
TypeChecker& typeChecker, ScopePtr targetScope, std::string_view definition, const std::string& packageName); TypeChecker& typeChecker, ScopePtr targetScope, std::string_view definition, const std::string& packageName);
std::optional<Mode> parseMode(const std::vector<std::string>& hotcomments); std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments);
std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr); std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr);

View file

@ -14,6 +14,7 @@ class AstStat;
class AstNameTable; class AstNameTable;
struct TypeChecker; struct TypeChecker;
struct Module; struct Module;
struct HotComment;
using ScopePtr = std::shared_ptr<struct Scope>; using ScopePtr = std::shared_ptr<struct Scope>;
@ -50,6 +51,7 @@ struct LintWarning
Code_TableOperations = 23, Code_TableOperations = 23,
Code_DuplicateCondition = 24, Code_DuplicateCondition = 24,
Code_MisleadingAndOr = 25, Code_MisleadingAndOr = 25,
Code_CommentDirective = 26,
Code__Count Code__Count
}; };
@ -60,7 +62,7 @@ struct LintWarning
static const char* getName(Code code); static const char* getName(Code code);
static Code parseName(const char* name); static Code parseName(const char* name);
static uint64_t parseMask(const std::vector<std::string>& hotcomments); static uint64_t parseMask(const std::vector<HotComment>& hotcomments);
}; };
struct LintResult struct LintResult
@ -90,7 +92,8 @@ struct LintOptions
void setDefaults(); void setDefaults();
}; };
std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module, const LintOptions& options); std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module,
const std::vector<HotComment>& hotcomments, const LintOptions& options);
std::vector<AstName> getDeprecatedGlobals(const AstNameTable& names); std::vector<AstName> getDeprecatedGlobals(const AstNameTable& names);

View file

@ -6,7 +6,7 @@
#include "Luau/TypedAllocator.h" #include "Luau/TypedAllocator.h"
#include "Luau/ParseOptions.h" #include "Luau/ParseOptions.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Parser.h" #include "Luau/ParseResult.h"
#include <memory> #include <memory>
#include <vector> #include <vector>
@ -37,8 +37,8 @@ struct SourceModule
AstStatBlock* root = nullptr; AstStatBlock* root = nullptr;
std::optional<Mode> mode; std::optional<Mode> mode;
uint64_t ignoreLints = 0;
std::vector<HotComment> hotcomments;
std::vector<Comment> commentLocations; std::vector<Comment> commentLocations;
SourceModule() SourceModule()

View file

@ -6,9 +6,6 @@
namespace Luau namespace Luau
{ {
struct Module; void quantify(TypeId ty, TypeLevel level);
using ModulePtr = std::shared_ptr<Module>;
void quantify(ModulePtr module, TypeId ty, TypeLevel level);
} // namespace Luau } // namespace Luau

View file

@ -101,9 +101,6 @@ struct Tarjan
// This is hot code, so we optimize recursion to a stack. // This is hot code, so we optimize recursion to a stack.
TarjanResult loop(); TarjanResult loop();
// Clear the state
void clear();
// Find or create the index for a vertex. // Find or create the index for a vertex.
// Return a boolean which is `true` if it's a freshly created index. // Return a boolean which is `true` if it's a freshly created index.
std::pair<int, bool> indexify(TypeId ty); std::pair<int, bool> indexify(TypeId ty);
@ -166,7 +163,17 @@ struct FindDirty : Tarjan
// and replaces them with clean ones. // and replaces them with clean ones.
struct Substitution : FindDirty struct Substitution : FindDirty
{ {
ModulePtr currentModule; protected:
Substitution(const TxnLog* log_, TypeArena* arena)
: arena(arena)
{
log = log_;
LUAU_ASSERT(log);
LUAU_ASSERT(arena);
}
public:
TypeArena* arena;
DenseHashMap<TypeId, TypeId> newTypes{nullptr}; DenseHashMap<TypeId, TypeId> newTypes{nullptr};
DenseHashMap<TypePackId, TypePackId> newPacks{nullptr}; DenseHashMap<TypePackId, TypePackId> newPacks{nullptr};
@ -192,12 +199,13 @@ struct Substitution : FindDirty
template<typename T> template<typename T>
TypeId addType(const T& tv) TypeId addType(const T& tv)
{ {
return currentModule->internalTypes.addType(tv); return arena->addType(tv);
} }
template<typename T> template<typename T>
TypePackId addTypePack(const T& tp) TypePackId addTypePack(const T& tp)
{ {
return currentModule->internalTypes.addTypePack(TypePackVar{tp}); return arena->addTypePack(TypePackVar{tp});
} }
}; };

View file

@ -5,7 +5,6 @@
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/Symbol.h" #include "Luau/Symbol.h"
#include "Luau/Parser.h"
#include "Luau/Substitution.h" #include "Luau/Substitution.h"
#include "Luau/TxnLog.h" #include "Luau/TxnLog.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
@ -37,6 +36,15 @@ struct Unifier;
// A substitution which replaces generic types in a given set by free types. // A substitution which replaces generic types in a given set by free types.
struct ReplaceGenerics : Substitution struct ReplaceGenerics : Substitution
{ {
ReplaceGenerics(
const TxnLog* log, TypeArena* arena, TypeLevel level, const std::vector<TypeId>& generics, const std::vector<TypePackId>& genericPacks)
: Substitution(log, arena)
, level(level)
, generics(generics)
, genericPacks(genericPacks)
{
}
TypeLevel level; TypeLevel level;
std::vector<TypeId> generics; std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks; std::vector<TypePackId> genericPacks;
@ -50,8 +58,13 @@ struct ReplaceGenerics : Substitution
// A substitution which replaces generic functions by monomorphic functions // A substitution which replaces generic functions by monomorphic functions
struct Instantiation : Substitution struct Instantiation : Substitution
{ {
Instantiation(const TxnLog* log, TypeArena* arena, TypeLevel level)
: Substitution(log, arena)
, level(level)
{
}
TypeLevel level; TypeLevel level;
ReplaceGenerics replaceGenerics;
bool ignoreChildren(TypeId ty) override; bool ignoreChildren(TypeId ty) override;
bool isDirty(TypeId ty) override; bool isDirty(TypeId ty) override;
bool isDirty(TypePackId tp) override; bool isDirty(TypePackId tp) override;
@ -62,6 +75,12 @@ struct Instantiation : Substitution
// A substitution which replaces free types by generic types. // A substitution which replaces free types by generic types.
struct Quantification : Substitution struct Quantification : Substitution
{ {
Quantification(TypeArena* arena, TypeLevel level)
: Substitution(TxnLog::empty(), arena)
, level(level)
{
}
TypeLevel level; TypeLevel level;
std::vector<TypeId> generics; std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks; std::vector<TypePackId> genericPacks;
@ -74,6 +93,13 @@ struct Quantification : Substitution
// A substitution which replaces free types by any // A substitution which replaces free types by any
struct Anyification : Substitution struct Anyification : Substitution
{ {
Anyification(TypeArena* arena, TypeId anyType, TypePackId anyTypePack)
: Substitution(TxnLog::empty(), arena)
, anyType(anyType)
, anyTypePack(anyTypePack)
{
}
TypeId anyType; TypeId anyType;
TypePackId anyTypePack; TypePackId anyTypePack;
bool isDirty(TypeId ty) override; bool isDirty(TypeId ty) override;
@ -85,6 +111,13 @@ struct Anyification : Substitution
// A substitution which replaces the type parameters of a type function by arguments // A substitution which replaces the type parameters of a type function by arguments
struct ApplyTypeFunction : Substitution struct ApplyTypeFunction : Substitution
{ {
ApplyTypeFunction(TypeArena* arena, TypeLevel level)
: Substitution(TxnLog::empty(), arena)
, level(level)
, encounteredForwardedType(false)
{
}
TypeLevel level; TypeLevel level;
bool encounteredForwardedType; bool encounteredForwardedType;
std::unordered_map<TypeId, TypeId> typeArguments; std::unordered_map<TypeId, TypeId> typeArguments;
@ -351,8 +384,7 @@ private:
// Note: `scope` must be a fresh scope. // Note: `scope` must be a fresh scope.
GenericTypeDefinitions createGenericTypes(const ScopePtr& scope, std::optional<TypeLevel> levelOpt, const AstNode& node, GenericTypeDefinitions createGenericTypes(const ScopePtr& scope, std::optional<TypeLevel> levelOpt, const AstNode& node,
const AstArray<AstGenericType>& genericNames, const AstArray<AstGenericTypePack>& genericPackNames, const AstArray<AstGenericType>& genericNames, const AstArray<AstGenericTypePack>& genericPackNames, bool useCache = false);
bool useCache = false);
public: public:
ErrorVec resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense); ErrorVec resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense);
@ -392,11 +424,6 @@ public:
ModulePtr currentModule; ModulePtr currentModule;
ModuleName currentModuleName; ModuleName currentModuleName;
Instantiation instantiation;
Quantification quantification;
Anyification anyification;
ApplyTypeFunction applyTypeFunction;
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope; std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
InternalErrorReporter* iceHandler; InternalErrorReporter* iceHandler;

View file

@ -7,6 +7,7 @@
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/Parser.h" // TODO: only needed for autocompleteSource which is deprecated
#include <algorithm> #include <algorithm>
#include <unordered_set> #include <unordered_set>
@ -14,9 +15,9 @@
LUAU_FASTFLAG(LuauUseCommittingTxnLog) LUAU_FASTFLAG(LuauUseCommittingTxnLog)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false); LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false);
LUAU_FASTFLAGVARIABLE(LuauCompleteBrokenStringParams, false);
LUAU_FASTFLAGVARIABLE(LuauMissingFollowACMetatables, false); LUAU_FASTFLAGVARIABLE(LuauMissingFollowACMetatables, false);
LUAU_FASTFLAGVARIABLE(PreferToCallFunctionsForIntersects, false); LUAU_FASTFLAGVARIABLE(PreferToCallFunctionsForIntersects, false);
LUAU_FASTFLAGVARIABLE(LuauIfElseExprFixCompletionIssue, false);
static const std::unordered_set<std::string> kStatementStartingKeywords = { static const std::unordered_set<std::string> kStatementStartingKeywords = {
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -380,7 +381,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
{ {
// We are walking up the class hierarchy, so if we encounter a property that we have // We are walking up the class hierarchy, so if we encounter a property that we have
// already populated, it takes precedence over the property we found just now. // already populated, it takes precedence over the property we found just now.
if (result.count(name) == 0 && name != Parser::errorName) if (result.count(name) == 0 && name != kParseNameError)
{ {
Luau::TypeId type = Luau::follow(prop.type); Luau::TypeId type = Luau::follow(prop.type);
TypeCorrectKind typeCorrect = indexType == PropIndexType::Key ? TypeCorrectKind::Correct TypeCorrectKind typeCorrect = indexType == PropIndexType::Key ? TypeCorrectKind::Correct
@ -948,9 +949,12 @@ AutocompleteEntryMap autocompleteTypeNames(const Module& module, Position positi
} }
} }
for (size_t i = 0; i < node->returnAnnotation.types.size; i++) if (!node->returnAnnotation)
return result;
for (size_t i = 0; i < node->returnAnnotation->types.size; i++)
{ {
AstType* ret = node->returnAnnotation.types.data[i]; AstType* ret = node->returnAnnotation->types.data[i];
if (ret->location.containsClosed(position)) if (ret->location.containsClosed(position))
{ {
@ -965,7 +969,7 @@ AutocompleteEntryMap autocompleteTypeNames(const Module& module, Position positi
} }
} }
if (AstTypePack* retTp = node->returnAnnotation.tailType) if (AstTypePack* retTp = node->returnAnnotation->tailType)
{ {
if (auto variadic = retTp->as<AstTypePackVariadic>()) if (auto variadic = retTp->as<AstTypePackVariadic>())
{ {
@ -1136,7 +1140,7 @@ static AutocompleteEntryMap autocompleteStatement(
AstNode* parent = ancestry.rbegin()[1]; AstNode* parent = ancestry.rbegin()[1];
if (AstStatIf* statIf = parent->as<AstStatIf>()) if (AstStatIf* statIf = parent->as<AstStatIf>())
{ {
if (!statIf->elsebody || (statIf->hasElse && statIf->elseLocation.containsClosed(position))) if (!statIf->elsebody || (statIf->elseLocation && statIf->elseLocation->containsClosed(position)))
{ {
result.emplace("else", AutocompleteEntry{AutocompleteEntryKind::Keyword}); result.emplace("else", AutocompleteEntry{AutocompleteEntryKind::Keyword});
result.emplace("elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}); result.emplace("elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword});
@ -1164,8 +1168,7 @@ static AutocompleteEntryMap autocompleteStatement(
return result; return result;
} }
// Returns true if completions were generated (completions will be inserted into 'outResult') // Returns true iff `node` was handled by this function (completions, if any, are returned in `outResult`)
// Returns false if no completions were generated
static bool autocompleteIfElseExpression( static bool autocompleteIfElseExpression(
const AstNode* node, const std::vector<AstNode*>& ancestry, const Position& position, AutocompleteEntryMap& outResult) const AstNode* node, const std::vector<AstNode*>& ancestry, const Position& position, AutocompleteEntryMap& outResult)
{ {
@ -1173,6 +1176,13 @@ static bool autocompleteIfElseExpression(
if (!parent) if (!parent)
return false; return false;
if (FFlag::LuauIfElseExprFixCompletionIssue && node->is<AstExprIfElse>())
{
// Don't try to complete when the current node is an if-else expression (i.e. only try to complete when the node is a child of an if-else
// expression.
return true;
}
AstExprIfElse* ifElseExpr = parent->as<AstExprIfElse>(); AstExprIfElse* ifElseExpr = parent->as<AstExprIfElse>();
if (!ifElseExpr || ifElseExpr->condition->location.containsClosed(position)) if (!ifElseExpr || ifElseExpr->condition->location.containsClosed(position))
{ {
@ -1310,7 +1320,7 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(const Source
return std::nullopt; return std::nullopt;
} }
if (!nodes.back()->is<AstExprConstantString>() && (!FFlag::LuauCompleteBrokenStringParams || !nodes.back()->is<AstExprError>())) if (!nodes.back()->is<AstExprConstantString>() && !nodes.back()->is<AstExprError>())
{ {
return std::nullopt; return std::nullopt;
} }
@ -1408,8 +1418,8 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
} }
else if (auto typeReference = node->as<AstTypeReference>()) else if (auto typeReference = node->as<AstTypeReference>())
{ {
if (typeReference->hasPrefix) if (typeReference->prefix)
return {autocompleteModuleTypes(*module, position, typeReference->prefix.value), finder.ancestry}; return {autocompleteModuleTypes(*module, position, typeReference->prefix->value), finder.ancestry};
else else
return {autocompleteTypeNames(*module, position, finder.ancestry), finder.ancestry}; return {autocompleteTypeNames(*module, position, finder.ancestry), finder.ancestry};
} }
@ -1419,9 +1429,9 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
} }
else if (AstStatLocal* statLocal = node->as<AstStatLocal>()) else if (AstStatLocal* statLocal = node->as<AstStatLocal>())
{ {
if (statLocal->vars.size == 1 && (!statLocal->hasEqualsSign || position < statLocal->equalsSignLocation.begin)) if (statLocal->vars.size == 1 && (!statLocal->equalsSignLocation || position < statLocal->equalsSignLocation->begin))
return {{{"function", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry}; return {{{"function", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
else if (statLocal->hasEqualsSign && position >= statLocal->equalsSignLocation.end) else if (statLocal->equalsSignLocation && position >= statLocal->equalsSignLocation->end)
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry}; return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
else else
return {}; return {};
@ -1449,7 +1459,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
if (!statForIn->hasIn || position <= statForIn->inLocation.begin) if (!statForIn->hasIn || position <= statForIn->inLocation.begin)
{ {
AstLocal* lastName = statForIn->vars.data[statForIn->vars.size - 1]; AstLocal* lastName = statForIn->vars.data[statForIn->vars.size - 1];
if (lastName->name == Parser::errorName || lastName->location.containsClosed(position)) if (lastName->name == kParseNameError || lastName->location.containsClosed(position))
{ {
// Here we are either working with a missing binding (as would be the case in a bare "for" keyword) or // Here we are either working with a missing binding (as would be the case in a bare "for" keyword) or
// the cursor is still touching a binding name. The user is still typing a new name, so we should not offer // the cursor is still touching a binding name. The user is still typing a new name, so we should not offer
@ -1499,7 +1509,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(finder.ancestry); statWhile && !statWhile->hasDo) else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(finder.ancestry); statWhile && !statWhile->hasDo)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry}; return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->hasElse) else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->elseLocation.has_value())
{ {
return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
finder.ancestry}; finder.ancestry};
@ -1508,11 +1518,11 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
{ {
if (statIf->condition->is<AstExprError>()) if (statIf->condition->is<AstExprError>())
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry}; return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
else if (!statIf->hasThen || statIf->thenLocation.containsClosed(position)) else if (!statIf->thenLocation || statIf->thenLocation->containsClosed(position))
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry}; return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
} }
else if (AstStatIf* statIf = extractStat<AstStatIf>(finder.ancestry); else if (AstStatIf* statIf = extractStat<AstStatIf>(finder.ancestry);
statIf && (!statIf->hasThen || statIf->thenLocation.containsClosed(position))) statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)))
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry}; return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry};
else if (AstStatRepeat* statRepeat = node->as<AstStatRepeat>(); statRepeat && statRepeat->condition->is<AstExprError>()) else if (AstStatRepeat* statRepeat = node->as<AstStatRepeat>(); statRepeat && statRepeat->condition->is<AstExprError>())
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry}; return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
@ -1612,6 +1622,7 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName
OwningAutocompleteResult autocompleteSource(Frontend& frontend, std::string_view source, Position position, StringCompletionCallback callback) OwningAutocompleteResult autocompleteSource(Frontend& frontend, std::string_view source, Position position, StringCompletionCallback callback)
{ {
// TODO: Remove #include "Luau/Parser.h" with this function
auto sourceModule = std::make_unique<SourceModule>(); auto sourceModule = std::make_unique<SourceModule>();
ParseOptions parseOptions; ParseOptions parseOptions;
parseOptions.captureComments = true; parseOptions.captureComments = true;

View file

@ -1,7 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Config.h" #include "Luau/Config.h"
#include "Luau/Parser.h" #include "Luau/Lexer.h"
#include "Luau/StringUtils.h" #include "Luau/StringUtils.h"
namespace namespace

View file

@ -167,7 +167,7 @@ declare function gcinfo(): number
foreach: <K, V>({[K]: V}, (K, V) -> ()) -> (), foreach: <K, V>({[K]: V}, (K, V) -> ()) -> (),
foreachi: <V>({V}, (number, V) -> ()) -> (), foreachi: <V>({V}, (number, V) -> ()) -> (),
move: <V>({V}, number, number, number, {V}?) -> (), move: <V>({V}, number, number, number, {V}?) -> {V},
clear: <K, V>({[K]: V}) -> (), clear: <K, V>({[K]: V}) -> (),
freeze: <K, V>({[K]: V}) -> {[K]: V}, freeze: <K, V>({[K]: V}) -> {[K]: V},

View file

@ -4,6 +4,7 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Config.h" #include "Luau/Config.h"
#include "Luau/FileResolver.h" #include "Luau/FileResolver.h"
#include "Luau/Parser.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/StringUtils.h" #include "Luau/StringUtils.h"
#include "Luau/TimeTrace.h" #include "Luau/TimeTrace.h"
@ -16,23 +17,25 @@
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckTwice, false)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
namespace Luau namespace Luau
{ {
std::optional<Mode> parseMode(const std::vector<std::string>& hotcomments) std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments)
{ {
for (const std::string& hc : hotcomments) for (const HotComment& hc : hotcomments)
{ {
if (hc == "nocheck") if (!hc.header)
continue;
if (hc.content == "nocheck")
return Mode::NoCheck; return Mode::NoCheck;
if (hc == "nonstrict") if (hc.content == "nonstrict")
return Mode::Nonstrict; return Mode::Nonstrict;
if (hc == "strict") if (hc.content == "strict")
return Mode::Strict; return Mode::Strict;
} }
@ -607,13 +610,15 @@ std::pair<SourceModule, LintResult> Frontend::lintFragment(std::string_view sour
SourceModule sourceModule = parse(ModuleName{}, source, config.parseOptions); SourceModule sourceModule = parse(ModuleName{}, source, config.parseOptions);
uint64_t ignoreLints = LintWarning::parseMask(sourceModule.hotcomments);
Luau::LintOptions lintOptions = enabledLintWarnings.value_or(config.enabledLint); Luau::LintOptions lintOptions = enabledLintWarnings.value_or(config.enabledLint);
lintOptions.warningMask &= sourceModule.ignoreLints; lintOptions.warningMask &= ~ignoreLints;
double timestamp = getTimestamp(); double timestamp = getTimestamp();
std::vector<LintWarning> warnings = std::vector<LintWarning> warnings = Luau::lint(sourceModule.root, *sourceModule.names.get(), typeChecker.globalScope, nullptr,
Luau::lint(sourceModule.root, *sourceModule.names.get(), typeChecker.globalScope, nullptr, enabledLintWarnings.value_or(config.enabledLint)); sourceModule.hotcomments, enabledLintWarnings.value_or(config.enabledLint));
stats.timeLint += getTimestamp() - timestamp; stats.timeLint += getTimestamp() - timestamp;
@ -651,8 +656,10 @@ LintResult Frontend::lint(const SourceModule& module, std::optional<Luau::LintOp
const Config& config = configResolver->getConfig(module.name); const Config& config = configResolver->getConfig(module.name);
uint64_t ignoreLints = LintWarning::parseMask(module.hotcomments);
LintOptions options = enabledLintWarnings.value_or(config.enabledLint); LintOptions options = enabledLintWarnings.value_or(config.enabledLint);
options.warningMask &= ~module.ignoreLints; options.warningMask &= ~ignoreLints;
Mode mode = module.mode.value_or(config.mode); Mode mode = module.mode.value_or(config.mode);
if (mode != Mode::NoCheck) if (mode != Mode::NoCheck)
@ -671,7 +678,7 @@ LintResult Frontend::lint(const SourceModule& module, std::optional<Luau::LintOp
double timestamp = getTimestamp(); double timestamp = getTimestamp();
std::vector<LintWarning> warnings = Luau::lint(module.root, *module.names, environmentScope, modulePtr.get(), options); std::vector<LintWarning> warnings = Luau::lint(module.root, *module.names, environmentScope, modulePtr.get(), module.hotcomments, options);
stats.timeLint += getTimestamp() - timestamp; stats.timeLint += getTimestamp() - timestamp;
@ -839,7 +846,6 @@ SourceModule Frontend::parse(const ModuleName& name, std::string_view src, const
{ {
sourceModule.root = parseResult.root; sourceModule.root = parseResult.root;
sourceModule.mode = parseMode(parseResult.hotcomments); sourceModule.mode = parseMode(parseResult.hotcomments);
sourceModule.ignoreLints = LintWarning::parseMask(parseResult.hotcomments);
} }
else else
{ {
@ -848,8 +854,13 @@ SourceModule Frontend::parse(const ModuleName& name, std::string_view src, const
} }
sourceModule.name = name; sourceModule.name = name;
if (parseOptions.captureComments) if (parseOptions.captureComments)
{
sourceModule.commentLocations = std::move(parseResult.commentLocations); sourceModule.commentLocations = std::move(parseResult.commentLocations);
sourceModule.hotcomments = std::move(parseResult.hotcomments);
}
return sourceModule; return sourceModule;
} }

View file

@ -158,6 +158,13 @@ struct AstJsonEncoder : public AstVisitor
{ {
writeString(str); writeString(str);
} }
void write(std::optional<AstName> name)
{
if (name)
write(*name);
else
writeRaw("null");
}
void write(AstName name) void write(AstName name)
{ {
writeString(name.value ? name.value : ""); writeString(name.value ? name.value : "");
@ -327,7 +334,7 @@ struct AstJsonEncoder : public AstVisitor
if (node->self) if (node->self)
PROP(self); PROP(self);
PROP(args); PROP(args);
if (node->hasReturnAnnotation) if (node->returnAnnotation)
PROP(returnAnnotation); PROP(returnAnnotation);
PROP(vararg); PROP(vararg);
PROP(varargLocation); PROP(varargLocation);
@ -341,6 +348,14 @@ struct AstJsonEncoder : public AstVisitor
}); });
} }
void write(const std::optional<AstTypeList>& typeList)
{
if (typeList)
write(*typeList);
else
writeRaw("null");
}
void write(const AstTypeList& typeList) void write(const AstTypeList& typeList)
{ {
writeRaw("{"); writeRaw("{");
@ -544,7 +559,7 @@ struct AstJsonEncoder : public AstVisitor
PROP(thenbody); PROP(thenbody);
if (node->elsebody) if (node->elsebody)
PROP(elsebody); PROP(elsebody);
PROP(hasThen); write("hasThen", node->thenLocation.has_value());
PROP(hasEnd); PROP(hasEnd);
}); });
} }
@ -728,7 +743,7 @@ struct AstJsonEncoder : public AstVisitor
void write(class AstTypeReference* node) void write(class AstTypeReference* node)
{ {
writeNode(node, "AstTypeReference", [&]() { writeNode(node, "AstTypeReference", [&]() {
if (node->hasPrefix) if (node->prefix)
PROP(prefix); PROP(prefix);
PROP(name); PROP(name);
PROP(parameters); PROP(parameters);

View file

@ -12,6 +12,8 @@
#include <math.h> #include <math.h>
#include <limits.h> #include <limits.h>
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
namespace Luau namespace Luau
{ {
@ -44,6 +46,7 @@ static const char* kWarningNames[] = {
"TableOperations", "TableOperations",
"DuplicateCondition", "DuplicateCondition",
"MisleadingAndOr", "MisleadingAndOr",
"CommentDirective",
}; };
// clang-format on // clang-format on
@ -732,13 +735,13 @@ private:
bool visit(AstTypeReference* node) override bool visit(AstTypeReference* node) override
{ {
if (!node->hasPrefix) if (!node->prefix)
return true; return true;
if (!imports.contains(node->prefix)) if (!imports.contains(*node->prefix))
return true; return true;
AstLocal* astLocal = imports[node->prefix]; AstLocal* astLocal = imports[*node->prefix];
Local& local = locals[astLocal]; Local& local = locals[astLocal];
LUAU_ASSERT(local.import); LUAU_ASSERT(local.import);
local.used = true; local.used = true;
@ -2527,13 +2530,108 @@ static void fillBuiltinGlobals(LintContext& context, const AstNameTable& names,
} }
} }
static const char* fuzzyMatch(std::string_view str, const char** array, size_t size)
{
if (FInt::LuauSuggestionDistance == 0)
return nullptr;
size_t bestDistance = FInt::LuauSuggestionDistance;
size_t bestMatch = size;
for (size_t i = 0; i < size; ++i)
{
size_t ed = editDistance(str, array[i]);
if (ed <= bestDistance)
{
bestDistance = ed;
bestMatch = i;
}
}
return bestMatch < size ? array[bestMatch] : nullptr;
}
static void lintComments(LintContext& context, const std::vector<HotComment>& hotcomments)
{
bool seenMode = false;
for (const HotComment& hc : hotcomments)
{
// We reserve --!<space> for various informational (non-directive) comments
if (hc.content.empty() || hc.content[0] == ' ' || hc.content[0] == '\t')
continue;
if (!hc.header)
{
emitWarning(context, LintWarning::Code_CommentDirective, hc.location,
"Comment directive is ignored because it is placed after the first non-comment token");
}
else
{
std::string::size_type space = hc.content.find_first_of(" \t");
std::string_view first = std::string_view(hc.content).substr(0, space);
if (first == "nolint")
{
std::string::size_type notspace = hc.content.find_first_not_of(" \t", space);
if (space == std::string::npos || notspace == std::string::npos)
{
// disables all lints
}
else if (LintWarning::parseName(hc.content.c_str() + notspace) == LintWarning::Code_Unknown)
{
const char* rule = hc.content.c_str() + notspace;
// skip Unknown
if (const char* suggestion = fuzzyMatch(rule, kWarningNames + 1, LintWarning::Code__Count - 1))
emitWarning(context, LintWarning::Code_CommentDirective, hc.location,
"nolint directive refers to unknown lint rule '%s'; did you mean '%s'?", rule, suggestion);
else
emitWarning(
context, LintWarning::Code_CommentDirective, hc.location, "nolint directive refers to unknown lint rule '%s'", rule);
}
}
else if (first == "nocheck" || first == "nonstrict" || first == "strict")
{
if (space != std::string::npos)
emitWarning(context, LintWarning::Code_CommentDirective, hc.location,
"Comment directive with the type checking mode has extra symbols at the end of the line");
else if (seenMode)
emitWarning(context, LintWarning::Code_CommentDirective, hc.location,
"Comment directive with the type checking mode has already been used");
else
seenMode = true;
}
else
{
static const char* kHotComments[] = {
"nolint",
"nocheck",
"nonstrict",
"strict",
};
if (const char* suggestion = fuzzyMatch(first, kHotComments, std::size(kHotComments)))
emitWarning(context, LintWarning::Code_CommentDirective, hc.location, "Unknown comment directive '%.*s'; did you mean '%s'?",
int(first.size()), first.data(), suggestion);
else
emitWarning(context, LintWarning::Code_CommentDirective, hc.location, "Unknown comment directive '%.*s'", int(first.size()),
first.data());
}
}
}
}
void LintOptions::setDefaults() void LintOptions::setDefaults()
{ {
// By default, we enable all warnings // By default, we enable all warnings
warningMask = ~0ull; warningMask = ~0ull;
} }
std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module, const LintOptions& options) std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module,
const std::vector<HotComment>& hotcomments, const LintOptions& options)
{ {
LintContext context; LintContext context;
@ -2609,6 +2707,9 @@ std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const Sc
if (context.warningEnabled(LintWarning::Code_MisleadingAndOr)) if (context.warningEnabled(LintWarning::Code_MisleadingAndOr))
LintMisleadingAndOr::process(context); LintMisleadingAndOr::process(context);
if (context.warningEnabled(LintWarning::Code_CommentDirective))
lintComments(context, hotcomments);
std::sort(context.result.begin(), context.result.end(), WarningComparator()); std::sort(context.result.begin(), context.result.end(), WarningComparator());
return context.result; return context.result;
@ -2630,23 +2731,30 @@ LintWarning::Code LintWarning::parseName(const char* name)
return Code_Unknown; return Code_Unknown;
} }
uint64_t LintWarning::parseMask(const std::vector<std::string>& hotcomments) uint64_t LintWarning::parseMask(const std::vector<HotComment>& hotcomments)
{ {
uint64_t result = 0; uint64_t result = 0;
for (const std::string& hc : hotcomments) for (const HotComment& hc : hotcomments)
{ {
if (hc.compare(0, 6, "nolint") != 0) if (!hc.header)
continue; continue;
std::string::size_type name = hc.find_first_not_of(" \t", 6); if (hc.content.compare(0, 6, "nolint") != 0)
continue;
std::string::size_type name = hc.content.find_first_not_of(" \t", 6);
// --!nolint disables everything // --!nolint disables everything
if (name == std::string::npos) if (name == std::string::npos)
return ~0ull; return ~0ull;
// --!nolint needs to be followed by a whitespace character
if (name == 6)
continue;
// --!nolint name disables the specific lint // --!nolint name disables the specific lint
LintWarning::Code code = LintWarning::parseName(hc.c_str() + name); LintWarning::Code code = LintWarning::parseName(hc.content.c_str() + name);
if (code != LintWarning::Code_Unknown) if (code != LintWarning::Code_Unknown)
result |= 1ull << int(code); result |= 1ull << int(code);

View file

@ -9,14 +9,12 @@ namespace Luau
struct Quantifier struct Quantifier
{ {
ModulePtr module;
TypeLevel level; TypeLevel level;
std::vector<TypeId> generics; std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks; std::vector<TypePackId> genericPacks;
Quantifier(ModulePtr module, TypeLevel level) Quantifier(TypeLevel level)
: module(module) : level(level)
, level(level)
{ {
} }
@ -76,9 +74,9 @@ struct Quantifier
} }
}; };
void quantify(ModulePtr module, TypeId ty, TypeLevel level) void quantify(TypeId ty, TypeLevel level)
{ {
Quantifier q{std::move(module), level}; Quantifier q{level};
DenseHashSet<void*> seen{nullptr}; DenseHashSet<void*> seen{nullptr};
visitTypeVarOnce(ty, q, seen); visitTypeVarOnce(ty, q, seen);

View file

@ -226,27 +226,11 @@ TarjanResult Tarjan::loop()
return TarjanResult::Ok; return TarjanResult::Ok;
} }
void Tarjan::clear()
{
typeToIndex.clear();
indexToType.clear();
packToIndex.clear();
indexToPack.clear();
lowlink.clear();
stack.clear();
onStack.clear();
edgesTy.clear();
edgesTp.clear();
worklist.clear();
}
TarjanResult Tarjan::visitRoot(TypeId ty) TarjanResult Tarjan::visitRoot(TypeId ty)
{ {
childCount = 0; childCount = 0;
ty = log->follow(ty); ty = log->follow(ty);
clear();
auto [index, fresh] = indexify(ty); auto [index, fresh] = indexify(ty);
worklist.push_back({index, -1, -1}); worklist.push_back({index, -1, -1});
return loop(); return loop();
@ -257,7 +241,6 @@ TarjanResult Tarjan::visitRoot(TypePackId tp)
childCount = 0; childCount = 0;
tp = log->follow(tp); tp = log->follow(tp);
clear();
auto [index, fresh] = indexify(tp); auto [index, fresh] = indexify(tp);
worklist.push_back({index, -1, -1}); worklist.push_back({index, -1, -1});
return loop(); return loop();
@ -314,21 +297,17 @@ void FindDirty::visitSCC(int index)
TarjanResult FindDirty::findDirty(TypeId ty) TarjanResult FindDirty::findDirty(TypeId ty)
{ {
dirty.clear();
return visitRoot(ty); return visitRoot(ty);
} }
TarjanResult FindDirty::findDirty(TypePackId tp) TarjanResult FindDirty::findDirty(TypePackId tp)
{ {
dirty.clear();
return visitRoot(tp); return visitRoot(tp);
} }
std::optional<TypeId> Substitution::substitute(TypeId ty) std::optional<TypeId> Substitution::substitute(TypeId ty)
{ {
ty = log->follow(ty); ty = log->follow(ty);
newTypes.clear();
newPacks.clear();
auto result = findDirty(ty); auto result = findDirty(ty);
if (result != TarjanResult::Ok) if (result != TarjanResult::Ok)
@ -347,8 +326,6 @@ std::optional<TypeId> Substitution::substitute(TypeId ty)
std::optional<TypePackId> Substitution::substitute(TypePackId tp) std::optional<TypePackId> Substitution::substitute(TypePackId tp)
{ {
tp = log->follow(tp); tp = log->follow(tp);
newTypes.clear();
newPacks.clear();
auto result = findDirty(tp); auto result = findDirty(tp);
if (result != TarjanResult::Ok) if (result != TarjanResult::Ok)

View file

@ -26,9 +26,10 @@
* 3. Cyclic dependencies can be resolved by picking an arbitrary statement to check first. * 3. Cyclic dependencies can be resolved by picking an arbitrary statement to check first.
*/ */
#include "Luau/Parser.h" #include "Luau/Ast.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/StringUtils.h"
#include <algorithm> #include <algorithm>
#include <deque> #include <deque>

View file

@ -933,12 +933,12 @@ struct Printer
writer.symbol(")"); writer.symbol(")");
if (writeTypes && func.hasReturnAnnotation) if (writeTypes && func.returnAnnotation)
{ {
writer.symbol(":"); writer.symbol(":");
writer.space(); writer.space();
visualizeTypeList(func.returnAnnotation, false); visualizeTypeList(*func.returnAnnotation, false);
} }
visualizeBlock(*func.body); visualizeBlock(*func.body);
@ -989,9 +989,9 @@ struct Printer
advance(typeAnnotation.location.begin); advance(typeAnnotation.location.begin);
if (const auto& a = typeAnnotation.as<AstTypeReference>()) if (const auto& a = typeAnnotation.as<AstTypeReference>())
{ {
if (a->hasPrefix) if (a->prefix)
{ {
writer.write(a->prefix.value); writer.write(a->prefix->value);
writer.symbol("."); writer.symbol(".");
} }

View file

@ -3,7 +3,6 @@
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/Parser.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
@ -476,12 +475,11 @@ public:
visitLocal(arg); visitLocal(arg);
} }
if (!fn->hasReturnAnnotation) if (!fn->returnAnnotation)
{ {
if (auto result = getScope(fn->body->location)) if (auto result = getScope(fn->body->location))
{ {
TypePackId ret = result->returnType; TypePackId ret = result->returnType;
fn->hasReturnAnnotation = true;
AstTypePack* variadicAnnotation = nullptr; AstTypePack* variadicAnnotation = nullptr;
const auto& [v, tail] = flatten(ret); const auto& [v, tail] = flatten(ret);

View file

@ -3,7 +3,6 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/ModuleResolver.h" #include "Luau/ModuleResolver.h"
#include "Luau/Parser.h"
#include "Luau/Quantify.h" #include "Luau/Quantify.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
@ -24,16 +23,12 @@ LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000)
LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 500) LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false) LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false)
LUAU_FASTFLAGVARIABLE(LuauGroupExpectedType, false)
LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false. LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false.
LUAU_FASTFLAG(LuauUseCommittingTxnLog) LUAU_FASTFLAG(LuauUseCommittingTxnLog)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false) LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false) LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false)
LUAU_FASTFLAGVARIABLE(LuauIfElseBranchTypeUnion, false)
LUAU_FASTFLAGVARIABLE(LuauIfElseExpectedType2, false)
LUAU_FASTFLAGVARIABLE(LuauImmutableTypes, false) LUAU_FASTFLAGVARIABLE(LuauImmutableTypes, false)
LUAU_FASTFLAGVARIABLE(LuauLengthOnCompositeType, false)
LUAU_FASTFLAGVARIABLE(LuauNoSealedTypeMod, false) LUAU_FASTFLAGVARIABLE(LuauNoSealedTypeMod, false)
LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false) LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false)
LUAU_FASTFLAGVARIABLE(LuauSealExports, false) LUAU_FASTFLAGVARIABLE(LuauSealExports, false)
@ -43,7 +38,6 @@ LUAU_FASTFLAGVARIABLE(LuauTypeAliasDefaults, false)
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false) LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false) LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
LUAU_FASTFLAGVARIABLE(LuauPerModuleUnificationCache, false)
LUAU_FASTFLAGVARIABLE(LuauProperTypeLevels, false) LUAU_FASTFLAGVARIABLE(LuauProperTypeLevels, false)
LUAU_FASTFLAGVARIABLE(LuauAscribeCorrectLevelToInferredProperitesOfFreeTables, false) LUAU_FASTFLAGVARIABLE(LuauAscribeCorrectLevelToInferredProperitesOfFreeTables, false)
LUAU_FASTFLAG(LuauUnionTagMatchFix) LUAU_FASTFLAG(LuauUnionTagMatchFix)
@ -293,13 +287,10 @@ ModulePtr TypeChecker::check(const SourceModule& module, Mode mode, std::optiona
GenericError{"Free types leaked into this module's public interface. This is an internal Luau error; please report it."}}); GenericError{"Free types leaked into this module's public interface. This is an internal Luau error; please report it."}});
} }
if (FFlag::LuauPerModuleUnificationCache) // Clear unifier cache since it's keyed off internal types that get deallocated
{ // This avoids fake cross-module cache hits and keeps cache size at bay when typechecking large module graphs.
// Clear unifier cache since it's keyed off internal types that get deallocated unifierState.cachedUnify.clear();
// This avoids fake cross-module cache hits and keeps cache size at bay when typechecking large module graphs. unifierState.skipCacheForType.clear();
unifierState.cachedUnify.clear();
unifierState.skipCacheForType.clear();
}
if (FFlag::LuauTwoPassAliasDefinitionFix) if (FFlag::LuauTwoPassAliasDefinitionFix)
duplicateTypeAliases.clear(); duplicateTypeAliases.clear();
@ -509,7 +500,7 @@ LUAU_NOINLINE void TypeChecker::checkBlockTypeAliases(const ScopePtr& scope, std
{ {
if (const auto& typealias = stat->as<AstStatTypeAlias>()) if (const auto& typealias = stat->as<AstStatTypeAlias>())
{ {
if (FFlag::LuauTwoPassAliasDefinitionFix && typealias->name == Parser::errorName) if (FFlag::LuauTwoPassAliasDefinitionFix && typealias->name == kParseNameError)
continue; continue;
auto& bindings = typealias->exported ? scope->exportedTypeBindings : scope->privateTypeBindings; auto& bindings = typealias->exported ? scope->exportedTypeBindings : scope->privateTypeBindings;
@ -1193,7 +1184,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
Name name = typealias.name.value; Name name = typealias.name.value;
// If the alias is missing a name, we can't do anything with it. Ignore it. // If the alias is missing a name, we can't do anything with it. Ignore it.
if (FFlag::LuauTwoPassAliasDefinitionFix && name == Parser::errorName) if (FFlag::LuauTwoPassAliasDefinitionFix && name == kParseNameError)
return; return;
std::optional<TypeFun> binding; std::optional<TypeFun> binding;
@ -1222,7 +1213,8 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
if (FFlag::LuauProperTypeLevels) if (FFlag::LuauProperTypeLevels)
aliasScope->level.subLevel = subLevel; aliasScope->level.subLevel = subLevel;
auto [generics, genericPacks] = createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks, /* useCache = */ true); auto [generics, genericPacks] =
createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks, /* useCache = */ true);
TypeId ty = freshType(aliasScope); TypeId ty = freshType(aliasScope);
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty); FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
@ -1464,7 +1456,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExpr&
ExprResult<TypeId> result; ExprResult<TypeId> result;
if (auto a = expr.as<AstExprGroup>()) if (auto a = expr.as<AstExprGroup>())
result = checkExpr(scope, *a->expr, FFlag::LuauGroupExpectedType ? expectedType : std::nullopt); result = checkExpr(scope, *a->expr, expectedType);
else if (expr.is<AstExprConstantNil>()) else if (expr.is<AstExprConstantNil>())
result = {nilType}; result = {nilType};
else if (const AstExprConstantBool* bexpr = expr.as<AstExprConstantBool>()) else if (const AstExprConstantBool* bexpr = expr.as<AstExprConstantBool>())
@ -1508,7 +1500,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExpr&
else if (auto a = expr.as<AstExprError>()) else if (auto a = expr.as<AstExprError>())
result = checkExpr(scope, *a); result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprIfElse>()) else if (auto a = expr.as<AstExprIfElse>())
result = checkExpr(scope, *a, FFlag::LuauIfElseExpectedType2 ? expectedType : std::nullopt); result = checkExpr(scope, *a, expectedType);
else else
ice("Unhandled AstExpr?"); ice("Unhandled AstExpr?");
@ -2093,6 +2085,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprUn
return {numberType}; return {numberType};
} }
case AstExprUnary::Len: case AstExprUnary::Len:
{
tablify(operandType); tablify(operandType);
operandType = stripFromNilAndReport(operandType, expr.location); operandType = stripFromNilAndReport(operandType, expr.location);
@ -2100,30 +2093,13 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprUn
if (get<ErrorTypeVar>(operandType)) if (get<ErrorTypeVar>(operandType))
return {errorRecoveryType(scope)}; return {errorRecoveryType(scope)};
if (FFlag::LuauLengthOnCompositeType) DenseHashSet<TypeId> seen{nullptr};
{
DenseHashSet<TypeId> seen{nullptr};
if (!hasLength(operandType, seen, &recursionCount)) if (!hasLength(operandType, seen, &recursionCount))
reportError(TypeError{expr.location, NotATable{operandType}}); reportError(TypeError{expr.location, NotATable{operandType}});
}
else
{
if (get<AnyTypeVar>(operandType))
return {numberType}; // Not strictly correct: metatables permit overriding this
if (auto p = get<PrimitiveTypeVar>(operandType))
{
if (p->type == PrimitiveTypeVar::String)
return {numberType};
}
if (!getTableType(operandType))
reportError(TypeError{expr.location, NotATable{operandType}});
}
return {numberType}; return {numberType};
}
default: default:
ice("Unknown AstExprUnary " + std::to_string(int(expr.op))); ice("Unknown AstExprUnary " + std::to_string(int(expr.op)));
} }
@ -2618,22 +2594,11 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIf
resolve(result.predicates, falseScope, false); resolve(result.predicates, falseScope, false);
ExprResult<TypeId> falseType = checkExpr(falseScope, *expr.falseExpr, expectedType); ExprResult<TypeId> falseType = checkExpr(falseScope, *expr.falseExpr, expectedType);
if (FFlag::LuauIfElseBranchTypeUnion) if (falseType.type == trueType.type)
{
if (falseType.type == trueType.type)
return {trueType.type};
std::vector<TypeId> types = reduceUnion({trueType.type, falseType.type});
return {types.size() == 1 ? types[0] : addType(UnionTypeVar{std::move(types)})};
}
else
{
unify(falseType.type, trueType.type, expr.location);
// TODO: normalize(UnionTypeVar{{trueType, falseType}})
// For now both trueType and falseType must be the same type.
return {trueType.type}; return {trueType.type};
}
std::vector<TypeId> types = reduceUnion({trueType.type, falseType.type});
return {types.size() == 1 ? types[0] : addType(UnionTypeVar{std::move(types)})};
} }
TypeId TypeChecker::checkLValue(const ScopePtr& scope, const AstExpr& expr) TypeId TypeChecker::checkLValue(const ScopePtr& scope, const AstExpr& expr)
@ -2986,8 +2951,8 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(
auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks); auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks);
TypePackId retPack; TypePackId retPack;
if (expr.hasReturnAnnotation) if (expr.returnAnnotation)
retPack = resolveTypePack(funScope, expr.returnAnnotation); retPack = resolveTypePack(funScope, *expr.returnAnnotation);
else if (isNonstrictMode()) else if (isNonstrictMode())
retPack = anyTypePack; retPack = anyTypePack;
else if (expectedFunctionType) else if (expectedFunctionType)
@ -3181,7 +3146,7 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE
// If we're in nonstrict mode we want to only report this missing return // If we're in nonstrict mode we want to only report this missing return
// statement if there are type annotations on the function. In strict mode // statement if there are type annotations on the function. In strict mode
// we report it regardless. // we report it regardless.
if (!isNonstrictMode() || function.hasReturnAnnotation) if (!isNonstrictMode() || function.returnAnnotation)
{ {
reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retType}); reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retType});
} }
@ -4403,11 +4368,7 @@ TypeId Instantiation::clean(TypeId ty)
// Annoyingly, we have to do this even if there are no generics, // Annoyingly, we have to do this even if there are no generics,
// to replace any generic tables. // to replace any generic tables.
replaceGenerics.log = log; ReplaceGenerics replaceGenerics{log, arena, level, ftv->generics, ftv->genericPacks};
replaceGenerics.level = level;
replaceGenerics.currentModule = currentModule;
replaceGenerics.generics.assign(ftv->generics.begin(), ftv->generics.end());
replaceGenerics.genericPacks.assign(ftv->genericPacks.begin(), ftv->genericPacks.end());
// TODO: What to do if this returns nullopt? // TODO: What to do if this returns nullopt?
// We don't have access to the error-reporting machinery // We don't have access to the error-reporting machinery
@ -4573,16 +4534,11 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
if (FFlag::LuauQuantifyInPlace2) if (FFlag::LuauQuantifyInPlace2)
{ {
Luau::quantify(currentModule, ty, scope->level); Luau::quantify(ty, scope->level);
return ty; return ty;
} }
quantification.log = TxnLog::empty(); Quantification quantification{&currentModule->internalTypes, scope->level};
quantification.level = scope->level;
quantification.generics.clear();
quantification.genericPacks.clear();
quantification.currentModule = currentModule;
std::optional<TypeId> qty = quantification.substitute(ty); std::optional<TypeId> qty = quantification.substitute(ty);
if (!qty.has_value()) if (!qty.has_value())
@ -4596,18 +4552,14 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
FunctionTypeVar* qftv = getMutable<FunctionTypeVar>(*qty); FunctionTypeVar* qftv = getMutable<FunctionTypeVar>(*qty);
LUAU_ASSERT(qftv); LUAU_ASSERT(qftv);
qftv->generics = quantification.generics; qftv->generics = std::move(quantification.generics);
qftv->genericPacks = quantification.genericPacks; qftv->genericPacks = std::move(quantification.genericPacks);
return *qty; return *qty;
} }
TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location location, const TxnLog* log) TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location location, const TxnLog* log)
{ {
LUAU_ASSERT(log != nullptr); Instantiation instantiation{FFlag::LuauUseCommittingTxnLog ? log : TxnLog::empty(), &currentModule->internalTypes, scope->level};
instantiation.log = FFlag::LuauUseCommittingTxnLog ? log : TxnLog::empty();
instantiation.level = scope->level;
instantiation.currentModule = currentModule;
std::optional<TypeId> instantiated = instantiation.substitute(ty); std::optional<TypeId> instantiated = instantiation.substitute(ty);
if (instantiated.has_value()) if (instantiated.has_value())
return *instantiated; return *instantiated;
@ -4620,10 +4572,7 @@ TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location locat
TypeId TypeChecker::anyify(const ScopePtr& scope, TypeId ty, Location location) TypeId TypeChecker::anyify(const ScopePtr& scope, TypeId ty, Location location)
{ {
anyification.log = TxnLog::empty(); Anyification anyification{&currentModule->internalTypes, anyType, anyTypePack};
anyification.anyType = anyType;
anyification.anyTypePack = anyTypePack;
anyification.currentModule = currentModule;
std::optional<TypeId> any = anyification.substitute(ty); std::optional<TypeId> any = anyification.substitute(ty);
if (any.has_value()) if (any.has_value())
return *any; return *any;
@ -4636,10 +4585,7 @@ TypeId TypeChecker::anyify(const ScopePtr& scope, TypeId ty, Location location)
TypePackId TypeChecker::anyify(const ScopePtr& scope, TypePackId ty, Location location) TypePackId TypeChecker::anyify(const ScopePtr& scope, TypePackId ty, Location location)
{ {
anyification.log = TxnLog::empty(); Anyification anyification{&currentModule->internalTypes, anyType, anyTypePack};
anyification.anyType = anyType;
anyification.anyTypePack = anyTypePack;
anyification.currentModule = currentModule;
std::optional<TypePackId> any = anyification.substitute(ty); std::optional<TypePackId> any = anyification.substitute(ty);
if (any.has_value()) if (any.has_value())
return *any; return *any;
@ -4823,7 +4769,8 @@ TypePackId TypeChecker::errorRecoveryTypePack(TypePackId guess)
return getSingletonTypes().errorRecoveryTypePack(guess); return getSingletonTypes().errorRecoveryTypePack(guess);
} }
TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense) { TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense)
{
return [this, sense](TypeId ty) -> std::optional<TypeId> { return [this, sense](TypeId ty) -> std::optional<TypeId> {
// any/error/free gets a special pass unconditionally because they can't be decided. // any/error/free gets a special pass unconditionally because they can't be decided.
if (get<AnyTypeVar>(ty) || get<ErrorTypeVar>(ty) || get<FreeTypeVar>(ty)) if (get<AnyTypeVar>(ty) || get<ErrorTypeVar>(ty) || get<FreeTypeVar>(ty))
@ -4904,8 +4851,8 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
if (const auto& lit = annotation.as<AstTypeReference>()) if (const auto& lit = annotation.as<AstTypeReference>())
{ {
std::optional<TypeFun> tf; std::optional<TypeFun> tf;
if (lit->hasPrefix) if (lit->prefix)
tf = scope->lookupImportedType(lit->prefix.value, lit->name.value); tf = scope->lookupImportedType(lit->prefix->value, lit->name.value);
else if (FFlag::DebugLuauMagicTypes && lit->name == "_luau_ice") else if (FFlag::DebugLuauMagicTypes && lit->name == "_luau_ice")
ice("_luau_ice encountered", lit->location); ice("_luau_ice encountered", lit->location);
@ -4932,12 +4879,12 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
if (!tf) if (!tf)
{ {
if (lit->name == Parser::errorName) if (lit->name == kParseNameError)
return errorRecoveryType(scope); return errorRecoveryType(scope);
std::string typeName; std::string typeName;
if (lit->hasPrefix) if (lit->prefix)
typeName = std::string(lit->prefix.value) + "."; typeName = std::string(lit->prefix->value) + ".";
typeName += lit->name.value; typeName += lit->name.value;
if (scope->lookupPack(typeName)) if (scope->lookupPack(typeName))
@ -5038,12 +4985,7 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
if (notEnoughParameters && hasDefaultParameters) if (notEnoughParameters && hasDefaultParameters)
{ {
// 'applyTypeFunction' is used to substitute default types that reference previous generic types // 'applyTypeFunction' is used to substitute default types that reference previous generic types
applyTypeFunction.log = TxnLog::empty(); ApplyTypeFunction applyTypeFunction{&currentModule->internalTypes, scope->level};
applyTypeFunction.typeArguments.clear();
applyTypeFunction.typePackArguments.clear();
applyTypeFunction.currentModule = currentModule;
applyTypeFunction.level = scope->level;
applyTypeFunction.encounteredForwardedType = false;
for (size_t i = 0; i < typesProvided; ++i) for (size_t i = 0; i < typesProvided; ++i)
applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeParams[i]; applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeParams[i];
@ -5362,18 +5304,14 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
if (tf.typeParams.empty() && tf.typePackParams.empty()) if (tf.typeParams.empty() && tf.typePackParams.empty())
return tf.type; return tf.type;
applyTypeFunction.typeArguments.clear(); ApplyTypeFunction applyTypeFunction{&currentModule->internalTypes, scope->level};
for (size_t i = 0; i < tf.typeParams.size(); ++i) for (size_t i = 0; i < tf.typeParams.size(); ++i)
applyTypeFunction.typeArguments[tf.typeParams[i].ty] = typeParams[i]; applyTypeFunction.typeArguments[tf.typeParams[i].ty] = typeParams[i];
applyTypeFunction.typePackArguments.clear();
for (size_t i = 0; i < tf.typePackParams.size(); ++i) for (size_t i = 0; i < tf.typePackParams.size(); ++i)
applyTypeFunction.typePackArguments[tf.typePackParams[i].tp] = typePackParams[i]; applyTypeFunction.typePackArguments[tf.typePackParams[i].tp] = typePackParams[i];
applyTypeFunction.log = TxnLog::empty();
applyTypeFunction.currentModule = currentModule;
applyTypeFunction.level = scope->level;
applyTypeFunction.encounteredForwardedType = false;
std::optional<TypeId> maybeInstantiated = applyTypeFunction.substitute(tf.type); std::optional<TypeId> maybeInstantiated = applyTypeFunction.substitute(tf.type);
if (!maybeInstantiated.has_value()) if (!maybeInstantiated.has_value())
{ {

View file

@ -5,6 +5,8 @@
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
LUAU_FASTFLAGVARIABLE(LuauTerminateCyclicMetatableIndexLookup, false)
namespace Luau namespace Luau
{ {
@ -48,9 +50,19 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, const Sc
} }
std::optional<TypeId> mtIndex = findMetatableEntry(errors, globalScope, ty, "__index", location); std::optional<TypeId> mtIndex = findMetatableEntry(errors, globalScope, ty, "__index", location);
int count = 0;
while (mtIndex) while (mtIndex)
{ {
TypeId index = follow(*mtIndex); TypeId index = follow(*mtIndex);
if (FFlag::LuauTerminateCyclicMetatableIndexLookup)
{
if (count >= 100)
return std::nullopt;
++count;
}
if (const auto& itt = getTableType(index)) if (const auto& itt = getTableType(index))
{ {
const auto& fit = itt->props.find(name); const auto& fit = itt->props.find(name);

View file

@ -23,8 +23,6 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauLengthOnCompositeType)
LUAU_FASTFLAGVARIABLE(LuauMetatableAreEqualRecursion, false)
LUAU_FASTFLAGVARIABLE(LuauRefactorTypeVarQuestions, false) LUAU_FASTFLAGVARIABLE(LuauRefactorTypeVarQuestions, false)
LUAU_FASTFLAG(LuauErrorRecoveryType) LUAU_FASTFLAG(LuauErrorRecoveryType)
LUAU_FASTFLAG(LuauUnionTagMatchFix) LUAU_FASTFLAG(LuauUnionTagMatchFix)
@ -385,8 +383,6 @@ bool maybeSingleton(TypeId ty)
bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount) bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
{ {
LUAU_ASSERT(FFlag::LuauLengthOnCompositeType);
RecursionLimiter _rl(recursionCount, FInt::LuauTypeInferRecursionLimit); RecursionLimiter _rl(recursionCount, FInt::LuauTypeInferRecursionLimit);
ty = follow(ty); ty = follow(ty);
@ -555,7 +551,7 @@ bool areEqual(SeenSet& seen, const TableTypeVar& lhs, const TableTypeVar& rhs)
static bool areEqual(SeenSet& seen, const MetatableTypeVar& lhs, const MetatableTypeVar& rhs) static bool areEqual(SeenSet& seen, const MetatableTypeVar& lhs, const MetatableTypeVar& rhs)
{ {
if (FFlag::LuauMetatableAreEqualRecursion && areSeen(seen, &lhs, &rhs)) if (areSeen(seen, &lhs, &rhs))
return true; return true;
return areEqual(seen, *lhs.table, *rhs.table) && areEqual(seen, *lhs.metatable, *rhs.metatable); return areEqual(seen, *lhs.table, *rhs.table) && areEqual(seen, *lhs.metatable, *rhs.metatable);

View file

@ -7,6 +7,9 @@
#ifndef WIN32_LEAN_AND_MEAN #ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#endif #endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h> #include <Windows.h>
const size_t kPageSize = 4096; const size_t kPageSize = 4096;

View file

@ -14,7 +14,6 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit); LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit); LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
LUAU_FASTFLAGVARIABLE(LuauCommittingTxnLogFreeTpPromote, false)
LUAU_FASTFLAG(LuauImmutableTypes) LUAU_FASTFLAG(LuauImmutableTypes)
LUAU_FASTFLAG(LuauUseCommittingTxnLog) LUAU_FASTFLAG(LuauUseCommittingTxnLog)
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000); LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000);
@ -23,7 +22,6 @@ LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false)
LUAU_FASTFLAG(LuauSingletonTypes) LUAU_FASTFLAG(LuauSingletonTypes)
LUAU_FASTFLAG(LuauErrorRecoveryType); LUAU_FASTFLAG(LuauErrorRecoveryType);
LUAU_FASTFLAG(LuauProperTypeLevels); LUAU_FASTFLAG(LuauProperTypeLevels);
LUAU_FASTFLAGVARIABLE(LuauUnifyPackTails, false)
LUAU_FASTFLAGVARIABLE(LuauUnionTagMatchFix, false) LUAU_FASTFLAGVARIABLE(LuauUnionTagMatchFix, false)
LUAU_FASTFLAGVARIABLE(LuauFollowWithCommittingTxnLogInAnyUnification, false) LUAU_FASTFLAGVARIABLE(LuauFollowWithCommittingTxnLogInAnyUnification, false)
@ -116,7 +114,7 @@ struct PromoteTypeLevels
{ {
// Surprise, it's actually a BoundTypePack that hasn't been committed yet. // Surprise, it's actually a BoundTypePack that hasn't been committed yet.
// Calling getMutable on this will trigger an assertion. // Calling getMutable on this will trigger an assertion.
if (FFlag::LuauCommittingTxnLogFreeTpPromote && FFlag::LuauUseCommittingTxnLog && !log.is<FreeTypePack>(tp)) if (FFlag::LuauUseCommittingTxnLog && !log.is<FreeTypePack>(tp))
return true; return true;
promote(tp, FFlag::LuauUseCommittingTxnLog ? log.getMutable<FreeTypePack>(tp) : getMutable<FreeTypePack>(tp)); promote(tp, FFlag::LuauUseCommittingTxnLog ? log.getMutable<FreeTypePack>(tp) : getMutable<FreeTypePack>(tp));
@ -1242,7 +1240,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
// If both are at the end, we're done // If both are at the end, we're done
if (!superIter.good() && !subIter.good()) if (!superIter.good() && !subIter.good())
{ {
if (FFlag::LuauUnifyPackTails && subTpv->tail && superTpv->tail) if (subTpv->tail && superTpv->tail)
{ {
tryUnify_(*subTpv->tail, *superTpv->tail); tryUnify_(*subTpv->tail, *superTpv->tail);
break; break;
@ -1250,9 +1248,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
const bool lFreeTail = superTpv->tail && log.getMutable<FreeTypePack>(log.follow(*superTpv->tail)) != nullptr; const bool lFreeTail = superTpv->tail && log.getMutable<FreeTypePack>(log.follow(*superTpv->tail)) != nullptr;
const bool rFreeTail = subTpv->tail && log.getMutable<FreeTypePack>(log.follow(*subTpv->tail)) != nullptr; const bool rFreeTail = subTpv->tail && log.getMutable<FreeTypePack>(log.follow(*subTpv->tail)) != nullptr;
if (!FFlag::LuauUnifyPackTails && lFreeTail && rFreeTail) if (lFreeTail)
tryUnify_(*subTpv->tail, *superTpv->tail);
else if (lFreeTail)
tryUnify_(emptyTp, *superTpv->tail); tryUnify_(emptyTp, *superTpv->tail);
else if (rFreeTail) else if (rFreeTail)
tryUnify_(emptyTp, *subTpv->tail); tryUnify_(emptyTp, *subTpv->tail);
@ -1448,7 +1444,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
// If both are at the end, we're done // If both are at the end, we're done
if (!superIter.good() && !subIter.good()) if (!superIter.good() && !subIter.good())
{ {
if (FFlag::LuauUnifyPackTails && subTpv->tail && superTpv->tail) if (subTpv->tail && superTpv->tail)
{ {
tryUnify_(*subTpv->tail, *superTpv->tail); tryUnify_(*subTpv->tail, *superTpv->tail);
break; break;
@ -1456,9 +1452,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
const bool lFreeTail = superTpv->tail && get<FreeTypePack>(follow(*superTpv->tail)) != nullptr; const bool lFreeTail = superTpv->tail && get<FreeTypePack>(follow(*superTpv->tail)) != nullptr;
const bool rFreeTail = subTpv->tail && get<FreeTypePack>(follow(*subTpv->tail)) != nullptr; const bool rFreeTail = subTpv->tail && get<FreeTypePack>(follow(*subTpv->tail)) != nullptr;
if (!FFlag::LuauUnifyPackTails && lFreeTail && rFreeTail) if (lFreeTail)
tryUnify_(*subTpv->tail, *superTpv->tail);
else if (lFreeTail)
tryUnify_(emptyTp, *superTpv->tail); tryUnify_(emptyTp, *superTpv->tail);
else if (rFreeTail) else if (rFreeTail)
tryUnify_(emptyTp, *subTpv->tail); tryUnify_(emptyTp, *subTpv->tail);

View file

@ -594,8 +594,7 @@ public:
AstArray<AstGenericTypePack> genericPacks; AstArray<AstGenericTypePack> genericPacks;
AstLocal* self; AstLocal* self;
AstArray<AstLocal*> args; AstArray<AstLocal*> args;
bool hasReturnAnnotation; std::optional<AstTypeList> returnAnnotation;
AstTypeList returnAnnotation;
bool vararg = false; bool vararg = false;
Location varargLocation; Location varargLocation;
AstTypePack* varargAnnotation; AstTypePack* varargAnnotation;
@ -740,7 +739,7 @@ class AstStatIf : public AstStat
public: public:
LUAU_RTTI(AstStatIf) LUAU_RTTI(AstStatIf)
AstStatIf(const Location& location, AstExpr* condition, AstStatBlock* thenbody, AstStat* elsebody, bool hasThen, const Location& thenLocation, AstStatIf(const Location& location, AstExpr* condition, AstStatBlock* thenbody, AstStat* elsebody, const std::optional<Location>& thenLocation,
const std::optional<Location>& elseLocation, bool hasEnd); const std::optional<Location>& elseLocation, bool hasEnd);
void visit(AstVisitor* visitor) override; void visit(AstVisitor* visitor) override;
@ -749,12 +748,10 @@ public:
AstStatBlock* thenbody; AstStatBlock* thenbody;
AstStat* elsebody; AstStat* elsebody;
bool hasThen = false; std::optional<Location> thenLocation;
Location thenLocation;
// Active for 'elseif' as well // Active for 'elseif' as well
bool hasElse = false; std::optional<Location> elseLocation;
Location elseLocation;
bool hasEnd = false; bool hasEnd = false;
}; };
@ -849,8 +846,7 @@ public:
AstArray<AstLocal*> vars; AstArray<AstLocal*> vars;
AstArray<AstExpr*> values; AstArray<AstExpr*> values;
bool hasEqualsSign = false; std::optional<Location> equalsSignLocation;
Location equalsSignLocation;
}; };
class AstStatFor : public AstStat class AstStatFor : public AstStat
@ -1053,9 +1049,8 @@ public:
void visit(AstVisitor* visitor) override; void visit(AstVisitor* visitor) override;
bool hasPrefix;
bool hasParameterList; bool hasParameterList;
AstName prefix; std::optional<AstName> prefix;
AstName name; AstName name;
AstArray<AstTypeOrPack> parameters; AstArray<AstTypeOrPack> parameters;
}; };

View file

@ -233,4 +233,9 @@ private:
bool readNames; bool readNames;
}; };
inline bool isSpace(char ch)
{
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\v' || ch == '\f';
}
} // namespace Luau } // namespace Luau

View file

@ -0,0 +1,69 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Common.h"
#include "Luau/Location.h"
#include "Luau/Lexer.h"
#include "Luau/StringUtils.h"
namespace Luau
{
class AstStatBlock;
class ParseError : public std::exception
{
public:
ParseError(const Location& location, const std::string& message);
virtual const char* what() const throw();
const Location& getLocation() const;
const std::string& getMessage() const;
static LUAU_NORETURN void raise(const Location& location, const char* format, ...) LUAU_PRINTF_ATTR(2, 3);
private:
Location location;
std::string message;
};
class ParseErrors : public std::exception
{
public:
ParseErrors(std::vector<ParseError> errors);
virtual const char* what() const throw();
const std::vector<ParseError>& getErrors() const;
private:
std::vector<ParseError> errors;
std::string message;
};
struct HotComment
{
bool header;
Location location;
std::string content;
};
struct Comment
{
Lexeme::Type type; // Comment, BlockComment, or BrokenComment
Location location;
};
struct ParseResult
{
AstStatBlock* root;
std::vector<HotComment> hotcomments;
std::vector<ParseError> errors;
std::vector<Comment> commentLocations;
};
static constexpr const char* kParseNameError = "%error-id%";
} // namespace Luau

View file

@ -4,6 +4,7 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Lexer.h" #include "Luau/Lexer.h"
#include "Luau/ParseOptions.h" #include "Luau/ParseOptions.h"
#include "Luau/ParseResult.h"
#include "Luau/StringUtils.h" #include "Luau/StringUtils.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/Common.h" #include "Luau/Common.h"
@ -14,37 +15,6 @@
namespace Luau namespace Luau
{ {
class ParseError : public std::exception
{
public:
ParseError(const Location& location, const std::string& message);
virtual const char* what() const throw();
const Location& getLocation() const;
const std::string& getMessage() const;
static LUAU_NORETURN void raise(const Location& location, const char* format, ...) LUAU_PRINTF_ATTR(2, 3);
private:
Location location;
std::string message;
};
class ParseErrors : public std::exception
{
public:
ParseErrors(std::vector<ParseError> errors);
virtual const char* what() const throw();
const std::vector<ParseError>& getErrors() const;
private:
std::vector<ParseError> errors;
std::string message;
};
template<typename T> template<typename T>
class TempVector class TempVector
{ {
@ -80,34 +50,17 @@ private:
size_t size_; size_t size_;
}; };
struct Comment
{
Lexeme::Type type; // Comment, BlockComment, or BrokenComment
Location location;
};
struct ParseResult
{
AstStatBlock* root;
std::vector<std::string> hotcomments;
std::vector<ParseError> errors;
std::vector<Comment> commentLocations;
};
class Parser class Parser
{ {
public: public:
static ParseResult parse( static ParseResult parse(
const char* buffer, std::size_t bufferSize, AstNameTable& names, Allocator& allocator, ParseOptions options = ParseOptions()); const char* buffer, std::size_t bufferSize, AstNameTable& names, Allocator& allocator, ParseOptions options = ParseOptions());
static constexpr const char* errorName = "%error-id%";
private: private:
struct Name; struct Name;
struct Binding; struct Binding;
Parser(const char* buffer, std::size_t bufferSize, AstNameTable& names, Allocator& allocator); Parser(const char* buffer, std::size_t bufferSize, AstNameTable& names, Allocator& allocator, const ParseOptions& options);
bool blockFollow(const Lexeme& l); bool blockFollow(const Lexeme& l);
@ -330,7 +283,7 @@ private:
AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...) AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
LUAU_PRINTF_ATTR(5, 6); LUAU_PRINTF_ATTR(5, 6);
const Lexeme& nextLexeme(); void nextLexeme();
struct Function struct Function
{ {
@ -386,6 +339,9 @@ private:
Allocator& allocator; Allocator& allocator;
std::vector<Comment> commentLocations; std::vector<Comment> commentLocations;
std::vector<HotComment> hotcomments;
bool hotcommentHeader = true;
unsigned int recursionCounter; unsigned int recursionCounter;

View file

@ -167,8 +167,7 @@ AstExprFunction::AstExprFunction(const Location& location, const AstArray<AstGen
, genericPacks(genericPacks) , genericPacks(genericPacks)
, self(self) , self(self)
, args(args) , args(args)
, hasReturnAnnotation(returnAnnotation.has_value()) , returnAnnotation(returnAnnotation)
, returnAnnotation()
, vararg(vararg.has_value()) , vararg(vararg.has_value())
, varargLocation(vararg.value_or(Location())) , varargLocation(vararg.value_or(Location()))
, varargAnnotation(varargAnnotation) , varargAnnotation(varargAnnotation)
@ -178,8 +177,6 @@ AstExprFunction::AstExprFunction(const Location& location, const AstArray<AstGen
, hasEnd(hasEnd) , hasEnd(hasEnd)
, argLocation(argLocation) , argLocation(argLocation)
{ {
if (returnAnnotation.has_value())
this->returnAnnotation = *returnAnnotation;
} }
void AstExprFunction::visit(AstVisitor* visitor) void AstExprFunction::visit(AstVisitor* visitor)
@ -195,8 +192,8 @@ void AstExprFunction::visit(AstVisitor* visitor)
if (varargAnnotation) if (varargAnnotation)
varargAnnotation->visit(visitor); varargAnnotation->visit(visitor);
if (hasReturnAnnotation) if (returnAnnotation)
visitTypeList(visitor, returnAnnotation); visitTypeList(visitor, *returnAnnotation);
body->visit(visitor); body->visit(visitor);
} }
@ -375,21 +372,16 @@ void AstStatBlock::visit(AstVisitor* visitor)
} }
} }
AstStatIf::AstStatIf(const Location& location, AstExpr* condition, AstStatBlock* thenbody, AstStat* elsebody, bool hasThen, AstStatIf::AstStatIf(const Location& location, AstExpr* condition, AstStatBlock* thenbody, AstStat* elsebody,
const Location& thenLocation, const std::optional<Location>& elseLocation, bool hasEnd) const std::optional<Location>& thenLocation, const std::optional<Location>& elseLocation, bool hasEnd)
: AstStat(ClassIndex(), location) : AstStat(ClassIndex(), location)
, condition(condition) , condition(condition)
, thenbody(thenbody) , thenbody(thenbody)
, elsebody(elsebody) , elsebody(elsebody)
, hasThen(hasThen)
, thenLocation(thenLocation) , thenLocation(thenLocation)
, elseLocation(elseLocation)
, hasEnd(hasEnd) , hasEnd(hasEnd)
{ {
if (bool(elseLocation))
{
hasElse = true;
this->elseLocation = *elseLocation;
}
} }
void AstStatIf::visit(AstVisitor* visitor) void AstStatIf::visit(AstVisitor* visitor)
@ -492,12 +484,8 @@ AstStatLocal::AstStatLocal(
: AstStat(ClassIndex(), location) : AstStat(ClassIndex(), location)
, vars(vars) , vars(vars)
, values(values) , values(values)
, equalsSignLocation(equalsSignLocation)
{ {
if (bool(equalsSignLocation))
{
hasEqualsSign = true;
this->equalsSignLocation = *equalsSignLocation;
}
} }
void AstStatLocal::visit(AstVisitor* visitor) void AstStatLocal::visit(AstVisitor* visitor)
@ -750,9 +738,8 @@ void AstStatError::visit(AstVisitor* visitor)
AstTypeReference::AstTypeReference( AstTypeReference::AstTypeReference(
const Location& location, std::optional<AstName> prefix, AstName name, bool hasParameterList, const AstArray<AstTypeOrPack>& parameters) const Location& location, std::optional<AstName> prefix, AstName name, bool hasParameterList, const AstArray<AstTypeOrPack>& parameters)
: AstType(ClassIndex(), location) : AstType(ClassIndex(), location)
, hasPrefix(bool(prefix))
, hasParameterList(hasParameterList) , hasParameterList(hasParameterList)
, prefix(prefix ? *prefix : AstName()) , prefix(prefix)
, name(name) , name(name)
, parameters(parameters) , parameters(parameters)
{ {

View file

@ -101,11 +101,6 @@ Lexeme::Lexeme(const Location& location, Type type, const char* name)
LUAU_ASSERT(type == Name || (type >= Reserved_BEGIN && type < Lexeme::Reserved_END)); LUAU_ASSERT(type == Name || (type >= Reserved_BEGIN && type < Lexeme::Reserved_END));
} }
static bool isComment(const Lexeme& lexeme)
{
return lexeme.type == Lexeme::Comment || lexeme.type == Lexeme::BlockComment;
}
static const char* kReserved[] = {"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", static const char* kReserved[] = {"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or",
"repeat", "return", "then", "true", "until", "while"}; "repeat", "return", "then", "true", "until", "while"};
@ -282,11 +277,6 @@ AstName AstNameTable::get(const char* name) const
return getWithType(name, strlen(name)).first; return getWithType(name, strlen(name)).first;
} }
inline bool isSpace(char ch)
{
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\v' || ch == '\f';
}
inline bool isAlpha(char ch) inline bool isAlpha(char ch)
{ {
// use or trick to convert to lower case and unsigned comparison to do range check // use or trick to convert to lower case and unsigned comparison to do range check
@ -372,7 +362,7 @@ const Lexeme& Lexer::next(bool skipComments)
prevLocation = lexeme.location; prevLocation = lexeme.location;
lexeme = readNext(); lexeme = readNext();
} while (skipComments && isComment(lexeme)); } while (skipComments && (lexeme.type == Lexeme::Comment || lexeme.type == Lexeme::BlockComment));
return lexeme; return lexeme;
} }

View file

@ -13,18 +13,15 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false) LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauParseTypeAliasDefaults, false) LUAU_FASTFLAGVARIABLE(LuauParseTypeAliasDefaults, false)
LUAU_FASTFLAGVARIABLE(LuauParseRecoverTypePackEllipsis, false) LUAU_FASTFLAGVARIABLE(LuauParseRecoverTypePackEllipsis, false)
LUAU_FASTFLAGVARIABLE(LuauStartingBrokenComment, false) LUAU_FASTFLAGVARIABLE(LuauParseAllHotComments, false)
LUAU_FASTFLAGVARIABLE(LuauTableFieldFunctionDebugname, false)
namespace Luau namespace Luau
{ {
inline bool isSpace(char ch)
{
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\v' || ch == '\f';
}
static bool isComment(const Lexeme& lexeme) static bool isComment(const Lexeme& lexeme)
{ {
LUAU_ASSERT(!FFlag::LuauParseAllHotComments);
return lexeme.type == Lexeme::Comment || lexeme.type == Lexeme::BlockComment; return lexeme.type == Lexeme::Comment || lexeme.type == Lexeme::BlockComment;
} }
@ -151,31 +148,37 @@ ParseResult Parser::parse(const char* buffer, size_t bufferSize, AstNameTable& n
{ {
LUAU_TIMETRACE_SCOPE("Parser::parse", "Parser"); LUAU_TIMETRACE_SCOPE("Parser::parse", "Parser");
Parser p(buffer, bufferSize, names, allocator); Parser p(buffer, bufferSize, names, allocator, FFlag::LuauParseAllHotComments ? options : ParseOptions());
try try
{ {
std::vector<std::string> hotcomments; if (FFlag::LuauParseAllHotComments)
while (isComment(p.lexer.current()) || p.lexer.current().type == Lexeme::BrokenComment)
{ {
const char* text = p.lexer.current().data; AstStatBlock* root = p.parseChunk();
unsigned int length = p.lexer.current().length;
if (length && text[0] == '!') return ParseResult{root, std::move(p.hotcomments), std::move(p.parseErrors), std::move(p.commentLocations)};
}
else
{
std::vector<HotComment> hotcomments;
while (isComment(p.lexer.current()) || p.lexer.current().type == Lexeme::BrokenComment)
{ {
unsigned int end = length; const char* text = p.lexer.current().data;
while (end > 0 && isSpace(text[end - 1])) unsigned int length = p.lexer.current().length;
--end;
hotcomments.push_back(std::string(text + 1, text + end)); if (length && text[0] == '!')
} {
unsigned int end = length;
while (end > 0 && isSpace(text[end - 1]))
--end;
const Lexeme::Type type = p.lexer.current().type; hotcomments.push_back({true, p.lexer.current().location, std::string(text + 1, text + end)});
const Location loc = p.lexer.current().location; }
const Lexeme::Type type = p.lexer.current().type;
const Location loc = p.lexer.current().location;
if (FFlag::LuauStartingBrokenComment)
{
if (options.captureComments) if (options.captureComments)
p.commentLocations.push_back(Comment{type, loc}); p.commentLocations.push_back(Comment{type, loc});
@ -184,22 +187,15 @@ ParseResult Parser::parse(const char* buffer, size_t bufferSize, AstNameTable& n
p.lexer.next(); p.lexer.next();
} }
else
{
p.lexer.next();
if (options.captureComments) p.lexer.setSkipComments(true);
p.commentLocations.push_back(Comment{type, loc});
} p.options = options;
AstStatBlock* root = p.parseChunk();
return ParseResult{root, hotcomments, p.parseErrors, std::move(p.commentLocations)};
} }
p.lexer.setSkipComments(true);
p.options = options;
AstStatBlock* root = p.parseChunk();
return ParseResult{root, hotcomments, p.parseErrors, std::move(p.commentLocations)};
} }
catch (ParseError& err) catch (ParseError& err)
{ {
@ -210,8 +206,9 @@ ParseResult Parser::parse(const char* buffer, size_t bufferSize, AstNameTable& n
} }
} }
Parser::Parser(const char* buffer, size_t bufferSize, AstNameTable& names, Allocator& allocator) Parser::Parser(const char* buffer, size_t bufferSize, AstNameTable& names, Allocator& allocator, const ParseOptions& options)
: lexer(buffer, bufferSize, names) : options(options)
, lexer(buffer, bufferSize, names)
, allocator(allocator) , allocator(allocator)
, recursionCounter(0) , recursionCounter(0)
, endMismatchSuspect(Location(), Lexeme::Eof) , endMismatchSuspect(Location(), Lexeme::Eof)
@ -224,14 +221,20 @@ Parser::Parser(const char* buffer, size_t bufferSize, AstNameTable& names, Alloc
nameSelf = names.addStatic("self"); nameSelf = names.addStatic("self");
nameNumber = names.addStatic("number"); nameNumber = names.addStatic("number");
nameError = names.addStatic(errorName); nameError = names.addStatic(kParseNameError);
nameNil = names.getOrAdd("nil"); // nil is a reserved keyword nameNil = names.getOrAdd("nil"); // nil is a reserved keyword
matchRecoveryStopOnToken.assign(Lexeme::Type::Reserved_END, 0); matchRecoveryStopOnToken.assign(Lexeme::Type::Reserved_END, 0);
matchRecoveryStopOnToken[Lexeme::Type::Eof] = 1; matchRecoveryStopOnToken[Lexeme::Type::Eof] = 1;
if (FFlag::LuauParseAllHotComments)
lexer.setSkipComments(true);
// read first lexeme // read first lexeme
nextLexeme(); nextLexeme();
// all hot comments parsed after the first non-comment lexeme are special in that they don't affect type checking / linting mode
hotcommentHeader = false;
} }
bool Parser::blockFollow(const Lexeme& l) bool Parser::blockFollow(const Lexeme& l)
@ -396,7 +399,9 @@ AstStat* Parser::parseIf()
AstExpr* cond = parseExpr(); AstExpr* cond = parseExpr();
Lexeme matchThen = lexer.current(); Lexeme matchThen = lexer.current();
bool hasThen = expectAndConsume(Lexeme::ReservedThen, "if statement"); std::optional<Location> thenLocation;
if (expectAndConsume(Lexeme::ReservedThen, "if statement"))
thenLocation = matchThen.location;
AstStatBlock* thenbody = parseBlock(); AstStatBlock* thenbody = parseBlock();
@ -434,7 +439,7 @@ AstStat* Parser::parseIf()
hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchThenElse); hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchThenElse);
} }
return allocator.alloc<AstStatIf>(Location(start, end), cond, thenbody, elsebody, hasThen, matchThen.location, elseLocation, hasEnd); return allocator.alloc<AstStatIf>(Location(start, end), cond, thenbody, elsebody, thenLocation, elseLocation, hasEnd);
} }
// while exp do block end // while exp do block end
@ -769,7 +774,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
{ {
// note: `type` token is already parsed for us, so we just need to parse the rest // note: `type` token is already parsed for us, so we just need to parse the rest
auto name = parseNameOpt("type name"); std::optional<Name> name = parseNameOpt("type name");
// Use error name if the name is missing // Use error name if the name is missing
if (!name) if (!name)
@ -925,7 +930,7 @@ AstStat* Parser::parseDeclaration(const Location& start)
return allocator.alloc<AstStatDeclareClass>(Location(classStart, classEnd), className.name, superName, copy(props)); return allocator.alloc<AstStatDeclareClass>(Location(classStart, classEnd), className.name, superName, copy(props));
} }
else if (auto globalName = parseNameOpt("global variable name")) else if (std::optional<Name> globalName = parseNameOpt("global variable name"))
{ {
expectAndConsume(':', "global variable declaration"); expectAndConsume(':', "global variable declaration");
@ -1066,7 +1071,7 @@ void Parser::parseExprList(TempVector<AstExpr*>& result)
Parser::Binding Parser::parseBinding() Parser::Binding Parser::parseBinding()
{ {
auto name = parseNameOpt("variable name"); std::optional<Name> name = parseNameOpt("variable name");
// Use placeholder if the name is missing // Use placeholder if the name is missing
if (!name) if (!name)
@ -1325,7 +1330,7 @@ AstType* Parser::parseTableTypeAnnotation()
} }
else else
{ {
auto name = parseNameOpt("table field"); std::optional<Name> name = parseNameOpt("table field");
if (!name) if (!name)
break; break;
@ -1422,7 +1427,7 @@ AstType* Parser::parseFunctionTypeAnnotationTail(const Lexeme& begin, AstArray<A
expectAndConsume(Lexeme::SkinnyArrow, "function type"); expectAndConsume(Lexeme::SkinnyArrow, "function type");
} }
const auto [endLocation, returnTypeList] = parseReturnTypeAnnotation(); auto [endLocation, returnTypeList] = parseReturnTypeAnnotation();
AstTypeList paramTypes = AstTypeList{params, varargAnnotation}; AstTypeList paramTypes = AstTypeList{params, varargAnnotation};
return allocator.alloc<AstTypeFunction>(Location(begin.location, endLocation), generics, genericPacks, paramTypes, paramNames, returnTypeList); return allocator.alloc<AstTypeFunction>(Location(begin.location, endLocation), generics, genericPacks, paramTypes, paramNames, returnTypeList);
@ -1869,7 +1874,7 @@ AstExpr* Parser::parseExpr(unsigned int limit)
// NAME // NAME
AstExpr* Parser::parseNameExpr(const char* context) AstExpr* Parser::parseNameExpr(const char* context)
{ {
auto name = parseNameOpt(context); std::optional<Name> name = parseNameOpt(context);
if (!name) if (!name)
return allocator.alloc<AstExprError>(lexer.current().location, copy<AstExpr*>({}), unsigned(parseErrors.size() - 1)); return allocator.alloc<AstExprError>(lexer.current().location, copy<AstExpr*>({}), unsigned(parseErrors.size() - 1));
@ -2233,6 +2238,12 @@ AstExpr* Parser::parseTableConstructor()
AstExpr* key = allocator.alloc<AstExprConstantString>(name.location, nameString); AstExpr* key = allocator.alloc<AstExprConstantString>(name.location, nameString);
AstExpr* value = parseExpr(); AstExpr* value = parseExpr();
if (FFlag::LuauTableFieldFunctionDebugname)
{
if (AstExprFunction* func = value->as<AstExprFunction>())
func->debugname = name.name;
}
items.push_back({AstExprTable::Item::Record, key, value}); items.push_back({AstExprTable::Item::Record, key, value});
} }
else else
@ -2313,7 +2324,7 @@ std::optional<Parser::Name> Parser::parseNameOpt(const char* context)
Parser::Name Parser::parseName(const char* context) Parser::Name Parser::parseName(const char* context)
{ {
if (auto name = parseNameOpt(context)) if (std::optional<Name> name = parseNameOpt(context))
return *name; return *name;
Location location = lexer.current().location; Location location = lexer.current().location;
@ -2324,7 +2335,7 @@ Parser::Name Parser::parseName(const char* context)
Parser::Name Parser::parseIndexName(const char* context, const Position& previous) Parser::Name Parser::parseIndexName(const char* context, const Position& previous)
{ {
if (auto name = parseNameOpt(context)) if (std::optional<Name> name = parseNameOpt(context))
return *name; return *name;
// If we have a reserved keyword next at the same line, assume it's an incomplete name // If we have a reserved keyword next at the same line, assume it's an incomplete name
@ -2379,7 +2390,7 @@ std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> Parser::parseG
if (shouldParseTypePackAnnotation(lexer)) if (shouldParseTypePackAnnotation(lexer))
{ {
auto typePack = parseTypePackAnnotation(); AstTypePack* typePack = parseTypePackAnnotation();
namePacks.push_back({name, nameLocation, typePack}); namePacks.push_back({name, nameLocation, typePack});
} }
@ -2451,7 +2462,7 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams()
{ {
if (shouldParseTypePackAnnotation(lexer)) if (shouldParseTypePackAnnotation(lexer))
{ {
auto typePack = parseTypePackAnnotation(); AstTypePack* typePack = parseTypePackAnnotation();
parameters.push_back({{}, typePack}); parameters.push_back({{}, typePack});
} }
@ -2821,25 +2832,57 @@ AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const
return allocator.alloc<AstTypeError>(location, types, isMissing, unsigned(parseErrors.size() - 1)); return allocator.alloc<AstTypeError>(location, types, isMissing, unsigned(parseErrors.size() - 1));
} }
const Lexeme& Parser::nextLexeme() void Parser::nextLexeme()
{ {
if (options.captureComments) if (options.captureComments)
{ {
while (true) if (FFlag::LuauParseAllHotComments)
{ {
const Lexeme& lexeme = lexer.next(/*skipComments*/ false); Lexeme::Type type = lexer.next(/* skipComments= */ false).type;
// Subtlety: Broken comments are weird because we record them as comments AND pass them to the parser as a lexeme.
// The parser will turn this into a proper syntax error. while (type == Lexeme::BrokenComment || type == Lexeme::Comment || type == Lexeme::BlockComment)
if (lexeme.type == Lexeme::BrokenComment) {
const Lexeme& lexeme = lexer.current();
commentLocations.push_back(Comment{lexeme.type, lexeme.location}); commentLocations.push_back(Comment{lexeme.type, lexeme.location});
if (isComment(lexeme))
commentLocations.push_back(Comment{lexeme.type, lexeme.location}); // Subtlety: Broken comments are weird because we record them as comments AND pass them to the parser as a lexeme.
else // The parser will turn this into a proper syntax error.
return lexeme; if (lexeme.type == Lexeme::BrokenComment)
return;
// Comments starting with ! are called "hot comments" and contain directives for type checking / linting
if (lexeme.type == Lexeme::Comment && lexeme.length && lexeme.data[0] == '!')
{
const char* text = lexeme.data;
unsigned int end = lexeme.length;
while (end > 0 && isSpace(text[end - 1]))
--end;
hotcomments.push_back({hotcommentHeader, lexeme.location, std::string(text + 1, text + end)});
}
type = lexer.next(/* skipComments= */ false).type;
}
}
else
{
while (true)
{
const Lexeme& lexeme = lexer.next(/*skipComments*/ false);
// Subtlety: Broken comments are weird because we record them as comments AND pass them to the parser as a lexeme.
// The parser will turn this into a proper syntax error.
if (lexeme.type == Lexeme::BrokenComment)
commentLocations.push_back(Comment{lexeme.type, lexeme.location});
if (isComment(lexeme))
commentLocations.push_back(Comment{lexeme.type, lexeme.location});
else
return;
}
} }
} }
else else
return lexer.next(); lexer.next();
} }
} // namespace Luau } // namespace Luau

View file

@ -9,6 +9,12 @@
#include <stdlib.h> #include <stdlib.h>
#ifdef _WIN32 #ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h> #include <Windows.h>
#endif #endif

View file

@ -4,8 +4,13 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#ifdef _WIN32 #ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <windows.h> #endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h>
#else #else
#include <dirent.h> #include <dirent.h>
#include <fcntl.h> #include <fcntl.h>

View file

@ -37,6 +37,8 @@ enum class CompileFormat
Binary Binary
}; };
constexpr int MaxTraversalLimit = 50;
struct GlobalOptions struct GlobalOptions
{ {
int optimizationLevel = 1; int optimizationLevel = 1;
@ -243,10 +245,115 @@ std::string runCode(lua_State* L, const std::string& source)
return std::string(); return std::string();
} }
// Replaces the top of the lua stack with the metatable __index for the value
// if it exists. Returns true iff __index exists.
static bool tryReplaceTopWithIndex(lua_State* L)
{
if (luaL_getmetafield(L, -1, "__index"))
{
// Remove the table leaving __index on the top of stack
lua_remove(L, -2);
return true;
}
return false;
}
// This function is similar to lua_gettable, but it avoids calling any
// lua callback functions (e.g. __index) which might modify the Lua VM state.
static void safeGetTable(lua_State* L, int tableIndex)
{
lua_pushvalue(L, tableIndex); // Duplicate the table
// The loop invariant is that the table to search is at -1
// and the key is at -2.
for (int loopCount = 0;; loopCount++)
{
lua_pushvalue(L, -2); // Duplicate the key
lua_rawget(L, -2); // Try to find the key
if (!lua_isnil(L, -1) || loopCount >= MaxTraversalLimit)
{
// Either the key has been found, and/or we have reached the max traversal limit
break;
}
else
{
lua_pop(L, 1); // Pop the nil result
if (!luaL_getmetafield(L, -1, "__index"))
{
lua_pushnil(L);
break;
}
else if (lua_istable(L, -1))
{
// Replace the current table being searched with __index table
lua_replace(L, -2);
}
else
{
lua_pop(L, 1); // Pop the value
lua_pushnil(L);
break;
}
}
}
lua_remove(L, -2); // Remove the table
lua_remove(L, -2); // Remove the original key
}
// completePartialMatches finds keys that match the specified 'prefix'
// Note: the table/object to be searched must be on the top of the Lua stack
static void completePartialMatches(lua_State* L, bool completeOnlyFunctions, const std::string& editBuffer, std::string_view prefix,
const AddCompletionCallback& addCompletionCallback)
{
for (int i = 0; i < MaxTraversalLimit && lua_istable(L, -1); i++)
{
// table, key
lua_pushnil(L);
// Loop over all the keys in the current table
while (lua_next(L, -2) != 0)
{
if (lua_type(L, -2) == LUA_TSTRING)
{
// table, key, value
std::string_view key = lua_tostring(L, -2);
int valueType = lua_type(L, -1);
// If the last separator was a ':' (i.e. a method call) then only functions should be completed.
bool requiredValueType = (!completeOnlyFunctions || valueType == LUA_TFUNCTION);
if (!key.empty() && requiredValueType && Luau::startsWith(key, prefix))
{
std::string completedComponent(key.substr(prefix.size()));
std::string completion(editBuffer + completedComponent);
if (valueType == LUA_TFUNCTION)
{
// Add an opening paren for function calls by default.
completion += "(";
}
addCompletionCallback(completion, std::string(key));
}
}
lua_pop(L, 1);
}
// Replace the current table being searched with an __index table if one exists
if (!tryReplaceTopWithIndex(L))
{
break;
}
}
}
static void completeIndexer(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback) static void completeIndexer(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback)
{ {
std::string_view lookup = editBuffer; std::string_view lookup = editBuffer;
char lastSep = 0; bool completeOnlyFunctions = false;
// Push the global variable table to begin the search
lua_pushvalue(L, LUA_GLOBALSINDEX);
for (;;) for (;;)
{ {
@ -255,60 +362,26 @@ static void completeIndexer(lua_State* L, const std::string& editBuffer, const A
if (sep == std::string_view::npos) if (sep == std::string_view::npos)
{ {
// table, key completePartialMatches(L, completeOnlyFunctions, editBuffer, prefix, addCompletionCallback);
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);
int valueType = lua_type(L, -1);
// If the last separator was a ':' (i.e. a method call) then only functions should be completed.
bool requiredValueType = (lastSep != ':' || valueType == LUA_TFUNCTION);
if (!key.empty() && requiredValueType && Luau::startsWith(key, prefix))
{
std::string completedComponent(key.substr(prefix.size()));
std::string completion(editBuffer + completedComponent);
if (valueType == LUA_TFUNCTION)
{
// Add an opening paren for function calls by default.
completion += "(";
}
addCompletionCallback(completion, std::string(key));
}
}
lua_pop(L, 1);
}
break; break;
} }
else else
{ {
// find the key in the table // find the key in the table
lua_pushlstring(L, prefix.data(), prefix.size()); lua_pushlstring(L, prefix.data(), prefix.size());
lua_rawget(L, -2); safeGetTable(L, -2);
lua_remove(L, -2); lua_remove(L, -2);
if (lua_type(L, -1) == LUA_TSTRING) if (lua_istable(L, -1) || tryReplaceTopWithIndex(L))
{ {
// Replace the string object with the string class to perform further lookups of string functions completeOnlyFunctions = lookup[sep] == ':';
// Note: We retrieve the string class from _G to prevent issues if the user assigns to `string`. lookup.remove_prefix(sep + 1);
lua_pop(L, 1); // Pop the string instance
lua_getglobal(L, "_G");
lua_pushlstring(L, "string", 6);
lua_rawget(L, -2);
lua_remove(L, -2); // Remove the global table
LUAU_ASSERT(lua_istable(L, -1));
} }
else if (!lua_istable(L, -1)) else
{
// Unable to search for keys, so stop searching
break; break;
}
lastSep = lookup[sep];
lookup.remove_prefix(sep + 1);
} }
} }
@ -317,12 +390,6 @@ static void completeIndexer(lua_State* L, const std::string& editBuffer, const A
void getCompletions(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback) void getCompletions(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback)
{ {
// look the value up in current global table first
lua_pushvalue(L, LUA_GLOBALSINDEX);
completeIndexer(L, editBuffer, addCompletionCallback);
// and in actual global table after that
lua_getglobal(L, "_G");
completeIndexer(L, editBuffer, addCompletionCallback); completeIndexer(L, editBuffer, addCompletionCallback);
} }
@ -365,9 +432,7 @@ struct LinenoiseScopedHistory
ic_set_history(historyFilepath.c_str(), -1 /* default entries (= 200) */); ic_set_history(historyFilepath.c_str(), -1 /* default entries (= 200) */);
} }
~LinenoiseScopedHistory() ~LinenoiseScopedHistory() {}
{
}
std::string historyFilepath; std::string historyFilepath;
}; };

View file

@ -104,6 +104,20 @@ if (MSVC AND MSVC_VERSION GREATER_EQUAL 1924)
set_source_files_properties(VM/src/lvmexecute.cpp PROPERTIES COMPILE_FLAGS /d2ssa-pre-) set_source_files_properties(VM/src/lvmexecute.cpp PROPERTIES COMPILE_FLAGS /d2ssa-pre-)
endif() endif()
# embed .natvis inside the library debug information
if(MSVC)
target_link_options(Luau.Ast INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/Ast.natvis)
target_link_options(Luau.Analysis INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/Analysis.natvis)
target_link_options(Luau.VM INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/VM.natvis)
endif()
# make .natvis visible inside the solution
if(MSVC_IDE)
target_sources(Luau.Ast PRIVATE tools/natvis/Ast.natvis)
target_sources(Luau.Analysis PRIVATE tools/natvis/Analysis.natvis)
target_sources(Luau.VM PRIVATE tools/natvis/VM.natvis)
endif()
if(LUAU_BUILD_CLI) if(LUAU_BUILD_CLI)
target_compile_options(Luau.Repl.CLI PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.Repl.CLI PRIVATE ${LUAU_OPTIONS})
target_compile_options(Luau.Analyze.CLI PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.Analyze.CLI PRIVATE ${LUAU_OPTIONS})

View file

@ -8,6 +8,7 @@ target_sources(Luau.Ast PRIVATE
Ast/include/Luau/Location.h Ast/include/Luau/Location.h
Ast/include/Luau/ParseOptions.h Ast/include/Luau/ParseOptions.h
Ast/include/Luau/Parser.h Ast/include/Luau/Parser.h
Ast/include/Luau/ParseResult.h
Ast/include/Luau/StringUtils.h Ast/include/Luau/StringUtils.h
Ast/include/Luau/TimeTrace.h Ast/include/Luau/TimeTrace.h

View file

@ -36,12 +36,9 @@ const char* luau_ident = "$Luau: Copyright (C) 2019-2022 Roblox Corporation $\n"
static Table* getcurrenv(lua_State* L) static Table* getcurrenv(lua_State* L)
{ {
if (L->ci == L->base_ci) /* no enclosing function? */ if (L->ci == L->base_ci) /* no enclosing function? */
return hvalue(gt(L)); /* use global table as environment */ return L->gt; /* use global table as environment */
else else
{ return curr_func(L)->env;
Closure* func = curr_func(L);
return func->env;
}
} }
static LUAU_NOINLINE TValue* pseudo2addr(lua_State* L, int idx) static LUAU_NOINLINE TValue* pseudo2addr(lua_State* L, int idx)
@ -53,11 +50,14 @@ static LUAU_NOINLINE TValue* pseudo2addr(lua_State* L, int idx)
return registry(L); return registry(L);
case LUA_ENVIRONINDEX: case LUA_ENVIRONINDEX:
{ {
sethvalue(L, &L->env, getcurrenv(L)); sethvalue(L, &L->global->pseudotemp, getcurrenv(L));
return &L->env; return &L->global->pseudotemp;
} }
case LUA_GLOBALSINDEX: case LUA_GLOBALSINDEX:
return gt(L); {
sethvalue(L, &L->global->pseudotemp, L->gt);
return &L->global->pseudotemp;
}
default: default:
{ {
Closure* func = curr_func(L); Closure* func = curr_func(L);
@ -237,6 +237,11 @@ void lua_replace(lua_State* L, int idx)
func->env = hvalue(L->top - 1); func->env = hvalue(L->top - 1);
luaC_barrier(L, func, L->top - 1); luaC_barrier(L, func, L->top - 1);
} }
else if (idx == LUA_GLOBALSINDEX)
{
api_check(L, ttistable(L->top - 1));
L->gt = hvalue(L->top - 1);
}
else else
{ {
setobj(L, o, L->top - 1); setobj(L, o, L->top - 1);
@ -783,7 +788,7 @@ void lua_getfenv(lua_State* L, int idx)
sethvalue(L, L->top, clvalue(o)->env); sethvalue(L, L->top, clvalue(o)->env);
break; break;
case LUA_TTHREAD: case LUA_TTHREAD:
setobj2s(L, L->top, gt(thvalue(o))); sethvalue(L, L->top, thvalue(o)->gt);
break; break;
default: default:
setnilvalue(L->top); setnilvalue(L->top);
@ -914,7 +919,7 @@ int lua_setfenv(lua_State* L, int idx)
clvalue(o)->env = hvalue(L->top - 1); clvalue(o)->env = hvalue(L->top - 1);
break; break;
case LUA_TTHREAD: case LUA_TTHREAD:
sethvalue(L, gt(thvalue(o)), hvalue(L->top - 1)); thvalue(o)->gt = hvalue(L->top - 1);
break; break;
default: default:
res = 0; res = 0;

View file

@ -419,7 +419,7 @@ static void getcoverage(Proto* p, int depth, int* buffer, size_t size, void* con
} }
const char* debugname = p->debugname ? getstr(p->debugname) : NULL; const char* debugname = p->debugname ? getstr(p->debugname) : NULL;
int linedefined = luaG_getline(p, 0); int linedefined = getlinedefined(p);
callback(context, debugname, linedefined, depth, buffer, size); callback(context, debugname, linedefined, depth, buffer, size);

View file

@ -17,6 +17,8 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAG(LuauReduceStackReallocs)
/* /*
** {====================================================== ** {======================================================
** Error-recovery functions ** Error-recovery functions
@ -164,13 +166,14 @@ static void correctstack(lua_State* L, TValue* oldstack)
void luaD_reallocstack(lua_State* L, int newsize) void luaD_reallocstack(lua_State* L, int newsize)
{ {
TValue* oldstack = L->stack; TValue* oldstack = L->stack;
int realsize = newsize + 1 + EXTRA_STACK; int realsize = newsize + (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK);
LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1); LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK));
luaM_reallocarray(L, L->stack, L->stacksize, realsize, TValue, L->memcat); luaM_reallocarray(L, L->stack, L->stacksize, realsize, TValue, L->memcat);
TValue* newstack = L->stack;
for (int i = L->stacksize; i < realsize; i++) for (int i = L->stacksize; i < realsize; i++)
setnilvalue(L->stack + i); /* erase new segment */ setnilvalue(newstack + i); /* erase new segment */
L->stacksize = realsize; L->stacksize = realsize;
L->stack_last = L->stack + newsize; L->stack_last = newstack + newsize;
correctstack(L, oldstack); correctstack(L, oldstack);
} }
@ -512,7 +515,7 @@ static void callerrfunc(lua_State* L, void* ud)
static void restore_stack_limit(lua_State* L) static void restore_stack_limit(lua_State* L)
{ {
LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1); LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK));
if (L->size_ci > LUAI_MAXCALLS) if (L->size_ci > LUAI_MAXCALLS)
{ /* there was an overflow? */ { /* there was an overflow? */
int inuse = cast_int(L->ci - L->base_ci); int inuse = cast_int(L->ci - L->base_ci);

View file

@ -11,7 +11,7 @@
if ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue)) \ if ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue)) \
luaD_growstack(L, n); \ luaD_growstack(L, n); \
else \ else \
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1)); condhardstacktests(luaD_reallocstack(L, L->stacksize - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK)));
#define incr_top(L) \ #define incr_top(L) \
{ \ { \

View file

@ -268,7 +268,7 @@ static void traverseclosure(global_State* g, Closure* cl)
static void traversestack(global_State* g, lua_State* l, bool clearstack) static void traversestack(global_State* g, lua_State* l, bool clearstack)
{ {
markvalue(g, gt(l)); markobject(g, l->gt);
if (l->namecall) if (l->namecall)
stringmark(l->namecall); stringmark(l->namecall);
for (StkId o = l->stack; o < l->top; o++) for (StkId o = l->stack; o < l->top; o++)
@ -643,7 +643,7 @@ static void markroot(lua_State* L)
g->weak = NULL; g->weak = NULL;
markobject(g, g->mainthread); markobject(g, g->mainthread);
/* make global table be traversed before main stack */ /* make global table be traversed before main stack */
markvalue(g, gt(g->mainthread)); markobject(g, g->mainthread->gt);
markvalue(g, registry(L)); markvalue(g, registry(L));
markmt(g); markmt(g);
g->gcstate = GCSpropagate; g->gcstate = GCSpropagate;

View file

@ -77,7 +77,7 @@
#define luaC_checkGC(L) \ #define luaC_checkGC(L) \
{ \ { \
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1)); \ condhardstacktests(luaD_reallocstack(L, L->stacksize - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK))); \
if (L->global->totalbytes >= L->global->GCthreshold) \ if (L->global->totalbytes >= L->global->GCthreshold) \
{ \ { \
condhardmemtests(luaC_validate(L), 1); \ condhardmemtests(luaC_validate(L), 1); \

View file

@ -88,7 +88,7 @@ static void validateclosure(global_State* g, Closure* cl)
static void validatestack(global_State* g, lua_State* l) static void validatestack(global_State* g, lua_State* l)
{ {
validateref(g, obj2gco(l), gt(l)); validateobjref(g, obj2gco(l), obj2gco(l->gt));
for (CallInfo* ci = l->base_ci; ci <= l->ci; ++ci) for (CallInfo* ci = l->base_ci; ci <= l->ci; ++ci)
{ {
@ -370,6 +370,7 @@ static void dumpclosure(FILE* f, Closure* cl)
fprintf(f, ",\"env\":"); fprintf(f, ",\"env\":");
dumpref(f, obj2gco(cl->env)); dumpref(f, obj2gco(cl->env));
if (cl->isC) if (cl->isC)
{ {
if (cl->nupvalues) if (cl->nupvalues)
@ -411,11 +412,8 @@ static void dumpthread(FILE* f, lua_State* th)
fprintf(f, "{\"type\":\"thread\",\"cat\":%d,\"size\":%d", th->memcat, int(size)); fprintf(f, "{\"type\":\"thread\",\"cat\":%d,\"size\":%d", th->memcat, int(size));
if (iscollectable(&th->l_gt)) fprintf(f, ",\"env\":");
{ dumpref(f, obj2gco(th->gt));
fprintf(f, ",\"env\":");
dumpref(f, gcvalue(&th->l_gt));
}
Closure* tcl = 0; Closure* tcl = 0;
for (CallInfo* ci = th->base_ci; ci <= th->ci; ++ci) for (CallInfo* ci = th->base_ci; ci <= th->ci; ++ci)

View file

@ -3,6 +3,12 @@
#include "lua.h" #include "lua.h"
#ifdef _WIN32 #ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h> #include <Windows.h>
#endif #endif

View file

@ -11,6 +11,7 @@
#include "ldebug.h" #include "ldebug.h"
LUAU_FASTFLAG(LuauGcPagedSweep) LUAU_FASTFLAG(LuauGcPagedSweep)
LUAU_FASTFLAGVARIABLE(LuauReduceStackReallocs, false)
/* /*
** Main thread combines a thread state and the global state ** Main thread combines a thread state and the global state
@ -31,10 +32,11 @@ static void stack_init(lua_State* L1, lua_State* L)
/* initialize stack array */ /* initialize stack array */
L1->stack = luaM_newarray(L, BASIC_STACK_SIZE + EXTRA_STACK, TValue, L1->memcat); L1->stack = luaM_newarray(L, BASIC_STACK_SIZE + EXTRA_STACK, TValue, L1->memcat);
L1->stacksize = BASIC_STACK_SIZE + EXTRA_STACK; L1->stacksize = BASIC_STACK_SIZE + EXTRA_STACK;
TValue* stack = L1->stack;
for (int i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++) for (int i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++)
setnilvalue(L1->stack + i); /* erase new stack */ setnilvalue(stack + i); /* erase new stack */
L1->top = L1->stack; L1->top = stack;
L1->stack_last = L1->stack + (L1->stacksize - EXTRA_STACK) - 1; L1->stack_last = stack + (L1->stacksize - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK));
/* initialize first ci */ /* initialize first ci */
L1->ci->func = L1->top; L1->ci->func = L1->top;
setnilvalue(L1->top++); /* `function' entry for this `ci' */ setnilvalue(L1->top++); /* `function' entry for this `ci' */
@ -55,7 +57,7 @@ static void f_luaopen(lua_State* L, void* ud)
{ {
global_State* g = L->global; global_State* g = L->global;
stack_init(L, L); /* init stack */ stack_init(L, L); /* init stack */
sethvalue(L, gt(L), luaH_new(L, 0, 2)); /* table of globals */ L->gt = luaH_new(L, 0, 2); /* table of globals */
sethvalue(L, registry(L), luaH_new(L, 0, 2)); /* registry */ sethvalue(L, registry(L), luaH_new(L, 0, 2)); /* registry */
luaS_resize(L, LUA_MINSTRTABSIZE); /* initial size of string table */ luaS_resize(L, LUA_MINSTRTABSIZE); /* initial size of string table */
luaT_init(L); luaT_init(L);
@ -69,6 +71,7 @@ static void preinit_state(lua_State* L, global_State* g)
L->global = g; L->global = g;
L->stack = NULL; L->stack = NULL;
L->stacksize = 0; L->stacksize = 0;
L->gt = NULL;
L->openupval = NULL; L->openupval = NULL;
L->size_ci = 0; L->size_ci = 0;
L->nCcalls = L->baseCcalls = 0; L->nCcalls = L->baseCcalls = 0;
@ -80,7 +83,6 @@ static void preinit_state(lua_State* L, global_State* g)
L->stackstate = 0; L->stackstate = 0;
L->activememcat = 0; L->activememcat = 0;
L->userdata = NULL; L->userdata = NULL;
setnilvalue(gt(L));
} }
static void close_state(lua_State* L) static void close_state(lua_State* L)
@ -116,7 +118,7 @@ lua_State* luaE_newthread(lua_State* L)
preinit_state(L1, L->global); preinit_state(L1, L->global);
L1->activememcat = L->activememcat; // inherit the active memory category L1->activememcat = L->activememcat; // inherit the active memory category
stack_init(L1, L); /* init stack */ stack_init(L1, L); /* init stack */
setobj2n(L, gt(L1), gt(L)); /* share table of globals */ L1->gt = L->gt; /* share table of globals */
L1->singlestep = L->singlestep; L1->singlestep = L->singlestep;
LUAU_ASSERT(iswhite(obj2gco(L1))); LUAU_ASSERT(iswhite(obj2gco(L1)));
return L1; return L1;
@ -144,14 +146,30 @@ void lua_resetthread(lua_State* L)
ci->top = ci->base + LUA_MINSTACK; ci->top = ci->base + LUA_MINSTACK;
setnilvalue(ci->func); setnilvalue(ci->func);
L->ci = ci; L->ci = ci;
luaD_reallocCI(L, BASIC_CI_SIZE); if (FFlag::LuauReduceStackReallocs)
{
if (L->size_ci != BASIC_CI_SIZE)
luaD_reallocCI(L, BASIC_CI_SIZE);
}
else
{
luaD_reallocCI(L, BASIC_CI_SIZE);
}
/* clear thread state */ /* clear thread state */
L->status = LUA_OK; L->status = LUA_OK;
L->base = L->ci->base; L->base = L->ci->base;
L->top = L->ci->base; L->top = L->ci->base;
L->nCcalls = L->baseCcalls = 0; L->nCcalls = L->baseCcalls = 0;
/* clear thread stack */ /* clear thread stack */
luaD_reallocstack(L, BASIC_STACK_SIZE); if (FFlag::LuauReduceStackReallocs)
{
if (L->stacksize != BASIC_STACK_SIZE + EXTRA_STACK)
luaD_reallocstack(L, BASIC_STACK_SIZE);
}
else
{
luaD_reallocstack(L, BASIC_STACK_SIZE);
}
for (int i = 0; i < L->stacksize; i++) for (int i = 0; i < L->stacksize; i++)
setnilvalue(L->stack + i); setnilvalue(L->stack + i);
} }
@ -193,6 +211,7 @@ lua_State* lua_newstate(lua_Alloc f, void* ud)
g->strt.size = 0; g->strt.size = 0;
g->strt.nuse = 0; g->strt.nuse = 0;
g->strt.hash = NULL; g->strt.hash = NULL;
setnilvalue(&g->pseudotemp);
setnilvalue(registry(L)); setnilvalue(registry(L));
g->gcstate = GCSpause; g->gcstate = GCSpause;
if (!FFlag::LuauGcPagedSweep) if (!FFlag::LuauGcPagedSweep)

View file

@ -5,9 +5,6 @@
#include "lobject.h" #include "lobject.h"
#include "ltm.h" #include "ltm.h"
/* table of globals */
#define gt(L) (&L->l_gt)
/* registry */ /* registry */
#define registry(L) (&L->global->registry) #define registry(L) (&L->global->registry)
@ -177,6 +174,8 @@ typedef struct global_State
TString* ttname[LUA_T_COUNT]; /* names for basic types */ TString* ttname[LUA_T_COUNT]; /* names for basic types */
TString* tmname[TM_N]; /* array with tag-method names */ TString* tmname[TM_N]; /* array with tag-method names */
TValue pseudotemp; /* storage for temporary values used in pseudo2addr */
TValue registry; /* registry table, used by lua_ref and LUA_REGISTRYINDEX */ TValue registry; /* registry table, used by lua_ref and LUA_REGISTRYINDEX */
int registryfree; /* next free slot in registry */ int registryfree; /* next free slot in registry */
@ -231,8 +230,7 @@ struct lua_State
int cachedslot; /* when table operations or INDEX/NEWINDEX is invoked from Luau, what is the expected slot for lookup? */ int cachedslot; /* when table operations or INDEX/NEWINDEX is invoked from Luau, what is the expected slot for lookup? */
TValue l_gt; /* table of globals */ Table* gt; /* table of globals */
TValue env; /* temporary place for environments */
UpVal* openupval; /* list of open upvalues in this stack */ UpVal* openupval; /* list of open upvalues in this stack */
GCObject* gclist; GCObject* gclist;

View file

@ -39,7 +39,7 @@
// When calling luau_callTM, we usually push the arguments to the top of the stack. // When calling luau_callTM, we usually push the arguments to the top of the stack.
// This is safe to do for complicated reasons: // This is safe to do for complicated reasons:
// - stack guarantees 1 + EXTRA_STACK room beyond stack_last (see luaD_reallocstack) // - stack guarantees EXTRA_STACK room beyond stack_last (see luaD_reallocstack)
// - stack reallocation copies values past stack_last // - stack reallocation copies values past stack_last
// All external function calls that can cause stack realloc or Lua calls have to be wrapped in VM_PROTECT // All external function calls that can cause stack realloc or Lua calls have to be wrapped in VM_PROTECT

View file

@ -116,12 +116,12 @@ static void resolveImportSafe(lua_State* L, Table* env, TValue* k, uint32_t id)
// note: we call getimport with nil propagation which means that accesses to table chains like A.B.C will resolve in nil // note: we call getimport with nil propagation which means that accesses to table chains like A.B.C will resolve in nil
// this is technically not necessary but it reduces the number of exceptions when loading scripts that rely on getfenv/setfenv for global // this is technically not necessary but it reduces the number of exceptions when loading scripts that rely on getfenv/setfenv for global
// injection // injection
luaV_getimport(L, hvalue(gt(L)), self->k, self->id, /* propagatenil= */ true); luaV_getimport(L, L->gt, self->k, self->id, /* propagatenil= */ true);
} }
}; };
ResolveImport ri = {k, id}; ResolveImport ri = {k, id};
if (hvalue(gt(L))->safeenv) if (L->gt->safeenv)
{ {
// luaD_pcall will make sure that if any C/Lua calls during import resolution fail, the thread state is restored back // luaD_pcall will make sure that if any C/Lua calls during import resolution fail, the thread state is restored back
int oldTop = lua_gettop(L); int oldTop = lua_gettop(L);
@ -171,7 +171,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
L->global->GCthreshold = SIZE_MAX; L->global->GCthreshold = SIZE_MAX;
// env is 0 for current environment and a stack index otherwise // env is 0 for current environment and a stack index otherwise
Table* envt = (env == 0) ? hvalue(gt(L)) : hvalue(luaA_toobject(L, env)); Table* envt = (env == 0) ? L->gt : hvalue(luaA_toobject(L, env));
TString* source = luaS_new(L, chunkname); TString* source = luaS_new(L, chunkname);

View file

@ -55,7 +55,7 @@ static void callTMres(lua_State* L, StkId res, const TValue* f, const TValue* p1
{ {
ptrdiff_t result = savestack(L, res); ptrdiff_t result = savestack(L, res);
// using stack room beyond top is technically safe here, but for very complicated reasons: // using stack room beyond top is technically safe here, but for very complicated reasons:
// * The stack guarantees 1 + EXTRA_STACK room beyond stack_last (see luaD_reallocstack) will be allocated // * The stack guarantees EXTRA_STACK room beyond stack_last (see luaD_reallocstack) will be allocated
// * we cannot move luaD_checkstack above because the arguments are *sometimes* pointers to the lua // * we cannot move luaD_checkstack above because the arguments are *sometimes* pointers to the lua
// stack and checkstack may invalidate those pointers // stack and checkstack may invalidate those pointers
// * we cannot use savestack/restorestack because the arguments are sometimes on the C++ stack // * we cannot use savestack/restorestack because the arguments are sometimes on the C++ stack
@ -76,7 +76,7 @@ static void callTMres(lua_State* L, StkId res, const TValue* f, const TValue* p1
static void callTM(lua_State* L, const TValue* f, const TValue* p1, const TValue* p2, const TValue* p3) static void callTM(lua_State* L, const TValue* f, const TValue* p1, const TValue* p2, const TValue* p3)
{ {
// using stack room beyond top is technically safe here, but for very complicated reasons: // using stack room beyond top is technically safe here, but for very complicated reasons:
// * The stack guarantees 1 + EXTRA_STACK room beyond stack_last (see luaD_reallocstack) will be allocated // * The stack guarantees EXTRA_STACK room beyond stack_last (see luaD_reallocstack) will be allocated
// * we cannot move luaD_checkstack above because the arguments are *sometimes* pointers to the lua // * we cannot move luaD_checkstack above because the arguments are *sometimes* pointers to the lua
// stack and checkstack may invalidate those pointers // stack and checkstack may invalidate those pointers
// * we cannot use savestack/restorestack because the arguments are sometimes on the C++ stack // * we cannot use savestack/restorestack because the arguments are sometimes on the C++ stack

View file

@ -32,7 +32,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size)
Luau::LintOptions lintOptions; Luau::LintOptions lintOptions;
lintOptions.warningMask = ~0ull; lintOptions.warningMask = ~0ull;
Luau::lint(parseResult.root, names, typeck.globalScope, nullptr, lintOptions); Luau::lint(parseResult.root, names, typeck.globalScope, nullptr, {}, lintOptions);
} }
return 0; return 0;

View file

@ -227,7 +227,7 @@ DEFINE_PROTO_FUZZER(const luau::StatBlock& message)
if (kFuzzLinter) if (kFuzzLinter)
{ {
Luau::LintOptions lintOptions = {~0u}; Luau::LintOptions lintOptions = {~0u};
Luau::lint(parseResult.root, names, sharedEnv.globalScope, module.get(), lintOptions); Luau::lint(parseResult.root, names, sharedEnv.globalScope, module.get(), {}, lintOptions);
} }
} }

View file

@ -1,7 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Autocomplete.h" #include "Luau/Autocomplete.h"
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Parser.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"
#include "Luau/VisitTypeVar.h" #include "Luau/VisitTypeVar.h"
@ -2610,6 +2609,27 @@ a = if temp then even elseif true then temp else e@9
CHECK(ac.entryMap.count("elseif") == 0); CHECK(ac.entryMap.count("elseif") == 0);
} }
TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_else_regression")
{
ScopedFastFlag FFlagLuauIfElseExprFixCompletionIssue("LuauIfElseExprFixCompletionIssue", true);
check(R"(
local abcdef = 0;
local temp = false
local even = true;
local a
a = if temp then even else@1
a = if temp then even else @2
a = if temp then even else abc@3
)");
auto ac = autocomplete('1');
CHECK(ac.entryMap.count("else") == 0);
ac = autocomplete('2');
CHECK(ac.entryMap.count("else") == 0);
ac = autocomplete('3');
CHECK(ac.entryMap.count("abcdef"));
}
TEST_CASE_FIXTURE(ACFixture, "autocomplete_explicit_type_pack") TEST_CASE_FIXTURE(ACFixture, "autocomplete_explicit_type_pack")
{ {
check(R"( check(R"(

View file

@ -10,6 +10,11 @@
#include <sstream> #include <sstream>
#include <string_view> #include <string_view>
namespace Luau
{
std::string rep(const std::string& s, size_t n);
}
using namespace Luau; using namespace Luau;
static std::string compileFunction(const char* source, uint32_t id) static std::string compileFunction(const char* source, uint32_t id)
@ -1960,15 +1965,6 @@ RETURN R8 -1
)"); )");
} }
static std::string rep(const std::string& s, size_t n)
{
std::string r;
r.reserve(s.length() * n);
for (size_t i = 0; i < n; ++i)
r += s;
return r;
}
TEST_CASE("RecursionParse") TEST_CASE("RecursionParse")
{ {
// The test forcibly pushes the stack limit during compilation; in NoOpt, the stack consumption is much larger so we need to reduce the limit to // The test forcibly pushes the stack limit during compilation; in NoOpt, the stack consumption is much larger so we need to reduce the limit to

View file

@ -492,6 +492,8 @@ TEST_CASE("DateTime")
TEST_CASE("Debug") TEST_CASE("Debug")
{ {
ScopedFastFlag luauTableFieldFunctionDebugname{"LuauTableFieldFunctionDebugname", true};
runConformance("debug.lua"); runConformance("debug.lua");
} }
@ -890,6 +892,12 @@ TEST_CASE("Coverage")
lua_pushstring(L, function); lua_pushstring(L, function);
lua_setfield(L, -2, "name"); lua_setfield(L, -2, "name");
lua_pushinteger(L, linedefined);
lua_setfield(L, -2, "linedefined");
lua_pushinteger(L, depth);
lua_setfield(L, -2, "depth");
for (size_t i = 0; i < size; ++i) for (size_t i = 0; i < size; ++i)
if (hits[i] != -1) if (hits[i] != -1)
{ {

View file

@ -2,6 +2,7 @@
#include "Fixture.h" #include "Fixture.h"
#include "Luau/AstQuery.h" #include "Luau/AstQuery.h"
#include "Luau/Parser.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"
#include "Luau/TypeAttach.h" #include "Luau/TypeAttach.h"
#include "Luau/Transpiler.h" #include "Luau/Transpiler.h"
@ -112,7 +113,7 @@ AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& pars
sourceModule->name = fromString(mainModuleName); sourceModule->name = fromString(mainModuleName);
sourceModule->root = result.root; sourceModule->root = result.root;
sourceModule->mode = parseMode(result.hotcomments); sourceModule->mode = parseMode(result.hotcomments);
sourceModule->ignoreLints = LintWarning::parseMask(result.hotcomments); sourceModule->hotcomments = std::move(result.hotcomments);
if (!result.errors.empty()) if (!result.errors.empty())
{ {
@ -157,6 +158,7 @@ CheckResult Fixture::check(const std::string& source)
LintResult Fixture::lint(const std::string& source, const std::optional<LintOptions>& lintOptions) LintResult Fixture::lint(const std::string& source, const std::optional<LintOptions>& lintOptions)
{ {
ParseOptions parseOptions; ParseOptions parseOptions;
parseOptions.captureComments = true;
configResolver.defaultConfig.mode = Mode::Nonstrict; configResolver.defaultConfig.mode = Mode::Nonstrict;
parse(source, parseOptions); parse(source, parseOptions);

View file

@ -8,7 +8,6 @@
#include "Luau/Linter.h" #include "Luau/Linter.h"
#include "Luau/Location.h" #include "Luau/Location.h"
#include "Luau/ModuleResolver.h" #include "Luau/ModuleResolver.h"
#include "Luau/Parser.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"

View file

@ -2,7 +2,6 @@
#include "Luau/AstQuery.h" #include "Luau/AstQuery.h"
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
#include "Luau/Parser.h"
#include "Luau/RequireTracer.h" #include "Luau/RequireTracer.h"
#include "Fixture.h" #include "Fixture.h"
@ -897,8 +896,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "clearStats")
TEST_CASE_FIXTURE(FrontendFixture, "typecheck_twice_for_ast_types") TEST_CASE_FIXTURE(FrontendFixture, "typecheck_twice_for_ast_types")
{ {
ScopedFastFlag sffs("LuauTypeCheckTwice", true);
fileResolver.source["Module/A"] = R"( fileResolver.source["Module/A"] = R"(
local a = 1 local a = 1
)"; )";

View file

@ -1395,9 +1395,7 @@ TEST_CASE_FIXTURE(Fixture, "DeprecatedApi")
TypeId colorType = typeChecker.globalTypes.addType(TableTypeVar{{}, std::nullopt, typeChecker.globalScope->level, Luau::TableState::Sealed}); TypeId colorType = typeChecker.globalTypes.addType(TableTypeVar{{}, std::nullopt, typeChecker.globalScope->level, Luau::TableState::Sealed});
getMutable<TableTypeVar>(colorType)->props = { getMutable<TableTypeVar>(colorType)->props = {{"toHSV", {typeChecker.anyType, /* deprecated= */ true, "Color3:ToHSV"}}};
{"toHSV", {typeChecker.anyType, /* deprecated= */ true, "Color3:ToHSV"} }
};
addGlobalBinding(typeChecker, "Color3", Binding{colorType, {}}); addGlobalBinding(typeChecker, "Color3", Binding{colorType, {}});
@ -1554,8 +1552,46 @@ _ = (math.random() < 0.5 and false) or 42 -- currently ignored
)"); )");
REQUIRE_EQ(result.warnings.size(), 2); REQUIRE_EQ(result.warnings.size(), 2);
CHECK_EQ(result.warnings[0].text, "The and-or expression always evaluates to the second alternative because the first alternative is false; consider using if-then-else expression instead"); CHECK_EQ(result.warnings[0].text, "The and-or expression always evaluates to the second alternative because the first alternative is false; "
CHECK_EQ(result.warnings[1].text, "The and-or expression always evaluates to the second alternative because the first alternative is nil; consider using if-then-else expression instead"); "consider using if-then-else expression instead");
CHECK_EQ(result.warnings[1].text, "The and-or expression always evaluates to the second alternative because the first alternative is nil; "
"consider using if-then-else expression instead");
}
TEST_CASE_FIXTURE(Fixture, "WrongComment")
{
ScopedFastFlag sff("LuauParseAllHotComments", true);
LintResult result = lint(R"(
--!strict
--!struct
--!nolintGlobal
--!nolint Global
--!nolint KnownGlobal
--!nolint UnknownGlobal
--! no more lint
--!strict here
do end
--!nolint
)");
REQUIRE_EQ(result.warnings.size(), 6);
CHECK_EQ(result.warnings[0].text, "Unknown comment directive 'struct'; did you mean 'strict'?");
CHECK_EQ(result.warnings[1].text, "Unknown comment directive 'nolintGlobal'");
CHECK_EQ(result.warnings[2].text, "nolint directive refers to unknown lint rule 'Global'");
CHECK_EQ(result.warnings[3].text, "nolint directive refers to unknown lint rule 'KnownGlobal'; did you mean 'UnknownGlobal'?");
CHECK_EQ(result.warnings[4].text, "Comment directive with the type checking mode has extra symbols at the end of the line");
CHECK_EQ(result.warnings[5].text, "Comment directive is ignored because it is placed after the first non-comment token");
}
TEST_CASE_FIXTURE(Fixture, "WrongCommentMuteSelf")
{
LintResult result = lint(R"(
--!nolint
--!struct
)");
REQUIRE_EQ(result.warnings.size(), 0); // --!nolint disables WrongComment lint :)
} }
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -1,5 +1,4 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Parser.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"

View file

@ -1,6 +1,5 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Parser.h" #include "Luau/Parser.h"
#include "Luau/TypeInfer.h"
#include "Fixture.h" #include "Fixture.h"
#include "ScopedFlags.h" #include "ScopedFlags.h"
@ -300,8 +299,9 @@ TEST_CASE_FIXTURE(Fixture, "functions_can_have_return_annotations")
AstStatFunction* statFunction = block->body.data[0]->as<AstStatFunction>(); AstStatFunction* statFunction = block->body.data[0]->as<AstStatFunction>();
REQUIRE(statFunction != nullptr); REQUIRE(statFunction != nullptr);
CHECK_EQ(statFunction->func->returnAnnotation.types.size, 1); REQUIRE(statFunction->func->returnAnnotation.has_value());
CHECK(statFunction->func->returnAnnotation.tailType == nullptr); CHECK_EQ(statFunction->func->returnAnnotation->types.size, 1);
CHECK(statFunction->func->returnAnnotation->tailType == nullptr);
} }
TEST_CASE_FIXTURE(Fixture, "functions_can_have_a_function_type_annotation") TEST_CASE_FIXTURE(Fixture, "functions_can_have_a_function_type_annotation")
@ -316,9 +316,9 @@ TEST_CASE_FIXTURE(Fixture, "functions_can_have_a_function_type_annotation")
AstStatFunction* statFunc = block->body.data[0]->as<AstStatFunction>(); AstStatFunction* statFunc = block->body.data[0]->as<AstStatFunction>();
REQUIRE(statFunc != nullptr); REQUIRE(statFunc != nullptr);
AstArray<AstType*>& retTypes = statFunc->func->returnAnnotation.types; REQUIRE(statFunc->func->returnAnnotation.has_value());
REQUIRE(statFunc->func->hasReturnAnnotation); CHECK(statFunc->func->returnAnnotation->tailType == nullptr);
CHECK(statFunc->func->returnAnnotation.tailType == nullptr); AstArray<AstType*>& retTypes = statFunc->func->returnAnnotation->types;
REQUIRE(retTypes.size == 1); REQUIRE(retTypes.size == 1);
AstTypeFunction* funTy = retTypes.data[0]->as<AstTypeFunction>(); AstTypeFunction* funTy = retTypes.data[0]->as<AstTypeFunction>();
@ -337,9 +337,9 @@ TEST_CASE_FIXTURE(Fixture, "function_return_type_should_disambiguate_from_functi
AstStatFunction* statFunc = block->body.data[0]->as<AstStatFunction>(); AstStatFunction* statFunc = block->body.data[0]->as<AstStatFunction>();
REQUIRE(statFunc != nullptr); REQUIRE(statFunc != nullptr);
AstArray<AstType*>& retTypes = statFunc->func->returnAnnotation.types; REQUIRE(statFunc->func->returnAnnotation.has_value());
REQUIRE(statFunc->func->hasReturnAnnotation); CHECK(statFunc->func->returnAnnotation->tailType == nullptr);
CHECK(statFunc->func->returnAnnotation.tailType == nullptr); AstArray<AstType*>& retTypes = statFunc->func->returnAnnotation->types;
REQUIRE(retTypes.size == 2); REQUIRE(retTypes.size == 2);
AstTypeReference* ty0 = retTypes.data[0]->as<AstTypeReference>(); AstTypeReference* ty0 = retTypes.data[0]->as<AstTypeReference>();
@ -363,9 +363,9 @@ TEST_CASE_FIXTURE(Fixture, "function_return_type_should_parse_as_function_type_a
AstStatFunction* statFunc = block->body.data[0]->as<AstStatFunction>(); AstStatFunction* statFunc = block->body.data[0]->as<AstStatFunction>();
REQUIRE(statFunc != nullptr); REQUIRE(statFunc != nullptr);
AstArray<AstType*>& retTypes = statFunc->func->returnAnnotation.types; REQUIRE(statFunc->func->returnAnnotation.has_value());
REQUIRE(statFunc->func->hasReturnAnnotation); CHECK(statFunc->func->returnAnnotation->tailType == nullptr);
CHECK(statFunc->func->returnAnnotation.tailType == nullptr); AstArray<AstType*>& retTypes = statFunc->func->returnAnnotation->types;
REQUIRE(retTypes.size == 1); REQUIRE(retTypes.size == 1);
AstTypeFunction* funTy = retTypes.data[0]->as<AstTypeFunction>(); AstTypeFunction* funTy = retTypes.data[0]->as<AstTypeFunction>();
@ -707,12 +707,25 @@ TEST_CASE_FIXTURE(Fixture, "mode_is_unset_if_no_hot_comment")
TEST_CASE_FIXTURE(Fixture, "sense_hot_comment_on_first_line") TEST_CASE_FIXTURE(Fixture, "sense_hot_comment_on_first_line")
{ {
ParseResult result = parseEx(" --!strict "); ParseOptions options;
options.captureComments = true;
ParseResult result = parseEx(" --!strict ", options);
std::optional<Mode> mode = parseMode(result.hotcomments); std::optional<Mode> mode = parseMode(result.hotcomments);
REQUIRE(bool(mode)); REQUIRE(bool(mode));
CHECK_EQ(int(*mode), int(Mode::Strict)); CHECK_EQ(int(*mode), int(Mode::Strict));
} }
TEST_CASE_FIXTURE(Fixture, "non_header_hot_comments")
{
ParseOptions options;
options.captureComments = true;
ParseResult result = parseEx("do end --!strict", options);
std::optional<Mode> mode = parseMode(result.hotcomments);
REQUIRE(!mode);
}
TEST_CASE_FIXTURE(Fixture, "stop_if_line_ends_with_hyphen") TEST_CASE_FIXTURE(Fixture, "stop_if_line_ends_with_hyphen")
{ {
CHECK_THROWS_AS(parse(" -"), std::exception); CHECK_THROWS_AS(parse(" -"), std::exception);
@ -720,7 +733,10 @@ TEST_CASE_FIXTURE(Fixture, "stop_if_line_ends_with_hyphen")
TEST_CASE_FIXTURE(Fixture, "nonstrict_mode") TEST_CASE_FIXTURE(Fixture, "nonstrict_mode")
{ {
ParseResult result = parseEx("--!nonstrict"); ParseOptions options;
options.captureComments = true;
ParseResult result = parseEx("--!nonstrict", options);
CHECK(result.errors.empty()); CHECK(result.errors.empty());
std::optional<Mode> mode = parseMode(result.hotcomments); std::optional<Mode> mode = parseMode(result.hotcomments);
REQUIRE(bool(mode)); REQUIRE(bool(mode));
@ -729,7 +745,10 @@ TEST_CASE_FIXTURE(Fixture, "nonstrict_mode")
TEST_CASE_FIXTURE(Fixture, "nocheck_mode") TEST_CASE_FIXTURE(Fixture, "nocheck_mode")
{ {
ParseResult result = parseEx("--!nocheck"); ParseOptions options;
options.captureComments = true;
ParseResult result = parseEx("--!nocheck", options);
CHECK(result.errors.empty()); CHECK(result.errors.empty());
std::optional<Mode> mode = parseMode(result.hotcomments); std::optional<Mode> mode = parseMode(result.hotcomments);
REQUIRE(bool(mode)); REQUIRE(bool(mode));
@ -1498,8 +1517,6 @@ return
TEST_CASE_FIXTURE(Fixture, "parse_error_broken_comment") TEST_CASE_FIXTURE(Fixture, "parse_error_broken_comment")
{ {
ScopedFastFlag luauStartingBrokenComment{"LuauStartingBrokenComment", true};
const char* expected = "Expected identifier when parsing expression, got unfinished comment"; const char* expected = "Expected identifier when parsing expression, got unfinished comment";
matchParseError("--[[unfinished work", expected); matchParseError("--[[unfinished work", expected);

View file

@ -73,7 +73,7 @@ public:
private: private:
std::unique_ptr<lua_State, void (*)(lua_State*)> luaState; std::unique_ptr<lua_State, void (*)(lua_State*)> luaState;
// This is a simplicitic and incomplete pretty printer. // This is a simplistic and incomplete pretty printer.
// It is included here to test that the pretty printer hook is being called. // It is included here to test that the pretty printer hook is being called.
// More elaborate tests to ensure correct output can be added if we introduce // More elaborate tests to ensure correct output can be added if we introduce
// a more feature rich pretty printer. // a more feature rich pretty printer.
@ -158,12 +158,25 @@ TEST_CASE_FIXTURE(ReplFixture, "CompleteGlobalVariables")
myvariable1 = 5 myvariable1 = 5
myvariable2 = 5 myvariable2 = 5
)"); )");
CompletionSet completions = getCompletionSet("myvar"); {
// Try to complete globals that are added by the user's script
CompletionSet completions = getCompletionSet("myvar");
std::string prefix = ""; std::string prefix = "";
CHECK(completions.size() == 2); CHECK(completions.size() == 2);
CHECK(checkCompletion(completions, prefix, "myvariable1")); CHECK(checkCompletion(completions, prefix, "myvariable1"));
CHECK(checkCompletion(completions, prefix, "myvariable2")); CHECK(checkCompletion(completions, prefix, "myvariable2"));
}
{
// Try completing some builtin functions
CompletionSet completions = getCompletionSet("math.m");
std::string prefix = "math.";
CHECK(completions.size() == 3);
CHECK(checkCompletion(completions, prefix, "max("));
CHECK(checkCompletion(completions, prefix, "min("));
CHECK(checkCompletion(completions, prefix, "modf("));
}
} }
TEST_CASE_FIXTURE(ReplFixture, "CompleteTableKeys") TEST_CASE_FIXTURE(ReplFixture, "CompleteTableKeys")
@ -206,4 +219,188 @@ TEST_CASE_FIXTURE(ReplFixture, "StringMethods")
} }
} }
TEST_CASE_FIXTURE(ReplFixture, "TableWithMetatableIndexTable")
{
runCode(L, R"(
-- Create 't' which is a table with a metatable with an __index table
mt = {}
mt.__index = mt
t = {}
setmetatable(t, mt)
mt.mtkey1 = {x="x value", y="y value", 1, 2}
mt.mtkey2 = 2
t.tkey1 = {data1 = 2, data2 = "str", 3, 4}
t.tkey2 = 4
)");
{
CompletionSet completions = getCompletionSet("t.t");
std::string prefix = "t.";
CHECK(completions.size() == 2);
CHECK(checkCompletion(completions, prefix, "tkey1"));
CHECK(checkCompletion(completions, prefix, "tkey2"));
}
{
CompletionSet completions = getCompletionSet("t.tkey1.data2:re");
std::string prefix = "t.tkey1.data2:";
CHECK(completions.size() == 2);
CHECK(checkCompletion(completions, prefix, "rep("));
CHECK(checkCompletion(completions, prefix, "reverse("));
}
{
CompletionSet completions = getCompletionSet("t.mtk");
std::string prefix = "t.";
CHECK(completions.size() == 2);
CHECK(checkCompletion(completions, prefix, "mtkey1"));
CHECK(checkCompletion(completions, prefix, "mtkey2"));
}
{
CompletionSet completions = getCompletionSet("t.mtkey1.");
std::string prefix = "t.mtkey1.";
CHECK(completions.size() == 2);
CHECK(checkCompletion(completions, prefix, "x"));
CHECK(checkCompletion(completions, prefix, "y"));
}
}
TEST_CASE_FIXTURE(ReplFixture, "TableWithMetatableIndexFunction")
{
runCode(L, R"(
-- Create 't' which is a table with a metatable with an __index function
mt = {}
mt.__index = function(table, key)
print("mt.__index called")
if key == "foo" then
return "FOO"
elseif key == "bar" then
return "BAR"
else
return nil
end
end
t = {}
setmetatable(t, mt)
t.tkey = 0
)");
{
CompletionSet completions = getCompletionSet("t.t");
std::string prefix = "t.";
CHECK(completions.size() == 1);
CHECK(checkCompletion(completions, prefix, "tkey"));
}
{
// t.foo is a valid key, but should not be completed because it requires calling an __index function
CompletionSet completions = getCompletionSet("t.foo");
CHECK(completions.size() == 0);
}
{
// t.foo is a valid key, but should not be found because it requires calling an __index function
CompletionSet completions = getCompletionSet("t.foo:");
CHECK(completions.size() == 0);
}
}
TEST_CASE_FIXTURE(ReplFixture, "TableWithMultipleMetatableIndexTables")
{
runCode(L, R"(
-- Create a table with a chain of metatables
mt2 = {}
mt2.__index = mt2
mt = {}
mt.__index = mt
setmetatable(mt, mt2)
t = {}
setmetatable(t, mt)
mt2.mt2key = {x=1, y=2}
mt.mtkey = 2
t.tkey = 3
)");
{
CompletionSet completions = getCompletionSet("t.");
std::string prefix = "t.";
CHECK(completions.size() == 4);
CHECK(checkCompletion(completions, prefix, "__index"));
CHECK(checkCompletion(completions, prefix, "tkey"));
CHECK(checkCompletion(completions, prefix, "mtkey"));
CHECK(checkCompletion(completions, prefix, "mt2key"));
}
{
CompletionSet completions = getCompletionSet("t.__index.");
std::string prefix = "t.__index.";
CHECK(completions.size() == 3);
CHECK(checkCompletion(completions, prefix, "__index"));
CHECK(checkCompletion(completions, prefix, "mtkey"));
CHECK(checkCompletion(completions, prefix, "mt2key"));
}
{
CompletionSet completions = getCompletionSet("t.mt2key.");
std::string prefix = "t.mt2key.";
CHECK(completions.size() == 2);
CHECK(checkCompletion(completions, prefix, "x"));
CHECK(checkCompletion(completions, prefix, "y"));
}
}
TEST_CASE_FIXTURE(ReplFixture, "TableWithDeepMetatableIndexTables")
{
runCode(L, R"(
-- Creates a table with a chain of metatables of length `count`
function makeChainedTable(count)
local result = {}
result.__index = result
result[string.format("entry%d", count)] = { count = count }
if count == 0 then
return result
else
return setmetatable(result, makeChainedTable(count - 1))
end
end
t30 = makeChainedTable(30)
t60 = makeChainedTable(60)
)");
{
// Check if entry0 exists
CompletionSet completions = getCompletionSet("t30.entry0");
std::string prefix = "t30.";
CHECK(checkCompletion(completions, prefix, "entry0"));
}
{
// Check if entry0.count exists
CompletionSet completions = getCompletionSet("t30.entry0.co");
std::string prefix = "t30.entry0.";
CHECK(checkCompletion(completions, prefix, "count"));
}
{
// Check if entry0 exists. With the max traversal limit of 50 in the repl, this should fail.
CompletionSet completions = getCompletionSet("t60.entry0");
CHECK(completions.size() == 0);
}
{
// Check if entry0.count exists. With the max traversal limit of 50 in the repl, this should fail.
CompletionSet completions = getCompletionSet("t60.entry0.co");
CHECK(completions.size() == 0);
}
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -1,6 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Parser.h"
#include "Luau/RequireTracer.h" #include "Luau/RequireTracer.h"
#include "Luau/Parser.h"
#include "Fixture.h" #include "Fixture.h"

View file

@ -604,9 +604,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_typevars_are_not_considered_to_escape_their_
*/ */
TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_unification_with_any") TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_unification_with_any")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {{"LuauTwoPassAliasDefinitionFix", true}};
{"LuauTwoPassAliasDefinitionFix", true}
};
CheckResult result = check(R"( CheckResult result = check(R"(
local function x() local function x()

View file

@ -1,6 +1,5 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Parser.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"

View file

@ -1,5 +1,4 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Parser.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"

View file

@ -1,6 +1,5 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Parser.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"

View file

@ -1,6 +1,5 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Parser.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"

View file

@ -1,5 +1,4 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Parser.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"

View file

@ -1,5 +1,4 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Parser.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"

View file

@ -1,5 +1,4 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Parser.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Fixture.h" #include "Fixture.h"

View file

@ -426,8 +426,6 @@ TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options")
{"LuauSingletonTypes", true}, {"LuauSingletonTypes", true},
{"LuauParseSingletonTypes", true}, {"LuauParseSingletonTypes", true},
{"LuauExpectedTypesOfProperties", true}, {"LuauExpectedTypesOfProperties", true},
{"LuauIfElseExpectedType2", true},
{"LuauIfElseBranchTypeUnion", true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(

View file

@ -1,6 +1,5 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Parser.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"
@ -2168,8 +2167,6 @@ b()
TEST_CASE_FIXTURE(Fixture, "length_operator_union") TEST_CASE_FIXTURE(Fixture, "length_operator_union")
{ {
ScopedFastFlag luauLengthOnCompositeType{"LuauLengthOnCompositeType", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local x: {number} | {string} local x: {number} | {string}
local y = #x local y = #x
@ -2180,8 +2177,6 @@ local y = #x
TEST_CASE_FIXTURE(Fixture, "length_operator_intersection") TEST_CASE_FIXTURE(Fixture, "length_operator_intersection")
{ {
ScopedFastFlag luauLengthOnCompositeType{"LuauLengthOnCompositeType", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local x: {number} & {z:string} -- mixed tables are evil local x: {number} & {z:string} -- mixed tables are evil
local y = #x local y = #x
@ -2192,8 +2187,6 @@ local y = #x
TEST_CASE_FIXTURE(Fixture, "length_operator_non_table_union") TEST_CASE_FIXTURE(Fixture, "length_operator_non_table_union")
{ {
ScopedFastFlag luauLengthOnCompositeType{"LuauLengthOnCompositeType", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local x: {number} | any | string local x: {number} | any | string
local y = #x local y = #x
@ -2204,8 +2197,6 @@ local y = #x
TEST_CASE_FIXTURE(Fixture, "length_operator_union_errors") TEST_CASE_FIXTURE(Fixture, "length_operator_union_errors")
{ {
ScopedFastFlag luauLengthOnCompositeType{"LuauLengthOnCompositeType", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local x: {number} | number | string local x: {number} | number | string
local y = #x local y = #x
@ -2214,4 +2205,38 @@ local y = #x
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
} }
TEST_CASE_FIXTURE(Fixture, "dont_hang_when_trying_to_look_up_in_cyclic_metatable_index")
{
ScopedFastFlag sff{"LuauTerminateCyclicMetatableIndexLookup", true};
// t :: t1 where t1 = {metatable {__index: t1, __tostring: (t1) -> string}}
CheckResult result = check(R"(
local mt = {}
local t = setmetatable({}, mt)
mt.__index = t
function mt:__tostring()
return t.p
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Type 't' does not have key 'p'", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "give_up_after_one_metatable_index_look_up")
{
CheckResult result = check(R"(
local data = { x = 5 }
local t1 = setmetatable({}, { __index = data })
local t2 = setmetatable({}, t1) -- note: must be t1, not a new table
local x1 = t1.x -- ok
local x2 = t2.x -- nope
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Type 't2' does not have key 'x'", toString(result.errors[0]));
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -2,7 +2,6 @@
#include "Luau/AstQuery.h" #include "Luau/AstQuery.h"
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Parser.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"
@ -4654,8 +4653,6 @@ a = setmetatable(a, { __call = function(x) end })
TEST_CASE_FIXTURE(Fixture, "infer_through_group_expr") TEST_CASE_FIXTURE(Fixture, "infer_through_group_expr")
{ {
ScopedFastFlag luauGroupExpectedType{"LuauGroupExpectedType", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(a: (number, number) -> number) return a(1, 3) end local function f(a: (number, number) -> number) return a(1, 3) end
f(((function(a, b) return a + b end))) f(((function(a, b) return a + b end)))
@ -4735,21 +4732,14 @@ local a = if false then "a" elseif false then "b" else "c"
TEST_CASE_FIXTURE(Fixture, "tc_if_else_expressions_type_union") TEST_CASE_FIXTURE(Fixture, "tc_if_else_expressions_type_union")
{ {
ScopedFastFlag sff3{"LuauIfElseBranchTypeUnion", true}; CheckResult result = check(R"(local a: number? = if true then 42 else nil)");
{ LUAU_REQUIRE_NO_ERRORS(result);
CheckResult result = check(R"(local a: number? = if true then 42 else nil)"); CHECK_EQ(toString(requireType("a"), {true}), "number?");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a"), {true}), "number?");
}
} }
TEST_CASE_FIXTURE(Fixture, "tc_if_else_expressions_expected_type_1") TEST_CASE_FIXTURE(Fixture, "tc_if_else_expressions_expected_type_1")
{ {
ScopedFastFlag luauIfElseExpectedType2{"LuauIfElseExpectedType2", true};
ScopedFastFlag luauIfElseBranchTypeUnion{"LuauIfElseBranchTypeUnion", true};
CheckResult result = check(R"( CheckResult result = check(R"(
type X = {number | string} type X = {number | string}
local a: X = if true then {"1", 2, 3} else {4, 5, 6} local a: X = if true then {"1", 2, 3} else {4, 5, 6}
@ -4761,9 +4751,6 @@ local a: X = if true then {"1", 2, 3} else {4, 5, 6}
TEST_CASE_FIXTURE(Fixture, "tc_if_else_expressions_expected_type_2") TEST_CASE_FIXTURE(Fixture, "tc_if_else_expressions_expected_type_2")
{ {
ScopedFastFlag luauIfElseExpectedType2{"LuauIfElseExpectedType2", true};
ScopedFastFlag luauIfElseBranchTypeUnion{"LuauIfElseBranchTypeUnion", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local a: number? = if true then 1 else nil local a: number? = if true then 1 else nil
)"); )");
@ -4773,8 +4760,6 @@ local a: number? = if true then 1 else nil
TEST_CASE_FIXTURE(Fixture, "tc_if_else_expressions_expected_type_3") TEST_CASE_FIXTURE(Fixture, "tc_if_else_expressions_expected_type_3")
{ {
ScopedFastFlag luauIfElseExpectedType2{"LuauIfElseExpectedType2", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function times<T>(n: any, f: () -> T) local function times<T>(n: any, f: () -> T)
local result: {T} = {} local result: {T} = {}
@ -5058,8 +5043,6 @@ end
TEST_CASE_FIXTURE(Fixture, "recursive_metatable_crash") TEST_CASE_FIXTURE(Fixture, "recursive_metatable_crash")
{ {
ScopedFastFlag luauMetatableAreEqualRecursion{"LuauMetatableAreEqualRecursion", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function getIt() local function getIt()
local y local y
@ -5076,8 +5059,6 @@ local c = a or b
TEST_CASE_FIXTURE(Fixture, "bound_typepack_promote") TEST_CASE_FIXTURE(Fixture, "bound_typepack_promote")
{ {
ScopedFastFlag luauCommittingTxnLogFreeTpPromote{"LuauCommittingTxnLogFreeTpPromote", true};
// No assertions should trigger // No assertions should trigger
check(R"( check(R"(
local function p() local function p()
@ -5251,7 +5232,6 @@ TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_string_singleton")
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauRefactorTypeVarQuestions", true}, {"LuauRefactorTypeVarQuestions", true},
{"LuauSingletonTypes", true}, {"LuauSingletonTypes", true},
{"LuauLengthOnCompositeType", true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -5272,7 +5252,6 @@ TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_union_of_string_singleton")
{"LuauDiscriminableUnions2", true}, {"LuauDiscriminableUnions2", true},
{"LuauRefactorTypeVarQuestions", true}, {"LuauRefactorTypeVarQuestions", true},
{"LuauSingletonTypes", true}, {"LuauSingletonTypes", true},
{"LuauLengthOnCompositeType", true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(

View file

@ -1,5 +1,4 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Parser.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"

View file

@ -1,6 +1,5 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Parser.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"
@ -930,8 +929,6 @@ type R = { m: F<R> }
TEST_CASE_FIXTURE(Fixture, "pack_tail_unification_check") TEST_CASE_FIXTURE(Fixture, "pack_tail_unification_check")
{ {
ScopedFastFlag luauUnifyPackTails{"LuauUnifyPackTails", true};
CheckResult result = check(R"( CheckResult result = check(R"(
local a: () -> (number, ...string) local a: () -> (number, ...string)
local b: () -> (number, ...boolean) local b: () -> (number, ...boolean)

View file

@ -1,5 +1,4 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Parser.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"

View file

@ -1,5 +1,4 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Parser.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"

View file

@ -1,5 +1,4 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Parser.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h" #include "Luau/TypeVar.h"

View file

@ -371,6 +371,15 @@ do
st, msg = coroutine.close(co) st, msg = coroutine.close(co)
assert(st and msg == nil) assert(st and msg == nil)
assert(f() == 42) assert(f() == 42)
-- closing a coroutine with a large stack
co = coroutine.create(function()
local function f(depth) return if depth > 0 then f(depth - 1) + depth else 0 end
coroutine.yield(f(100))
end)
assert(coroutine.resume(co))
st, msg = coroutine.close(co)
assert(st and msg == nil)
end end
return 'OK' return 'OK'

View file

@ -49,16 +49,24 @@ foo()
c = getcoverage(foo) c = getcoverage(foo)
assert(#c == 1) assert(#c == 1)
assert(c[1].name == "foo") assert(c[1].name == "foo")
assert(c[1].linedefined == 4)
assert(c[1].depth == 0)
assert(validate(c[1], {5, 6, 7}, {})) assert(validate(c[1], {5, 6, 7}, {}))
bar() bar()
c = getcoverage(bar) c = getcoverage(bar)
assert(#c == 3) assert(#c == 3)
assert(c[1].name == "bar") assert(c[1].name == "bar")
assert(c[1].linedefined == 10)
assert(c[1].depth == 0)
assert(validate(c[1], {11, 15, 19}, {})) assert(validate(c[1], {11, 15, 19}, {}))
assert(c[2].name == "one") assert(c[2].name == "one")
assert(c[2].linedefined == 11)
assert(c[2].depth == 1)
assert(validate(c[2], {12}, {})) assert(validate(c[2], {12}, {}))
assert(c[3].name == nil) assert(c[3].name == nil)
assert(c[3].linedefined == 15)
assert(c[3].depth == 1)
assert(validate(c[3], {}, {16})) assert(validate(c[3], {}, {16}))
return 'OK' return 'OK'

View file

@ -76,6 +76,9 @@ assert(baz(co, 2, "n") == nil)
assert(baz(math.sqrt, "n") == "sqrt") assert(baz(math.sqrt, "n") == "sqrt")
assert(baz(math.sqrt, "f") == math.sqrt) -- yes this is pointless assert(baz(math.sqrt, "f") == math.sqrt) -- yes this is pointless
local t = { foo = function() return 1 end }
assert(baz(t.foo, "n") == "foo")
-- info multi-arg returns -- info multi-arg returns
function quux(...) function quux(...)
return {debug.info(...)} return {debug.info(...)}

View file

@ -10,6 +10,9 @@
#ifndef WIN32_LEAN_AND_MEAN #ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#endif #endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h> // IsDebuggerPresent #include <Windows.h> // IsDebuggerPresent
#endif #endif
@ -52,7 +55,7 @@ static bool debuggerPresent()
#endif #endif
} }
static int assertionHandler(const char* expr, const char* file, int line, const char* function) static int testAssertionHandler(const char* expr, const char* file, int line, const char* function)
{ {
if (debuggerPresent()) if (debuggerPresent())
LUAU_DEBUGBREAK(); LUAU_DEBUGBREAK();
@ -218,7 +221,7 @@ static void setFastFlags(const std::vector<doctest::String>& flags)
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
Luau::assertHandler() = assertionHandler; Luau::assertHandler() = testAssertionHandler;
doctest::registerReporter<BoostLikeReporter>("boost", 0, true); doctest::registerReporter<BoostLikeReporter>("boost", 0, true);

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="Luau::AnyTypeVar">
<DisplayString>AnyTypeVar</DisplayString>
</Type>
<Type Name="Luau::Variant&lt;*&gt;">
<DisplayString Condition="typeId == 0" Optional="true">{{ index=0, value={*($T1*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 1" Optional="true">{{ index=1, value={*($T2*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 2" Optional="true">{{ index=2, value={*($T3*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 3" Optional="true">{{ index=3, value={*($T4*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 4" Optional="true">{{ index=4, value={*($T5*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 5" Optional="true">{{ index=5, value={*($T6*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 6" Optional="true">{{ index=6, value={*($T7*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 7" Optional="true">{{ index=7, value={*($T8*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 8" Optional="true">{{ index=8, value={*($T9*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 9" Optional="true">{{ index=9, value={*($T10*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 10" Optional="true">{{ index=10, value={*($T11*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 11" Optional="true">{{ index=11, value={*($T12*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 12" Optional="true">{{ index=12, value={*($T13*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 13" Optional="true">{{ index=13, value={*($T14*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 14" Optional="true">{{ index=14, value={*($T15*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 15" Optional="true">{{ index=15, value={*($T16*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 16" Optional="true">{{ index=16, value={*($T17*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 17" Optional="true">{{ index=17, value={*($T18*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 18" Optional="true">{{ index=18, value={*($T19*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 19" Optional="true">{{ index=19, value={*($T20*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 20" Optional="true">{{ index=20, value={*($T21*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 21" Optional="true">{{ index=21, value={*($T22*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 22" Optional="true">{{ index=22, value={*($T23*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 23" Optional="true">{{ index=23, value={*($T24*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 24" Optional="true">{{ index=24, value={*($T25*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 25" Optional="true">{{ index=25, value={*($T26*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 26" Optional="true">{{ index=26, value={*($T27*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 27" Optional="true">{{ index=27, value={*($T28*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 28" Optional="true">{{ index=28, value={*($T29*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 29" Optional="true">{{ index=29, value={*($T30*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 30" Optional="true">{{ index=30, value={*($T31*)storage} }}</DisplayString>
<DisplayString Condition="typeId == 31" Optional="true">{{ index=31, value={*($T32*)storage} }}</DisplayString>
<Expand>
<Item Name="index">typeId</Item>
<Item Name="[value]" Condition="typeId == 0" Optional="true">*($T1*)storage</Item>
<Item Name="[value]" Condition="typeId == 1" Optional="true">*($T2*)storage</Item>
<Item Name="[value]" Condition="typeId == 2" Optional="true">*($T3*)storage</Item>
<Item Name="[value]" Condition="typeId == 3" Optional="true">*($T4*)storage</Item>
<Item Name="[value]" Condition="typeId == 4" Optional="true">*($T5*)storage</Item>
<Item Name="[value]" Condition="typeId == 5" Optional="true">*($T6*)storage</Item>
<Item Name="[value]" Condition="typeId == 6" Optional="true">*($T7*)storage</Item>
<Item Name="[value]" Condition="typeId == 7" Optional="true">*($T8*)storage</Item>
<Item Name="[value]" Condition="typeId == 8" Optional="true">*($T9*)storage</Item>
<Item Name="[value]" Condition="typeId == 9" Optional="true">*($T10*)storage</Item>
<Item Name="[value]" Condition="typeId == 10" Optional="true">*($T11*)storage</Item>
<Item Name="[value]" Condition="typeId == 11" Optional="true">*($T12*)storage</Item>
<Item Name="[value]" Condition="typeId == 12" Optional="true">*($T13*)storage</Item>
<Item Name="[value]" Condition="typeId == 13" Optional="true">*($T14*)storage</Item>
<Item Name="[value]" Condition="typeId == 14" Optional="true">*($T15*)storage</Item>
<Item Name="[value]" Condition="typeId == 15" Optional="true">*($T16*)storage</Item>
<Item Name="[value]" Condition="typeId == 16" Optional="true">*($T17*)storage</Item>
<Item Name="[value]" Condition="typeId == 17" Optional="true">*($T18*)storage</Item>
<Item Name="[value]" Condition="typeId == 18" Optional="true">*($T19*)storage</Item>
<Item Name="[value]" Condition="typeId == 19" Optional="true">*($T20*)storage</Item>
<Item Name="[value]" Condition="typeId == 20" Optional="true">*($T21*)storage</Item>
<Item Name="[value]" Condition="typeId == 21" Optional="true">*($T22*)storage</Item>
<Item Name="[value]" Condition="typeId == 22" Optional="true">*($T23*)storage</Item>
<Item Name="[value]" Condition="typeId == 23" Optional="true">*($T24*)storage</Item>
<Item Name="[value]" Condition="typeId == 24" Optional="true">*($T25*)storage</Item>
<Item Name="[value]" Condition="typeId == 25" Optional="true">*($T26*)storage</Item>
<Item Name="[value]" Condition="typeId == 26" Optional="true">*($T27*)storage</Item>
<Item Name="[value]" Condition="typeId == 27" Optional="true">*($T28*)storage</Item>
<Item Name="[value]" Condition="typeId == 28" Optional="true">*($T29*)storage</Item>
<Item Name="[value]" Condition="typeId == 29" Optional="true">*($T30*)storage</Item>
<Item Name="[value]" Condition="typeId == 30" Optional="true">*($T31*)storage</Item>
<Item Name="[value]" Condition="typeId == 31" Optional="true">*($T32*)storage</Item>
</Expand>
</Type>
</AutoVisualizer>

25
tools/natvis/Ast.natvis Normal file
View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="Luau::AstArray&lt;*&gt;">
<DisplayString>AstArray size={size}</DisplayString>
<Expand>
<Item Name="[size]">size</Item>
<ArrayItems>
<Size>size</Size>
<ValuePointer>data</ValuePointer>
</ArrayItems>
</Expand>
</Type>
<Type Name="Luau::TempVector&lt;*&gt;">
<Expand>
<Item Name="[size]">size_</Item>
<ArrayItems>
<Size>size_</Size>
<ValuePointer>storage._Mypair._Myval2._Myfirst + offset</ValuePointer>
</ArrayItems>
</Expand>
</Type>
</AutoVisualizer>

269
tools/natvis/VM.natvis Normal file
View file

@ -0,0 +1,269 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="::lua_TValue">
<DisplayString Condition="tt == lua_Type::LUA_TNIL">nil</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TBOOLEAN">{(bool)value.b}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TLIGHTUSERDATA">lightuserdata {value.p}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TNUMBER">number = {value.n}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TVECTOR">vector = {value.v[0]}, {value.v[1]}, {*(float*)&amp;extra}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TSTRING">{value.gc->ts}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TTABLE">{value.gc->h}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TFUNCTION">function {value.gc->cl,view(short)}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TUSERDATA">userdata {value.gc->u}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TTHREAD">thread {value.gc->th}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TPROTO">proto {value.gc->p}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TUPVAL">upvalue {value.gc->uv}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TDEADKEY">deadkey</DisplayString>
<DisplayString>empty</DisplayString>
<Expand>
<Item Name="[lightuserdata]" Condition="tt == lua_Type::LUA_TLIGHTUSERDATA">value.p</Item>
<Item Name="[string]" Condition="tt == lua_Type::LUA_TSTRING">value.gc->ts</Item>
<Item Name="[table]" Condition="tt == lua_Type::LUA_TTABLE">value.gc->h</Item>
<Item Name="[C function]" Condition="tt == lua_Type::LUA_TFUNCTION &amp;&amp; value.gc-&gt;cl.isC">value.gc->cl</Item>
<Item Name="[Lua function]" Condition="tt == lua_Type::LUA_TFUNCTION &amp;&amp; !value.gc-&gt;cl.isC">value.gc->cl</Item>
<Item Name="[userdata]" Condition="tt == lua_Type::LUA_TUSERDATA">value.gc->u</Item>
<Item Name="[thread]" Condition="tt == lua_Type::LUA_TTHREAD">value.gc->th</Item>
<Item Name="[proto]" Condition="tt == lua_Type::LUA_TPROTO">value.gc->p</Item>
<Item Name="[upvalue]" Condition="tt == lua_Type::LUA_TUPVAL">value.gc->uv</Item>
<Synthetic Name="[gc]" Condition="tt >= lua_Type::LUA_TSTRING">
<DisplayString Condition="value.gc-&gt;gch.marked &amp; 8">fixed ({(int)value.gc-&gt;gch.marked})</DisplayString>
<DisplayString Condition="value.gc-&gt;gch.marked &amp; 4">black ({(int)value.gc-&gt;gch.marked})</DisplayString>
<DisplayString Condition="value.gc-&gt;gch.marked &amp; 1">white ({(int)value.gc-&gt;gch.marked})</DisplayString>
<DisplayString Condition="value.gc-&gt;gch.marked &amp; 2">white ({(int)value.gc-&gt;gch.marked})</DisplayString>
<DisplayString Condition="(value.gc-&gt;gch.marked &amp; 3) == 0">gray ({(int)value.gc-&gt;gch.marked})</DisplayString>
</Synthetic>
</Expand>
</Type>
<Type Name="::TKey">
<DisplayString Condition="tt == lua_Type::LUA_TNIL">nil</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TBOOLEAN">{(bool)value.b}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TLIGHTUSERDATA">lightuserdata {value.p}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TNUMBER">number = {value.n}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TVECTOR">vector = {value.v[0]}, {value.v[1]}, {*(float*)&amp;extra}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TSTRING">{value.gc->ts}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TTABLE">{value.gc->h}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TFUNCTION">function {value.gc->cl,view(short)}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TUSERDATA">userdata {value.gc->u}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TTHREAD">thread {value.gc->th}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TPROTO">proto {value.gc->p}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TUPVAL">upvalue {value.gc->uv}</DisplayString>
<DisplayString Condition="tt == lua_Type::LUA_TDEADKEY">deadkey</DisplayString>
<DisplayString>empty</DisplayString>
<Expand>
<Item Name="[lightuserdata]" Condition="tt == lua_Type::LUA_TLIGHTUSERDATA">(void**)value.p</Item>
<Item Name="[string]" Condition="tt == lua_Type::LUA_TSTRING">value.gc->ts</Item>
<Item Name="[table]" Condition="tt == lua_Type::LUA_TTABLE">value.gc->h</Item>
<Item Name="[C function]" Condition="tt == lua_Type::LUA_TFUNCTION &amp;&amp; value.gc-&gt;cl.isC">value.gc->cl</Item>
<Item Name="[Lua function]" Condition="tt == lua_Type::LUA_TFUNCTION &amp;&amp; !value.gc-&gt;cl.isC">value.gc->cl</Item>
<Item Name="[userdata]" Condition="tt == lua_Type::LUA_TUSERDATA">value.gc->u</Item>
<Item Name="[thread]" Condition="tt == lua_Type::LUA_TTHREAD">value.gc->th</Item>
<Item Name="[proto]" Condition="tt == lua_Type::LUA_TPROTO">value.gc->p</Item>
<Item Name="[upvalue]" Condition="tt == lua_Type::LUA_TUPVAL">value.gc->uv</Item>
<Item Name="[next]">next</Item>
</Expand>
</Type>
<Type Name="::LuaNode">
<DisplayString Condition="key.tt != lua_Type::LUA_TNIL || val.tt != lua_Type::LUA_TNIL">{key,na} = {val}</DisplayString>
<DisplayString Condition="key.tt == lua_Type::LUA_TNIL &amp;&amp; val.tt == 0">---</DisplayString>
</Type>
<Type Name="::Table">
<DisplayString>table</DisplayString>
<Expand>
<Item Name="metatable" Condition="metatable">metatable</Item>
<Synthetic Name="hash">
<DisplayString>[size] {1&lt;&lt;lsizenode}</DisplayString>
<Expand>
<IndexListItems>
<Size>1&lt;&lt;lsizenode</Size>
<ValueNode>node[$i]</ValueNode>
</IndexListItems>
</Expand>
</Synthetic>
<Synthetic Name="array">
<DisplayString>[size] {sizearray}</DisplayString>
<Expand>
<IndexListItems>
<Size>sizearray</Size>
<ValueNode>array[$i]</ValueNode>
</IndexListItems>
</Expand>
</Synthetic>
</Expand>
</Type>
<Type Name="::Udata">
<Expand>
<CustomListItems>
<Variable Name="count" InitialValue="1&lt;&lt;metatable->lsizenode" />
<Variable Name="i" InitialValue="0" />
<Size>1</Size>
<Loop>
<Break Condition="i &gt;= count" />
<If Condition="metatable->node[i].key.tt == lua_Type::LUA_TSTRING">
<If Condition="strcmp(metatable-&gt;node[i].key.value.gc->ts.data, &quot;__type&quot;) == 0">
<Item Name="[type]">metatable-&gt;node[i].val</Item>
<Break/>
</If>
</If>
<Exec>i = i + 1</Exec>
</Loop>
<If Condition="i == count">
<Item Name="[type]">"unknown",sb</Item>
</If>
</CustomListItems>
<Item Name="tag">tag</Item>
<Item Name="len">len</Item>
<Item Name="metatable">metatable</Item>
<Item Name="data">data</Item>
</Expand>
</Type>
<Type Name="::Closure">
<DisplayString Condition="isC == 1" IncludeView="short">{c.f,na}</DisplayString>
<DisplayString Condition="isC == 0" IncludeView="short">{l.p,na}</DisplayString>
<DisplayString Condition="isC == 1" ExcludeView="short">{c}</DisplayString>
<DisplayString Condition="isC == 0" ExcludeView="short">{l}</DisplayString>
<DisplayString>invalid</DisplayString>
</Type>
<Type Name="::TString">
<DisplayString>{data,s}</DisplayString>
</Type>
<Type Name ="::lua_State">
<DisplayString Condition="ci-&gt;func-&gt;value.gc-&gt;cl.isC">
{ci-&gt;func-&gt;value.gc-&gt;cl.c.f,na}
</DisplayString>
<DisplayString Condition="!ci-&gt;func-&gt;value.gc-&gt;cl.isC &amp;&amp; ci-&gt;func-&gt;value.gc-&gt;cl.l.p-&gt;debugname" Optional="true">
{ci-&gt;func-&gt;value.gc-&gt;cl.l.p-&gt;source-&gt;data,sb}:{ci-&gt;func-&gt;value.gc-&gt;cl.l.p-&gt;linedefined,d} {ci-&gt;func-&gt;value.gc-&gt;cl.l.p-&gt;debugname->data,sb}
</DisplayString>
<DisplayString Condition="!ci-&gt;func-&gt;value.gc-&gt;cl.isC" Optional="true">
{ci-&gt;func-&gt;value.gc-&gt;cl.l.p-&gt;source-&gt;data,sb}:{ci-&gt;func-&gt;value.gc-&gt;cl.l.p-&gt;linedefined,d}
</DisplayString>
<DisplayString>thread</DisplayString>
<Expand>
<Synthetic Name="[call stack]">
<DisplayString>{ci-base_ci} frames</DisplayString>
<Expand>
<IndexListItems>
<Size>ci-base_ci</Size>
<!-- the +1 is omitted here to avoid some issues with a blank call -->
<ValueNode>
base_ci[ci-base_ci - $i].func-&gt;value.gc-&gt;cl,view(short)
</ValueNode>
</IndexListItems>
</Expand>
</Synthetic>
<Synthetic Name="[top frame stack]">
<DisplayString>{top-base} values</DisplayString>
<Expand>
<ArrayItems>
<Size>top-base</Size>
<ValuePointer>base</ValuePointer>
</ArrayItems>
</Expand>
</Synthetic>
<Synthetic Name="[stack]">
<DisplayString>{top-stack} values</DisplayString>
<Expand>
<ArrayItems>
<Size>top-stack</Size>
<ValuePointer>stack</ValuePointer>
</ArrayItems>
</Expand>
</Synthetic>
<Synthetic Name="[open upvals]" Condition="openupval != 0">
<Expand>
<LinkedListItems>
<HeadPointer>openupval</HeadPointer>
<NextPointer>u.l.next</NextPointer>
<ValueNode>this</ValueNode>
</LinkedListItems>
</Expand>
</Synthetic>
<Item Name="l_gt">l_gt</Item>
<Item Name="env">env</Item>
<Item Name="userdata">userdata</Item>
</Expand>
</Type>
<Type Name="::Proto">
<DisplayString Condition="debugname">{source->data,sb}:{linedefined} function {debugname->data,sb} [{(int)numparams} arg, {(int)nups} upval]</DisplayString>
<DisplayString Condition="!debugname">{source->data,sb}:{linedefined} [{(int)numparams} arg, {(int)nups} upval]</DisplayString>
<Expand>
<Item Name="debugname">debugname</Item>
<Synthetic Name="[constants]">
<DisplayString>constants</DisplayString>
<Expand>
<IndexListItems>
<Size>sizek</Size>
<ValueNode>k[$i]</ValueNode>
</IndexListItems>
</Expand>
</Synthetic>
<Synthetic Name="[locals]">
<DisplayString>locals</DisplayString>
<Expand>
<IndexListItems>
<Size>sizelocvars</Size>
<ValueNode>locvars[$i]</ValueNode>
</IndexListItems>
</Expand>
</Synthetic>
<Synthetic Name="[bytecode]">
<DisplayString>bytecode</DisplayString>
<Expand>
<IndexListItems>
<Size>sizecode</Size>
<ValueNode>code[$i]</ValueNode>
</IndexListItems>
</Expand>
</Synthetic>
<Synthetic Name="[functions]">
<DisplayString>functions</DisplayString>
<Expand>
<IndexListItems>
<Size>sizep</Size>
<ValueNode>p[$i]</ValueNode>
</IndexListItems>
</Expand>
</Synthetic>
<Synthetic Name="[upvals]">
<DisplayString>upvals</DisplayString>
<Expand>
<IndexListItems>
<Size>sizeupvalues</Size>
<ValueNode>upvalues[$i]</ValueNode>
</IndexListItems>
</Expand>
</Synthetic>
<Item Name="source">source</Item>
</Expand>
</Type>
<Type Name="::GCheader">
<Expand>
<Synthetic Name="[type]">
<DisplayString>{(lua_Type)tt}</DisplayString>
</Synthetic>
<Synthetic Name="[gc]">
<DisplayString Condition="marked &amp; 8">fixed ({(int)marked})</DisplayString>
<DisplayString Condition="marked &amp; 4">black ({(int)marked})</DisplayString>
<DisplayString Condition="marked &amp; 1">white ({(int)marked})</DisplayString>
<DisplayString Condition="marked &amp; 2">white ({(int)marked})</DisplayString>
<DisplayString Condition="(marked &amp; 3) == 0">gray ({(int)marked})</DisplayString>
<DisplayString>unknown</DisplayString>
</Synthetic>
<Item Name="memcat">memcat</Item>
</Expand>
</Type>
</AutoVisualizer>