From 5b784650594ffbef70c731fb6c9cef136226121c Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Thu, 17 Feb 2022 17:18:01 -0800 Subject: [PATCH] Sync to upstream/release/514 (#372) --- Analysis/include/Luau/Documentation.h | 3 + Analysis/include/Luau/Frontend.h | 3 +- Analysis/include/Luau/Linter.h | 7 +- Analysis/include/Luau/Module.h | 4 +- Analysis/include/Luau/Quantify.h | 5 +- Analysis/include/Luau/Substitution.h | 20 +- Analysis/include/Luau/TypeInfer.h | 45 +++- Analysis/src/Autocomplete.cpp | 45 ++-- Analysis/src/Config.cpp | 2 +- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 2 +- Analysis/src/Frontend.cpp | 35 ++- Analysis/src/JsonEncoder.cpp | 21 +- Analysis/src/Linter.cpp | 126 ++++++++- Analysis/src/Quantify.cpp | 10 +- Analysis/src/Substitution.cpp | 23 -- Analysis/src/TopoSortStatements.cpp | 3 +- Analysis/src/Transpiler.cpp | 8 +- Analysis/src/TypeAttach.cpp | 4 +- Analysis/src/TypeInfer.cpp | 142 +++-------- Analysis/src/TypeUtils.cpp | 12 + Analysis/src/TypeVar.cpp | 6 +- Analysis/src/TypedAllocator.cpp | 3 + Analysis/src/Unifier.cpp | 16 +- Ast/include/Luau/Ast.h | 17 +- Ast/include/Luau/Lexer.h | 5 + Ast/include/Luau/ParseResult.h | 69 +++++ Ast/include/Luau/Parser.h | 56 +--- Ast/src/Ast.cpp | 29 +-- Ast/src/Lexer.cpp | 12 +- Ast/src/Parser.cpp | 161 +++++++----- Ast/src/TimeTrace.cpp | 6 + CLI/FileUtils.cpp | 7 +- CLI/Repl.cpp | 167 ++++++++---- CMakeLists.txt | 14 + Sources.cmake | 1 + VM/src/lapi.cpp | 25 +- VM/src/ldebug.cpp | 2 +- VM/src/ldo.cpp | 13 +- VM/src/ldo.h | 2 +- VM/src/lgc.cpp | 4 +- VM/src/lgc.h | 2 +- VM/src/lgcdebug.cpp | 10 +- VM/src/lperf.cpp | 6 + VM/src/lstate.cpp | 35 ++- VM/src/lstate.h | 8 +- VM/src/lvmexecute.cpp | 2 +- VM/src/lvmload.cpp | 6 +- VM/src/lvmutils.cpp | 4 +- fuzz/linter.cpp | 2 +- fuzz/proto.cpp | 2 +- tests/Autocomplete.test.cpp | 22 +- tests/Compiler.test.cpp | 14 +- tests/Conformance.test.cpp | 8 + tests/Fixture.cpp | 4 +- tests/Fixture.h | 1 - tests/Frontend.test.cpp | 3 - tests/Linter.test.cpp | 48 +++- tests/NonstrictMode.test.cpp | 1 - tests/Parser.test.cpp | 51 ++-- tests/Repl.test.cpp | 209 ++++++++++++++- tests/RequireTracer.test.cpp | 2 +- tests/TypeInfer.aliases.test.cpp | 6 +- tests/TypeInfer.annotations.test.cpp | 1 - tests/TypeInfer.builtins.test.cpp | 1 - tests/TypeInfer.classes.test.cpp | 1 - tests/TypeInfer.definitions.test.cpp | 1 - tests/TypeInfer.generics.test.cpp | 1 - tests/TypeInfer.intersectionTypes.test.cpp | 1 - tests/TypeInfer.provisional.test.cpp | 1 - tests/TypeInfer.singletons.test.cpp | 2 - tests/TypeInfer.tables.test.cpp | 43 +++- tests/TypeInfer.test.cpp | 27 +- tests/TypeInfer.tryUnify.test.cpp | 1 - tests/TypeInfer.typePacks.cpp | 3 - tests/TypeInfer.unionTypes.test.cpp | 1 - tests/TypePack.test.cpp | 1 - tests/TypeVar.test.cpp | 1 - tests/conformance/coroutine.lua | 9 + tests/conformance/coverage.lua | 8 + tests/conformance/debug.lua | 3 + tests/main.cpp | 7 +- tools/natvis/Analysis.natvis | 78 ++++++ tools/natvis/Ast.natvis | 25 ++ tools/natvis/VM.natvis | 269 ++++++++++++++++++++ 84 files changed, 1492 insertions(+), 574 deletions(-) create mode 100644 Ast/include/Luau/ParseResult.h create mode 100644 tools/natvis/Analysis.natvis create mode 100644 tools/natvis/Ast.natvis create mode 100644 tools/natvis/VM.natvis diff --git a/Analysis/include/Luau/Documentation.h b/Analysis/include/Luau/Documentation.h index 68ff3a7c2..7a2b56ffb 100644 --- a/Analysis/include/Luau/Documentation.h +++ b/Analysis/include/Luau/Documentation.h @@ -21,6 +21,7 @@ struct BasicDocumentation { std::string documentation; std::string learnMoreLink; + std::string codeSample; }; struct FunctionParameterDocumentation @@ -37,6 +38,7 @@ struct FunctionDocumentation std::vector parameters; std::vector returns; std::string learnMoreLink; + std::string codeSample; }; struct OverloadedFunctionDocumentation @@ -52,6 +54,7 @@ struct TableDocumentation std::string documentation; Luau::DenseHashMap keys; std::string learnMoreLink; + std::string codeSample; }; using DocumentationDatabase = Luau::DenseHashMap; diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index 1f64db30c..0bf8f362c 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -24,6 +24,7 @@ struct TypeChecker; struct FileResolver; struct ModuleResolver; struct ParseResult; +struct HotComment; struct LoadDefinitionFileResult { @@ -35,7 +36,7 @@ struct LoadDefinitionFileResult LoadDefinitionFileResult loadDefinitionFile( TypeChecker& typeChecker, ScopePtr targetScope, std::string_view definition, const std::string& packageName); -std::optional parseMode(const std::vector& hotcomments); +std::optional parseMode(const std::vector& hotcomments); std::vector parsePathExpr(const AstExpr& pathExpr); diff --git a/Analysis/include/Luau/Linter.h b/Analysis/include/Luau/Linter.h index ec3c124d7..6c7ce47fe 100644 --- a/Analysis/include/Luau/Linter.h +++ b/Analysis/include/Luau/Linter.h @@ -14,6 +14,7 @@ class AstStat; class AstNameTable; struct TypeChecker; struct Module; +struct HotComment; using ScopePtr = std::shared_ptr; @@ -50,6 +51,7 @@ struct LintWarning Code_TableOperations = 23, Code_DuplicateCondition = 24, Code_MisleadingAndOr = 25, + Code_CommentDirective = 26, Code__Count }; @@ -60,7 +62,7 @@ struct LintWarning static const char* getName(Code code); static Code parseName(const char* name); - static uint64_t parseMask(const std::vector& hotcomments); + static uint64_t parseMask(const std::vector& hotcomments); }; struct LintResult @@ -90,7 +92,8 @@ struct LintOptions void setDefaults(); }; -std::vector lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module, const LintOptions& options); +std::vector lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module, + const std::vector& hotcomments, const LintOptions& options); std::vector getDeprecatedGlobals(const AstNameTable& names); diff --git a/Analysis/include/Luau/Module.h b/Analysis/include/Luau/Module.h index 1bf0473c1..612007711 100644 --- a/Analysis/include/Luau/Module.h +++ b/Analysis/include/Luau/Module.h @@ -6,7 +6,7 @@ #include "Luau/TypedAllocator.h" #include "Luau/ParseOptions.h" #include "Luau/Error.h" -#include "Luau/Parser.h" +#include "Luau/ParseResult.h" #include #include @@ -37,8 +37,8 @@ struct SourceModule AstStatBlock* root = nullptr; std::optional mode; - uint64_t ignoreLints = 0; + std::vector hotcomments; std::vector commentLocations; SourceModule() diff --git a/Analysis/include/Luau/Quantify.h b/Analysis/include/Luau/Quantify.h index f46df1460..e48cad404 100644 --- a/Analysis/include/Luau/Quantify.h +++ b/Analysis/include/Luau/Quantify.h @@ -6,9 +6,6 @@ namespace Luau { -struct Module; -using ModulePtr = std::shared_ptr; - -void quantify(ModulePtr module, TypeId ty, TypeLevel level); +void quantify(TypeId ty, TypeLevel level); } // namespace Luau diff --git a/Analysis/include/Luau/Substitution.h b/Analysis/include/Luau/Substitution.h index f85b42690..9662d5b39 100644 --- a/Analysis/include/Luau/Substitution.h +++ b/Analysis/include/Luau/Substitution.h @@ -101,9 +101,6 @@ struct Tarjan // This is hot code, so we optimize recursion to a stack. TarjanResult loop(); - // Clear the state - void clear(); - // Find or create the index for a vertex. // Return a boolean which is `true` if it's a freshly created index. std::pair indexify(TypeId ty); @@ -166,7 +163,17 @@ struct FindDirty : Tarjan // and replaces them with clean ones. 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 newTypes{nullptr}; DenseHashMap newPacks{nullptr}; @@ -192,12 +199,13 @@ struct Substitution : FindDirty template TypeId addType(const T& tv) { - return currentModule->internalTypes.addType(tv); + return arena->addType(tv); } + template TypePackId addTypePack(const T& tp) { - return currentModule->internalTypes.addTypePack(TypePackVar{tp}); + return arena->addTypePack(TypePackVar{tp}); } }; diff --git a/Analysis/include/Luau/TypeInfer.h b/Analysis/include/Luau/TypeInfer.h index 5592fa1f5..3c5ded3cc 100644 --- a/Analysis/include/Luau/TypeInfer.h +++ b/Analysis/include/Luau/TypeInfer.h @@ -5,7 +5,6 @@ #include "Luau/Error.h" #include "Luau/Module.h" #include "Luau/Symbol.h" -#include "Luau/Parser.h" #include "Luau/Substitution.h" #include "Luau/TxnLog.h" #include "Luau/TypePack.h" @@ -37,6 +36,15 @@ struct Unifier; // A substitution which replaces generic types in a given set by free types. struct ReplaceGenerics : Substitution { + ReplaceGenerics( + const TxnLog* log, TypeArena* arena, TypeLevel level, const std::vector& generics, const std::vector& genericPacks) + : Substitution(log, arena) + , level(level) + , generics(generics) + , genericPacks(genericPacks) + { + } + TypeLevel level; std::vector generics; std::vector genericPacks; @@ -50,8 +58,13 @@ struct ReplaceGenerics : Substitution // A substitution which replaces generic functions by monomorphic functions struct Instantiation : Substitution { + Instantiation(const TxnLog* log, TypeArena* arena, TypeLevel level) + : Substitution(log, arena) + , level(level) + { + } + TypeLevel level; - ReplaceGenerics replaceGenerics; bool ignoreChildren(TypeId ty) override; bool isDirty(TypeId ty) override; bool isDirty(TypePackId tp) override; @@ -62,6 +75,12 @@ struct Instantiation : Substitution // A substitution which replaces free types by generic types. struct Quantification : Substitution { + Quantification(TypeArena* arena, TypeLevel level) + : Substitution(TxnLog::empty(), arena) + , level(level) + { + } + TypeLevel level; std::vector generics; std::vector genericPacks; @@ -74,6 +93,13 @@ struct Quantification : Substitution // A substitution which replaces free types by any struct Anyification : Substitution { + Anyification(TypeArena* arena, TypeId anyType, TypePackId anyTypePack) + : Substitution(TxnLog::empty(), arena) + , anyType(anyType) + , anyTypePack(anyTypePack) + { + } + TypeId anyType; TypePackId anyTypePack; 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 struct ApplyTypeFunction : Substitution { + ApplyTypeFunction(TypeArena* arena, TypeLevel level) + : Substitution(TxnLog::empty(), arena) + , level(level) + , encounteredForwardedType(false) + { + } + TypeLevel level; bool encounteredForwardedType; std::unordered_map typeArguments; @@ -351,8 +384,7 @@ struct TypeChecker // Note: `scope` must be a fresh scope. GenericTypeDefinitions createGenericTypes(const ScopePtr& scope, std::optional levelOpt, const AstNode& node, - const AstArray& genericNames, const AstArray& genericPackNames, - bool useCache = false); + const AstArray& genericNames, const AstArray& genericPackNames, bool useCache = false); public: ErrorVec resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense); @@ -392,11 +424,6 @@ struct TypeChecker ModulePtr currentModule; ModuleName currentModuleName; - Instantiation instantiation; - Quantification quantification; - Anyification anyification; - ApplyTypeFunction applyTypeFunction; - std::function prepareModuleScope; InternalErrorReporter* iceHandler; diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 85099e12c..5a1ae3975 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -7,6 +7,7 @@ #include "Luau/ToString.h" #include "Luau/TypeInfer.h" #include "Luau/TypePack.h" +#include "Luau/Parser.h" // TODO: only needed for autocompleteSource which is deprecated #include #include @@ -14,9 +15,9 @@ LUAU_FASTFLAG(LuauUseCommittingTxnLog) LUAU_FASTFLAGVARIABLE(LuauAutocompleteAvoidMutation, false); -LUAU_FASTFLAGVARIABLE(LuauCompleteBrokenStringParams, false); LUAU_FASTFLAGVARIABLE(LuauMissingFollowACMetatables, false); LUAU_FASTFLAGVARIABLE(PreferToCallFunctionsForIntersects, false); +LUAU_FASTFLAGVARIABLE(LuauIfElseExprFixCompletionIssue, false); static const std::unordered_set kStatementStartingKeywords = { "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 // 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); 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)) { @@ -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()) { @@ -1136,7 +1140,7 @@ static AutocompleteEntryMap autocompleteStatement( AstNode* parent = ancestry.rbegin()[1]; if (AstStatIf* statIf = parent->as()) { - 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("elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}); @@ -1164,8 +1168,7 @@ static AutocompleteEntryMap autocompleteStatement( return result; } -// Returns true if completions were generated (completions will be inserted into 'outResult') -// Returns false if no completions were generated +// Returns true iff `node` was handled by this function (completions, if any, are returned in `outResult`) static bool autocompleteIfElseExpression( const AstNode* node, const std::vector& ancestry, const Position& position, AutocompleteEntryMap& outResult) { @@ -1173,6 +1176,13 @@ static bool autocompleteIfElseExpression( if (!parent) return false; + if (FFlag::LuauIfElseExprFixCompletionIssue && node->is()) + { + // 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(); if (!ifElseExpr || ifElseExpr->condition->location.containsClosed(position)) { @@ -1310,7 +1320,7 @@ static std::optional autocompleteStringParams(const Source return std::nullopt; } - if (!nodes.back()->is() && (!FFlag::LuauCompleteBrokenStringParams || !nodes.back()->is())) + if (!nodes.back()->is() && !nodes.back()->is()) { return std::nullopt; } @@ -1408,8 +1418,8 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M } else if (auto typeReference = node->as()) { - if (typeReference->hasPrefix) - return {autocompleteModuleTypes(*module, position, typeReference->prefix.value), finder.ancestry}; + if (typeReference->prefix) + return {autocompleteModuleTypes(*module, position, typeReference->prefix->value), finder.ancestry}; else 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()) { - 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}; - 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}; else return {}; @@ -1449,7 +1459,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M if (!statForIn->hasIn || position <= statForIn->inLocation.begin) { 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 // 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(finder.ancestry); statWhile && !statWhile->hasDo) return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry}; - else if (AstStatIf* statIf = node->as(); statIf && !statIf->hasElse) + else if (AstStatIf* statIf = node->as(); statIf && !statIf->elseLocation.has_value()) { return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry}; @@ -1508,11 +1518,11 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M { if (statIf->condition->is()) 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}; } else if (AstStatIf* statIf = extractStat(finder.ancestry); - statIf && (!statIf->hasThen || statIf->thenLocation.containsClosed(position))) + statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position))) return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, finder.ancestry}; else if (AstStatRepeat* statRepeat = node->as(); statRepeat && statRepeat->condition->is()) 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) { + // TODO: Remove #include "Luau/Parser.h" with this function auto sourceModule = std::make_unique(); ParseOptions parseOptions; parseOptions.captureComments = true; diff --git a/Analysis/src/Config.cpp b/Analysis/src/Config.cpp index d9fc44f81..35a2259d1 100644 --- a/Analysis/src/Config.cpp +++ b/Analysis/src/Config.cpp @@ -1,7 +1,7 @@ // 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/Parser.h" +#include "Luau/Lexer.h" #include "Luau/StringUtils.h" namespace diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index f3ef88fc5..bf6e1193f 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -167,7 +167,7 @@ declare function gcinfo(): number foreach: ({[K]: V}, (K, V) -> ()) -> (), foreachi: ({V}, (number, V) -> ()) -> (), - move: ({V}, number, number, number, {V}?) -> (), + move: ({V}, number, number, number, {V}?) -> {V}, clear: ({[K]: V}) -> (), freeze: ({[K]: V}) -> {[K]: V}, diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 9001b19df..d8906f6e7 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -4,6 +4,7 @@ #include "Luau/Common.h" #include "Luau/Config.h" #include "Luau/FileResolver.h" +#include "Luau/Parser.h" #include "Luau/Scope.h" #include "Luau/StringUtils.h" #include "Luau/TimeTrace.h" @@ -16,23 +17,25 @@ #include LUAU_FASTFLAG(LuauInferInNoCheckMode) -LUAU_FASTFLAGVARIABLE(LuauTypeCheckTwice, false) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false) namespace Luau { -std::optional parseMode(const std::vector& hotcomments) +std::optional parseMode(const std::vector& 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; - if (hc == "nonstrict") + if (hc.content == "nonstrict") return Mode::Nonstrict; - if (hc == "strict") + if (hc.content == "strict") return Mode::Strict; } @@ -607,13 +610,15 @@ std::pair Frontend::lintFragment(std::string_view sour SourceModule sourceModule = parse(ModuleName{}, source, config.parseOptions); + uint64_t ignoreLints = LintWarning::parseMask(sourceModule.hotcomments); + Luau::LintOptions lintOptions = enabledLintWarnings.value_or(config.enabledLint); - lintOptions.warningMask &= sourceModule.ignoreLints; + lintOptions.warningMask &= ~ignoreLints; double timestamp = getTimestamp(); - std::vector warnings = - Luau::lint(sourceModule.root, *sourceModule.names.get(), typeChecker.globalScope, nullptr, enabledLintWarnings.value_or(config.enabledLint)); + std::vector warnings = Luau::lint(sourceModule.root, *sourceModule.names.get(), typeChecker.globalScope, nullptr, + sourceModule.hotcomments, enabledLintWarnings.value_or(config.enabledLint)); stats.timeLint += getTimestamp() - timestamp; @@ -651,8 +656,10 @@ LintResult Frontend::lint(const SourceModule& module, std::optionalgetConfig(module.name); + uint64_t ignoreLints = LintWarning::parseMask(module.hotcomments); + LintOptions options = enabledLintWarnings.value_or(config.enabledLint); - options.warningMask &= ~module.ignoreLints; + options.warningMask &= ~ignoreLints; Mode mode = module.mode.value_or(config.mode); if (mode != Mode::NoCheck) @@ -671,7 +678,7 @@ LintResult Frontend::lint(const SourceModule& module, std::optional warnings = Luau::lint(module.root, *module.names, environmentScope, modulePtr.get(), options); + std::vector warnings = Luau::lint(module.root, *module.names, environmentScope, modulePtr.get(), module.hotcomments, options); stats.timeLint += getTimestamp() - timestamp; @@ -839,7 +846,6 @@ SourceModule Frontend::parse(const ModuleName& name, std::string_view src, const { sourceModule.root = parseResult.root; sourceModule.mode = parseMode(parseResult.hotcomments); - sourceModule.ignoreLints = LintWarning::parseMask(parseResult.hotcomments); } else { @@ -848,8 +854,13 @@ SourceModule Frontend::parse(const ModuleName& name, std::string_view src, const } sourceModule.name = name; + if (parseOptions.captureComments) + { sourceModule.commentLocations = std::move(parseResult.commentLocations); + sourceModule.hotcomments = std::move(parseResult.hotcomments); + } + return sourceModule; } diff --git a/Analysis/src/JsonEncoder.cpp b/Analysis/src/JsonEncoder.cpp index 0d48f2ca9..ec3991581 100644 --- a/Analysis/src/JsonEncoder.cpp +++ b/Analysis/src/JsonEncoder.cpp @@ -158,6 +158,13 @@ struct AstJsonEncoder : public AstVisitor { writeString(str); } + void write(std::optional name) + { + if (name) + write(*name); + else + writeRaw("null"); + } void write(AstName name) { writeString(name.value ? name.value : ""); @@ -327,7 +334,7 @@ struct AstJsonEncoder : public AstVisitor if (node->self) PROP(self); PROP(args); - if (node->hasReturnAnnotation) + if (node->returnAnnotation) PROP(returnAnnotation); PROP(vararg); PROP(varargLocation); @@ -341,6 +348,14 @@ struct AstJsonEncoder : public AstVisitor }); } + void write(const std::optional& typeList) + { + if (typeList) + write(*typeList); + else + writeRaw("null"); + } + void write(const AstTypeList& typeList) { writeRaw("{"); @@ -544,7 +559,7 @@ struct AstJsonEncoder : public AstVisitor PROP(thenbody); if (node->elsebody) PROP(elsebody); - PROP(hasThen); + write("hasThen", node->thenLocation.has_value()); PROP(hasEnd); }); } @@ -728,7 +743,7 @@ struct AstJsonEncoder : public AstVisitor void write(class AstTypeReference* node) { writeNode(node, "AstTypeReference", [&]() { - if (node->hasPrefix) + if (node->prefix) PROP(prefix); PROP(name); PROP(parameters); diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 2ba6a0fce..8d7d2d97f 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -12,6 +12,8 @@ #include #include +LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4) + namespace Luau { @@ -44,6 +46,7 @@ static const char* kWarningNames[] = { "TableOperations", "DuplicateCondition", "MisleadingAndOr", + "CommentDirective", }; // clang-format on @@ -732,13 +735,13 @@ class LintLocalHygiene : AstVisitor bool visit(AstTypeReference* node) override { - if (!node->hasPrefix) + if (!node->prefix) return true; - if (!imports.contains(node->prefix)) + if (!imports.contains(*node->prefix)) return true; - AstLocal* astLocal = imports[node->prefix]; + AstLocal* astLocal = imports[*node->prefix]; Local& local = locals[astLocal]; LUAU_ASSERT(local.import); 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& hotcomments) +{ + bool seenMode = false; + + for (const HotComment& hc : hotcomments) + { + // We reserve --! 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() { // By default, we enable all warnings warningMask = ~0ull; } -std::vector lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module, const LintOptions& options) +std::vector lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module, + const std::vector& hotcomments, const LintOptions& options) { LintContext context; @@ -2609,6 +2707,9 @@ std::vector lint(AstStat* root, const AstNameTable& names, const Sc if (context.warningEnabled(LintWarning::Code_MisleadingAndOr)) LintMisleadingAndOr::process(context); + if (context.warningEnabled(LintWarning::Code_CommentDirective)) + lintComments(context, hotcomments); + std::sort(context.result.begin(), context.result.end(), WarningComparator()); return context.result; @@ -2630,23 +2731,30 @@ LintWarning::Code LintWarning::parseName(const char* name) return Code_Unknown; } -uint64_t LintWarning::parseMask(const std::vector& hotcomments) +uint64_t LintWarning::parseMask(const std::vector& hotcomments) { 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; + + if (hc.content.compare(0, 6, "nolint") != 0) continue; - std::string::size_type name = hc.find_first_not_of(" \t", 6); + std::string::size_type name = hc.content.find_first_not_of(" \t", 6); // --!nolint disables everything if (name == std::string::npos) return ~0ull; + // --!nolint needs to be followed by a whitespace character + if (name == 6) + continue; + // --!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) result |= 1ull << int(code); diff --git a/Analysis/src/Quantify.cpp b/Analysis/src/Quantify.cpp index 04ebffc1b..94e169f1f 100644 --- a/Analysis/src/Quantify.cpp +++ b/Analysis/src/Quantify.cpp @@ -9,14 +9,12 @@ namespace Luau struct Quantifier { - ModulePtr module; TypeLevel level; std::vector generics; std::vector genericPacks; - Quantifier(ModulePtr module, TypeLevel level) - : module(module) - , level(level) + Quantifier(TypeLevel 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 seen{nullptr}; visitTypeVarOnce(ty, q, seen); diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index bacbca762..770c7a47d 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -226,27 +226,11 @@ TarjanResult Tarjan::loop() 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) { childCount = 0; ty = log->follow(ty); - clear(); auto [index, fresh] = indexify(ty); worklist.push_back({index, -1, -1}); return loop(); @@ -257,7 +241,6 @@ TarjanResult Tarjan::visitRoot(TypePackId tp) childCount = 0; tp = log->follow(tp); - clear(); auto [index, fresh] = indexify(tp); worklist.push_back({index, -1, -1}); return loop(); @@ -314,21 +297,17 @@ void FindDirty::visitSCC(int index) TarjanResult FindDirty::findDirty(TypeId ty) { - dirty.clear(); return visitRoot(ty); } TarjanResult FindDirty::findDirty(TypePackId tp) { - dirty.clear(); return visitRoot(tp); } std::optional Substitution::substitute(TypeId ty) { ty = log->follow(ty); - newTypes.clear(); - newPacks.clear(); auto result = findDirty(ty); if (result != TarjanResult::Ok) @@ -347,8 +326,6 @@ std::optional Substitution::substitute(TypeId ty) std::optional Substitution::substitute(TypePackId tp) { tp = log->follow(tp); - newTypes.clear(); - newPacks.clear(); auto result = findDirty(tp); if (result != TarjanResult::Ok) diff --git a/Analysis/src/TopoSortStatements.cpp b/Analysis/src/TopoSortStatements.cpp index dba694be0..678001bf8 100644 --- a/Analysis/src/TopoSortStatements.cpp +++ b/Analysis/src/TopoSortStatements.cpp @@ -26,9 +26,10 @@ * 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/Common.h" +#include "Luau/StringUtils.h" #include #include diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index f59086834..54bd0d5e7 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -933,12 +933,12 @@ struct Printer writer.symbol(")"); - if (writeTypes && func.hasReturnAnnotation) + if (writeTypes && func.returnAnnotation) { writer.symbol(":"); writer.space(); - visualizeTypeList(func.returnAnnotation, false); + visualizeTypeList(*func.returnAnnotation, false); } visualizeBlock(*func.body); @@ -989,9 +989,9 @@ struct Printer advance(typeAnnotation.location.begin); if (const auto& a = typeAnnotation.as()) { - if (a->hasPrefix) + if (a->prefix) { - writer.write(a->prefix.value); + writer.write(a->prefix->value); writer.symbol("."); } diff --git a/Analysis/src/TypeAttach.cpp b/Analysis/src/TypeAttach.cpp index 2208213f7..d575e023d 100644 --- a/Analysis/src/TypeAttach.cpp +++ b/Analysis/src/TypeAttach.cpp @@ -3,7 +3,6 @@ #include "Luau/Error.h" #include "Luau/Module.h" -#include "Luau/Parser.h" #include "Luau/RecursionCounter.h" #include "Luau/Scope.h" #include "Luau/ToString.h" @@ -476,12 +475,11 @@ class TypeAttacher : public AstVisitor visitLocal(arg); } - if (!fn->hasReturnAnnotation) + if (!fn->returnAnnotation) { if (auto result = getScope(fn->body->location)) { TypePackId ret = result->returnType; - fn->hasReturnAnnotation = true; AstTypePack* variadicAnnotation = nullptr; const auto& [v, tail] = flatten(ret); diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index f1c314cd2..c29699b7d 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -3,7 +3,6 @@ #include "Luau/Common.h" #include "Luau/ModuleResolver.h" -#include "Luau/Parser.h" #include "Luau/Quantify.h" #include "Luau/RecursionCounter.h" #include "Luau/Scope.h" @@ -24,16 +23,12 @@ LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000) LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 500) LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false) -LUAU_FASTFLAGVARIABLE(LuauGroupExpectedType, false) LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false. LUAU_FASTFLAG(LuauUseCommittingTxnLog) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false) LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false) -LUAU_FASTFLAGVARIABLE(LuauIfElseBranchTypeUnion, false) -LUAU_FASTFLAGVARIABLE(LuauIfElseExpectedType2, false) LUAU_FASTFLAGVARIABLE(LuauImmutableTypes, false) -LUAU_FASTFLAGVARIABLE(LuauLengthOnCompositeType, false) LUAU_FASTFLAGVARIABLE(LuauNoSealedTypeMod, false) LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false) LUAU_FASTFLAGVARIABLE(LuauSealExports, false) @@ -43,7 +38,6 @@ LUAU_FASTFLAGVARIABLE(LuauTypeAliasDefaults, false) LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false) LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false) -LUAU_FASTFLAGVARIABLE(LuauPerModuleUnificationCache, false) LUAU_FASTFLAGVARIABLE(LuauProperTypeLevels, false) LUAU_FASTFLAGVARIABLE(LuauAscribeCorrectLevelToInferredProperitesOfFreeTables, false) 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."}}); } - 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. - unifierState.cachedUnify.clear(); - unifierState.skipCacheForType.clear(); - } + // 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. + unifierState.cachedUnify.clear(); + unifierState.skipCacheForType.clear(); if (FFlag::LuauTwoPassAliasDefinitionFix) duplicateTypeAliases.clear(); @@ -509,7 +500,7 @@ LUAU_NOINLINE void TypeChecker::checkBlockTypeAliases(const ScopePtr& scope, std { if (const auto& typealias = stat->as()) { - if (FFlag::LuauTwoPassAliasDefinitionFix && typealias->name == Parser::errorName) + if (FFlag::LuauTwoPassAliasDefinitionFix && typealias->name == kParseNameError) continue; 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; // 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; std::optional binding; @@ -1222,7 +1213,8 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias if (FFlag::LuauProperTypeLevels) 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); FreeTypeVar* ftv = getMutable(ty); @@ -1464,7 +1456,7 @@ ExprResult TypeChecker::checkExpr(const ScopePtr& scope, const AstExpr& ExprResult result; if (auto a = expr.as()) - result = checkExpr(scope, *a->expr, FFlag::LuauGroupExpectedType ? expectedType : std::nullopt); + result = checkExpr(scope, *a->expr, expectedType); else if (expr.is()) result = {nilType}; else if (const AstExprConstantBool* bexpr = expr.as()) @@ -1508,7 +1500,7 @@ ExprResult TypeChecker::checkExpr(const ScopePtr& scope, const AstExpr& else if (auto a = expr.as()) result = checkExpr(scope, *a); else if (auto a = expr.as()) - result = checkExpr(scope, *a, FFlag::LuauIfElseExpectedType2 ? expectedType : std::nullopt); + result = checkExpr(scope, *a, expectedType); else ice("Unhandled AstExpr?"); @@ -2093,6 +2085,7 @@ ExprResult TypeChecker::checkExpr(const ScopePtr& scope, const AstExprUn return {numberType}; } case AstExprUnary::Len: + { tablify(operandType); operandType = stripFromNilAndReport(operandType, expr.location); @@ -2100,30 +2093,13 @@ ExprResult TypeChecker::checkExpr(const ScopePtr& scope, const AstExprUn if (get(operandType)) return {errorRecoveryType(scope)}; - if (FFlag::LuauLengthOnCompositeType) - { - DenseHashSet seen{nullptr}; - - if (!hasLength(operandType, seen, &recursionCount)) - reportError(TypeError{expr.location, NotATable{operandType}}); - } - else - { - if (get(operandType)) - return {numberType}; // Not strictly correct: metatables permit overriding this - - if (auto p = get(operandType)) - { - if (p->type == PrimitiveTypeVar::String) - return {numberType}; - } + DenseHashSet seen{nullptr}; - if (!getTableType(operandType)) - reportError(TypeError{expr.location, NotATable{operandType}}); - } + if (!hasLength(operandType, seen, &recursionCount)) + reportError(TypeError{expr.location, NotATable{operandType}}); return {numberType}; - + } default: ice("Unknown AstExprUnary " + std::to_string(int(expr.op))); } @@ -2618,22 +2594,11 @@ ExprResult TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIf resolve(result.predicates, falseScope, false); ExprResult falseType = checkExpr(falseScope, *expr.falseExpr, expectedType); - if (FFlag::LuauIfElseBranchTypeUnion) - { - if (falseType.type == trueType.type) - return {trueType.type}; - - std::vector 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. + if (falseType.type == trueType.type) return {trueType.type}; - } + + std::vector 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) @@ -2986,8 +2951,8 @@ std::pair TypeChecker::checkFunctionSignature( auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks); TypePackId retPack; - if (expr.hasReturnAnnotation) - retPack = resolveTypePack(funScope, expr.returnAnnotation); + if (expr.returnAnnotation) + retPack = resolveTypePack(funScope, *expr.returnAnnotation); else if (isNonstrictMode()) retPack = anyTypePack; 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 // statement if there are type annotations on the function. In strict mode // we report it regardless. - if (!isNonstrictMode() || function.hasReturnAnnotation) + if (!isNonstrictMode() || function.returnAnnotation) { 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, // to replace any generic tables. - replaceGenerics.log = log; - replaceGenerics.level = level; - replaceGenerics.currentModule = currentModule; - replaceGenerics.generics.assign(ftv->generics.begin(), ftv->generics.end()); - replaceGenerics.genericPacks.assign(ftv->genericPacks.begin(), ftv->genericPacks.end()); + ReplaceGenerics replaceGenerics{log, arena, level, ftv->generics, ftv->genericPacks}; // TODO: What to do if this returns nullopt? // 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) { - Luau::quantify(currentModule, ty, scope->level); + Luau::quantify(ty, scope->level); return ty; } - quantification.log = TxnLog::empty(); - quantification.level = scope->level; - quantification.generics.clear(); - quantification.genericPacks.clear(); - quantification.currentModule = currentModule; - + Quantification quantification{¤tModule->internalTypes, scope->level}; std::optional qty = quantification.substitute(ty); if (!qty.has_value()) @@ -4596,18 +4552,14 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location FunctionTypeVar* qftv = getMutable(*qty); LUAU_ASSERT(qftv); - qftv->generics = quantification.generics; - qftv->genericPacks = quantification.genericPacks; + qftv->generics = std::move(quantification.generics); + qftv->genericPacks = std::move(quantification.genericPacks); return *qty; } TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location location, const TxnLog* log) { - LUAU_ASSERT(log != nullptr); - - instantiation.log = FFlag::LuauUseCommittingTxnLog ? log : TxnLog::empty(); - instantiation.level = scope->level; - instantiation.currentModule = currentModule; + Instantiation instantiation{FFlag::LuauUseCommittingTxnLog ? log : TxnLog::empty(), ¤tModule->internalTypes, scope->level}; std::optional instantiated = instantiation.substitute(ty); if (instantiated.has_value()) 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) { - anyification.log = TxnLog::empty(); - anyification.anyType = anyType; - anyification.anyTypePack = anyTypePack; - anyification.currentModule = currentModule; + Anyification anyification{¤tModule->internalTypes, anyType, anyTypePack}; std::optional any = anyification.substitute(ty); if (any.has_value()) 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) { - anyification.log = TxnLog::empty(); - anyification.anyType = anyType; - anyification.anyTypePack = anyTypePack; - anyification.currentModule = currentModule; + Anyification anyification{¤tModule->internalTypes, anyType, anyTypePack}; std::optional any = anyification.substitute(ty); if (any.has_value()) return *any; @@ -4823,7 +4769,8 @@ TypePackId TypeChecker::errorRecoveryTypePack(TypePackId guess) return getSingletonTypes().errorRecoveryTypePack(guess); } -TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense) { +TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense) +{ return [this, sense](TypeId ty) -> std::optional { // any/error/free gets a special pass unconditionally because they can't be decided. if (get(ty) || get(ty) || get(ty)) @@ -4904,8 +4851,8 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation if (const auto& lit = annotation.as()) { std::optional tf; - if (lit->hasPrefix) - tf = scope->lookupImportedType(lit->prefix.value, lit->name.value); + if (lit->prefix) + tf = scope->lookupImportedType(lit->prefix->value, lit->name.value); else if (FFlag::DebugLuauMagicTypes && lit->name == "_luau_ice") ice("_luau_ice encountered", lit->location); @@ -4932,12 +4879,12 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation if (!tf) { - if (lit->name == Parser::errorName) + if (lit->name == kParseNameError) return errorRecoveryType(scope); std::string typeName; - if (lit->hasPrefix) - typeName = std::string(lit->prefix.value) + "."; + if (lit->prefix) + typeName = std::string(lit->prefix->value) + "."; typeName += lit->name.value; if (scope->lookupPack(typeName)) @@ -5038,12 +4985,7 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation if (notEnoughParameters && hasDefaultParameters) { // 'applyTypeFunction' is used to substitute default types that reference previous generic types - applyTypeFunction.log = TxnLog::empty(); - applyTypeFunction.typeArguments.clear(); - applyTypeFunction.typePackArguments.clear(); - applyTypeFunction.currentModule = currentModule; - applyTypeFunction.level = scope->level; - applyTypeFunction.encounteredForwardedType = false; + ApplyTypeFunction applyTypeFunction{¤tModule->internalTypes, scope->level}; for (size_t i = 0; i < typesProvided; ++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()) return tf.type; - applyTypeFunction.typeArguments.clear(); + ApplyTypeFunction applyTypeFunction{¤tModule->internalTypes, scope->level}; + for (size_t i = 0; i < tf.typeParams.size(); ++i) applyTypeFunction.typeArguments[tf.typeParams[i].ty] = typeParams[i]; - applyTypeFunction.typePackArguments.clear(); for (size_t i = 0; i < tf.typePackParams.size(); ++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 maybeInstantiated = applyTypeFunction.substitute(tf.type); if (!maybeInstantiated.has_value()) { diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 8c6d5e49f..593b54c84 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -5,6 +5,8 @@ #include "Luau/ToString.h" #include "Luau/TypeInfer.h" +LUAU_FASTFLAGVARIABLE(LuauTerminateCyclicMetatableIndexLookup, false) + namespace Luau { @@ -48,9 +50,19 @@ std::optional findTablePropertyRespectingMeta(ErrorVec& errors, const Sc } std::optional mtIndex = findMetatableEntry(errors, globalScope, ty, "__index", location); + int count = 0; while (mtIndex) { TypeId index = follow(*mtIndex); + + if (FFlag::LuauTerminateCyclicMetatableIndexLookup) + { + if (count >= 100) + return std::nullopt; + + ++count; + } + if (const auto& itt = getTableType(index)) { const auto& fit = itt->props.find(name); diff --git a/Analysis/src/TypeVar.cpp b/Analysis/src/TypeVar.cpp index 7e438e31c..b2358c277 100644 --- a/Analysis/src/TypeVar.cpp +++ b/Analysis/src/TypeVar.cpp @@ -23,8 +23,6 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINT(LuauTypeInferRecursionLimit) -LUAU_FASTFLAG(LuauLengthOnCompositeType) -LUAU_FASTFLAGVARIABLE(LuauMetatableAreEqualRecursion, false) LUAU_FASTFLAGVARIABLE(LuauRefactorTypeVarQuestions, false) LUAU_FASTFLAG(LuauErrorRecoveryType) LUAU_FASTFLAG(LuauUnionTagMatchFix) @@ -385,8 +383,6 @@ bool maybeSingleton(TypeId ty) bool hasLength(TypeId ty, DenseHashSet& seen, int* recursionCount) { - LUAU_ASSERT(FFlag::LuauLengthOnCompositeType); - RecursionLimiter _rl(recursionCount, FInt::LuauTypeInferRecursionLimit); 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) { - if (FFlag::LuauMetatableAreEqualRecursion && areSeen(seen, &lhs, &rhs)) + if (areSeen(seen, &lhs, &rhs)) return true; return areEqual(seen, *lhs.table, *rhs.table) && areEqual(seen, *lhs.metatable, *rhs.metatable); diff --git a/Analysis/src/TypedAllocator.cpp b/Analysis/src/TypedAllocator.cpp index f037351e5..c7f31822d 100644 --- a/Analysis/src/TypedAllocator.cpp +++ b/Analysis/src/TypedAllocator.cpp @@ -7,6 +7,9 @@ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif +#ifndef NOMINMAX +#define NOMINMAX +#endif #include const size_t kPageSize = 4096; diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index a8ad51593..322f6ebf5 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -14,7 +14,6 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit); LUAU_FASTINT(LuauTypeInferTypePackLoopLimit); -LUAU_FASTFLAGVARIABLE(LuauCommittingTxnLogFreeTpPromote, false) LUAU_FASTFLAG(LuauImmutableTypes) LUAU_FASTFLAG(LuauUseCommittingTxnLog) LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000); @@ -23,7 +22,6 @@ LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false) LUAU_FASTFLAG(LuauSingletonTypes) LUAU_FASTFLAG(LuauErrorRecoveryType); LUAU_FASTFLAG(LuauProperTypeLevels); -LUAU_FASTFLAGVARIABLE(LuauUnifyPackTails, false) LUAU_FASTFLAGVARIABLE(LuauUnionTagMatchFix, false) LUAU_FASTFLAGVARIABLE(LuauFollowWithCommittingTxnLogInAnyUnification, false) @@ -116,7 +114,7 @@ struct PromoteTypeLevels { // Surprise, it's actually a BoundTypePack that hasn't been committed yet. // Calling getMutable on this will trigger an assertion. - if (FFlag::LuauCommittingTxnLogFreeTpPromote && FFlag::LuauUseCommittingTxnLog && !log.is(tp)) + if (FFlag::LuauUseCommittingTxnLog && !log.is(tp)) return true; promote(tp, FFlag::LuauUseCommittingTxnLog ? log.getMutable(tp) : getMutable(tp)); @@ -1242,7 +1240,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal // If both are at the end, we're done if (!superIter.good() && !subIter.good()) { - if (FFlag::LuauUnifyPackTails && subTpv->tail && superTpv->tail) + if (subTpv->tail && superTpv->tail) { tryUnify_(*subTpv->tail, *superTpv->tail); break; @@ -1250,9 +1248,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal const bool lFreeTail = superTpv->tail && log.getMutable(log.follow(*superTpv->tail)) != nullptr; const bool rFreeTail = subTpv->tail && log.getMutable(log.follow(*subTpv->tail)) != nullptr; - if (!FFlag::LuauUnifyPackTails && lFreeTail && rFreeTail) - tryUnify_(*subTpv->tail, *superTpv->tail); - else if (lFreeTail) + if (lFreeTail) tryUnify_(emptyTp, *superTpv->tail); else if (rFreeTail) 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 (!superIter.good() && !subIter.good()) { - if (FFlag::LuauUnifyPackTails && subTpv->tail && superTpv->tail) + if (subTpv->tail && superTpv->tail) { tryUnify_(*subTpv->tail, *superTpv->tail); break; @@ -1456,9 +1452,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal const bool lFreeTail = superTpv->tail && get(follow(*superTpv->tail)) != nullptr; const bool rFreeTail = subTpv->tail && get(follow(*subTpv->tail)) != nullptr; - if (!FFlag::LuauUnifyPackTails && lFreeTail && rFreeTail) - tryUnify_(*subTpv->tail, *superTpv->tail); - else if (lFreeTail) + if (lFreeTail) tryUnify_(emptyTp, *superTpv->tail); else if (rFreeTail) tryUnify_(emptyTp, *subTpv->tail); diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index ac5950c0a..31cd01ccd 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -594,8 +594,7 @@ class AstExprFunction : public AstExpr AstArray genericPacks; AstLocal* self; AstArray args; - bool hasReturnAnnotation; - AstTypeList returnAnnotation; + std::optional returnAnnotation; bool vararg = false; Location varargLocation; AstTypePack* varargAnnotation; @@ -740,7 +739,7 @@ class AstStatIf : public AstStat public: 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& thenLocation, const std::optional& elseLocation, bool hasEnd); void visit(AstVisitor* visitor) override; @@ -749,12 +748,10 @@ class AstStatIf : public AstStat AstStatBlock* thenbody; AstStat* elsebody; - bool hasThen = false; - Location thenLocation; + std::optional thenLocation; // Active for 'elseif' as well - bool hasElse = false; - Location elseLocation; + std::optional elseLocation; bool hasEnd = false; }; @@ -849,8 +846,7 @@ class AstStatLocal : public AstStat AstArray vars; AstArray values; - bool hasEqualsSign = false; - Location equalsSignLocation; + std::optional equalsSignLocation; }; class AstStatFor : public AstStat @@ -1053,9 +1049,8 @@ class AstTypeReference : public AstType void visit(AstVisitor* visitor) override; - bool hasPrefix; bool hasParameterList; - AstName prefix; + std::optional prefix; AstName name; AstArray parameters; }; diff --git a/Ast/include/Luau/Lexer.h b/Ast/include/Luau/Lexer.h index 460ef0565..d7d867f48 100644 --- a/Ast/include/Luau/Lexer.h +++ b/Ast/include/Luau/Lexer.h @@ -233,4 +233,9 @@ class Lexer bool readNames; }; +inline bool isSpace(char ch) +{ + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\v' || ch == '\f'; +} + } // namespace Luau diff --git a/Ast/include/Luau/ParseResult.h b/Ast/include/Luau/ParseResult.h new file mode 100644 index 000000000..17ce2e3bb --- /dev/null +++ b/Ast/include/Luau/ParseResult.h @@ -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 errors); + + virtual const char* what() const throw(); + + const std::vector& getErrors() const; + +private: + std::vector 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 hotcomments; + std::vector errors; + + std::vector commentLocations; +}; + +static constexpr const char* kParseNameError = "%error-id%"; + +} // namespace Luau diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index 40ecdcdd5..4b5ae3150 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -4,6 +4,7 @@ #include "Luau/Ast.h" #include "Luau/Lexer.h" #include "Luau/ParseOptions.h" +#include "Luau/ParseResult.h" #include "Luau/StringUtils.h" #include "Luau/DenseHash.h" #include "Luau/Common.h" @@ -14,37 +15,6 @@ 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 errors); - - virtual const char* what() const throw(); - - const std::vector& getErrors() const; - -private: - std::vector errors; - std::string message; -}; - template class TempVector { @@ -80,34 +50,17 @@ class TempVector size_t size_; }; -struct Comment -{ - Lexeme::Type type; // Comment, BlockComment, or BrokenComment - Location location; -}; - -struct ParseResult -{ - AstStatBlock* root; - std::vector hotcomments; - std::vector errors; - - std::vector commentLocations; -}; - class Parser { public: static ParseResult parse( const char* buffer, std::size_t bufferSize, AstNameTable& names, Allocator& allocator, ParseOptions options = ParseOptions()); - static constexpr const char* errorName = "%error-id%"; - private: struct Name; 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); @@ -330,7 +283,7 @@ class Parser AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray& types, bool isMissing, const char* format, ...) LUAU_PRINTF_ATTR(5, 6); - const Lexeme& nextLexeme(); + void nextLexeme(); struct Function { @@ -386,6 +339,9 @@ class Parser Allocator& allocator; std::vector commentLocations; + std::vector hotcomments; + + bool hotcommentHeader = true; unsigned int recursionCounter; diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index 9b5bc0c71..24a280da1 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -167,8 +167,7 @@ AstExprFunction::AstExprFunction(const Location& location, const AstArrayreturnAnnotation = *returnAnnotation; } void AstExprFunction::visit(AstVisitor* visitor) @@ -195,8 +192,8 @@ void AstExprFunction::visit(AstVisitor* visitor) if (varargAnnotation) varargAnnotation->visit(visitor); - if (hasReturnAnnotation) - visitTypeList(visitor, returnAnnotation); + if (returnAnnotation) + visitTypeList(visitor, *returnAnnotation); 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, - const Location& thenLocation, const std::optional& elseLocation, bool hasEnd) +AstStatIf::AstStatIf(const Location& location, AstExpr* condition, AstStatBlock* thenbody, AstStat* elsebody, + const std::optional& thenLocation, const std::optional& elseLocation, bool hasEnd) : AstStat(ClassIndex(), location) , condition(condition) , thenbody(thenbody) , elsebody(elsebody) - , hasThen(hasThen) , thenLocation(thenLocation) + , elseLocation(elseLocation) , hasEnd(hasEnd) { - if (bool(elseLocation)) - { - hasElse = true; - this->elseLocation = *elseLocation; - } } void AstStatIf::visit(AstVisitor* visitor) @@ -492,12 +484,8 @@ AstStatLocal::AstStatLocal( : AstStat(ClassIndex(), location) , vars(vars) , values(values) + , equalsSignLocation(equalsSignLocation) { - if (bool(equalsSignLocation)) - { - hasEqualsSign = true; - this->equalsSignLocation = *equalsSignLocation; - } } void AstStatLocal::visit(AstVisitor* visitor) @@ -750,9 +738,8 @@ void AstStatError::visit(AstVisitor* visitor) AstTypeReference::AstTypeReference( const Location& location, std::optional prefix, AstName name, bool hasParameterList, const AstArray& parameters) : AstType(ClassIndex(), location) - , hasPrefix(bool(prefix)) , hasParameterList(hasParameterList) - , prefix(prefix ? *prefix : AstName()) + , prefix(prefix) , name(name) , parameters(parameters) { diff --git a/Ast/src/Lexer.cpp b/Ast/src/Lexer.cpp index a7aa24ca9..d56c88608 100644 --- a/Ast/src/Lexer.cpp +++ b/Ast/src/Lexer.cpp @@ -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)); } -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", "repeat", "return", "then", "true", "until", "while"}; @@ -282,11 +277,6 @@ AstName AstNameTable::get(const char* name) const 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) { // 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; lexeme = readNext(); - } while (skipComments && isComment(lexeme)); + } while (skipComments && (lexeme.type == Lexeme::Comment || lexeme.type == Lexeme::BlockComment)); return lexeme; } diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 30b32f914..235d6349d 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -13,18 +13,15 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false) LUAU_FASTFLAGVARIABLE(LuauParseTypeAliasDefaults, false) LUAU_FASTFLAGVARIABLE(LuauParseRecoverTypePackEllipsis, false) -LUAU_FASTFLAGVARIABLE(LuauStartingBrokenComment, false) +LUAU_FASTFLAGVARIABLE(LuauParseAllHotComments, false) +LUAU_FASTFLAGVARIABLE(LuauTableFieldFunctionDebugname, false) 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) { + LUAU_ASSERT(!FFlag::LuauParseAllHotComments); 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"); - Parser p(buffer, bufferSize, names, allocator); + Parser p(buffer, bufferSize, names, allocator, FFlag::LuauParseAllHotComments ? options : ParseOptions()); try { - std::vector hotcomments; + if (FFlag::LuauParseAllHotComments) + { + AstStatBlock* root = p.parseChunk(); - while (isComment(p.lexer.current()) || p.lexer.current().type == Lexeme::BrokenComment) + return ParseResult{root, std::move(p.hotcomments), std::move(p.parseErrors), std::move(p.commentLocations)}; + } + else { - const char* text = p.lexer.current().data; - unsigned int length = p.lexer.current().length; + std::vector hotcomments; - if (length && text[0] == '!') + while (isComment(p.lexer.current()) || p.lexer.current().type == Lexeme::BrokenComment) { - unsigned int end = length; - while (end > 0 && isSpace(text[end - 1])) - --end; + const char* text = p.lexer.current().data; + unsigned int length = p.lexer.current().length; - 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; - const Location loc = p.lexer.current().location; + hotcomments.push_back({true, p.lexer.current().location, std::string(text + 1, text + end)}); + } + + const Lexeme::Type type = p.lexer.current().type; + const Location loc = p.lexer.current().location; - if (FFlag::LuauStartingBrokenComment) - { if (options.captureComments) 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(); } - else - { - p.lexer.next(); - if (options.captureComments) - p.commentLocations.push_back(Comment{type, loc}); - } - } - - p.lexer.setSkipComments(true); + p.lexer.setSkipComments(true); - p.options = options; + p.options = options; - AstStatBlock* root = p.parseChunk(); + AstStatBlock* root = p.parseChunk(); - return ParseResult{root, hotcomments, p.parseErrors, std::move(p.commentLocations)}; + return ParseResult{root, hotcomments, p.parseErrors, std::move(p.commentLocations)}; + } } 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) - : lexer(buffer, bufferSize, names) +Parser::Parser(const char* buffer, size_t bufferSize, AstNameTable& names, Allocator& allocator, const ParseOptions& options) + : options(options) + , lexer(buffer, bufferSize, names) , allocator(allocator) , recursionCounter(0) , endMismatchSuspect(Location(), Lexeme::Eof) @@ -224,14 +221,20 @@ Parser::Parser(const char* buffer, size_t bufferSize, AstNameTable& names, Alloc nameSelf = names.addStatic("self"); nameNumber = names.addStatic("number"); - nameError = names.addStatic(errorName); + nameError = names.addStatic(kParseNameError); nameNil = names.getOrAdd("nil"); // nil is a reserved keyword matchRecoveryStopOnToken.assign(Lexeme::Type::Reserved_END, 0); matchRecoveryStopOnToken[Lexeme::Type::Eof] = 1; + if (FFlag::LuauParseAllHotComments) + lexer.setSkipComments(true); + // read first lexeme 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) @@ -396,7 +399,9 @@ AstStat* Parser::parseIf() AstExpr* cond = parseExpr(); Lexeme matchThen = lexer.current(); - bool hasThen = expectAndConsume(Lexeme::ReservedThen, "if statement"); + std::optional thenLocation; + if (expectAndConsume(Lexeme::ReservedThen, "if statement")) + thenLocation = matchThen.location; AstStatBlock* thenbody = parseBlock(); @@ -434,7 +439,7 @@ AstStat* Parser::parseIf() hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchThenElse); } - return allocator.alloc(Location(start, end), cond, thenbody, elsebody, hasThen, matchThen.location, elseLocation, hasEnd); + return allocator.alloc(Location(start, end), cond, thenbody, elsebody, thenLocation, elseLocation, hasEnd); } // 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 - auto name = parseNameOpt("type name"); + std::optional name = parseNameOpt("type name"); // Use error name if the name is missing if (!name) @@ -925,7 +930,7 @@ AstStat* Parser::parseDeclaration(const Location& start) return allocator.alloc(Location(classStart, classEnd), className.name, superName, copy(props)); } - else if (auto globalName = parseNameOpt("global variable name")) + else if (std::optional globalName = parseNameOpt("global variable name")) { expectAndConsume(':', "global variable declaration"); @@ -1066,7 +1071,7 @@ void Parser::parseExprList(TempVector& result) Parser::Binding Parser::parseBinding() { - auto name = parseNameOpt("variable name"); + std::optional name = parseNameOpt("variable name"); // Use placeholder if the name is missing if (!name) @@ -1325,7 +1330,7 @@ AstType* Parser::parseTableTypeAnnotation() } else { - auto name = parseNameOpt("table field"); + std::optional name = parseNameOpt("table field"); if (!name) break; @@ -1422,7 +1427,7 @@ AstType* Parser::parseFunctionTypeAnnotationTail(const Lexeme& begin, AstArray(Location(begin.location, endLocation), generics, genericPacks, paramTypes, paramNames, returnTypeList); @@ -1869,7 +1874,7 @@ AstExpr* Parser::parseExpr(unsigned int limit) // NAME AstExpr* Parser::parseNameExpr(const char* context) { - auto name = parseNameOpt(context); + std::optional name = parseNameOpt(context); if (!name) return allocator.alloc(lexer.current().location, copy({}), unsigned(parseErrors.size() - 1)); @@ -2233,6 +2238,12 @@ AstExpr* Parser::parseTableConstructor() AstExpr* key = allocator.alloc(name.location, nameString); AstExpr* value = parseExpr(); + if (FFlag::LuauTableFieldFunctionDebugname) + { + if (AstExprFunction* func = value->as()) + func->debugname = name.name; + } + items.push_back({AstExprTable::Item::Record, key, value}); } else @@ -2313,7 +2324,7 @@ std::optional Parser::parseNameOpt(const char* context) Parser::Name Parser::parseName(const char* context) { - if (auto name = parseNameOpt(context)) + if (std::optional name = parseNameOpt(context)) return *name; 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) { - if (auto name = parseNameOpt(context)) + if (std::optional name = parseNameOpt(context)) return *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> Parser::parseG if (shouldParseTypePackAnnotation(lexer)) { - auto typePack = parseTypePackAnnotation(); + AstTypePack* typePack = parseTypePackAnnotation(); namePacks.push_back({name, nameLocation, typePack}); } @@ -2451,7 +2462,7 @@ AstArray Parser::parseTypeParams() { if (shouldParseTypePackAnnotation(lexer)) { - auto typePack = parseTypePackAnnotation(); + AstTypePack* typePack = parseTypePackAnnotation(); parameters.push_back({{}, typePack}); } @@ -2821,25 +2832,57 @@ AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const return allocator.alloc(location, types, isMissing, unsigned(parseErrors.size() - 1)); } -const Lexeme& Parser::nextLexeme() +void Parser::nextLexeme() { if (options.captureComments) { - while (true) + if (FFlag::LuauParseAllHotComments) { - 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)) + Lexeme::Type type = lexer.next(/* skipComments= */ false).type; + + while (type == Lexeme::BrokenComment || type == Lexeme::Comment || type == Lexeme::BlockComment) + { + const Lexeme& lexeme = lexer.current(); commentLocations.push_back(Comment{lexeme.type, lexeme.location}); - else - return lexeme; + + // 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) + 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 - return lexer.next(); + lexer.next(); } } // namespace Luau diff --git a/Ast/src/TimeTrace.cpp b/Ast/src/TimeTrace.cpp index ded50e53e..8079830b3 100644 --- a/Ast/src/TimeTrace.cpp +++ b/Ast/src/TimeTrace.cpp @@ -9,6 +9,12 @@ #include #ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif #include #endif diff --git a/CLI/FileUtils.cpp b/CLI/FileUtils.cpp index fe005aece..fb6ac3734 100644 --- a/CLI/FileUtils.cpp +++ b/CLI/FileUtils.cpp @@ -4,8 +4,13 @@ #include "Luau/Common.h" #ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN -#include +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include #else #include #include diff --git a/CLI/Repl.cpp b/CLI/Repl.cpp index 9a6e25c28..13304d57f 100644 --- a/CLI/Repl.cpp +++ b/CLI/Repl.cpp @@ -37,6 +37,8 @@ enum class CompileFormat Binary }; +constexpr int MaxTraversalLimit = 50; + struct GlobalOptions { int optimizationLevel = 1; @@ -243,72 +245,143 @@ std::string runCode(lua_State* L, const std::string& source) return std::string(); } -static void completeIndexer(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback) +// 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) { - std::string_view lookup = editBuffer; - char lastSep = 0; - - for (;;) + if (luaL_getmetafield(L, -1, "__index")) { - size_t sep = lookup.find_first_of(".:"); - std::string_view prefix = lookup.substr(0, sep); + // Remove the table leaving __index on the top of stack + lua_remove(L, -2); + return true; + } + return false; +} - if (sep == std::string_view::npos) + +// 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) { - // table, key - lua_pushnil(L); + // 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; + } + } + } - while (lua_next(L, -2) != 0) + 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) { - if (lua_type(L, -2) == LUA_TSTRING) - { - // table, key, value - std::string_view key = lua_tostring(L, -2); - int valueType = lua_type(L, -1); + // 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 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)) + if (!key.empty() && requiredValueType && Luau::startsWith(key, prefix)) + { + std::string completedComponent(key.substr(prefix.size())); + std::string completion(editBuffer + completedComponent); + if (valueType == LUA_TFUNCTION) { - 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)); + // Add an opening paren for function calls by default. + completion += "("; } + addCompletionCallback(completion, std::string(key)); } - lua_pop(L, 1); } + 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) +{ + std::string_view lookup = editBuffer; + bool completeOnlyFunctions = false; + + // Push the global variable table to begin the search + lua_pushvalue(L, LUA_GLOBALSINDEX); + + for (;;) + { + size_t sep = lookup.find_first_of(".:"); + std::string_view prefix = lookup.substr(0, sep); + if (sep == std::string_view::npos) + { + completePartialMatches(L, completeOnlyFunctions, editBuffer, prefix, addCompletionCallback); break; } else { // find the key in the table lua_pushlstring(L, prefix.data(), prefix.size()); - lua_rawget(L, -2); + safeGetTable(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 - // Note: We retrieve the string class from _G to prevent issues if the user assigns to `string`. - 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)); + completeOnlyFunctions = lookup[sep] == ':'; + lookup.remove_prefix(sep + 1); } - else if (!lua_istable(L, -1)) + else + { + // Unable to search for keys, so stop searching 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) { - // 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); } @@ -365,9 +432,7 @@ struct LinenoiseScopedHistory ic_set_history(historyFilepath.c_str(), -1 /* default entries (= 200) */); } - ~LinenoiseScopedHistory() - { - } + ~LinenoiseScopedHistory() {} std::string historyFilepath; }; diff --git a/CMakeLists.txt b/CMakeLists.txt index c19d2b40b..c6ccebc54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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-) 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) target_compile_options(Luau.Repl.CLI PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.Analyze.CLI PRIVATE ${LUAU_OPTIONS}) diff --git a/Sources.cmake b/Sources.cmake index 773f6f351..615641eb1 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -8,6 +8,7 @@ target_sources(Luau.Ast PRIVATE Ast/include/Luau/Location.h Ast/include/Luau/ParseOptions.h Ast/include/Luau/Parser.h + Ast/include/Luau/ParseResult.h Ast/include/Luau/StringUtils.h Ast/include/Luau/TimeTrace.h diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index 5cffba63c..29d5f397e 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -36,12 +36,9 @@ const char* luau_ident = "$Luau: Copyright (C) 2019-2022 Roblox Corporation $\n" static Table* getcurrenv(lua_State* L) { 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 - { - Closure* func = curr_func(L); - return func->env; - } + return curr_func(L)->env; } 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); case LUA_ENVIRONINDEX: { - sethvalue(L, &L->env, getcurrenv(L)); - return &L->env; + sethvalue(L, &L->global->pseudotemp, getcurrenv(L)); + return &L->global->pseudotemp; } case LUA_GLOBALSINDEX: - return gt(L); + { + sethvalue(L, &L->global->pseudotemp, L->gt); + return &L->global->pseudotemp; + } default: { Closure* func = curr_func(L); @@ -237,6 +237,11 @@ void lua_replace(lua_State* L, int idx) func->env = hvalue(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 { 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); break; case LUA_TTHREAD: - setobj2s(L, L->top, gt(thvalue(o))); + sethvalue(L, L->top, thvalue(o)->gt); break; default: setnilvalue(L->top); @@ -914,7 +919,7 @@ int lua_setfenv(lua_State* L, int idx) clvalue(o)->env = hvalue(L->top - 1); break; case LUA_TTHREAD: - sethvalue(L, gt(thvalue(o)), hvalue(L->top - 1)); + thvalue(o)->gt = hvalue(L->top - 1); break; default: res = 0; diff --git a/VM/src/ldebug.cpp b/VM/src/ldebug.cpp index e9930f7ab..a4f93c621 100644 --- a/VM/src/ldebug.cpp +++ b/VM/src/ldebug.cpp @@ -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; - int linedefined = luaG_getline(p, 0); + int linedefined = getlinedefined(p); callback(context, debugname, linedefined, depth, buffer, size); diff --git a/VM/src/ldo.cpp b/VM/src/ldo.cpp index a3982bc68..d87f06618 100644 --- a/VM/src/ldo.cpp +++ b/VM/src/ldo.cpp @@ -17,6 +17,8 @@ #include +LUAU_FASTFLAG(LuauReduceStackReallocs) + /* ** {====================================================== ** Error-recovery functions @@ -164,13 +166,14 @@ static void correctstack(lua_State* L, TValue* oldstack) void luaD_reallocstack(lua_State* L, int newsize) { TValue* oldstack = L->stack; - int realsize = newsize + 1 + EXTRA_STACK; - LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1); + int realsize = newsize + (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK); + 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); + TValue* newstack = L->stack; 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->stack_last = L->stack + newsize; + L->stack_last = newstack + newsize; correctstack(L, oldstack); } @@ -512,7 +515,7 @@ static void callerrfunc(lua_State* L, void* ud) 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) { /* there was an overflow? */ int inuse = cast_int(L->ci - L->base_ci); diff --git a/VM/src/ldo.h b/VM/src/ldo.h index 72807f0f3..1c1480d68 100644 --- a/VM/src/ldo.h +++ b/VM/src/ldo.h @@ -11,7 +11,7 @@ if ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue)) \ luaD_growstack(L, n); \ else \ - condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1)); + condhardstacktests(luaD_reallocstack(L, L->stacksize - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK))); #define incr_top(L) \ { \ diff --git a/VM/src/lgc.cpp b/VM/src/lgc.cpp index 835572fa7..724b24b2a 100644 --- a/VM/src/lgc.cpp +++ b/VM/src/lgc.cpp @@ -268,7 +268,7 @@ static void traverseclosure(global_State* g, Closure* cl) static void traversestack(global_State* g, lua_State* l, bool clearstack) { - markvalue(g, gt(l)); + markobject(g, l->gt); if (l->namecall) stringmark(l->namecall); for (StkId o = l->stack; o < l->top; o++) @@ -643,7 +643,7 @@ static void markroot(lua_State* L) g->weak = NULL; markobject(g, g->mainthread); /* make global table be traversed before main stack */ - markvalue(g, gt(g->mainthread)); + markobject(g, g->mainthread->gt); markvalue(g, registry(L)); markmt(g); g->gcstate = GCSpropagate; diff --git a/VM/src/lgc.h b/VM/src/lgc.h index 528d09446..2acb5d8aa 100644 --- a/VM/src/lgc.h +++ b/VM/src/lgc.h @@ -77,7 +77,7 @@ #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) \ { \ condhardmemtests(luaC_validate(L), 1); \ diff --git a/VM/src/lgcdebug.cpp b/VM/src/lgcdebug.cpp index ce1965200..30242e526 100644 --- a/VM/src/lgcdebug.cpp +++ b/VM/src/lgcdebug.cpp @@ -88,7 +88,7 @@ static void validateclosure(global_State* g, Closure* cl) 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) { @@ -370,6 +370,7 @@ static void dumpclosure(FILE* f, Closure* cl) fprintf(f, ",\"env\":"); dumpref(f, obj2gco(cl->env)); + if (cl->isC) { 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)); - if (iscollectable(&th->l_gt)) - { - fprintf(f, ",\"env\":"); - dumpref(f, gcvalue(&th->l_gt)); - } + fprintf(f, ",\"env\":"); + dumpref(f, obj2gco(th->gt)); Closure* tcl = 0; for (CallInfo* ci = th->base_ci; ci <= th->ci; ++ci) diff --git a/VM/src/lperf.cpp b/VM/src/lperf.cpp index 2f6c72972..da68e3766 100644 --- a/VM/src/lperf.cpp +++ b/VM/src/lperf.cpp @@ -3,6 +3,12 @@ #include "lua.h" #ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif #include #endif diff --git a/VM/src/lstate.cpp b/VM/src/lstate.cpp index 6762c6380..d6d127c02 100644 --- a/VM/src/lstate.cpp +++ b/VM/src/lstate.cpp @@ -11,6 +11,7 @@ #include "ldebug.h" LUAU_FASTFLAG(LuauGcPagedSweep) +LUAU_FASTFLAGVARIABLE(LuauReduceStackReallocs, false) /* ** 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 */ L1->stack = luaM_newarray(L, BASIC_STACK_SIZE + EXTRA_STACK, TValue, L1->memcat); L1->stacksize = BASIC_STACK_SIZE + EXTRA_STACK; + TValue* stack = L1->stack; for (int i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++) - setnilvalue(L1->stack + i); /* erase new stack */ - L1->top = L1->stack; - L1->stack_last = L1->stack + (L1->stacksize - EXTRA_STACK) - 1; + setnilvalue(stack + i); /* erase new stack */ + L1->top = stack; + L1->stack_last = stack + (L1->stacksize - (FFlag::LuauReduceStackReallocs ? EXTRA_STACK : 1 + EXTRA_STACK)); /* initialize first ci */ L1->ci->func = L1->top; 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; 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 */ luaS_resize(L, LUA_MINSTRTABSIZE); /* initial size of string table */ luaT_init(L); @@ -69,6 +71,7 @@ static void preinit_state(lua_State* L, global_State* g) L->global = g; L->stack = NULL; L->stacksize = 0; + L->gt = NULL; L->openupval = NULL; L->size_ci = 0; L->nCcalls = L->baseCcalls = 0; @@ -80,7 +83,6 @@ static void preinit_state(lua_State* L, global_State* g) L->stackstate = 0; L->activememcat = 0; L->userdata = NULL; - setnilvalue(gt(L)); } static void close_state(lua_State* L) @@ -116,7 +118,7 @@ lua_State* luaE_newthread(lua_State* L) preinit_state(L1, L->global); L1->activememcat = L->activememcat; // inherit the active memory category 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; LUAU_ASSERT(iswhite(obj2gco(L1))); return L1; @@ -144,14 +146,30 @@ void lua_resetthread(lua_State* L) ci->top = ci->base + LUA_MINSTACK; setnilvalue(ci->func); 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 */ L->status = LUA_OK; L->base = L->ci->base; L->top = L->ci->base; L->nCcalls = L->baseCcalls = 0; /* 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++) setnilvalue(L->stack + i); } @@ -193,6 +211,7 @@ lua_State* lua_newstate(lua_Alloc f, void* ud) g->strt.size = 0; g->strt.nuse = 0; g->strt.hash = NULL; + setnilvalue(&g->pseudotemp); setnilvalue(registry(L)); g->gcstate = GCSpause; if (!FFlag::LuauGcPagedSweep) diff --git a/VM/src/lstate.h b/VM/src/lstate.h index 0708b71f3..6dd891382 100644 --- a/VM/src/lstate.h +++ b/VM/src/lstate.h @@ -5,9 +5,6 @@ #include "lobject.h" #include "ltm.h" -/* table of globals */ -#define gt(L) (&L->l_gt) - /* 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* 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 */ 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? */ - TValue l_gt; /* table of globals */ - TValue env; /* temporary place for environments */ + Table* gt; /* table of globals */ UpVal* openupval; /* list of open upvalues in this stack */ GCObject* gclist; diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index c3b662a2c..6c31d36f2 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -39,7 +39,7 @@ // When calling luau_callTM, we usually push the arguments to the top of the stack. // 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 // All external function calls that can cause stack realloc or Lua calls have to be wrapped in VM_PROTECT diff --git a/VM/src/lvmload.cpp b/VM/src/lvmload.cpp index 2472cd902..4e5435b7f 100644 --- a/VM/src/lvmload.cpp +++ b/VM/src/lvmload.cpp @@ -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 // this is technically not necessary but it reduces the number of exceptions when loading scripts that rely on getfenv/setfenv for global // 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}; - 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 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; // 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); diff --git a/VM/src/lvmutils.cpp b/VM/src/lvmutils.cpp index 31dd59c86..8a18a4d46 100644 --- a/VM/src/lvmutils.cpp +++ b/VM/src/lvmutils.cpp @@ -55,7 +55,7 @@ static void callTMres(lua_State* L, StkId res, const TValue* f, const TValue* p1 { ptrdiff_t result = savestack(L, res); // 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 // stack and checkstack may invalidate those pointers // * 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) { // 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 // stack and checkstack may invalidate those pointers // * we cannot use savestack/restorestack because the arguments are sometimes on the C++ stack diff --git a/fuzz/linter.cpp b/fuzz/linter.cpp index 55e0888b9..04638d23d 100644 --- a/fuzz/linter.cpp +++ b/fuzz/linter.cpp @@ -32,7 +32,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) Luau::LintOptions lintOptions; lintOptions.warningMask = ~0ull; - Luau::lint(parseResult.root, names, typeck.globalScope, nullptr, lintOptions); + Luau::lint(parseResult.root, names, typeck.globalScope, nullptr, {}, lintOptions); } return 0; diff --git a/fuzz/proto.cpp b/fuzz/proto.cpp index 27e534927..f407248a5 100644 --- a/fuzz/proto.cpp +++ b/fuzz/proto.cpp @@ -227,7 +227,7 @@ DEFINE_PROTO_FUZZER(const luau::StatBlock& message) if (kFuzzLinter) { Luau::LintOptions lintOptions = {~0u}; - Luau::lint(parseResult.root, names, sharedEnv.globalScope, module.get(), lintOptions); + Luau::lint(parseResult.root, names, sharedEnv.globalScope, module.get(), {}, lintOptions); } } diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 59e125740..1978a0d31 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -1,7 +1,6 @@ // 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/BuiltinDefinitions.h" -#include "Luau/Parser.h" #include "Luau/TypeInfer.h" #include "Luau/TypeVar.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); } +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") { check(R"( diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index cd7a21d80..f982c86fa 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -10,6 +10,11 @@ #include #include +namespace Luau +{ +std::string rep(const std::string& s, size_t n); +} + using namespace Luau; 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") { // 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 diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 8b58d2ce8..b09c1efb9 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -492,6 +492,8 @@ TEST_CASE("DateTime") TEST_CASE("Debug") { + ScopedFastFlag luauTableFieldFunctionDebugname{"LuauTableFieldFunctionDebugname", true}; + runConformance("debug.lua"); } @@ -890,6 +892,12 @@ TEST_CASE("Coverage") lua_pushstring(L, function); 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) if (hits[i] != -1) { diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index c74bfa272..dbdd06a44 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -2,6 +2,7 @@ #include "Fixture.h" #include "Luau/AstQuery.h" +#include "Luau/Parser.h" #include "Luau/TypeVar.h" #include "Luau/TypeAttach.h" #include "Luau/Transpiler.h" @@ -112,7 +113,7 @@ AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& pars sourceModule->name = fromString(mainModuleName); sourceModule->root = result.root; sourceModule->mode = parseMode(result.hotcomments); - sourceModule->ignoreLints = LintWarning::parseMask(result.hotcomments); + sourceModule->hotcomments = std::move(result.hotcomments); 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) { ParseOptions parseOptions; + parseOptions.captureComments = true; configResolver.defaultConfig.mode = Mode::Nonstrict; parse(source, parseOptions); diff --git a/tests/Fixture.h b/tests/Fixture.h index ab852ef6d..4e45a952a 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -8,7 +8,6 @@ #include "Luau/Linter.h" #include "Luau/Location.h" #include "Luau/ModuleResolver.h" -#include "Luau/Parser.h" #include "Luau/ToString.h" #include "Luau/TypeInfer.h" #include "Luau/TypeVar.h" diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index ea1a08fe7..8a59acd18 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -2,7 +2,6 @@ #include "Luau/AstQuery.h" #include "Luau/BuiltinDefinitions.h" #include "Luau/Frontend.h" -#include "Luau/Parser.h" #include "Luau/RequireTracer.h" #include "Fixture.h" @@ -897,8 +896,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "clearStats") TEST_CASE_FIXTURE(FrontendFixture, "typecheck_twice_for_ast_types") { - ScopedFastFlag sffs("LuauTypeCheckTwice", true); - fileResolver.source["Module/A"] = R"( local a = 1 )"; diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index 577415fca..d4b973607 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -1395,12 +1395,10 @@ TEST_CASE_FIXTURE(Fixture, "DeprecatedApi") TypeId colorType = typeChecker.globalTypes.addType(TableTypeVar{{}, std::nullopt, typeChecker.globalScope->level, Luau::TableState::Sealed}); - getMutable(colorType)->props = { - {"toHSV", {typeChecker.anyType, /* deprecated= */ true, "Color3:ToHSV"} } - }; + getMutable(colorType)->props = {{"toHSV", {typeChecker.anyType, /* deprecated= */ true, "Color3:ToHSV"}}}; addGlobalBinding(typeChecker, "Color3", Binding{colorType, {}}); - + freeze(typeChecker.globalTypes); LintResult result = lintTyped(R"( @@ -1554,8 +1552,46 @@ _ = (math.random() < 0.5 and false) or 42 -- currently ignored )"); 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[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"); + 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[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(); diff --git a/tests/NonstrictMode.test.cpp b/tests/NonstrictMode.test.cpp index 931a8403a..5bad99014 100644 --- a/tests/NonstrictMode.test.cpp +++ b/tests/NonstrictMode.test.cpp @@ -1,5 +1,4 @@ // 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/TypeInfer.h" #include "Luau/TypeVar.h" diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index c1a8887b6..0d4c088dd 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -1,6 +1,5 @@ // 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 "Fixture.h" #include "ScopedFlags.h" @@ -300,8 +299,9 @@ TEST_CASE_FIXTURE(Fixture, "functions_can_have_return_annotations") AstStatFunction* statFunction = block->body.data[0]->as(); REQUIRE(statFunction != nullptr); - CHECK_EQ(statFunction->func->returnAnnotation.types.size, 1); - CHECK(statFunction->func->returnAnnotation.tailType == nullptr); + REQUIRE(statFunction->func->returnAnnotation.has_value()); + 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") @@ -316,9 +316,9 @@ TEST_CASE_FIXTURE(Fixture, "functions_can_have_a_function_type_annotation") AstStatFunction* statFunc = block->body.data[0]->as(); REQUIRE(statFunc != nullptr); - AstArray& retTypes = statFunc->func->returnAnnotation.types; - REQUIRE(statFunc->func->hasReturnAnnotation); - CHECK(statFunc->func->returnAnnotation.tailType == nullptr); + REQUIRE(statFunc->func->returnAnnotation.has_value()); + CHECK(statFunc->func->returnAnnotation->tailType == nullptr); + AstArray& retTypes = statFunc->func->returnAnnotation->types; REQUIRE(retTypes.size == 1); AstTypeFunction* funTy = retTypes.data[0]->as(); @@ -337,9 +337,9 @@ TEST_CASE_FIXTURE(Fixture, "function_return_type_should_disambiguate_from_functi AstStatFunction* statFunc = block->body.data[0]->as(); REQUIRE(statFunc != nullptr); - AstArray& retTypes = statFunc->func->returnAnnotation.types; - REQUIRE(statFunc->func->hasReturnAnnotation); - CHECK(statFunc->func->returnAnnotation.tailType == nullptr); + REQUIRE(statFunc->func->returnAnnotation.has_value()); + CHECK(statFunc->func->returnAnnotation->tailType == nullptr); + AstArray& retTypes = statFunc->func->returnAnnotation->types; REQUIRE(retTypes.size == 2); AstTypeReference* ty0 = retTypes.data[0]->as(); @@ -363,9 +363,9 @@ TEST_CASE_FIXTURE(Fixture, "function_return_type_should_parse_as_function_type_a AstStatFunction* statFunc = block->body.data[0]->as(); REQUIRE(statFunc != nullptr); - AstArray& retTypes = statFunc->func->returnAnnotation.types; - REQUIRE(statFunc->func->hasReturnAnnotation); - CHECK(statFunc->func->returnAnnotation.tailType == nullptr); + REQUIRE(statFunc->func->returnAnnotation.has_value()); + CHECK(statFunc->func->returnAnnotation->tailType == nullptr); + AstArray& retTypes = statFunc->func->returnAnnotation->types; REQUIRE(retTypes.size == 1); AstTypeFunction* funTy = retTypes.data[0]->as(); @@ -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") { - ParseResult result = parseEx(" --!strict "); + ParseOptions options; + options.captureComments = true; + + ParseResult result = parseEx(" --!strict ", options); std::optional mode = parseMode(result.hotcomments); REQUIRE(bool(mode)); 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 = parseMode(result.hotcomments); + REQUIRE(!mode); +} + TEST_CASE_FIXTURE(Fixture, "stop_if_line_ends_with_hyphen") { 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") { - ParseResult result = parseEx("--!nonstrict"); + ParseOptions options; + options.captureComments = true; + + ParseResult result = parseEx("--!nonstrict", options); CHECK(result.errors.empty()); std::optional mode = parseMode(result.hotcomments); REQUIRE(bool(mode)); @@ -729,7 +745,10 @@ TEST_CASE_FIXTURE(Fixture, "nonstrict_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()); std::optional mode = parseMode(result.hotcomments); REQUIRE(bool(mode)); @@ -1498,8 +1517,6 @@ return TEST_CASE_FIXTURE(Fixture, "parse_error_broken_comment") { - ScopedFastFlag luauStartingBrokenComment{"LuauStartingBrokenComment", true}; - const char* expected = "Expected identifier when parsing expression, got unfinished comment"; matchParseError("--[[unfinished work", expected); diff --git a/tests/Repl.test.cpp b/tests/Repl.test.cpp index 1f9c97397..87a1e1e2a 100644 --- a/tests/Repl.test.cpp +++ b/tests/Repl.test.cpp @@ -73,7 +73,7 @@ class ReplFixture private: std::unique_ptr 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. // More elaborate tests to ensure correct output can be added if we introduce // a more feature rich pretty printer. @@ -158,12 +158,25 @@ TEST_CASE_FIXTURE(ReplFixture, "CompleteGlobalVariables") myvariable1 = 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 = ""; + CHECK(completions.size() == 2); + CHECK(checkCompletion(completions, prefix, "myvariable1")); + CHECK(checkCompletion(completions, prefix, "myvariable2")); + } + { + // Try completing some builtin functions + CompletionSet completions = getCompletionSet("math.m"); - std::string prefix = ""; - CHECK(completions.size() == 2); - CHECK(checkCompletion(completions, prefix, "myvariable1")); - CHECK(checkCompletion(completions, prefix, "myvariable2")); + 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") @@ -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(); diff --git a/tests/RequireTracer.test.cpp b/tests/RequireTracer.test.cpp index b9fd04d69..ba03f3638 100644 --- a/tests/RequireTracer.test.cpp +++ b/tests/RequireTracer.test.cpp @@ -1,6 +1,6 @@ // 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/Parser.h" #include "Fixture.h" diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index a87292683..31d7ef10b 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -598,15 +598,13 @@ TEST_CASE_FIXTURE(Fixture, "generic_typevars_are_not_considered_to_escape_their_ /* * The two-pass alias definition system starts by ascribing a free TypeVar to each alias. It then * circles back to fill in the actual type later on. - * + * * If this free type is unified with something degenerate like `any`, we need to take extra care * to ensure that the alias actually binds to the type that the user expected. */ TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_unification_with_any") { - ScopedFastFlag sff[] = { - {"LuauTwoPassAliasDefinitionFix", true} - }; + ScopedFastFlag sff[] = {{"LuauTwoPassAliasDefinitionFix", true}}; CheckResult result = check(R"( local function x() diff --git a/tests/TypeInfer.annotations.test.cpp b/tests/TypeInfer.annotations.test.cpp index 572b882d8..2ad11d01c 100644 --- a/tests/TypeInfer.annotations.test.cpp +++ b/tests/TypeInfer.annotations.test.cpp @@ -1,6 +1,5 @@ // 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/Parser.h" #include "Luau/TypeInfer.h" #include "Luau/TypeVar.h" diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index df06884d8..f3dfb214d 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -1,5 +1,4 @@ // 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/BuiltinDefinitions.h" diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index 0283ae192..98fa66eb1 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -1,6 +1,5 @@ // 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/Parser.h" #include "Luau/TypeInfer.h" #include "Luau/TypeVar.h" diff --git a/tests/TypeInfer.definitions.test.cpp b/tests/TypeInfer.definitions.test.cpp index 2652486be..c6d55793d 100644 --- a/tests/TypeInfer.definitions.test.cpp +++ b/tests/TypeInfer.definitions.test.cpp @@ -1,6 +1,5 @@ // 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/Parser.h" #include "Luau/TypeInfer.h" #include "Luau/TypeVar.h" diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 8a2c6f27e..f8fccf6b0 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -1,5 +1,4 @@ // 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/TypeVar.h" diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index 93c0baf6d..d677e28d8 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -1,5 +1,4 @@ // 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/TypeVar.h" diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index 2bcd840c8..eee0e0f17 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -1,5 +1,4 @@ // 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 "Fixture.h" diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index df365fda4..9021700dc 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -426,8 +426,6 @@ TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options") {"LuauSingletonTypes", true}, {"LuauParseSingletonTypes", true}, {"LuauExpectedTypesOfProperties", true}, - {"LuauIfElseExpectedType2", true}, - {"LuauIfElseBranchTypeUnion", true}, }; CheckResult result = check(R"( diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index f19cb618b..6bcd4b99a 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -1,6 +1,5 @@ // 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/Parser.h" #include "Luau/TypeInfer.h" #include "Luau/TypeVar.h" @@ -2168,8 +2167,6 @@ b() TEST_CASE_FIXTURE(Fixture, "length_operator_union") { - ScopedFastFlag luauLengthOnCompositeType{"LuauLengthOnCompositeType", true}; - CheckResult result = check(R"( local x: {number} | {string} local y = #x @@ -2180,8 +2177,6 @@ local y = #x TEST_CASE_FIXTURE(Fixture, "length_operator_intersection") { - ScopedFastFlag luauLengthOnCompositeType{"LuauLengthOnCompositeType", true}; - CheckResult result = check(R"( local x: {number} & {z:string} -- mixed tables are evil local y = #x @@ -2192,8 +2187,6 @@ local y = #x TEST_CASE_FIXTURE(Fixture, "length_operator_non_table_union") { - ScopedFastFlag luauLengthOnCompositeType{"LuauLengthOnCompositeType", true}; - CheckResult result = check(R"( local x: {number} | any | string local y = #x @@ -2204,8 +2197,6 @@ local y = #x TEST_CASE_FIXTURE(Fixture, "length_operator_union_errors") { - ScopedFastFlag luauLengthOnCompositeType{"LuauLengthOnCompositeType", true}; - CheckResult result = check(R"( local x: {number} | number | string local y = #x @@ -2214,4 +2205,38 @@ local y = #x 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(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 531a382f5..323585712 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -2,7 +2,6 @@ #include "Luau/AstQuery.h" #include "Luau/BuiltinDefinitions.h" -#include "Luau/Parser.h" #include "Luau/Scope.h" #include "Luau/TypeInfer.h" #include "Luau/TypeVar.h" @@ -4654,8 +4653,6 @@ a = setmetatable(a, { __call = function(x) end }) TEST_CASE_FIXTURE(Fixture, "infer_through_group_expr") { - ScopedFastFlag luauGroupExpectedType{"LuauGroupExpectedType", true}; - CheckResult result = check(R"( local function f(a: (number, number) -> number) return a(1, 3) 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") { - ScopedFastFlag sff3{"LuauIfElseBranchTypeUnion", true}; - - { - CheckResult result = check(R"(local a: number? = if true then 42 else nil)"); + CheckResult result = check(R"(local a: number? = if true then 42 else nil)"); - LUAU_REQUIRE_NO_ERRORS(result); - 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") { - ScopedFastFlag luauIfElseExpectedType2{"LuauIfElseExpectedType2", true}; - ScopedFastFlag luauIfElseBranchTypeUnion{"LuauIfElseBranchTypeUnion", true}; - CheckResult result = check(R"( type X = {number | string} 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") { - ScopedFastFlag luauIfElseExpectedType2{"LuauIfElseExpectedType2", true}; - ScopedFastFlag luauIfElseBranchTypeUnion{"LuauIfElseBranchTypeUnion", true}; - CheckResult result = check(R"( 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") { - ScopedFastFlag luauIfElseExpectedType2{"LuauIfElseExpectedType2", true}; - CheckResult result = check(R"( local function times(n: any, f: () -> T) local result: {T} = {} @@ -5058,8 +5043,6 @@ end TEST_CASE_FIXTURE(Fixture, "recursive_metatable_crash") { - ScopedFastFlag luauMetatableAreEqualRecursion{"LuauMetatableAreEqualRecursion", true}; - CheckResult result = check(R"( local function getIt() local y @@ -5076,8 +5059,6 @@ local c = a or b TEST_CASE_FIXTURE(Fixture, "bound_typepack_promote") { - ScopedFastFlag luauCommittingTxnLogFreeTpPromote{"LuauCommittingTxnLogFreeTpPromote", true}; - // No assertions should trigger check(R"( local function p() @@ -5251,7 +5232,6 @@ TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_string_singleton") {"LuauDiscriminableUnions2", true}, {"LuauRefactorTypeVarQuestions", true}, {"LuauSingletonTypes", true}, - {"LuauLengthOnCompositeType", true}, }; CheckResult result = check(R"( @@ -5272,7 +5252,6 @@ TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_union_of_string_singleton") {"LuauDiscriminableUnions2", true}, {"LuauRefactorTypeVarQuestions", true}, {"LuauSingletonTypes", true}, - {"LuauLengthOnCompositeType", true}, }; CheckResult result = check(R"( diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index 8c7fb79ab..4669ea8eb 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -1,5 +1,4 @@ // 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/TypeInfer.h" #include "Luau/TypeVar.h" diff --git a/tests/TypeInfer.typePacks.cpp b/tests/TypeInfer.typePacks.cpp index 079870f57..cbe2e48f2 100644 --- a/tests/TypeInfer.typePacks.cpp +++ b/tests/TypeInfer.typePacks.cpp @@ -1,6 +1,5 @@ // 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/Parser.h" #include "Luau/TypeInfer.h" #include "Luau/TypeVar.h" @@ -930,8 +929,6 @@ type R = { m: F } TEST_CASE_FIXTURE(Fixture, "pack_tail_unification_check") { - ScopedFastFlag luauUnifyPackTails{"LuauUnifyPackTails", true}; - CheckResult result = check(R"( local a: () -> (number, ...string) local b: () -> (number, ...boolean) diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 759794e63..3b53ddfe3 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -1,5 +1,4 @@ // 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/TypeVar.h" diff --git a/tests/TypePack.test.cpp b/tests/TypePack.test.cpp index 8b056544a..c4931578d 100644 --- a/tests/TypePack.test.cpp +++ b/tests/TypePack.test.cpp @@ -1,5 +1,4 @@ // 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/TypeVar.h" diff --git a/tests/TypeVar.test.cpp b/tests/TypeVar.test.cpp index 329e7b1f6..e43161fa3 100644 --- a/tests/TypeVar.test.cpp +++ b/tests/TypeVar.test.cpp @@ -1,5 +1,4 @@ // 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/TypeInfer.h" #include "Luau/TypeVar.h" diff --git a/tests/conformance/coroutine.lua b/tests/conformance/coroutine.lua index f2ecc96bb..b4f81bbae 100644 --- a/tests/conformance/coroutine.lua +++ b/tests/conformance/coroutine.lua @@ -371,6 +371,15 @@ do st, msg = coroutine.close(co) assert(st and msg == nil) 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 return 'OK' diff --git a/tests/conformance/coverage.lua b/tests/conformance/coverage.lua index f899603f9..14d843a43 100644 --- a/tests/conformance/coverage.lua +++ b/tests/conformance/coverage.lua @@ -49,16 +49,24 @@ foo() c = getcoverage(foo) assert(#c == 1) assert(c[1].name == "foo") +assert(c[1].linedefined == 4) +assert(c[1].depth == 0) assert(validate(c[1], {5, 6, 7}, {})) bar() c = getcoverage(bar) assert(#c == 3) assert(c[1].name == "bar") +assert(c[1].linedefined == 10) +assert(c[1].depth == 0) assert(validate(c[1], {11, 15, 19}, {})) assert(c[2].name == "one") +assert(c[2].linedefined == 11) +assert(c[2].depth == 1) assert(validate(c[2], {12}, {})) assert(c[3].name == nil) +assert(c[3].linedefined == 15) +assert(c[3].depth == 1) assert(validate(c[3], {}, {16})) return 'OK' diff --git a/tests/conformance/debug.lua b/tests/conformance/debug.lua index 0e4100005..0c8cc2d87 100644 --- a/tests/conformance/debug.lua +++ b/tests/conformance/debug.lua @@ -76,6 +76,9 @@ assert(baz(co, 2, "n") == nil) assert(baz(math.sqrt, "n") == "sqrt") 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 function quux(...) return {debug.info(...)} diff --git a/tests/main.cpp b/tests/main.cpp index cd24e100f..2af9f7023 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -10,6 +10,9 @@ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif +#ifndef NOMINMAX +#define NOMINMAX +#endif #include // IsDebuggerPresent #endif @@ -52,7 +55,7 @@ static bool debuggerPresent() #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()) LUAU_DEBUGBREAK(); @@ -218,7 +221,7 @@ static void setFastFlags(const std::vector& flags) int main(int argc, char** argv) { - Luau::assertHandler() = assertionHandler; + Luau::assertHandler() = testAssertionHandler; doctest::registerReporter("boost", 0, true); diff --git a/tools/natvis/Analysis.natvis b/tools/natvis/Analysis.natvis new file mode 100644 index 000000000..5de0140e2 --- /dev/null +++ b/tools/natvis/Analysis.natvis @@ -0,0 +1,78 @@ + + + + + AnyTypeVar + + + + {{ index=0, value={*($T1*)storage} }} + {{ index=1, value={*($T2*)storage} }} + {{ index=2, value={*($T3*)storage} }} + {{ index=3, value={*($T4*)storage} }} + {{ index=4, value={*($T5*)storage} }} + {{ index=5, value={*($T6*)storage} }} + {{ index=6, value={*($T7*)storage} }} + {{ index=7, value={*($T8*)storage} }} + {{ index=8, value={*($T9*)storage} }} + {{ index=9, value={*($T10*)storage} }} + {{ index=10, value={*($T11*)storage} }} + {{ index=11, value={*($T12*)storage} }} + {{ index=12, value={*($T13*)storage} }} + {{ index=13, value={*($T14*)storage} }} + {{ index=14, value={*($T15*)storage} }} + {{ index=15, value={*($T16*)storage} }} + {{ index=16, value={*($T17*)storage} }} + {{ index=17, value={*($T18*)storage} }} + {{ index=18, value={*($T19*)storage} }} + {{ index=19, value={*($T20*)storage} }} + {{ index=20, value={*($T21*)storage} }} + {{ index=21, value={*($T22*)storage} }} + {{ index=22, value={*($T23*)storage} }} + {{ index=23, value={*($T24*)storage} }} + {{ index=24, value={*($T25*)storage} }} + {{ index=25, value={*($T26*)storage} }} + {{ index=26, value={*($T27*)storage} }} + {{ index=27, value={*($T28*)storage} }} + {{ index=28, value={*($T29*)storage} }} + {{ index=29, value={*($T30*)storage} }} + {{ index=30, value={*($T31*)storage} }} + {{ index=31, value={*($T32*)storage} }} + + typeId + *($T1*)storage + *($T2*)storage + *($T3*)storage + *($T4*)storage + *($T5*)storage + *($T6*)storage + *($T7*)storage + *($T8*)storage + *($T9*)storage + *($T10*)storage + *($T11*)storage + *($T12*)storage + *($T13*)storage + *($T14*)storage + *($T15*)storage + *($T16*)storage + *($T17*)storage + *($T18*)storage + *($T19*)storage + *($T20*)storage + *($T21*)storage + *($T22*)storage + *($T23*)storage + *($T24*)storage + *($T25*)storage + *($T26*)storage + *($T27*)storage + *($T28*)storage + *($T29*)storage + *($T30*)storage + *($T31*)storage + *($T32*)storage + + + + diff --git a/tools/natvis/Ast.natvis b/tools/natvis/Ast.natvis new file mode 100644 index 000000000..322eb8f67 --- /dev/null +++ b/tools/natvis/Ast.natvis @@ -0,0 +1,25 @@ + + + + + AstArray size={size} + + size + + size + data + + + + + + + size_ + + size_ + storage._Mypair._Myval2._Myfirst + offset + + + + + diff --git a/tools/natvis/VM.natvis b/tools/natvis/VM.natvis new file mode 100644 index 000000000..9924e194f --- /dev/null +++ b/tools/natvis/VM.natvis @@ -0,0 +1,269 @@ + + + + + nil + {(bool)value.b} + lightuserdata {value.p} + number = {value.n} + vector = {value.v[0]}, {value.v[1]}, {*(float*)&extra} + {value.gc->ts} + {value.gc->h} + function {value.gc->cl,view(short)} + userdata {value.gc->u} + thread {value.gc->th} + proto {value.gc->p} + upvalue {value.gc->uv} + deadkey + empty + + value.p + value.gc->ts + value.gc->h + value.gc->cl + value.gc->cl + value.gc->u + value.gc->th + value.gc->p + value.gc->uv + + fixed ({(int)value.gc->gch.marked}) + black ({(int)value.gc->gch.marked}) + white ({(int)value.gc->gch.marked}) + white ({(int)value.gc->gch.marked}) + gray ({(int)value.gc->gch.marked}) + + + + + + nil + {(bool)value.b} + lightuserdata {value.p} + number = {value.n} + vector = {value.v[0]}, {value.v[1]}, {*(float*)&extra} + {value.gc->ts} + {value.gc->h} + function {value.gc->cl,view(short)} + userdata {value.gc->u} + thread {value.gc->th} + proto {value.gc->p} + upvalue {value.gc->uv} + deadkey + empty + + (void**)value.p + value.gc->ts + value.gc->h + value.gc->cl + value.gc->cl + value.gc->u + value.gc->th + value.gc->p + value.gc->uv + + next + + + + + {key,na} = {val} + --- + + + + table + + metatable + + + [size] {1<<lsizenode} + + + 1<<lsizenode + node[$i] + + + + + [size] {sizearray} + + + sizearray + array[$i] + + + + + + + + + + + + + 1 + + + + + metatable->node[i].val + + + + i = i + 1 + + + "unknown",sb + + + tag + len + metatable + data + + + + + {c.f,na} + {l.p,na} + {c} + {l} + invalid + + + + {data,s} + + + + + {ci->func->value.gc->cl.c.f,na} + + + {ci->func->value.gc->cl.l.p->source->data,sb}:{ci->func->value.gc->cl.l.p->linedefined,d} {ci->func->value.gc->cl.l.p->debugname->data,sb} + + + {ci->func->value.gc->cl.l.p->source->data,sb}:{ci->func->value.gc->cl.l.p->linedefined,d} + + thread + + + {ci-base_ci} frames + + + ci-base_ci + + + base_ci[ci-base_ci - $i].func->value.gc->cl,view(short) + + + + + + {top-base} values + + + top-base + base + + + + + {top-stack} values + + + top-stack + stack + + + + + + + openupval + u.l.next + this + + + + l_gt + env + userdata + + + + + {source->data,sb}:{linedefined} function {debugname->data,sb} [{(int)numparams} arg, {(int)nups} upval] + {source->data,sb}:{linedefined} [{(int)numparams} arg, {(int)nups} upval] + + debugname + + constants + + + sizek + k[$i] + + + + + locals + + + sizelocvars + locvars[$i] + + + + + bytecode + + + sizecode + code[$i] + + + + + functions + + + sizep + p[$i] + + + + + upvals + + + sizeupvalues + upvalues[$i] + + + + + source + + + + + + + {(lua_Type)tt} + + + fixed ({(int)marked}) + black ({(int)marked}) + white ({(int)marked}) + white ({(int)marked}) + gray ({(int)marked}) + unknown + + memcat + + + +