Merge remote-tracking branch 'upstream/master' into prototyping-strict-mode

This commit is contained in:
ajeffrey@roblox.com 2022-02-23 18:30:19 -06:00
commit 2b52a6eb68
157 changed files with 3677 additions and 1179 deletions

View file

@ -9,12 +9,14 @@ on:
- 'papers/**' - 'papers/**'
- 'rfcs/**' - 'rfcs/**'
- '*.md' - '*.md'
- 'prototyping/**'
pull_request: pull_request:
paths-ignore: paths-ignore:
- 'docs/**' - 'docs/**'
- 'papers/**' - 'papers/**'
- 'rfcs/**' - 'rfcs/**'
- '*.md' - '*.md'
- 'prototyping/**'
jobs: jobs:
unix: unix:

View file

@ -8,12 +8,18 @@ on:
paths: paths:
- '.github/workflows/**' - '.github/workflows/**'
- 'prototyping/**' - 'prototyping/**'
- 'Analysis/src/JsonEncoder.cpp' - 'Analysis/**'
- 'Ast/**'
- 'CLI/Ast.cpp'
- 'CLI/FileUtils.*'
pull_request: pull_request:
paths: paths:
- '.github/workflows/**' - '.github/workflows/**'
- 'prototyping/**' - 'prototyping/**'
- 'Analysis/src/JsonEncoder.cpp' - 'Analysis/**'
- 'Ast/**'
- 'CLI/Ast.cpp'
- 'CLI/FileUtils.*'
jobs: jobs:
linux: linux:
@ -28,6 +34,11 @@ jobs:
with: with:
path: ~/.cabal/store path: ~/.cabal/store
key: prototyping-${{ runner.os }}-${{ matrix.agda }} key: prototyping-${{ runner.os }}-${{ matrix.agda }}
- uses: actions/cache@v2
id: luau-ast-cache
with:
path: ./build
key: prototyping-${{ runner.os }}-${{ hashFiles('Ast/**', 'Analysis/**', 'CLI/Ast.cpp', 'CLI/FileUtils.*')}}
- name: install cabal - name: install cabal
run: sudo apt-get install -y cabal-install run: sudo apt-get install -y cabal-install
- name: cabal update - name: cabal update
@ -37,22 +48,35 @@ jobs:
working-directory: prototyping working-directory: prototyping
run: | run: |
cabal install Agda-${{ matrix.agda }} cabal install Agda-${{ matrix.agda }}
cabal install --lib scientific --package-env . cabal install --lib scientific vector aeson --package-env .
cabal install --lib vector --package-env . - name: check targets
cabal install --lib aeson --package-env .
- name: check examples
working-directory: prototyping working-directory: prototyping
run: ~/.cabal/bin/agda Examples.agda run: |
- name: build PrettyPrinter ~/.cabal/bin/agda Examples.agda
~/.cabal/bin/agda Properties.agda
- name: build executables
working-directory: prototyping working-directory: prototyping
run: ~/.cabal/bin/agda --compile --ghc-flag=-v PrettyPrinter.agda run: |
~/.cabal/bin/agda --compile PrettyPrinter.agda
~/.cabal/bin/agda --compile Interpreter.agda
- name: cmake configure - name: cmake configure
run: cmake . if: steps.luau-ast-cache.outputs.cache-hit != 'true'
run: |
mkdir -p build
cd build
cmake build ../
- name: cmake build luau-ast - name: cmake build luau-ast
run: cmake --build . --target Luau.Ast.CLI if: steps.luau-ast-cache.outputs.cache-hit != 'true'
- name: run smoketest run: |
cmake --build ./build --target Luau.Ast.CLI
- name: run tests
working-directory: prototyping working-directory: prototyping
run: ../luau-ast Examples/SmokeTest.lua | ./PrettyPrinter > Examples/SmokeTestOutput.lua run: |
- name: diff smoketest mkdir test-failures
working-directory: prototyping python tests.py -l ../build/luau-ast --write-diff-failures --diff-failure-location test-failures/
run: diff Examples/SmokeTest.lua Examples/SmokeTestOutput.lua - uses: actions/upload-artifact@v2
if: failure()
with:
name: test failures
path: prototyping/test-failures
retention-days: 5

View file

@ -9,6 +9,7 @@ on:
- 'papers/**' - 'papers/**'
- 'rfcs/**' - 'rfcs/**'
- '*.md' - '*.md'
- 'prototyping/**'
jobs: jobs:
build: build:

View file

@ -86,6 +86,8 @@ struct OwningAutocompleteResult
}; };
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback); AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback);
// Deprecated, do not use in new work.
OwningAutocompleteResult autocompleteSource(Frontend& frontend, std::string_view source, Position position, StringCompletionCallback callback); OwningAutocompleteResult autocompleteSource(Frontend& frontend, std::string_view source, Position position, StringCompletionCallback callback);
} // namespace Luau } // namespace Luau

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -307,8 +307,8 @@ private:
// //
// We can't use a DenseHashMap here because we need a non-const iterator // We can't use a DenseHashMap here because we need a non-const iterator
// over the map when we concatenate. // over the map when we concatenate.
std::unordered_map<TypeId, std::unique_ptr<PendingType>> typeVarChanges; std::unordered_map<TypeId, std::unique_ptr<PendingType>, DenseHashPointer> typeVarChanges;
std::unordered_map<TypePackId, std::unique_ptr<PendingTypePack>> typePackChanges; std::unordered_map<TypePackId, std::unique_ptr<PendingTypePack>, DenseHashPointer> typePackChanges;
TxnLog* parent = nullptr; TxnLog* parent = nullptr;

View file

@ -5,7 +5,6 @@
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/Symbol.h" #include "Luau/Symbol.h"
#include "Luau/Parser.h"
#include "Luau/Substitution.h" #include "Luau/Substitution.h"
#include "Luau/TxnLog.h" #include "Luau/TxnLog.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
@ -37,6 +36,15 @@ struct Unifier;
// A substitution which replaces generic types in a given set by free types. // A substitution which replaces generic types in a given set by free types.
struct ReplaceGenerics : Substitution struct ReplaceGenerics : Substitution
{ {
ReplaceGenerics(
const TxnLog* log, TypeArena* arena, TypeLevel level, const std::vector<TypeId>& generics, const std::vector<TypePackId>& genericPacks)
: Substitution(log, arena)
, level(level)
, generics(generics)
, genericPacks(genericPacks)
{
}
TypeLevel level; TypeLevel level;
std::vector<TypeId> generics; std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks; std::vector<TypePackId> genericPacks;
@ -50,8 +58,13 @@ struct ReplaceGenerics : Substitution
// A substitution which replaces generic functions by monomorphic functions // A substitution which replaces generic functions by monomorphic functions
struct Instantiation : Substitution struct Instantiation : Substitution
{ {
Instantiation(const TxnLog* log, TypeArena* arena, TypeLevel level)
: Substitution(log, arena)
, level(level)
{
}
TypeLevel level; TypeLevel level;
ReplaceGenerics replaceGenerics;
bool ignoreChildren(TypeId ty) override; bool ignoreChildren(TypeId ty) override;
bool isDirty(TypeId ty) override; bool isDirty(TypeId ty) override;
bool isDirty(TypePackId tp) override; bool isDirty(TypePackId tp) override;
@ -62,6 +75,12 @@ struct Instantiation : Substitution
// A substitution which replaces free types by generic types. // A substitution which replaces free types by generic types.
struct Quantification : Substitution struct Quantification : Substitution
{ {
Quantification(TypeArena* arena, TypeLevel level)
: Substitution(TxnLog::empty(), arena)
, level(level)
{
}
TypeLevel level; TypeLevel level;
std::vector<TypeId> generics; std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks; std::vector<TypePackId> genericPacks;
@ -74,6 +93,13 @@ struct Quantification : Substitution
// A substitution which replaces free types by any // A substitution which replaces free types by any
struct Anyification : Substitution struct Anyification : Substitution
{ {
Anyification(TypeArena* arena, TypeId anyType, TypePackId anyTypePack)
: Substitution(TxnLog::empty(), arena)
, anyType(anyType)
, anyTypePack(anyTypePack)
{
}
TypeId anyType; TypeId anyType;
TypePackId anyTypePack; TypePackId anyTypePack;
bool isDirty(TypeId ty) override; bool isDirty(TypeId ty) override;
@ -85,6 +111,13 @@ struct Anyification : Substitution
// A substitution which replaces the type parameters of a type function by arguments // A substitution which replaces the type parameters of a type function by arguments
struct ApplyTypeFunction : Substitution struct ApplyTypeFunction : Substitution
{ {
ApplyTypeFunction(TypeArena* arena, TypeLevel level)
: Substitution(TxnLog::empty(), arena)
, level(level)
, encounteredForwardedType(false)
{
}
TypeLevel level; TypeLevel level;
bool encounteredForwardedType; bool encounteredForwardedType;
std::unordered_map<TypeId, TypeId> typeArguments; std::unordered_map<TypeId, TypeId> typeArguments;
@ -103,6 +136,11 @@ struct GenericTypeDefinitions
std::vector<GenericTypePackDefinition> genericPacks; std::vector<GenericTypePackDefinition> genericPacks;
}; };
struct HashBoolNamePair
{
size_t operator()(const std::pair<bool, Name>& pair) const;
};
// All TypeVars are retained via Environment::typeVars. All TypeIds // All TypeVars are retained via Environment::typeVars. All TypeIds
// within a program are borrowed pointers into this set. // within a program are borrowed pointers into this set.
struct TypeChecker struct TypeChecker
@ -346,8 +384,7 @@ private:
// Note: `scope` must be a fresh scope. // Note: `scope` must be a fresh scope.
GenericTypeDefinitions createGenericTypes(const ScopePtr& scope, std::optional<TypeLevel> levelOpt, const AstNode& node, GenericTypeDefinitions createGenericTypes(const ScopePtr& scope, std::optional<TypeLevel> levelOpt, const AstNode& node,
const AstArray<AstGenericType>& genericNames, const AstArray<AstGenericTypePack>& genericPackNames, const AstArray<AstGenericType>& genericNames, const AstArray<AstGenericTypePack>& genericPackNames, bool useCache = false);
bool useCache = false);
public: public:
ErrorVec resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense); ErrorVec resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense);
@ -387,11 +424,6 @@ public:
ModulePtr currentModule; ModulePtr currentModule;
ModuleName currentModuleName; ModuleName currentModuleName;
Instantiation instantiation;
Quantification quantification;
Anyification anyification;
ApplyTypeFunction applyTypeFunction;
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope; std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
InternalErrorReporter* iceHandler; InternalErrorReporter* iceHandler;
@ -411,6 +443,12 @@ public:
private: private:
int checkRecursionCount = 0; int checkRecursionCount = 0;
int recursionCount = 0; int recursionCount = 0;
/**
* We use this to avoid doing second-pass analysis of type aliases that are duplicates. We record a pair
* (exported, name) to properly deal with the case where the two duplicates do not have the same export status.
*/
DenseHashSet<std::pair<bool, Name>, HashBoolNamePair> duplicateTypeAliases;
}; };
// Unit test hook // Unit test hook

View file

@ -54,9 +54,6 @@ struct TypePackVar
bool persistent = false; bool persistent = false;
// Pointer to the type arena that allocated this type. // Pointer to the type arena that allocated this type.
// Do not depend on the value of this under any circumstances. This is for
// debugging purposes only. This is only set in debug builds; it is nullptr
// in all other environments.
TypeArena* owningArena = nullptr; TypeArena* owningArena = nullptr;
}; };

View file

@ -449,9 +449,6 @@ struct TypeVar final
std::optional<std::string> documentationSymbol; std::optional<std::string> documentationSymbol;
// Pointer to the type arena that allocated this type. // Pointer to the type arena that allocated this type.
// Do not depend on the value of this under any circumstances. This is for
// debugging purposes only. This is only set in debug builds; it is nullptr
// in all other environments.
TypeArena* owningArena = nullptr; TypeArena* owningArena = nullptr;
bool operator==(const TypeVar& rhs) const; bool operator==(const TypeVar& rhs) const;

View file

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

View file

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

View file

@ -1,8 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAGVARIABLE(LuauFixTonumberReturnType, false)
namespace Luau namespace Luau
{ {
@ -115,6 +113,7 @@ declare function gcinfo(): number
declare function error<T>(message: T, level: number?) declare function error<T>(message: T, level: number?)
declare function tostring<T>(value: T): string declare function tostring<T>(value: T): string
declare function tonumber<T>(value: T, radix: number?): number?
declare function rawequal<T1, T2>(a: T1, b: T2): boolean declare function rawequal<T1, T2>(a: T1, b: T2): boolean
declare function rawget<K, V>(tab: {[K]: V}, k: K): V declare function rawget<K, V>(tab: {[K]: V}, k: K): V
@ -168,7 +167,7 @@ declare function gcinfo(): number
foreach: <K, V>({[K]: V}, (K, V) -> ()) -> (), foreach: <K, V>({[K]: V}, (K, V) -> ()) -> (),
foreachi: <V>({V}, (number, V) -> ()) -> (), foreachi: <V>({V}, (number, V) -> ()) -> (),
move: <V>({V}, number, number, number, {V}?) -> (), move: <V>({V}, number, number, number, {V}?) -> {V},
clear: <K, V>({[K]: V}) -> (), clear: <K, V>({[K]: V}) -> (),
freeze: <K, V>({[K]: V}) -> {[K]: V}, freeze: <K, V>({[K]: V}) -> {[K]: V},
@ -200,14 +199,7 @@ declare function gcinfo(): number
std::string getBuiltinDefinitionSource() std::string getBuiltinDefinitionSource()
{ {
std::string result = kBuiltinDefinitionLuaSrc; return kBuiltinDefinitionLuaSrc;
if (FFlag::LuauFixTonumberReturnType)
result += "declare function tonumber<T>(value: T, radix: number?): number?\n";
else
result += "declare function tonumber<T>(value: T, radix: number?): number\n";
return result;
} }
} // namespace Luau } // namespace Luau

View file

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

View file

@ -150,10 +150,21 @@ struct AstJsonEncoder : public AstVisitor
{ {
writeRaw(std::to_string(i)); writeRaw(std::to_string(i));
} }
void write(std::nullptr_t)
{
writeRaw("null");
}
void write(std::string_view str) void write(std::string_view str)
{ {
writeString(str); writeString(str);
} }
void write(std::optional<AstName> name)
{
if (name)
write(*name);
else
writeRaw("null");
}
void write(AstName name) void write(AstName name)
{ {
writeString(name.value ? name.value : ""); writeString(name.value ? name.value : "");
@ -177,7 +188,16 @@ struct AstJsonEncoder : public AstVisitor
void write(AstLocal* local) void write(AstLocal* local)
{ {
write(local->name); writeRaw("{");
bool c = pushComma();
if (local->annotation != nullptr)
write("type", local->annotation);
else
write("type", nullptr);
write("name", local->name);
write("location", local->location);
popComma(c);
writeRaw("}");
} }
void writeNode(AstNode* node) void writeNode(AstNode* node)
@ -314,7 +334,7 @@ struct AstJsonEncoder : public AstVisitor
if (node->self) if (node->self)
PROP(self); PROP(self);
PROP(args); PROP(args);
if (node->hasReturnAnnotation) if (node->returnAnnotation)
PROP(returnAnnotation); PROP(returnAnnotation);
PROP(vararg); PROP(vararg);
PROP(varargLocation); PROP(varargLocation);
@ -328,6 +348,14 @@ struct AstJsonEncoder : public AstVisitor
}); });
} }
void write(const std::optional<AstTypeList>& typeList)
{
if (typeList)
write(*typeList);
else
writeRaw("null");
}
void write(const AstTypeList& typeList) void write(const AstTypeList& typeList)
{ {
writeRaw("{"); writeRaw("{");
@ -531,7 +559,7 @@ struct AstJsonEncoder : public AstVisitor
PROP(thenbody); PROP(thenbody);
if (node->elsebody) if (node->elsebody)
PROP(elsebody); PROP(elsebody);
PROP(hasThen); write("hasThen", node->thenLocation.has_value());
PROP(hasEnd); PROP(hasEnd);
}); });
} }
@ -715,7 +743,7 @@ struct AstJsonEncoder : public AstVisitor
void write(class AstTypeReference* node) void write(class AstTypeReference* node)
{ {
writeNode(node, "AstTypeReference", [&]() { writeNode(node, "AstTypeReference", [&]() {
if (node->hasPrefix) if (node->prefix)
PROP(prefix); PROP(prefix);
PROP(name); PROP(name);
PROP(parameters); PROP(parameters);

View file

@ -12,6 +12,8 @@
#include <math.h> #include <math.h>
#include <limits.h> #include <limits.h>
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
namespace Luau namespace Luau
{ {
@ -43,6 +45,8 @@ static const char* kWarningNames[] = {
"DeprecatedApi", "DeprecatedApi",
"TableOperations", "TableOperations",
"DuplicateCondition", "DuplicateCondition",
"MisleadingAndOr",
"CommentDirective",
}; };
// clang-format on // clang-format on
@ -731,13 +735,13 @@ private:
bool visit(AstTypeReference* node) override bool visit(AstTypeReference* node) override
{ {
if (!node->hasPrefix) if (!node->prefix)
return true; return true;
if (!imports.contains(node->prefix)) if (!imports.contains(*node->prefix))
return true; return true;
AstLocal* astLocal = imports[node->prefix]; AstLocal* astLocal = imports[*node->prefix];
Local& local = locals[astLocal]; Local& local = locals[astLocal];
LUAU_ASSERT(local.import); LUAU_ASSERT(local.import);
local.used = true; local.used = true;
@ -2040,18 +2044,28 @@ private:
const Property* prop = lookupClassProp(cty, node->index.value); const Property* prop = lookupClassProp(cty, node->index.value);
if (prop && prop->deprecated) if (prop && prop->deprecated)
{ report(node->location, *prop, cty->name.c_str(), node->index.value);
if (!prop->deprecatedSuggestion.empty()) }
emitWarning(*context, LintWarning::Code_DeprecatedApi, node->location, "Member '%s.%s' is deprecated, use '%s' instead", else if (const TableTypeVar* tty = get<TableTypeVar>(follow(*ty)))
cty->name.c_str(), node->index.value, prop->deprecatedSuggestion.c_str()); {
else auto prop = tty->props.find(node->index.value);
emitWarning(*context, LintWarning::Code_DeprecatedApi, node->location, "Member '%s.%s' is deprecated", cty->name.c_str(),
node->index.value); if (prop != tty->props.end() && prop->second.deprecated)
} report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
} }
return true; return true;
} }
void report(const Location& location, const Property& prop, const char* container, const char* field)
{
std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str());
if (container)
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s.%s' is deprecated%s", container, field, suggestion.c_str());
else
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated%s", field, suggestion.c_str());
}
}; };
class LintTableOperations : AstVisitor class LintTableOperations : AstVisitor
@ -2257,6 +2271,39 @@ private:
return false; return false;
} }
bool visit(AstExprIfElse* expr) override
{
if (!expr->falseExpr->is<AstExprIfElse>())
return true;
// if..elseif chain detected, we need to unroll it
std::vector<AstExpr*> conditions;
conditions.reserve(2);
AstExprIfElse* head = expr;
while (head)
{
head->condition->visit(this);
head->trueExpr->visit(this);
conditions.push_back(head->condition);
if (head->falseExpr->is<AstExprIfElse>())
{
head = head->falseExpr->as<AstExprIfElse>();
continue;
}
head->falseExpr->visit(this);
break;
}
detectDuplicates(conditions);
// block recursive visits so that we only analyze each chain once
return false;
}
bool visit(AstExprBinary* expr) override bool visit(AstExprBinary* expr) override
{ {
if (expr->op != AstExprBinary::And && expr->op != AstExprBinary::Or) if (expr->op != AstExprBinary::And && expr->op != AstExprBinary::Or)
@ -2418,6 +2465,46 @@ private:
} }
}; };
class LintMisleadingAndOr : AstVisitor
{
public:
LUAU_NOINLINE static void process(LintContext& context)
{
LintMisleadingAndOr pass;
pass.context = &context;
context.root->visit(&pass);
}
private:
LintContext* context;
bool visit(AstExprBinary* node) override
{
if (node->op != AstExprBinary::Or)
return true;
AstExprBinary* and_ = node->left->as<AstExprBinary>();
if (!and_ || and_->op != AstExprBinary::And)
return true;
const char* alt = nullptr;
if (and_->right->is<AstExprConstantNil>())
alt = "nil";
else if (AstExprConstantBool* c = and_->right->as<AstExprConstantBool>(); c && c->value == false)
alt = "false";
if (alt)
emitWarning(*context, LintWarning::Code_MisleadingAndOr, node->location,
"The and-or expression always evaluates to the second alternative because the first alternative is %s; consider using if-then-else "
"expression instead",
alt);
return true;
}
};
static void fillBuiltinGlobals(LintContext& context, const AstNameTable& names, const ScopePtr& env) static void fillBuiltinGlobals(LintContext& context, const AstNameTable& names, const ScopePtr& env)
{ {
ScopePtr current = env; ScopePtr current = env;
@ -2443,13 +2530,108 @@ static void fillBuiltinGlobals(LintContext& context, const AstNameTable& names,
} }
} }
static const char* fuzzyMatch(std::string_view str, const char** array, size_t size)
{
if (FInt::LuauSuggestionDistance == 0)
return nullptr;
size_t bestDistance = FInt::LuauSuggestionDistance;
size_t bestMatch = size;
for (size_t i = 0; i < size; ++i)
{
size_t ed = editDistance(str, array[i]);
if (ed <= bestDistance)
{
bestDistance = ed;
bestMatch = i;
}
}
return bestMatch < size ? array[bestMatch] : nullptr;
}
static void lintComments(LintContext& context, const std::vector<HotComment>& hotcomments)
{
bool seenMode = false;
for (const HotComment& hc : hotcomments)
{
// We reserve --!<space> for various informational (non-directive) comments
if (hc.content.empty() || hc.content[0] == ' ' || hc.content[0] == '\t')
continue;
if (!hc.header)
{
emitWarning(context, LintWarning::Code_CommentDirective, hc.location,
"Comment directive is ignored because it is placed after the first non-comment token");
}
else
{
std::string::size_type space = hc.content.find_first_of(" \t");
std::string_view first = std::string_view(hc.content).substr(0, space);
if (first == "nolint")
{
std::string::size_type notspace = hc.content.find_first_not_of(" \t", space);
if (space == std::string::npos || notspace == std::string::npos)
{
// disables all lints
}
else if (LintWarning::parseName(hc.content.c_str() + notspace) == LintWarning::Code_Unknown)
{
const char* rule = hc.content.c_str() + notspace;
// skip Unknown
if (const char* suggestion = fuzzyMatch(rule, kWarningNames + 1, LintWarning::Code__Count - 1))
emitWarning(context, LintWarning::Code_CommentDirective, hc.location,
"nolint directive refers to unknown lint rule '%s'; did you mean '%s'?", rule, suggestion);
else
emitWarning(
context, LintWarning::Code_CommentDirective, hc.location, "nolint directive refers to unknown lint rule '%s'", rule);
}
}
else if (first == "nocheck" || first == "nonstrict" || first == "strict")
{
if (space != std::string::npos)
emitWarning(context, LintWarning::Code_CommentDirective, hc.location,
"Comment directive with the type checking mode has extra symbols at the end of the line");
else if (seenMode)
emitWarning(context, LintWarning::Code_CommentDirective, hc.location,
"Comment directive with the type checking mode has already been used");
else
seenMode = true;
}
else
{
static const char* kHotComments[] = {
"nolint",
"nocheck",
"nonstrict",
"strict",
};
if (const char* suggestion = fuzzyMatch(first, kHotComments, std::size(kHotComments)))
emitWarning(context, LintWarning::Code_CommentDirective, hc.location, "Unknown comment directive '%.*s'; did you mean '%s'?",
int(first.size()), first.data(), suggestion);
else
emitWarning(context, LintWarning::Code_CommentDirective, hc.location, "Unknown comment directive '%.*s'", int(first.size()),
first.data());
}
}
}
}
void LintOptions::setDefaults() void LintOptions::setDefaults()
{ {
// By default, we enable all warnings // By default, we enable all warnings
warningMask = ~0ull; warningMask = ~0ull;
} }
std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module, const LintOptions& options) std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module,
const std::vector<HotComment>& hotcomments, const LintOptions& options)
{ {
LintContext context; LintContext context;
@ -2522,6 +2704,12 @@ std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const Sc
if (context.warningEnabled(LintWarning::Code_DuplicateLocal)) if (context.warningEnabled(LintWarning::Code_DuplicateLocal))
LintDuplicateLocal::process(context); LintDuplicateLocal::process(context);
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()); std::sort(context.result.begin(), context.result.end(), WarningComparator());
return context.result; return context.result;
@ -2543,23 +2731,30 @@ LintWarning::Code LintWarning::parseName(const char* name)
return Code_Unknown; return Code_Unknown;
} }
uint64_t LintWarning::parseMask(const std::vector<std::string>& hotcomments) uint64_t LintWarning::parseMask(const std::vector<HotComment>& hotcomments)
{ {
uint64_t result = 0; uint64_t result = 0;
for (const std::string& hc : hotcomments) for (const HotComment& hc : hotcomments)
{ {
if (hc.compare(0, 6, "nolint") != 0) if (!hc.header)
continue; continue;
std::string::size_type name = hc.find_first_not_of(" \t", 6); if (hc.content.compare(0, 6, "nolint") != 0)
continue;
std::string::size_type name = hc.content.find_first_not_of(" \t", 6);
// --!nolint disables everything // --!nolint disables everything
if (name == std::string::npos) if (name == std::string::npos)
return ~0ull; return ~0ull;
// --!nolint needs to be followed by a whitespace character
if (name == 6)
continue;
// --!nolint name disables the specific lint // --!nolint name disables the specific lint
LintWarning::Code code = LintWarning::parseName(hc.c_str() + name); LintWarning::Code code = LintWarning::parseName(hc.content.c_str() + name);
if (code != LintWarning::Code_Unknown) if (code != LintWarning::Code_Unknown)
result |= 1ull << int(code); result |= 1ull << int(code);

View file

@ -12,10 +12,10 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false) LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false) // Remove with FFlagLuauImmutableTypes
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300) LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
LUAU_FASTFLAG(LuauTypeAliasDefaults) LUAU_FASTFLAG(LuauTypeAliasDefaults)
LUAU_FASTFLAG(LuauImmutableTypes)
LUAU_FASTFLAGVARIABLE(LuauPrepopulateUnionOptionsBeforeAllocation, false) LUAU_FASTFLAGVARIABLE(LuauPrepopulateUnionOptionsBeforeAllocation, false)
namespace Luau namespace Luau
@ -66,7 +66,7 @@ TypeId TypeArena::addTV(TypeVar&& tv)
{ {
TypeId allocated = typeVars.allocate(std::move(tv)); TypeId allocated = typeVars.allocate(std::move(tv));
if (FFlag::DebugLuauTrackOwningArena) if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this; asMutable(allocated)->owningArena = this;
return allocated; return allocated;
@ -76,7 +76,7 @@ TypeId TypeArena::freshType(TypeLevel level)
{ {
TypeId allocated = typeVars.allocate(FreeTypeVar{level}); TypeId allocated = typeVars.allocate(FreeTypeVar{level});
if (FFlag::DebugLuauTrackOwningArena) if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this; asMutable(allocated)->owningArena = this;
return allocated; return allocated;
@ -86,7 +86,7 @@ TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
{ {
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)}); TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
if (FFlag::DebugLuauTrackOwningArena) if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this; asMutable(allocated)->owningArena = this;
return allocated; return allocated;
@ -96,7 +96,7 @@ TypePackId TypeArena::addTypePack(std::vector<TypeId> types)
{ {
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)}); TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
if (FFlag::DebugLuauTrackOwningArena) if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this; asMutable(allocated)->owningArena = this;
return allocated; return allocated;
@ -106,7 +106,7 @@ TypePackId TypeArena::addTypePack(TypePack tp)
{ {
TypePackId allocated = typePacks.allocate(std::move(tp)); TypePackId allocated = typePacks.allocate(std::move(tp));
if (FFlag::DebugLuauTrackOwningArena) if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this; asMutable(allocated)->owningArena = this;
return allocated; return allocated;
@ -116,7 +116,7 @@ TypePackId TypeArena::addTypePack(TypePackVar tp)
{ {
TypePackId allocated = typePacks.allocate(std::move(tp)); TypePackId allocated = typePacks.allocate(std::move(tp));
if (FFlag::DebugLuauTrackOwningArena) if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this; asMutable(allocated)->owningArena = this;
return allocated; return allocated;
@ -454,8 +454,16 @@ TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks
TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks, cloneState}; TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks, cloneState};
Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into. Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into.
// TODO: Make this work when the arena of 'res' might be frozen if (FFlag::LuauImmutableTypes)
asMutable(res)->documentationSymbol = typeId->documentationSymbol; {
// Persistent types are not being cloned and we get the original type back which might be read-only
if (!res->persistent)
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
}
else
{
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
}
} }
return res; return res;

View file

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

View file

@ -2,6 +2,8 @@
#include "Luau/Scope.h" #include "Luau/Scope.h"
LUAU_FASTFLAG(LuauTwoPassAliasDefinitionFix);
namespace Luau namespace Luau
{ {
@ -17,6 +19,8 @@ Scope::Scope(const ScopePtr& parent, int subLevel)
, returnType(parent->returnType) , returnType(parent->returnType)
, level(parent->level.incr()) , level(parent->level.incr())
{ {
if (FFlag::LuauTwoPassAliasDefinitionFix)
level = level.incr();
level.subLevel = subLevel; level.subLevel = subLevel;
} }

View file

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

View file

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

View file

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

View file

@ -250,6 +250,10 @@ PendingTypePack* TxnLog::queue(TypePackId tp)
PendingType* TxnLog::pending(TypeId ty) const PendingType* TxnLog::pending(TypeId ty) const
{ {
// This function will technically work if `this` is nullptr, but this
// indicates a bug, so we explicitly assert.
LUAU_ASSERT(static_cast<const void*>(this) != nullptr);
for (const TxnLog* current = this; current; current = current->parent) for (const TxnLog* current = this; current; current = current->parent)
{ {
if (auto it = current->typeVarChanges.find(ty); it != current->typeVarChanges.end()) if (auto it = current->typeVarChanges.find(ty); it != current->typeVarChanges.end())
@ -261,6 +265,10 @@ PendingType* TxnLog::pending(TypeId ty) const
PendingTypePack* TxnLog::pending(TypePackId tp) const PendingTypePack* TxnLog::pending(TypePackId tp) const
{ {
// This function will technically work if `this` is nullptr, but this
// indicates a bug, so we explicitly assert.
LUAU_ASSERT(static_cast<const void*>(this) != nullptr);
for (const TxnLog* current = this; current; current = current->parent) for (const TxnLog* current = this; current; current = current->parent)
{ {
if (auto it = current->typePackChanges.find(tp); it != current->typePackChanges.end()) if (auto it = current->typePackChanges.find(tp); it != current->typePackChanges.end())

View file

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

View file

@ -3,7 +3,6 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/ModuleResolver.h" #include "Luau/ModuleResolver.h"
#include "Luau/Parser.h"
#include "Luau/Quantify.h" #include "Luau/Quantify.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
@ -24,30 +23,29 @@ LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000)
LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 500) LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false) LUAU_FASTFLAGVARIABLE(LuauEqConstraint, false)
LUAU_FASTFLAGVARIABLE(LuauGroupExpectedType, false)
LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false. LUAU_FASTFLAGVARIABLE(LuauWeakEqConstraint, false) // Eventually removed as false.
LUAU_FASTFLAG(LuauUseCommittingTxnLog) LUAU_FASTFLAG(LuauUseCommittingTxnLog)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false) LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false) LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false)
LUAU_FASTFLAGVARIABLE(LuauIfElseBranchTypeUnion, false) LUAU_FASTFLAGVARIABLE(LuauImmutableTypes, false)
LUAU_FASTFLAGVARIABLE(LuauIfElseExpectedType2, false)
LUAU_FASTFLAGVARIABLE(LuauLengthOnCompositeType, false)
LUAU_FASTFLAGVARIABLE(LuauNoSealedTypeMod, false) LUAU_FASTFLAGVARIABLE(LuauNoSealedTypeMod, false)
LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false) LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false)
LUAU_FASTFLAGVARIABLE(LuauSealExports, false) LUAU_FASTFLAGVARIABLE(LuauSealExports, false)
LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false) LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions, false) LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions2, false)
LUAU_FASTFLAGVARIABLE(LuauTypeAliasDefaults, false) LUAU_FASTFLAGVARIABLE(LuauTypeAliasDefaults, false)
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false) LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false) LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false)
LUAU_FASTFLAGVARIABLE(LuauPerModuleUnificationCache, false)
LUAU_FASTFLAGVARIABLE(LuauProperTypeLevels, false) LUAU_FASTFLAGVARIABLE(LuauProperTypeLevels, false)
LUAU_FASTFLAGVARIABLE(LuauAscribeCorrectLevelToInferredProperitesOfFreeTables, false) LUAU_FASTFLAGVARIABLE(LuauAscribeCorrectLevelToInferredProperitesOfFreeTables, false)
LUAU_FASTFLAG(LuauUnionTagMatchFix) LUAU_FASTFLAG(LuauUnionTagMatchFix)
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false) LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false) LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
LUAU_FASTFLAGVARIABLE(LuauAnotherTypeLevelFix, false)
namespace Luau namespace Luau
{ {
@ -213,6 +211,11 @@ static bool isMetamethod(const Name& name)
name == "__metatable" || name == "__eq" || name == "__lt" || name == "__le" || name == "__mode"; name == "__metatable" || name == "__eq" || name == "__lt" || name == "__le" || name == "__mode";
} }
size_t HashBoolNamePair::operator()(const std::pair<bool, Name>& pair) const
{
return std::hash<bool>()(pair.first) ^ std::hash<Name>()(pair.second);
}
TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHandler) TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHandler)
: resolver(resolver) : resolver(resolver)
, iceHandler(iceHandler) , iceHandler(iceHandler)
@ -225,6 +228,7 @@ TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHan
, anyType(getSingletonTypes().anyType) , anyType(getSingletonTypes().anyType)
, optionalNumberType(getSingletonTypes().optionalNumberType) , optionalNumberType(getSingletonTypes().optionalNumberType)
, anyTypePack(getSingletonTypes().anyTypePack) , anyTypePack(getSingletonTypes().anyTypePack)
, duplicateTypeAliases{{false, {}}}
{ {
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})); globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
@ -283,13 +287,13 @@ ModulePtr TypeChecker::check(const SourceModule& module, Mode mode, std::optiona
GenericError{"Free types leaked into this module's public interface. This is an internal Luau error; please report it."}}); GenericError{"Free types leaked into this module's public interface. This is an internal Luau error; please report it."}});
} }
if (FFlag::LuauPerModuleUnificationCache) // Clear unifier cache since it's keyed off internal types that get deallocated
{ // This avoids fake cross-module cache hits and keeps cache size at bay when typechecking large module graphs.
// Clear unifier cache since it's keyed off internal types that get deallocated unifierState.cachedUnify.clear();
// This avoids fake cross-module cache hits and keeps cache size at bay when typechecking large module graphs. unifierState.skipCacheForType.clear();
unifierState.cachedUnify.clear();
unifierState.skipCacheForType.clear(); if (FFlag::LuauTwoPassAliasDefinitionFix)
} duplicateTypeAliases.clear();
return std::move(currentModule); return std::move(currentModule);
} }
@ -496,6 +500,9 @@ LUAU_NOINLINE void TypeChecker::checkBlockTypeAliases(const ScopePtr& scope, std
{ {
if (const auto& typealias = stat->as<AstStatTypeAlias>()) if (const auto& typealias = stat->as<AstStatTypeAlias>())
{ {
if (FFlag::LuauTwoPassAliasDefinitionFix && typealias->name == kParseNameError)
continue;
auto& bindings = typealias->exported ? scope->exportedTypeBindings : scope->privateTypeBindings; auto& bindings = typealias->exported ? scope->exportedTypeBindings : scope->privateTypeBindings;
Name name = typealias->name.value; Name name = typealias->name.value;
@ -1176,6 +1183,10 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
// Once with forwardDeclare, and once without. // Once with forwardDeclare, and once without.
Name name = typealias.name.value; Name name = typealias.name.value;
// If the alias is missing a name, we can't do anything with it. Ignore it.
if (FFlag::LuauTwoPassAliasDefinitionFix && name == kParseNameError)
return;
std::optional<TypeFun> binding; std::optional<TypeFun> binding;
if (auto it = scope->exportedTypeBindings.find(name); it != scope->exportedTypeBindings.end()) if (auto it = scope->exportedTypeBindings.find(name); it != scope->exportedTypeBindings.end())
binding = it->second; binding = it->second;
@ -1192,6 +1203,8 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}}); reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}});
bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)}; bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
if (FFlag::LuauTwoPassAliasDefinitionFix)
duplicateTypeAliases.insert({typealias.exported, name});
} }
else else
{ {
@ -1200,7 +1213,8 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
if (FFlag::LuauProperTypeLevels) if (FFlag::LuauProperTypeLevels)
aliasScope->level.subLevel = subLevel; aliasScope->level.subLevel = subLevel;
auto [generics, genericPacks] = createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks, /* useCache = */ true); auto [generics, genericPacks] =
createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks, /* useCache = */ true);
TypeId ty = freshType(aliasScope); TypeId ty = freshType(aliasScope);
FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty); FreeTypeVar* ftv = getMutable<FreeTypeVar>(ty);
@ -1211,6 +1225,11 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
} }
else else
{ {
// If the first pass failed (this should mean a duplicate definition), the second pass isn't going to be
// interesting.
if (FFlag::LuauTwoPassAliasDefinitionFix && duplicateTypeAliases.find({typealias.exported, name}))
return;
if (!binding) if (!binding)
ice("Not predeclared"); ice("Not predeclared");
@ -1235,7 +1254,8 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
if (auto ttv = getMutable<TableTypeVar>(follow(ty))) if (auto ttv = getMutable<TableTypeVar>(follow(ty)))
{ {
// If the table is already named and we want to rename the type function, we have to bind new alias to a copy // If the table is already named and we want to rename the type function, we have to bind new alias to a copy
if (ttv->name) // Additionally, we can't modify types that come from other modules
if (ttv->name || (FFlag::LuauImmutableTypes && follow(ty)->owningArena != &currentModule->internalTypes))
{ {
bool sameTys = std::equal(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), binding->typeParams.begin(), bool sameTys = std::equal(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), binding->typeParams.begin(),
binding->typeParams.end(), [](auto&& itp, auto&& tp) { binding->typeParams.end(), [](auto&& itp, auto&& tp) {
@ -1247,7 +1267,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
}); });
// Copy can be skipped if this is an identical alias // Copy can be skipped if this is an identical alias
if (ttv->name != name || !sameTys || !sameTps) if ((FFlag::LuauImmutableTypes && !ttv->name) || ttv->name != name || !sameTys || !sameTps)
{ {
// This is a shallow clone, original recursive links to self are not updated // This is a shallow clone, original recursive links to self are not updated
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state}; TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
@ -1279,9 +1299,17 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
} }
} }
else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty))) else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty)))
mtv->syntheticName = name; {
// We can't modify types that come from other modules
if (!FFlag::LuauImmutableTypes || follow(ty)->owningArena == &currentModule->internalTypes)
mtv->syntheticName = name;
}
unify(ty, bindingsMap[name].type, typealias.location); TypeId& bindingType = bindingsMap[name].type;
bool ok = unify(ty, bindingType, typealias.location);
if (FFlag::LuauTwoPassAliasDefinitionFix && ok)
bindingType = ty;
} }
} }
@ -1428,7 +1456,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExpr&
ExprResult<TypeId> result; ExprResult<TypeId> result;
if (auto a = expr.as<AstExprGroup>()) if (auto a = expr.as<AstExprGroup>())
result = checkExpr(scope, *a->expr, FFlag::LuauGroupExpectedType ? expectedType : std::nullopt); result = checkExpr(scope, *a->expr, expectedType);
else if (expr.is<AstExprConstantNil>()) else if (expr.is<AstExprConstantNil>())
result = {nilType}; result = {nilType};
else if (const AstExprConstantBool* bexpr = expr.as<AstExprConstantBool>()) else if (const AstExprConstantBool* bexpr = expr.as<AstExprConstantBool>())
@ -1472,7 +1500,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExpr&
else if (auto a = expr.as<AstExprError>()) else if (auto a = expr.as<AstExprError>())
result = checkExpr(scope, *a); result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprIfElse>()) else if (auto a = expr.as<AstExprIfElse>())
result = checkExpr(scope, *a, FFlag::LuauIfElseExpectedType2 ? expectedType : std::nullopt); result = checkExpr(scope, *a, expectedType);
else else
ice("Unhandled AstExpr?"); ice("Unhandled AstExpr?");
@ -1564,7 +1592,12 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprCa
else if (auto vtp = get<VariadicTypePack>(retPack)) else if (auto vtp = get<VariadicTypePack>(retPack))
return {vtp->ty, std::move(result.predicates)}; return {vtp->ty, std::move(result.predicates)};
else if (get<Unifiable::Generic>(retPack)) else if (get<Unifiable::Generic>(retPack))
ice("Unexpected abstract type pack!", expr.location); {
if (FFlag::LuauReturnAnyInsteadOfICE)
return {anyType, std::move(result.predicates)};
else
ice("Unexpected abstract type pack!", expr.location);
}
else else
ice("Unknown TypePack type!", expr.location); ice("Unknown TypePack type!", expr.location);
} }
@ -1614,11 +1647,23 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
tablify(type); tablify(type);
const PrimitiveTypeVar* primitiveType = get<PrimitiveTypeVar>(type); if (FFlag::LuauDiscriminableUnions2)
if (primitiveType && primitiveType->type == PrimitiveTypeVar::String)
{ {
if (std::optional<TypeId> mtIndex = findMetatableEntry(type, "__index", location)) if (isString(type))
{
std::optional<TypeId> mtIndex = findMetatableEntry(stringType, "__index", location);
LUAU_ASSERT(mtIndex);
type = *mtIndex; type = *mtIndex;
}
}
else
{
const PrimitiveTypeVar* primitiveType = get<PrimitiveTypeVar>(type);
if (primitiveType && primitiveType->type == PrimitiveTypeVar::String)
{
if (std::optional<TypeId> mtIndex = findMetatableEntry(type, "__index", location))
type = *mtIndex;
}
} }
if (TableTypeVar* tableType = getMutableTableType(type)) if (TableTypeVar* tableType = getMutableTableType(type))
@ -2040,6 +2085,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprUn
return {numberType}; return {numberType};
} }
case AstExprUnary::Len: case AstExprUnary::Len:
{
tablify(operandType); tablify(operandType);
operandType = stripFromNilAndReport(operandType, expr.location); operandType = stripFromNilAndReport(operandType, expr.location);
@ -2047,30 +2093,13 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprUn
if (get<ErrorTypeVar>(operandType)) if (get<ErrorTypeVar>(operandType))
return {errorRecoveryType(scope)}; return {errorRecoveryType(scope)};
if (FFlag::LuauLengthOnCompositeType) DenseHashSet<TypeId> seen{nullptr};
{
DenseHashSet<TypeId> seen{nullptr};
if (!hasLength(operandType, seen, &recursionCount)) if (!hasLength(operandType, seen, &recursionCount))
reportError(TypeError{expr.location, NotATable{operandType}}); reportError(TypeError{expr.location, NotATable{operandType}});
}
else
{
if (get<AnyTypeVar>(operandType))
return {numberType}; // Not strictly correct: metatables permit overriding this
if (auto p = get<PrimitiveTypeVar>(operandType))
{
if (p->type == PrimitiveTypeVar::String)
return {numberType};
}
if (!getTableType(operandType))
reportError(TypeError{expr.location, NotATable{operandType}});
}
return {numberType}; return {numberType};
}
default: default:
ice("Unknown AstExprUnary " + std::to_string(int(expr.op))); ice("Unknown AstExprUnary " + std::to_string(int(expr.op)));
} }
@ -2476,7 +2505,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right); auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right);
return {checkBinaryOperation(FFlag::LuauDiscriminableUnions ? scope : innerScope, expr, lhsTy, rhsTy), return {checkBinaryOperation(FFlag::LuauDiscriminableUnions2 ? scope : innerScope, expr, lhsTy, rhsTy),
{AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}}; {AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
} }
else if (expr.op == AstExprBinary::Or) else if (expr.op == AstExprBinary::Or)
@ -2489,7 +2518,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right); auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right);
// Because of C++, I'm not sure if lhsPredicates was not moved out by the time we call checkBinaryOperation. // Because of C++, I'm not sure if lhsPredicates was not moved out by the time we call checkBinaryOperation.
TypeId result = checkBinaryOperation(FFlag::LuauDiscriminableUnions ? scope : innerScope, expr, lhsTy, rhsTy, lhsPredicates); TypeId result = checkBinaryOperation(FFlag::LuauDiscriminableUnions2 ? scope : innerScope, expr, lhsTy, rhsTy, lhsPredicates);
return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}}; return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
} }
else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe) else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe)
@ -2497,8 +2526,8 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi
if (auto predicate = tryGetTypeGuardPredicate(expr)) if (auto predicate = tryGetTypeGuardPredicate(expr))
return {booleanType, {std::move(*predicate)}}; return {booleanType, {std::move(*predicate)}};
ExprResult<TypeId> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions); ExprResult<TypeId> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions2);
ExprResult<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions); ExprResult<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions2);
PredicateVec predicates; PredicateVec predicates;
@ -2565,22 +2594,11 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIf
resolve(result.predicates, falseScope, false); resolve(result.predicates, falseScope, false);
ExprResult<TypeId> falseType = checkExpr(falseScope, *expr.falseExpr, expectedType); ExprResult<TypeId> falseType = checkExpr(falseScope, *expr.falseExpr, expectedType);
if (FFlag::LuauIfElseBranchTypeUnion) if (falseType.type == trueType.type)
{
if (falseType.type == trueType.type)
return {trueType.type};
std::vector<TypeId> types = reduceUnion({trueType.type, falseType.type});
return {types.size() == 1 ? types[0] : addType(UnionTypeVar{std::move(types)})};
}
else
{
unify(falseType.type, trueType.type, expr.location);
// TODO: normalize(UnionTypeVar{{trueType, falseType}})
// For now both trueType and falseType must be the same type.
return {trueType.type}; return {trueType.type};
}
std::vector<TypeId> types = reduceUnion({trueType.type, falseType.type});
return {types.size() == 1 ? types[0] : addType(UnionTypeVar{std::move(types)})};
} }
TypeId TypeChecker::checkLValue(const ScopePtr& scope, const AstExpr& expr) TypeId TypeChecker::checkLValue(const ScopePtr& scope, const AstExpr& expr)
@ -2785,12 +2803,16 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
} }
else if (exprTable->state == TableState::Unsealed || exprTable->state == TableState::Free) else if (exprTable->state == TableState::Unsealed || exprTable->state == TableState::Free)
{ {
TypeId resultType = freshType(scope); TypeId resultType = freshType(FFlag::LuauAnotherTypeLevelFix ? exprTable->level : scope->level);
exprTable->indexer = TableIndexer{anyIfNonstrict(indexType), anyIfNonstrict(resultType)}; exprTable->indexer = TableIndexer{anyIfNonstrict(indexType), anyIfNonstrict(resultType)};
return resultType; return resultType;
} }
else else
{ {
/*
* If we use [] indexing to fetch a property from a sealed table that has no indexer, we have no idea if it will
* work, so we just mint a fresh type, return that, and hope for the best.
*/
TypeId resultType = freshType(scope); TypeId resultType = freshType(scope);
return resultType; return resultType;
} }
@ -2929,8 +2951,8 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(
auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks); auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks);
TypePackId retPack; TypePackId retPack;
if (expr.hasReturnAnnotation) if (expr.returnAnnotation)
retPack = resolveTypePack(funScope, expr.returnAnnotation); retPack = resolveTypePack(funScope, *expr.returnAnnotation);
else if (isNonstrictMode()) else if (isNonstrictMode())
retPack = anyTypePack; retPack = anyTypePack;
else if (expectedFunctionType) else if (expectedFunctionType)
@ -3124,7 +3146,7 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE
// If we're in nonstrict mode we want to only report this missing return // If we're in nonstrict mode we want to only report this missing return
// statement if there are type annotations on the function. In strict mode // statement if there are type annotations on the function. In strict mode
// we report it regardless. // we report it regardless.
if (!isNonstrictMode() || function.hasReturnAnnotation) if (!isNonstrictMode() || function.returnAnnotation)
{ {
reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retType}); reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retType});
} }
@ -4195,6 +4217,9 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
return errorRecoveryType(scope); return errorRecoveryType(scope);
} }
if (FFlag::LuauImmutableTypes)
return *moduleType;
SeenTypes seenTypes; SeenTypes seenTypes;
SeenTypePacks seenTypePacks; SeenTypePacks seenTypePacks;
CloneState cloneState; CloneState cloneState;
@ -4343,11 +4368,7 @@ TypeId Instantiation::clean(TypeId ty)
// Annoyingly, we have to do this even if there are no generics, // Annoyingly, we have to do this even if there are no generics,
// to replace any generic tables. // to replace any generic tables.
replaceGenerics.log = log; ReplaceGenerics replaceGenerics{log, arena, level, ftv->generics, ftv->genericPacks};
replaceGenerics.level = level;
replaceGenerics.currentModule = currentModule;
replaceGenerics.generics.assign(ftv->generics.begin(), ftv->generics.end());
replaceGenerics.genericPacks.assign(ftv->genericPacks.begin(), ftv->genericPacks.end());
// TODO: What to do if this returns nullopt? // TODO: What to do if this returns nullopt?
// We don't have access to the error-reporting machinery // We don't have access to the error-reporting machinery
@ -4513,16 +4534,11 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
if (FFlag::LuauQuantifyInPlace2) if (FFlag::LuauQuantifyInPlace2)
{ {
Luau::quantify(currentModule, ty, scope->level); Luau::quantify(ty, scope->level);
return ty; return ty;
} }
quantification.log = TxnLog::empty(); Quantification quantification{&currentModule->internalTypes, scope->level};
quantification.level = scope->level;
quantification.generics.clear();
quantification.genericPacks.clear();
quantification.currentModule = currentModule;
std::optional<TypeId> qty = quantification.substitute(ty); std::optional<TypeId> qty = quantification.substitute(ty);
if (!qty.has_value()) if (!qty.has_value())
@ -4536,18 +4552,14 @@ TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location
FunctionTypeVar* qftv = getMutable<FunctionTypeVar>(*qty); FunctionTypeVar* qftv = getMutable<FunctionTypeVar>(*qty);
LUAU_ASSERT(qftv); LUAU_ASSERT(qftv);
qftv->generics = quantification.generics; qftv->generics = std::move(quantification.generics);
qftv->genericPacks = quantification.genericPacks; qftv->genericPacks = std::move(quantification.genericPacks);
return *qty; return *qty;
} }
TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location location, const TxnLog* log) TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location location, const TxnLog* log)
{ {
LUAU_ASSERT(log != nullptr); Instantiation instantiation{FFlag::LuauUseCommittingTxnLog ? log : TxnLog::empty(), &currentModule->internalTypes, scope->level};
instantiation.log = FFlag::LuauUseCommittingTxnLog ? log : TxnLog::empty();
instantiation.level = scope->level;
instantiation.currentModule = currentModule;
std::optional<TypeId> instantiated = instantiation.substitute(ty); std::optional<TypeId> instantiated = instantiation.substitute(ty);
if (instantiated.has_value()) if (instantiated.has_value())
return *instantiated; return *instantiated;
@ -4560,10 +4572,7 @@ TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location locat
TypeId TypeChecker::anyify(const ScopePtr& scope, TypeId ty, Location location) TypeId TypeChecker::anyify(const ScopePtr& scope, TypeId ty, Location location)
{ {
anyification.log = TxnLog::empty(); Anyification anyification{&currentModule->internalTypes, anyType, anyTypePack};
anyification.anyType = anyType;
anyification.anyTypePack = anyTypePack;
anyification.currentModule = currentModule;
std::optional<TypeId> any = anyification.substitute(ty); std::optional<TypeId> any = anyification.substitute(ty);
if (any.has_value()) if (any.has_value())
return *any; return *any;
@ -4576,10 +4585,7 @@ TypeId TypeChecker::anyify(const ScopePtr& scope, TypeId ty, Location location)
TypePackId TypeChecker::anyify(const ScopePtr& scope, TypePackId ty, Location location) TypePackId TypeChecker::anyify(const ScopePtr& scope, TypePackId ty, Location location)
{ {
anyification.log = TxnLog::empty(); Anyification anyification{&currentModule->internalTypes, anyType, anyTypePack};
anyification.anyType = anyType;
anyification.anyTypePack = anyTypePack;
anyification.currentModule = currentModule;
std::optional<TypePackId> any = anyification.substitute(ty); std::optional<TypePackId> any = anyification.substitute(ty);
if (any.has_value()) if (any.has_value())
return *any; return *any;
@ -4763,7 +4769,8 @@ TypePackId TypeChecker::errorRecoveryTypePack(TypePackId guess)
return getSingletonTypes().errorRecoveryTypePack(guess); return getSingletonTypes().errorRecoveryTypePack(guess);
} }
TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense) { TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense)
{
return [this, sense](TypeId ty) -> std::optional<TypeId> { return [this, sense](TypeId ty) -> std::optional<TypeId> {
// any/error/free gets a special pass unconditionally because they can't be decided. // any/error/free gets a special pass unconditionally because they can't be decided.
if (get<AnyTypeVar>(ty) || get<ErrorTypeVar>(ty) || get<FreeTypeVar>(ty)) if (get<AnyTypeVar>(ty) || get<ErrorTypeVar>(ty) || get<FreeTypeVar>(ty))
@ -4844,8 +4851,8 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
if (const auto& lit = annotation.as<AstTypeReference>()) if (const auto& lit = annotation.as<AstTypeReference>())
{ {
std::optional<TypeFun> tf; std::optional<TypeFun> tf;
if (lit->hasPrefix) if (lit->prefix)
tf = scope->lookupImportedType(lit->prefix.value, lit->name.value); tf = scope->lookupImportedType(lit->prefix->value, lit->name.value);
else if (FFlag::DebugLuauMagicTypes && lit->name == "_luau_ice") else if (FFlag::DebugLuauMagicTypes && lit->name == "_luau_ice")
ice("_luau_ice encountered", lit->location); ice("_luau_ice encountered", lit->location);
@ -4872,12 +4879,12 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
if (!tf) if (!tf)
{ {
if (lit->name == Parser::errorName) if (lit->name == kParseNameError)
return errorRecoveryType(scope); return errorRecoveryType(scope);
std::string typeName; std::string typeName;
if (lit->hasPrefix) if (lit->prefix)
typeName = std::string(lit->prefix.value) + "."; typeName = std::string(lit->prefix->value) + ".";
typeName += lit->name.value; typeName += lit->name.value;
if (scope->lookupPack(typeName)) if (scope->lookupPack(typeName))
@ -4978,12 +4985,7 @@ TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation
if (notEnoughParameters && hasDefaultParameters) if (notEnoughParameters && hasDefaultParameters)
{ {
// 'applyTypeFunction' is used to substitute default types that reference previous generic types // 'applyTypeFunction' is used to substitute default types that reference previous generic types
applyTypeFunction.log = TxnLog::empty(); ApplyTypeFunction applyTypeFunction{&currentModule->internalTypes, scope->level};
applyTypeFunction.typeArguments.clear();
applyTypeFunction.typePackArguments.clear();
applyTypeFunction.currentModule = currentModule;
applyTypeFunction.level = scope->level;
applyTypeFunction.encounteredForwardedType = false;
for (size_t i = 0; i < typesProvided; ++i) for (size_t i = 0; i < typesProvided; ++i)
applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeParams[i]; applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeParams[i];
@ -5302,18 +5304,14 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
if (tf.typeParams.empty() && tf.typePackParams.empty()) if (tf.typeParams.empty() && tf.typePackParams.empty())
return tf.type; return tf.type;
applyTypeFunction.typeArguments.clear(); ApplyTypeFunction applyTypeFunction{&currentModule->internalTypes, scope->level};
for (size_t i = 0; i < tf.typeParams.size(); ++i) for (size_t i = 0; i < tf.typeParams.size(); ++i)
applyTypeFunction.typeArguments[tf.typeParams[i].ty] = typeParams[i]; applyTypeFunction.typeArguments[tf.typeParams[i].ty] = typeParams[i];
applyTypeFunction.typePackArguments.clear();
for (size_t i = 0; i < tf.typePackParams.size(); ++i) for (size_t i = 0; i < tf.typePackParams.size(); ++i)
applyTypeFunction.typePackArguments[tf.typePackParams[i].tp] = typePackParams[i]; applyTypeFunction.typePackArguments[tf.typePackParams[i].tp] = typePackParams[i];
applyTypeFunction.log = TxnLog::empty();
applyTypeFunction.currentModule = currentModule;
applyTypeFunction.level = scope->level;
applyTypeFunction.encounteredForwardedType = false;
std::optional<TypeId> maybeInstantiated = applyTypeFunction.substitute(tf.type); std::optional<TypeId> maybeInstantiated = applyTypeFunction.substitute(tf.type);
if (!maybeInstantiated.has_value()) if (!maybeInstantiated.has_value())
{ {
@ -5446,7 +5444,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st
void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate) void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate)
{ {
LUAU_ASSERT(FFlag::LuauDiscriminableUnions); LUAU_ASSERT(FFlag::LuauDiscriminableUnions2);
const LValue* target = &lvalue; const LValue* target = &lvalue;
std::optional<LValue> key; // If set, we know we took the base of the lvalue path and should be walking down each option of the base's type. std::optional<LValue> key; // If set, we know we took the base of the lvalue path and should be walking down each option of the base's type.
@ -5659,7 +5657,7 @@ void TypeChecker::resolve(const TruthyPredicate& truthyP, ErrorVec& errVec, Refi
return std::nullopt; return std::nullopt;
}; };
if (FFlag::LuauDiscriminableUnions) if (FFlag::LuauDiscriminableUnions2)
{ {
std::optional<TypeId> ty = resolveLValue(refis, scope, truthyP.lvalue); std::optional<TypeId> ty = resolveLValue(refis, scope, truthyP.lvalue);
if (ty && fromOr) if (ty && fromOr)
@ -5772,7 +5770,7 @@ void TypeChecker::resolve(const IsAPredicate& isaP, ErrorVec& errVec, Refinement
return res; return res;
}; };
if (FFlag::LuauDiscriminableUnions) if (FFlag::LuauDiscriminableUnions2)
{ {
refineLValue(isaP.lvalue, refis, scope, predicate); refineLValue(isaP.lvalue, refis, scope, predicate);
} }
@ -5847,7 +5845,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec
if (auto it = primitives.find(typeguardP.kind); it != primitives.end()) if (auto it = primitives.find(typeguardP.kind); it != primitives.end())
{ {
if (FFlag::LuauDiscriminableUnions) if (FFlag::LuauDiscriminableUnions2)
{ {
refineLValue(typeguardP.lvalue, refis, scope, it->second(sense)); refineLValue(typeguardP.lvalue, refis, scope, it->second(sense));
return; return;
@ -5869,7 +5867,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec
} }
auto fail = [&](const TypeErrorData& err) { auto fail = [&](const TypeErrorData& err) {
if (!FFlag::LuauDiscriminableUnions) if (!FFlag::LuauDiscriminableUnions2)
errVec.push_back(TypeError{typeguardP.location, err}); errVec.push_back(TypeError{typeguardP.location, err});
addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope)); addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
}; };
@ -5901,7 +5899,7 @@ void TypeChecker::resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMa
return {ty}; return {ty};
}; };
if (FFlag::LuauDiscriminableUnions) if (FFlag::LuauDiscriminableUnions2)
{ {
std::vector<TypeId> rhs = options(eqP.type); std::vector<TypeId> rhs = options(eqP.type);

View file

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

View file

@ -23,11 +23,10 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauLengthOnCompositeType)
LUAU_FASTFLAGVARIABLE(LuauMetatableAreEqualRecursion, false)
LUAU_FASTFLAGVARIABLE(LuauRefactorTypeVarQuestions, false) LUAU_FASTFLAGVARIABLE(LuauRefactorTypeVarQuestions, false)
LUAU_FASTFLAG(LuauErrorRecoveryType) LUAU_FASTFLAG(LuauErrorRecoveryType)
LUAU_FASTFLAG(LuauUnionTagMatchFix) LUAU_FASTFLAG(LuauUnionTagMatchFix)
LUAU_FASTFLAG(LuauDiscriminableUnions2)
namespace Luau namespace Luau
{ {
@ -384,8 +383,6 @@ bool maybeSingleton(TypeId ty)
bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount) bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
{ {
LUAU_ASSERT(FFlag::LuauLengthOnCompositeType);
RecursionLimiter _rl(recursionCount, FInt::LuauTypeInferRecursionLimit); RecursionLimiter _rl(recursionCount, FInt::LuauTypeInferRecursionLimit);
ty = follow(ty); ty = follow(ty);
@ -393,7 +390,8 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
if (seen.contains(ty)) if (seen.contains(ty))
return true; return true;
if (isPrim(ty, PrimitiveTypeVar::String) || get<AnyTypeVar>(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty)) bool isStr = FFlag::LuauDiscriminableUnions2 ? isString(ty) : isPrim(ty, PrimitiveTypeVar::String);
if (isStr || get<AnyTypeVar>(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty))
return true; return true;
if (auto uty = get<UnionTypeVar>(ty)) if (auto uty = get<UnionTypeVar>(ty))
@ -553,7 +551,7 @@ bool areEqual(SeenSet& seen, const TableTypeVar& lhs, const TableTypeVar& rhs)
static bool areEqual(SeenSet& seen, const MetatableTypeVar& lhs, const MetatableTypeVar& rhs) static bool areEqual(SeenSet& seen, const MetatableTypeVar& lhs, const MetatableTypeVar& rhs)
{ {
if (FFlag::LuauMetatableAreEqualRecursion && areSeen(seen, &lhs, &rhs)) if (areSeen(seen, &lhs, &rhs))
return true; return true;
return areEqual(seen, *lhs.table, *rhs.table) && areEqual(seen, *lhs.metatable, *rhs.metatable); return areEqual(seen, *lhs.table, *rhs.table) && areEqual(seen, *lhs.metatable, *rhs.metatable);

View file

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

View file

@ -14,7 +14,7 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit); LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit); LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
LUAU_FASTFLAGVARIABLE(LuauCommittingTxnLogFreeTpPromote, false) LUAU_FASTFLAG(LuauImmutableTypes)
LUAU_FASTFLAG(LuauUseCommittingTxnLog) LUAU_FASTFLAG(LuauUseCommittingTxnLog)
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000); LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000);
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false); LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
@ -22,8 +22,8 @@ LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false)
LUAU_FASTFLAG(LuauSingletonTypes) LUAU_FASTFLAG(LuauSingletonTypes)
LUAU_FASTFLAG(LuauErrorRecoveryType); LUAU_FASTFLAG(LuauErrorRecoveryType);
LUAU_FASTFLAG(LuauProperTypeLevels); LUAU_FASTFLAG(LuauProperTypeLevels);
LUAU_FASTFLAGVARIABLE(LuauUnifyPackTails, false)
LUAU_FASTFLAGVARIABLE(LuauUnionTagMatchFix, false) LUAU_FASTFLAGVARIABLE(LuauUnionTagMatchFix, false)
LUAU_FASTFLAGVARIABLE(LuauFollowWithCommittingTxnLogInAnyUnification, false)
namespace Luau namespace Luau
{ {
@ -32,11 +32,13 @@ struct PromoteTypeLevels
{ {
DEPRECATED_TxnLog& DEPRECATED_log; DEPRECATED_TxnLog& DEPRECATED_log;
TxnLog& log; TxnLog& log;
const TypeArena* typeArena = nullptr;
TypeLevel minLevel; TypeLevel minLevel;
explicit PromoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, TypeLevel minLevel) explicit PromoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel)
: DEPRECATED_log(DEPRECATED_log) : DEPRECATED_log(DEPRECATED_log)
, log(log) , log(log)
, typeArena(typeArena)
, minLevel(minLevel) , minLevel(minLevel)
{ {
} }
@ -65,8 +67,12 @@ struct PromoteTypeLevels
} }
template<typename TID, typename T> template<typename TID, typename T>
bool operator()(TID, const T&) bool operator()(TID ty, const T&)
{ {
// Type levels of types from other modules are already global, so we don't need to promote anything inside
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
return false;
return true; return true;
} }
@ -83,12 +89,20 @@ struct PromoteTypeLevels
bool operator()(TypeId ty, const FunctionTypeVar&) bool operator()(TypeId ty, const FunctionTypeVar&)
{ {
// Type levels of types from other modules are already global, so we don't need to promote anything inside
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
return false;
promote(ty, FFlag::LuauUseCommittingTxnLog ? log.getMutable<FunctionTypeVar>(ty) : getMutable<FunctionTypeVar>(ty)); promote(ty, FFlag::LuauUseCommittingTxnLog ? log.getMutable<FunctionTypeVar>(ty) : getMutable<FunctionTypeVar>(ty));
return true; return true;
} }
bool operator()(TypeId ty, const TableTypeVar& ttv) bool operator()(TypeId ty, const TableTypeVar& ttv)
{ {
// Type levels of types from other modules are already global, so we don't need to promote anything inside
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
return false;
if (ttv.state != TableState::Free && ttv.state != TableState::Generic) if (ttv.state != TableState::Free && ttv.state != TableState::Generic)
return true; return true;
@ -100,7 +114,7 @@ struct PromoteTypeLevels
{ {
// Surprise, it's actually a BoundTypePack that hasn't been committed yet. // Surprise, it's actually a BoundTypePack that hasn't been committed yet.
// Calling getMutable on this will trigger an assertion. // Calling getMutable on this will trigger an assertion.
if (FFlag::LuauCommittingTxnLogFreeTpPromote && FFlag::LuauUseCommittingTxnLog && !log.is<FreeTypePack>(tp)) if (FFlag::LuauUseCommittingTxnLog && !log.is<FreeTypePack>(tp))
return true; return true;
promote(tp, FFlag::LuauUseCommittingTxnLog ? log.getMutable<FreeTypePack>(tp) : getMutable<FreeTypePack>(tp)); promote(tp, FFlag::LuauUseCommittingTxnLog ? log.getMutable<FreeTypePack>(tp) : getMutable<FreeTypePack>(tp));
@ -108,24 +122,33 @@ struct PromoteTypeLevels
} }
}; };
void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, TypeLevel minLevel, TypeId ty) void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypeId ty)
{ {
PromoteTypeLevels ptl{DEPRECATED_log, log, minLevel}; // Type levels of types from other modules are already global, so we don't need to promote anything inside
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
return;
PromoteTypeLevels ptl{DEPRECATED_log, log, typeArena, minLevel};
DenseHashSet<void*> seen{nullptr}; DenseHashSet<void*> seen{nullptr};
visitTypeVarOnce(ty, ptl, seen); visitTypeVarOnce(ty, ptl, seen);
} }
void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, TypeLevel minLevel, TypePackId tp) void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp)
{ {
PromoteTypeLevels ptl{DEPRECATED_log, log, minLevel}; // Type levels of types from other modules are already global, so we don't need to promote anything inside
if (FFlag::LuauImmutableTypes && tp->owningArena != typeArena)
return;
PromoteTypeLevels ptl{DEPRECATED_log, log, typeArena, minLevel};
DenseHashSet<void*> seen{nullptr}; DenseHashSet<void*> seen{nullptr};
visitTypeVarOnce(tp, ptl, seen); visitTypeVarOnce(tp, ptl, seen);
} }
struct SkipCacheForType struct SkipCacheForType
{ {
SkipCacheForType(const DenseHashMap<TypeId, bool>& skipCacheForType) SkipCacheForType(const DenseHashMap<TypeId, bool>& skipCacheForType, const TypeArena* typeArena)
: skipCacheForType(skipCacheForType) : skipCacheForType(skipCacheForType)
, typeArena(typeArena)
{ {
} }
@ -152,6 +175,10 @@ struct SkipCacheForType
bool operator()(TypeId ty, const TableTypeVar&) bool operator()(TypeId ty, const TableTypeVar&)
{ {
// Types from other modules don't contain mutable elements and are ok to cache
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
return false;
TableTypeVar& ttv = *getMutable<TableTypeVar>(ty); TableTypeVar& ttv = *getMutable<TableTypeVar>(ty);
if (ttv.boundTo) if (ttv.boundTo)
@ -172,6 +199,10 @@ struct SkipCacheForType
template<typename T> template<typename T>
bool operator()(TypeId ty, const T& t) bool operator()(TypeId ty, const T& t)
{ {
// Types from other modules don't contain mutable elements and are ok to cache
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
return false;
const bool* prev = skipCacheForType.find(ty); const bool* prev = skipCacheForType.find(ty);
if (prev && *prev) if (prev && *prev)
@ -184,8 +215,12 @@ struct SkipCacheForType
} }
template<typename T> template<typename T>
bool operator()(TypePackId, const T&) bool operator()(TypePackId tp, const T&)
{ {
// Types from other modules don't contain mutable elements and are ok to cache
if (FFlag::LuauImmutableTypes && tp->owningArena != typeArena)
return false;
return true; return true;
} }
@ -208,6 +243,7 @@ struct SkipCacheForType
} }
const DenseHashMap<TypeId, bool>& skipCacheForType; const DenseHashMap<TypeId, bool>& skipCacheForType;
const TypeArena* typeArena = nullptr;
bool result = false; bool result = false;
}; };
@ -422,13 +458,13 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
{ {
if (FFlag::LuauUseCommittingTxnLog) if (FFlag::LuauUseCommittingTxnLog)
{ {
promoteTypeLevels(DEPRECATED_log, log, superLevel, subTy); promoteTypeLevels(DEPRECATED_log, log, types, superLevel, subTy);
log.replace(superTy, BoundTypeVar(subTy)); log.replace(superTy, BoundTypeVar(subTy));
} }
else else
{ {
if (FFlag::LuauProperTypeLevels) if (FFlag::LuauProperTypeLevels)
promoteTypeLevels(DEPRECATED_log, log, superLevel, subTy); promoteTypeLevels(DEPRECATED_log, log, types, superLevel, subTy);
else if (auto subLevel = getMutableLevel(subTy)) else if (auto subLevel = getMutableLevel(subTy))
{ {
if (!subLevel->subsumes(superFree->level)) if (!subLevel->subsumes(superFree->level))
@ -466,13 +502,13 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
{ {
if (FFlag::LuauUseCommittingTxnLog) if (FFlag::LuauUseCommittingTxnLog)
{ {
promoteTypeLevels(DEPRECATED_log, log, subLevel, superTy); promoteTypeLevels(DEPRECATED_log, log, types, subLevel, superTy);
log.replace(subTy, BoundTypeVar(superTy)); log.replace(subTy, BoundTypeVar(superTy));
} }
else else
{ {
if (FFlag::LuauProperTypeLevels) if (FFlag::LuauProperTypeLevels)
promoteTypeLevels(DEPRECATED_log, log, subLevel, superTy); promoteTypeLevels(DEPRECATED_log, log, types, subLevel, superTy);
else if (auto superLevel = getMutableLevel(superTy)) else if (auto superLevel = getMutableLevel(superTy))
{ {
if (!superLevel->subsumes(subFree->level)) if (!superLevel->subsumes(subFree->level))
@ -849,7 +885,7 @@ void Unifier::cacheResult(TypeId subTy, TypeId superTy)
return; return;
auto skipCacheFor = [this](TypeId ty) { auto skipCacheFor = [this](TypeId ty) {
SkipCacheForType visitor{sharedState.skipCacheForType}; SkipCacheForType visitor{sharedState.skipCacheForType, types};
visitTypeVarOnce(ty, visitor, sharedState.seenAny); visitTypeVarOnce(ty, visitor, sharedState.seenAny);
sharedState.skipCacheForType[ty] = visitor.result; sharedState.skipCacheForType[ty] = visitor.result;
@ -1204,7 +1240,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
// If both are at the end, we're done // If both are at the end, we're done
if (!superIter.good() && !subIter.good()) if (!superIter.good() && !subIter.good())
{ {
if (FFlag::LuauUnifyPackTails && subTpv->tail && superTpv->tail) if (subTpv->tail && superTpv->tail)
{ {
tryUnify_(*subTpv->tail, *superTpv->tail); tryUnify_(*subTpv->tail, *superTpv->tail);
break; break;
@ -1212,9 +1248,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
const bool lFreeTail = superTpv->tail && log.getMutable<FreeTypePack>(log.follow(*superTpv->tail)) != nullptr; const bool lFreeTail = superTpv->tail && log.getMutable<FreeTypePack>(log.follow(*superTpv->tail)) != nullptr;
const bool rFreeTail = subTpv->tail && log.getMutable<FreeTypePack>(log.follow(*subTpv->tail)) != nullptr; const bool rFreeTail = subTpv->tail && log.getMutable<FreeTypePack>(log.follow(*subTpv->tail)) != nullptr;
if (!FFlag::LuauUnifyPackTails && lFreeTail && rFreeTail) if (lFreeTail)
tryUnify_(*subTpv->tail, *superTpv->tail);
else if (lFreeTail)
tryUnify_(emptyTp, *superTpv->tail); tryUnify_(emptyTp, *superTpv->tail);
else if (rFreeTail) else if (rFreeTail)
tryUnify_(emptyTp, *subTpv->tail); tryUnify_(emptyTp, *subTpv->tail);
@ -1410,7 +1444,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
// If both are at the end, we're done // If both are at the end, we're done
if (!superIter.good() && !subIter.good()) if (!superIter.good() && !subIter.good())
{ {
if (FFlag::LuauUnifyPackTails && subTpv->tail && superTpv->tail) if (subTpv->tail && superTpv->tail)
{ {
tryUnify_(*subTpv->tail, *superTpv->tail); tryUnify_(*subTpv->tail, *superTpv->tail);
break; break;
@ -1418,9 +1452,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
const bool lFreeTail = superTpv->tail && get<FreeTypePack>(follow(*superTpv->tail)) != nullptr; const bool lFreeTail = superTpv->tail && get<FreeTypePack>(follow(*superTpv->tail)) != nullptr;
const bool rFreeTail = subTpv->tail && get<FreeTypePack>(follow(*subTpv->tail)) != nullptr; const bool rFreeTail = subTpv->tail && get<FreeTypePack>(follow(*subTpv->tail)) != nullptr;
if (!FFlag::LuauUnifyPackTails && lFreeTail && rFreeTail) if (lFreeTail)
tryUnify_(*subTpv->tail, *superTpv->tail);
else if (lFreeTail)
tryUnify_(emptyTp, *superTpv->tail); tryUnify_(emptyTp, *superTpv->tail);
else if (rFreeTail) else if (rFreeTail)
tryUnify_(emptyTp, *subTpv->tail); tryUnify_(emptyTp, *subTpv->tail);
@ -1637,32 +1669,35 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
tryUnify_(subFunction->retType, superFunction->retType); tryUnify_(subFunction->retType, superFunction->retType);
} }
if (FFlag::LuauUseCommittingTxnLog) if (!FFlag::LuauImmutableTypes)
{ {
if (superFunction->definition && !subFunction->definition && !subTy->persistent) if (FFlag::LuauUseCommittingTxnLog)
{ {
PendingType* newSubTy = log.queue(subTy); if (superFunction->definition && !subFunction->definition && !subTy->persistent)
FunctionTypeVar* newSubFtv = getMutable<FunctionTypeVar>(newSubTy); {
LUAU_ASSERT(newSubFtv); PendingType* newSubTy = log.queue(subTy);
newSubFtv->definition = superFunction->definition; FunctionTypeVar* newSubFtv = getMutable<FunctionTypeVar>(newSubTy);
LUAU_ASSERT(newSubFtv);
newSubFtv->definition = superFunction->definition;
}
else if (!superFunction->definition && subFunction->definition && !superTy->persistent)
{
PendingType* newSuperTy = log.queue(superTy);
FunctionTypeVar* newSuperFtv = getMutable<FunctionTypeVar>(newSuperTy);
LUAU_ASSERT(newSuperFtv);
newSuperFtv->definition = subFunction->definition;
}
} }
else if (!superFunction->definition && subFunction->definition && !superTy->persistent) else
{ {
PendingType* newSuperTy = log.queue(superTy); if (superFunction->definition && !subFunction->definition && !subTy->persistent)
FunctionTypeVar* newSuperFtv = getMutable<FunctionTypeVar>(newSuperTy); {
LUAU_ASSERT(newSuperFtv); subFunction->definition = superFunction->definition;
newSuperFtv->definition = subFunction->definition; }
} else if (!superFunction->definition && subFunction->definition && !superTy->persistent)
} {
else superFunction->definition = subFunction->definition;
{ }
if (superFunction->definition && !subFunction->definition && !subTy->persistent)
{
subFunction->definition = superFunction->definition;
}
else if (!superFunction->definition && subFunction->definition && !superTy->persistent)
{
superFunction->definition = subFunction->definition;
} }
} }
@ -2631,7 +2666,7 @@ static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>&
{ {
while (true) while (true)
{ {
a = follow(a); a = FFlag::LuauFollowWithCommittingTxnLogInAnyUnification ? state.log.follow(a) : follow(a);
if (seenTypePacks.find(a)) if (seenTypePacks.find(a))
break; break;
@ -2738,7 +2773,7 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever
} }
static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHashSet<TypeId>& seen, DenseHashSet<TypePackId>& seenTypePacks, static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHashSet<TypeId>& seen, DenseHashSet<TypePackId>& seenTypePacks,
TypeId anyType, TypePackId anyTypePack) const TypeArena* typeArena, TypeId anyType, TypePackId anyTypePack)
{ {
while (!queue.empty()) while (!queue.empty())
{ {
@ -2746,8 +2781,14 @@ static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHas
{ {
TypeId ty = state.log.follow(queue.back()); TypeId ty = state.log.follow(queue.back());
queue.pop_back(); queue.pop_back();
// Types from other modules don't have free types
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
continue;
if (seen.find(ty)) if (seen.find(ty))
continue; continue;
seen.insert(ty); seen.insert(ty);
if (state.log.getMutable<FreeTypeVar>(ty)) if (state.log.getMutable<FreeTypeVar>(ty))
@ -2853,7 +2894,7 @@ void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy)
sharedState.tempSeenTy.clear(); sharedState.tempSeenTy.clear();
sharedState.tempSeenTp.clear(); sharedState.tempSeenTp.clear();
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, getSingletonTypes().anyType, anyTP); Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, getSingletonTypes().anyType, anyTP);
} }
void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp) void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
@ -2869,7 +2910,7 @@ void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
queueTypePack(queue, sharedState.tempSeenTp, *this, subTy, anyTp); queueTypePack(queue, sharedState.tempSeenTp, *this, subTy, anyTp);
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, anyTy, anyTp); Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, anyTy, anyTp);
} }
std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name) std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Repl.h"
#include "lua.h" #include "lua.h"
#include "lualib.h" #include "lualib.h"
@ -35,16 +37,19 @@ enum class CompileFormat
Binary Binary
}; };
constexpr int MaxTraversalLimit = 50;
struct GlobalOptions struct GlobalOptions
{ {
int optimizationLevel = 1; int optimizationLevel = 1;
int debugLevel = 1;
} globalOptions; } globalOptions;
static Luau::CompileOptions copts() static Luau::CompileOptions copts()
{ {
Luau::CompileOptions result = {}; Luau::CompileOptions result = {};
result.optimizationLevel = globalOptions.optimizationLevel; result.optimizationLevel = globalOptions.optimizationLevel;
result.debugLevel = 1; result.debugLevel = globalOptions.debugLevel;
result.coverageLevel = coverageActive() ? 2 : 0; result.coverageLevel = coverageActive() ? 2 : 0;
return result; return result;
@ -240,11 +245,115 @@ std::string runCode(lua_State* L, const std::string& source)
return std::string(); return std::string();
} }
static void completeIndexer(ic_completion_env_t* cenv, const char* editBuffer) // Replaces the top of the lua stack with the metatable __index for the value
// if it exists. Returns true iff __index exists.
static bool tryReplaceTopWithIndex(lua_State* L)
{
if (luaL_getmetafield(L, -1, "__index"))
{
// Remove the table leaving __index on the top of stack
lua_remove(L, -2);
return true;
}
return false;
}
// This function is similar to lua_gettable, but it avoids calling any
// lua callback functions (e.g. __index) which might modify the Lua VM state.
static void safeGetTable(lua_State* L, int tableIndex)
{
lua_pushvalue(L, tableIndex); // Duplicate the table
// The loop invariant is that the table to search is at -1
// and the key is at -2.
for (int loopCount = 0;; loopCount++)
{
lua_pushvalue(L, -2); // Duplicate the key
lua_rawget(L, -2); // Try to find the key
if (!lua_isnil(L, -1) || loopCount >= MaxTraversalLimit)
{
// Either the key has been found, and/or we have reached the max traversal limit
break;
}
else
{
lua_pop(L, 1); // Pop the nil result
if (!luaL_getmetafield(L, -1, "__index"))
{
lua_pushnil(L);
break;
}
else if (lua_istable(L, -1))
{
// Replace the current table being searched with __index table
lua_replace(L, -2);
}
else
{
lua_pop(L, 1); // Pop the value
lua_pushnil(L);
break;
}
}
}
lua_remove(L, -2); // Remove the table
lua_remove(L, -2); // Remove the original key
}
// completePartialMatches finds keys that match the specified 'prefix'
// Note: the table/object to be searched must be on the top of the Lua stack
static void completePartialMatches(lua_State* L, bool completeOnlyFunctions, const std::string& editBuffer, std::string_view prefix,
const AddCompletionCallback& addCompletionCallback)
{
for (int i = 0; i < MaxTraversalLimit && lua_istable(L, -1); i++)
{
// table, key
lua_pushnil(L);
// Loop over all the keys in the current table
while (lua_next(L, -2) != 0)
{
if (lua_type(L, -2) == LUA_TSTRING)
{
// table, key, value
std::string_view key = lua_tostring(L, -2);
int valueType = lua_type(L, -1);
// If the last separator was a ':' (i.e. a method call) then only functions should be completed.
bool requiredValueType = (!completeOnlyFunctions || valueType == LUA_TFUNCTION);
if (!key.empty() && requiredValueType && Luau::startsWith(key, prefix))
{
std::string completedComponent(key.substr(prefix.size()));
std::string completion(editBuffer + completedComponent);
if (valueType == LUA_TFUNCTION)
{
// Add an opening paren for function calls by default.
completion += "(";
}
addCompletionCallback(completion, std::string(key));
}
}
lua_pop(L, 1);
}
// Replace the current table being searched with an __index table if one exists
if (!tryReplaceTopWithIndex(L))
{
break;
}
}
}
static void completeIndexer(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback)
{ {
auto* L = reinterpret_cast<lua_State*>(ic_completion_arg(cenv));
std::string_view lookup = editBuffer; std::string_view lookup = editBuffer;
char lastSep = 0; bool completeOnlyFunctions = false;
// Push the global variable table to begin the search
lua_pushvalue(L, LUA_GLOBALSINDEX);
for (;;) for (;;)
{ {
@ -253,65 +362,46 @@ static void completeIndexer(ic_completion_env_t* cenv, const char* editBuffer)
if (sep == std::string_view::npos) if (sep == std::string_view::npos)
{ {
// table, key completePartialMatches(L, completeOnlyFunctions, editBuffer, prefix, addCompletionCallback);
lua_pushnil(L);
while (lua_next(L, -2) != 0)
{
if (lua_type(L, -2) == LUA_TSTRING)
{
// table, key, value
std::string_view key = lua_tostring(L, -2);
int valueType = lua_type(L, -1);
// If the last separator was a ':' (i.e. a method call) then only functions should be completed.
bool requiredValueType = (lastSep != ':' || valueType == LUA_TFUNCTION);
if (!key.empty() && requiredValueType && Luau::startsWith(key, prefix))
{
std::string completedComponent(key.substr(prefix.size()));
std::string completion(editBuffer + completedComponent);
if (valueType == LUA_TFUNCTION)
{
// Add an opening paren for function calls by default.
completion += "(";
}
ic_add_completion_ex(cenv, completion.data(), key.data(), nullptr);
}
}
lua_pop(L, 1);
}
break; break;
} }
else else
{ {
// find the key in the table // find the key in the table
lua_pushlstring(L, prefix.data(), prefix.size()); lua_pushlstring(L, prefix.data(), prefix.size());
lua_rawget(L, -2); safeGetTable(L, -2);
lua_remove(L, -2); lua_remove(L, -2);
if (lua_type(L, -1) == LUA_TSTRING) if (lua_istable(L, -1) || tryReplaceTopWithIndex(L))
{ {
// Replace the string object with the string class to perform further lookups of string functions completeOnlyFunctions = lookup[sep] == ':';
// Note: We retrieve the string class from _G to prevent issues if the user assigns to `string`. lookup.remove_prefix(sep + 1);
lua_getglobal(L, "_G");
lua_pushlstring(L, "string", 6);
lua_rawget(L, -2);
lua_remove(L, -2);
LUAU_ASSERT(lua_istable(L, -1));
} }
else if (!lua_istable(L, -1)) else
{
// Unable to search for keys, so stop searching
break; break;
}
lastSep = lookup[sep];
lookup.remove_prefix(sep + 1);
} }
} }
lua_pop(L, 1); lua_pop(L, 1);
} }
void getCompletions(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback)
{
completeIndexer(L, editBuffer, addCompletionCallback);
}
static void icGetCompletions(ic_completion_env_t* cenv, const char* editBuffer)
{
auto* L = reinterpret_cast<lua_State*>(ic_completion_arg(cenv));
getCompletions(L, std::string(editBuffer), [cenv](const std::string& completion, const std::string& display) {
ic_add_completion_ex(cenv, completion.data(), display.data(), nullptr);
});
}
static bool isMethodOrFunctionChar(const char* s, long len) static bool isMethodOrFunctionChar(const char* s, long len)
{ {
char c = *s; char c = *s;
@ -320,15 +410,7 @@ static bool isMethodOrFunctionChar(const char* s, long len)
static void completeRepl(ic_completion_env_t* cenv, const char* editBuffer) static void completeRepl(ic_completion_env_t* cenv, const char* editBuffer)
{ {
auto* L = reinterpret_cast<lua_State*>(ic_completion_arg(cenv)); ic_complete_word(cenv, editBuffer, icGetCompletions, isMethodOrFunctionChar);
// look the value up in current global table first
lua_pushvalue(L, LUA_GLOBALSINDEX);
ic_complete_word(cenv, editBuffer, completeIndexer, isMethodOrFunctionChar);
// and in actual global table after that
lua_getglobal(L, "_G");
ic_complete_word(cenv, editBuffer, completeIndexer, isMethodOrFunctionChar);
} }
struct LinenoiseScopedHistory struct LinenoiseScopedHistory
@ -350,9 +432,7 @@ struct LinenoiseScopedHistory
ic_set_history(historyFilepath.c_str(), -1 /* default entries (= 200) */); ic_set_history(historyFilepath.c_str(), -1 /* default entries (= 200) */);
} }
~LinenoiseScopedHistory() ~LinenoiseScopedHistory() {}
{
}
std::string historyFilepath; std::string historyFilepath;
}; };
@ -372,19 +452,20 @@ static void runReplImpl(lua_State* L)
for (;;) for (;;)
{ {
const char* line = ic_readline(buffer.empty() ? "" : ">"); const char* prompt = buffer.empty() ? "" : ">";
std::unique_ptr<char, void (*)(void*)> line(ic_readline(prompt), free);
if (!line) if (!line)
break; break;
if (buffer.empty() && runCode(L, std::string("return ") + line) == std::string()) if (buffer.empty() && runCode(L, std::string("return ") + line.get()) == std::string())
{ {
ic_history_add(line); ic_history_add(line.get());
continue; continue;
} }
if (!buffer.empty()) if (!buffer.empty())
buffer += "\n"; buffer += "\n";
buffer += line; buffer += line.get();
std::string error = runCode(L, buffer); std::string error = runCode(L, buffer);
@ -400,7 +481,6 @@ static void runReplImpl(lua_State* L)
ic_history_add(buffer.c_str()); ic_history_add(buffer.c_str());
buffer.clear(); buffer.clear();
free((void*)line);
} }
} }
@ -504,7 +584,7 @@ static bool compileFile(const char* name, CompileFormat format)
if (format == CompileFormat::Text) if (format == CompileFormat::Text)
{ {
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source); bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals);
bcb.setDumpSource(*source); bcb.setDumpSource(*source);
} }
@ -549,7 +629,8 @@ static void displayHelp(const char* argv0)
printf(" --coverage: collect code coverage while running the code and output results to coverage.out\n"); printf(" --coverage: collect code coverage while running the code and output results to coverage.out\n");
printf(" -h, --help: Display this usage message.\n"); printf(" -h, --help: Display this usage message.\n");
printf(" -i, --interactive: Run an interactive REPL after executing the last script specified.\n"); printf(" -i, --interactive: Run an interactive REPL after executing the last script specified.\n");
printf(" -O<n>: use compiler optimization level (n=0-2).\n"); printf(" -O<n>: compile with optimization level n (default 1, n should be between 0 and 2).\n");
printf(" -g<n>: compile with debug level n (default 1, n should be between 0 and 2).\n");
printf(" --profile[=N]: profile the code using N Hz sampling (default 10000) and output results to profile.out\n"); printf(" --profile[=N]: profile the code using N Hz sampling (default 10000) and output results to profile.out\n");
printf(" --timetrace: record compiler time tracing information into trace.json\n"); printf(" --timetrace: record compiler time tracing information into trace.json\n");
} }
@ -620,6 +701,16 @@ int replMain(int argc, char** argv)
} }
globalOptions.optimizationLevel = level; globalOptions.optimizationLevel = level;
} }
else if (strncmp(argv[i], "-g", 2) == 0)
{
int level = atoi(argv[i] + 2);
if (level < 0 || level > 2)
{
fprintf(stderr, "Error: Debug level must be between 0 and 2 inclusive.\n");
return 1;
}
globalOptions.debugLevel = level;
}
else if (strcmp(argv[i], "--profile") == 0) else if (strcmp(argv[i], "--profile") == 0)
{ {
profile = 10000; // default to 10 KHz profile = 10000; // default to 10 KHz

View file

@ -3,10 +3,15 @@
#include "lua.h" #include "lua.h"
#include <functional>
#include <string> #include <string>
using AddCompletionCallback = std::function<void(const std::string& completion, const std::string& display)>;
// Note: These are internal functions which are being exposed in a header // Note: These are internal functions which are being exposed in a header
// so they can be included by unit tests. // so they can be included by unit tests.
int replMain(int argc, char** argv);
void setupState(lua_State* L); void setupState(lua_State* L);
std::string runCode(lua_State* L, const std::string& source); std::string runCode(lua_State* L, const std::string& source);
void getCompletions(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback);
int replMain(int argc, char** argv);

View file

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

View file

@ -6,8 +6,6 @@
#include <algorithm> #include <algorithm>
#include <string.h> #include <string.h>
LUAU_FASTFLAGVARIABLE(LuauBytecodeV2Write, false)
namespace Luau namespace Luau
{ {
@ -510,7 +508,7 @@ uint32_t BytecodeBuilder::getDebugPC() const
void BytecodeBuilder::finalize() void BytecodeBuilder::finalize()
{ {
LUAU_ASSERT(bytecode.empty()); LUAU_ASSERT(bytecode.empty());
bytecode = char(FFlag::LuauBytecodeV2Write ? LBC_VERSION_FUTURE : LBC_VERSION); bytecode = char(LBC_VERSION_FUTURE);
writeStringTable(bytecode); writeStringTable(bytecode);
@ -611,9 +609,7 @@ void BytecodeBuilder::writeFunction(std::string& ss, uint32_t id) const
writeVarInt(ss, child); writeVarInt(ss, child);
// debug info // debug info
if (FFlag::LuauBytecodeV2Write) writeVarInt(ss, func.debuglinedefined);
writeVarInt(ss, func.debuglinedefined);
writeVarInt(ss, func.debugname); writeVarInt(ss, func.debugname);
bool hasLines = true; bool hasLines = true;

View file

@ -15,7 +15,6 @@
#include <bitset> #include <bitset>
#include <math.h> #include <math.h>
LUAU_FASTFLAGVARIABLE(LuauCompileTableIndexOpt, false)
LUAU_FASTFLAG(LuauCompileSelectBuiltin2) LUAU_FASTFLAG(LuauCompileSelectBuiltin2)
namespace Luau namespace Luau
@ -1182,18 +1181,9 @@ struct Compiler
const AstExprTable::Item& item = expr->items.data[i]; const AstExprTable::Item& item = expr->items.data[i];
LUAU_ASSERT(item.key); // no list portion => all items have keys LUAU_ASSERT(item.key); // no list portion => all items have keys
if (FFlag::LuauCompileTableIndexOpt) const Constant* ckey = constants.find(item.key);
{
const Constant* ckey = constants.find(item.key);
indexSize += (ckey && ckey->type == Constant::Type_Number && ckey->valueNumber == double(indexSize + 1)); indexSize += (ckey && ckey->type == Constant::Type_Number && ckey->valueNumber == double(indexSize + 1));
}
else
{
AstExprConstantNumber* ckey = item.key->as<AstExprConstantNumber>();
indexSize += (ckey && ckey->value == double(indexSize + 1));
}
} }
// we only perform the optimization if we don't have any other []-keys // we only perform the optimization if we don't have any other []-keys
@ -1295,43 +1285,10 @@ struct Compiler
{ {
RegScope rsi(this); RegScope rsi(this);
if (FFlag::LuauCompileTableIndexOpt) LValue lv = compileLValueIndex(reg, key, rsi);
{ uint8_t rv = compileExprAuto(value, rsi);
LValue lv = compileLValueIndex(reg, key, rsi);
uint8_t rv = compileExprAuto(value, rsi);
compileAssign(lv, rv); compileAssign(lv, rv);
}
else
{
// Optimization: use SETTABLEKS/SETTABLEN for literal keys, this happens often as part of usual table construction syntax
if (AstExprConstantString* ckey = key->as<AstExprConstantString>())
{
BytecodeBuilder::StringRef cname = sref(ckey->value);
int32_t cid = bytecode.addConstantString(cname);
if (cid < 0)
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
uint8_t rv = compileExprAuto(value, rsi);
bytecode.emitABC(LOP_SETTABLEKS, rv, reg, uint8_t(BytecodeBuilder::getStringHash(cname)));
bytecode.emitAux(cid);
}
else if (AstExprConstantNumber* ckey = key->as<AstExprConstantNumber>();
ckey && ckey->value >= 1 && ckey->value <= 256 && double(int(ckey->value)) == ckey->value)
{
uint8_t rv = compileExprAuto(value, rsi);
bytecode.emitABC(LOP_SETTABLEN, rv, reg, uint8_t(int(ckey->value) - 1));
}
else
{
uint8_t rk = compileExprAuto(key, rsi);
uint8_t rv = compileExprAuto(value, rsi);
bytecode.emitABC(LOP_SETTABLE, rv, reg, rk);
}
}
} }
// items without a key are set using SETLIST so that we can initialize large arrays quickly // items without a key are set using SETLIST so that we can initialize large arrays quickly
else else
@ -1439,8 +1396,7 @@ struct Compiler
uint8_t rt = compileExprAuto(expr->expr, rs); uint8_t rt = compileExprAuto(expr->expr, rs);
uint8_t i = uint8_t(int(cv->valueNumber) - 1); uint8_t i = uint8_t(int(cv->valueNumber) - 1);
if (FFlag::LuauCompileTableIndexOpt) setDebugLine(expr->index);
setDebugLine(expr->index);
bytecode.emitABC(LOP_GETTABLEN, target, rt, i); bytecode.emitABC(LOP_GETTABLEN, target, rt, i);
} }
@ -1453,8 +1409,7 @@ struct Compiler
if (cid < 0) if (cid < 0)
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile"); CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
if (FFlag::LuauCompileTableIndexOpt) setDebugLine(expr->index);
setDebugLine(expr->index);
bytecode.emitABC(LOP_GETTABLEKS, target, rt, uint8_t(BytecodeBuilder::getStringHash(iname))); bytecode.emitABC(LOP_GETTABLEKS, target, rt, uint8_t(BytecodeBuilder::getStringHash(iname)));
bytecode.emitAux(cid); bytecode.emitAux(cid);
@ -1853,8 +1808,7 @@ struct Compiler
void compileLValueUse(const LValue& lv, uint8_t reg, bool set) void compileLValueUse(const LValue& lv, uint8_t reg, bool set)
{ {
if (FFlag::LuauCompileTableIndexOpt) setDebugLine(lv.location);
setDebugLine(lv.location);
switch (lv.kind) switch (lv.kind)
{ {

View file

@ -8,6 +8,7 @@ target_sources(Luau.Ast PRIVATE
Ast/include/Luau/Location.h Ast/include/Luau/Location.h
Ast/include/Luau/ParseOptions.h Ast/include/Luau/ParseOptions.h
Ast/include/Luau/Parser.h Ast/include/Luau/Parser.h
Ast/include/Luau/ParseResult.h
Ast/include/Luau/StringUtils.h Ast/include/Luau/StringUtils.h
Ast/include/Luau/TimeTrace.h Ast/include/Luau/TimeTrace.h
@ -193,11 +194,12 @@ if(TARGET Luau.Analyze.CLI)
CLI/Analyze.cpp) CLI/Analyze.cpp)
endif() endif()
if (TARGET Luau.Ast.CLI) if(TARGET Luau.Ast.CLI)
target_sources(Luau.Ast.CLI PRIVATE target_sources(Luau.Ast.CLI PRIVATE
CLI/Ast.cpp
CLI/FileUtils.h CLI/FileUtils.h
CLI/FileUtils.cpp CLI/FileUtils.cpp
CLI/Ast.cpp) )
endif() endif()
if(TARGET Luau.UnitTest) if(TARGET Luau.UnitTest)

View file

@ -178,11 +178,11 @@ LUA_API int lua_pushthread(lua_State* L);
/* /*
** get functions (Lua -> stack) ** get functions (Lua -> stack)
*/ */
LUA_API void lua_gettable(lua_State* L, int idx); LUA_API int lua_gettable(lua_State* L, int idx);
LUA_API void lua_getfield(lua_State* L, int idx, const char* k); LUA_API int lua_getfield(lua_State* L, int idx, const char* k);
LUA_API void lua_rawgetfield(lua_State* L, int idx, const char* k); LUA_API int lua_rawgetfield(lua_State* L, int idx, const char* k);
LUA_API void lua_rawget(lua_State* L, int idx); LUA_API int lua_rawget(lua_State* L, int idx);
LUA_API void lua_rawgeti(lua_State* L, int idx, int n); LUA_API int lua_rawgeti(lua_State* L, int idx, int n);
LUA_API void lua_createtable(lua_State* L, int narr, int nrec); LUA_API void lua_createtable(lua_State* L, int narr, int nrec);
LUA_API void lua_setreadonly(lua_State* L, int idx, int enabled); LUA_API void lua_setreadonly(lua_State* L, int idx, int enabled);

View file

@ -36,12 +36,9 @@ const char* luau_ident = "$Luau: Copyright (C) 2019-2022 Roblox Corporation $\n"
static Table* getcurrenv(lua_State* L) static Table* getcurrenv(lua_State* L)
{ {
if (L->ci == L->base_ci) /* no enclosing function? */ if (L->ci == L->base_ci) /* no enclosing function? */
return hvalue(gt(L)); /* use global table as environment */ return L->gt; /* use global table as environment */
else else
{ return curr_func(L)->env;
Closure* func = curr_func(L);
return func->env;
}
} }
static LUAU_NOINLINE TValue* pseudo2addr(lua_State* L, int idx) static LUAU_NOINLINE TValue* pseudo2addr(lua_State* L, int idx)
@ -53,11 +50,14 @@ static LUAU_NOINLINE TValue* pseudo2addr(lua_State* L, int idx)
return registry(L); return registry(L);
case LUA_ENVIRONINDEX: case LUA_ENVIRONINDEX:
{ {
sethvalue(L, &L->env, getcurrenv(L)); sethvalue(L, &L->global->pseudotemp, getcurrenv(L));
return &L->env; return &L->global->pseudotemp;
} }
case LUA_GLOBALSINDEX: case LUA_GLOBALSINDEX:
return gt(L); {
sethvalue(L, &L->global->pseudotemp, L->gt);
return &L->global->pseudotemp;
}
default: default:
{ {
Closure* func = curr_func(L); Closure* func = curr_func(L);
@ -237,6 +237,11 @@ void lua_replace(lua_State* L, int idx)
func->env = hvalue(L->top - 1); func->env = hvalue(L->top - 1);
luaC_barrier(L, func, L->top - 1); luaC_barrier(L, func, L->top - 1);
} }
else if (idx == LUA_GLOBALSINDEX)
{
api_check(L, ttistable(L->top - 1));
L->gt = hvalue(L->top - 1);
}
else else
{ {
setobj(L, o, L->top - 1); setobj(L, o, L->top - 1);
@ -654,16 +659,16 @@ int lua_pushthread(lua_State* L)
** get functions (Lua -> stack) ** get functions (Lua -> stack)
*/ */
void lua_gettable(lua_State* L, int idx) int lua_gettable(lua_State* L, int idx)
{ {
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
StkId t = index2addr(L, idx); StkId t = index2addr(L, idx);
api_checkvalidindex(L, t); api_checkvalidindex(L, t);
luaV_gettable(L, t, L->top - 1, L->top - 1); luaV_gettable(L, t, L->top - 1, L->top - 1);
return; return ttype(L->top - 1);
} }
void lua_getfield(lua_State* L, int idx, const char* k) int lua_getfield(lua_State* L, int idx, const char* k)
{ {
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
StkId t = index2addr(L, idx); StkId t = index2addr(L, idx);
@ -672,10 +677,10 @@ void lua_getfield(lua_State* L, int idx, const char* k)
setsvalue(L, &key, luaS_new(L, k)); setsvalue(L, &key, luaS_new(L, k));
luaV_gettable(L, t, &key, L->top); luaV_gettable(L, t, &key, L->top);
api_incr_top(L); api_incr_top(L);
return; return ttype(L->top - 1);
} }
void lua_rawgetfield(lua_State* L, int idx, const char* k) int lua_rawgetfield(lua_State* L, int idx, const char* k)
{ {
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
StkId t = index2addr(L, idx); StkId t = index2addr(L, idx);
@ -684,26 +689,26 @@ void lua_rawgetfield(lua_State* L, int idx, const char* k)
setsvalue(L, &key, luaS_new(L, k)); setsvalue(L, &key, luaS_new(L, k));
setobj2s(L, L->top, luaH_getstr(hvalue(t), tsvalue(&key))); setobj2s(L, L->top, luaH_getstr(hvalue(t), tsvalue(&key)));
api_incr_top(L); api_incr_top(L);
return; return ttype(L->top - 1);
} }
void lua_rawget(lua_State* L, int idx) int lua_rawget(lua_State* L, int idx)
{ {
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
StkId t = index2addr(L, idx); StkId t = index2addr(L, idx);
api_check(L, ttistable(t)); api_check(L, ttistable(t));
setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1)); setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1));
return; return ttype(L->top - 1);
} }
void lua_rawgeti(lua_State* L, int idx, int n) int lua_rawgeti(lua_State* L, int idx, int n)
{ {
luaC_checkthreadsleep(L); luaC_checkthreadsleep(L);
StkId t = index2addr(L, idx); StkId t = index2addr(L, idx);
api_check(L, ttistable(t)); api_check(L, ttistable(t));
setobj2s(L, L->top, luaH_getnum(hvalue(t), n)); setobj2s(L, L->top, luaH_getnum(hvalue(t), n));
api_incr_top(L); api_incr_top(L);
return; return ttype(L->top - 1);
} }
void lua_createtable(lua_State* L, int narray, int nrec) void lua_createtable(lua_State* L, int narray, int nrec)
@ -783,7 +788,7 @@ void lua_getfenv(lua_State* L, int idx)
sethvalue(L, L->top, clvalue(o)->env); sethvalue(L, L->top, clvalue(o)->env);
break; break;
case LUA_TTHREAD: case LUA_TTHREAD:
setobj2s(L, L->top, gt(thvalue(o))); sethvalue(L, L->top, thvalue(o)->gt);
break; break;
default: default:
setnilvalue(L->top); setnilvalue(L->top);
@ -914,7 +919,7 @@ int lua_setfenv(lua_State* L, int idx)
clvalue(o)->env = hvalue(L->top - 1); clvalue(o)->env = hvalue(L->top - 1);
break; break;
case LUA_TTHREAD: case LUA_TTHREAD:
sethvalue(L, gt(thvalue(o)), hvalue(L->top - 1)); thvalue(o)->gt = hvalue(L->top - 1);
break; break;
default: default:
res = 0; res = 0;

View file

@ -1098,7 +1098,7 @@ static int luauF_select(lua_State* L, StkId res, TValue* arg0, int nresults, Stk
int i = int(nvalue(arg0)); int i = int(nvalue(arg0));
// i >= 1 && i <= n // i >= 1 && i <= n
if (unsigned(i - 1) <= unsigned(n)) if (unsigned(i - 1) < unsigned(n))
{ {
setobj2s(L, res, L->base - n + (i - 1)); setobj2s(L, res, L->base - n + (i - 1));
return 1; return 1;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -88,7 +88,7 @@ static void validateclosure(global_State* g, Closure* cl)
static void validatestack(global_State* g, lua_State* l) static void validatestack(global_State* g, lua_State* l)
{ {
validateref(g, obj2gco(l), gt(l)); validateobjref(g, obj2gco(l), obj2gco(l->gt));
for (CallInfo* ci = l->base_ci; ci <= l->ci; ++ci) for (CallInfo* ci = l->base_ci; ci <= l->ci; ++ci)
{ {
@ -250,6 +250,8 @@ void luaC_validate(lua_State* L)
if (FFlag::LuauGcPagedSweep) if (FFlag::LuauGcPagedSweep)
{ {
validategco(L, NULL, obj2gco(g->mainthread));
luaM_visitgco(L, L, validategco); luaM_visitgco(L, L, validategco);
} }
else else
@ -368,6 +370,7 @@ static void dumpclosure(FILE* f, Closure* cl)
fprintf(f, ",\"env\":"); fprintf(f, ",\"env\":");
dumpref(f, obj2gco(cl->env)); dumpref(f, obj2gco(cl->env));
if (cl->isC) if (cl->isC)
{ {
if (cl->nupvalues) if (cl->nupvalues)
@ -409,11 +412,8 @@ static void dumpthread(FILE* f, lua_State* th)
fprintf(f, "{\"type\":\"thread\",\"cat\":%d,\"size\":%d", th->memcat, int(size)); fprintf(f, "{\"type\":\"thread\",\"cat\":%d,\"size\":%d", th->memcat, int(size));
if (iscollectable(&th->l_gt)) fprintf(f, ",\"env\":");
{ dumpref(f, obj2gco(th->gt));
fprintf(f, ",\"env\":");
dumpref(f, gcvalue(&th->l_gt));
}
Closure* tcl = 0; Closure* tcl = 0;
for (CallInfo* ci = th->base_ci; ci <= th->ci; ++ci) for (CallInfo* ci = th->base_ci; ci <= th->ci; ++ci)
@ -565,6 +565,8 @@ void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State*
if (FFlag::LuauGcPagedSweep) if (FFlag::LuauGcPagedSweep)
{ {
dumpgco(f, NULL, obj2gco(g->mainthread));
luaM_visitgco(L, f, dumpgco); luaM_visitgco(L, f, dumpgco);
} }
else else

View file

@ -8,6 +8,76 @@
#include <string.h> #include <string.h>
/*
* Luau heap uses a size-segregated page structure, with individual pages and large allocations
* allocated using system heap (via frealloc callback).
*
* frealloc callback serves as a general, if slow, allocation callback that can allocate, free or
* resize allocations:
*
* void* frealloc(void* ud, void* ptr, size_t oldsize, size_t newsize);
*
* frealloc(ud, NULL, 0, x) creates a new block of size x
* frealloc(ud, p, x, 0) frees the block p (must return NULL)
* frealloc(ud, NULL, 0, 0) does nothing, equivalent to free(NULL)
*
* frealloc returns NULL if it cannot create or reallocate the area
* (any reallocation to an equal or smaller size cannot fail!)
*
* On top of this, Luau implements heap storage which is split into two types of allocations:
*
* - GCO, short for "garbage collected objects"
* - other objects (for example, arrays stored inside table objects)
*
* The heap layout for these two allocation types is a bit different.
*
* All GCO are allocated in pages, which is a block of memory of ~16K in size that has a page header
* (lua_Page). Each page contains 1..N blocks of the same size, where N is selected to fill the page
* completely. This amortizes the allocation cost and increases locality. Each GCO block starts with
* the GC header (GCheader) which contains the object type, mark bits and other GC metadata. If the
* GCO block is free (not used), then it must have the type set to TNIL; in this case the block can
* be part of the per-page free list, the link for that list is stored after the header (freegcolink).
*
* Importantly, the GCO block doesn't have any back references to the page it's allocated in, so it's
* impossible to free it in isolation - GCO blocks are freed by sweeping the pages they belong to,
* using luaM_freegco which must specify the page; this is called by page sweeper that traverses the
* entire page's worth of objects. For this reason it's also important that freed GCO blocks keep the
* GC header intact and accessible (with type = NIL) so that the sweeper can access it.
*
* Some GCOs are too large to fit in a 16K page without excessive fragmentation (the size threshold is
* currently 512 bytes); in this case, we allocate a dedicated small page with just a single block's worth
* storage space, but that requires allocating an extra page header. In effect large GCOs are a little bit
* less memory efficient, but this allows us to uniformly sweep small and large GCOs using page lists.
*
* All GCO pages are linked in a large intrusive linked list (global_State::allgcopages). Additionally,
* for each block size there's a page free list that contains pages that have at least one free block
* (global_State::freegcopages). This free list is used to make sure object allocation is O(1).
*
* Compared to GCOs, regular allocations have two important differences: they can be freed in isolation,
* and they don't start with a GC header. Because of this, each allocation is prefixed with block metadata,
* which contains the pointer to the page for allocated blocks, and the pointer to the next free block
* inside the page for freed blocks.
* For regular allocations that are too large to fit in a page (using the same threshold of 512 bytes),
* we don't allocate a separate page, instead simply using frealloc to allocate a vanilla block of memory.
*
* Just like GCO pages, we store a page free list (global_State::freepages) that allows O(1) allocation;
* there is no global list for non-GCO pages since we never need to traverse them directly.
*
* In both cases, we pick the page by computing the size class from the block size which rounds the block
* size up to reduce the chance that we'll allocate pages that have very few allocated blocks. The size
* class strategy is determined by SizeClassConfig constructor.
*
* Note that when the last block in a page is freed, we immediately free the page with frealloc - the
* memory manager doesn't currently attempt to keep unused memory around. This can result in excessive
* allocation traffic and can be mitigated by adding a page cache in the future.
*
* For both GCO and non-GCO pages, the per-page block allocation combines bump pointer style allocation
* (lua_Page::freeNext) and per-page free list (lua_Page::freeList). We use the bump allocator to allocate
* the contents of the page, and the free list for further reuse; this allows shorter page setup times
* which results in less variance between allocation cost, as well as tighter sweep bounds for newly
* allocated pages.
*/
LUAU_FASTFLAG(LuauGcPagedSweep) LUAU_FASTFLAG(LuauGcPagedSweep)
#ifndef __has_feature #ifndef __has_feature
@ -56,6 +126,7 @@ static_assert(offsetof(GCObject, ts) == 0, "TString data must be located at the
const size_t kSizeClasses = LUA_SIZECLASSES; const size_t kSizeClasses = LUA_SIZECLASSES;
const size_t kMaxSmallSize = 512; const size_t kMaxSmallSize = 512;
const size_t kPageSize = 16 * 1024 - 24; // slightly under 16KB since that results in less fragmentation due to heap metadata const size_t kPageSize = 16 * 1024 - 24; // slightly under 16KB since that results in less fragmentation due to heap metadata
const size_t kBlockHeader = sizeof(double) > sizeof(void*) ? sizeof(double) : sizeof(void*); // suitable for aligning double & void* on all platforms const size_t kBlockHeader = sizeof(double) > sizeof(void*) ? sizeof(double) : sizeof(void*); // suitable for aligning double & void* on all platforms
const size_t kGCOLinkOffset = (sizeof(GCheader) + sizeof(void*) - 1) & ~(sizeof(void*) - 1); // GCO pages contain freelist links after the GC header const size_t kGCOLinkOffset = (sizeof(GCheader) + sizeof(void*) - 1) & ~(sizeof(void*) - 1); // GCO pages contain freelist links after the GC header
@ -107,24 +178,6 @@ const SizeClassConfig kSizeClassConfig;
#define metadata(block) (*(void**)(block)) #define metadata(block) (*(void**)(block))
#define freegcolink(block) (*(void**)((char*)block + kGCOLinkOffset)) #define freegcolink(block) (*(void**)((char*)block + kGCOLinkOffset))
/*
** About the realloc function:
** void * frealloc (void *ud, void *ptr, size_t osize, size_t nsize);
** (`osize' is the old size, `nsize' is the new size)
**
** Lua ensures that (ptr == NULL) iff (osize == 0).
**
** * frealloc(ud, NULL, 0, x) creates a new block of size `x'
**
** * frealloc(ud, p, x, 0) frees the block `p'
** (in this specific case, frealloc must return NULL).
** particularly, frealloc(ud, NULL, 0, 0) does nothing
** (which is equivalent to free(NULL) in ANSI C)
**
** frealloc returns NULL if it cannot create or reallocate the area
** (any reallocation to an equal or smaller size cannot fail!)
*/
struct lua_Page struct lua_Page
{ {
// list of pages with free blocks // list of pages with free blocks
@ -135,13 +188,12 @@ struct lua_Page
lua_Page* gcolistprev; lua_Page* gcolistprev;
lua_Page* gcolistnext; lua_Page* gcolistnext;
int busyBlocks; int pageSize; // page size in bytes, including page header
int blockSize; int blockSize; // block size in bytes, including block header (for non-GCO)
void* freeList; void* freeList; // next free block in this page; linked with metadata()/freegcolink()
int freeNext; int freeNext; // next free block offset in this page, in bytes; when negative, freeList is used instead
int busyBlocks; // number of blocks allocated out of this page
int pageSize;
union union
{ {
@ -177,7 +229,7 @@ static lua_Page* newpageold(lua_State* L, uint8_t sizeClass)
page->gcolistprev = NULL; page->gcolistprev = NULL;
page->gcolistnext = NULL; page->gcolistnext = NULL;
page->busyBlocks = 0; page->pageSize = kPageSize;
page->blockSize = blockSize; page->blockSize = blockSize;
// note: we start with the last block in the page and move downward // note: we start with the last block in the page and move downward
@ -185,6 +237,7 @@ static lua_Page* newpageold(lua_State* L, uint8_t sizeClass)
// additionally, GC stores objects in singly linked lists, and this way the GC lists end up in increasing pointer order // additionally, GC stores objects in singly linked lists, and this way the GC lists end up in increasing pointer order
page->freeList = NULL; page->freeList = NULL;
page->freeNext = (blockCount - 1) * blockSize; page->freeNext = (blockCount - 1) * blockSize;
page->busyBlocks = 0;
// prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!) // prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!)
LUAU_ASSERT(!g->freepages[sizeClass]); LUAU_ASSERT(!g->freepages[sizeClass]);
@ -214,7 +267,7 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int
page->gcolistprev = NULL; page->gcolistprev = NULL;
page->gcolistnext = NULL; page->gcolistnext = NULL;
page->busyBlocks = 0; page->pageSize = pageSize;
page->blockSize = blockSize; page->blockSize = blockSize;
// note: we start with the last block in the page and move downward // note: we start with the last block in the page and move downward
@ -222,8 +275,7 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int
// additionally, GC stores objects in singly linked lists, and this way the GC lists end up in increasing pointer order // additionally, GC stores objects in singly linked lists, and this way the GC lists end up in increasing pointer order
page->freeList = NULL; page->freeList = NULL;
page->freeNext = (blockCount - 1) * blockSize; page->freeNext = (blockCount - 1) * blockSize;
page->busyBlocks = 0;
page->pageSize = pageSize;
if (gcopageset) if (gcopageset)
{ {
@ -406,8 +458,7 @@ static void* newgcoblock(lua_State* L, int sizeClass)
page->next = NULL; page->next = NULL;
} }
// the user data is right after the metadata return block;
return (char*)block;
} }
static void freeblock(lua_State* L, int sizeClass, void* block) static void freeblock(lua_State* L, int sizeClass, void* block)
@ -421,6 +472,7 @@ static void freeblock(lua_State* L, int sizeClass, void* block)
lua_Page* page = (lua_Page*)metadata(block); lua_Page* page = (lua_Page*)metadata(block);
LUAU_ASSERT(page && page->busyBlocks > 0); LUAU_ASSERT(page && page->busyBlocks > 0);
LUAU_ASSERT(size_t(page->blockSize) == kSizeClassConfig.sizeOfClass[sizeClass] + kBlockHeader); LUAU_ASSERT(size_t(page->blockSize) == kSizeClassConfig.sizeOfClass[sizeClass] + kBlockHeader);
LUAU_ASSERT(block >= page->data && block < (char*)page + page->pageSize);
// if the page wasn't in the page free list, it should be now since it got a block! // if the page wasn't in the page free list, it should be now since it got a block!
if (!page->freeList && page->freeNext < 0) if (!page->freeList && page->freeNext < 0)
@ -455,6 +507,9 @@ static void freeblock(lua_State* L, int sizeClass, void* block)
static void freegcoblock(lua_State* L, int sizeClass, void* block, lua_Page* page) static void freegcoblock(lua_State* L, int sizeClass, void* block, lua_Page* page)
{ {
LUAU_ASSERT(FFlag::LuauGcPagedSweep); LUAU_ASSERT(FFlag::LuauGcPagedSweep);
LUAU_ASSERT(page && page->busyBlocks > 0);
LUAU_ASSERT(page->blockSize == kSizeClassConfig.sizeOfClass[sizeClass]);
LUAU_ASSERT(block >= page->data && block < (char*)page + page->pageSize);
global_State* g = L->global; global_State* g = L->global;
@ -575,6 +630,8 @@ void luaM_freegco_(lua_State* L, GCObject* block, size_t osize, uint8_t memcat,
else else
{ {
LUAU_ASSERT(page->busyBlocks == 1); LUAU_ASSERT(page->busyBlocks == 1);
LUAU_ASSERT(size_t(page->blockSize) == osize);
LUAU_ASSERT((void*)block == page->data);
freepage(L, &g->allgcopages, page); freepage(L, &g->allgcopages, page);
} }
@ -626,8 +683,12 @@ void luaM_getpagewalkinfo(lua_Page* page, char** start, char** end, int* busyBlo
int blockCount = (page->pageSize - offsetof(lua_Page, data)) / page->blockSize; int blockCount = (page->pageSize - offsetof(lua_Page, data)) / page->blockSize;
*start = page->data + page->freeNext + page->blockSize; LUAU_ASSERT(page->freeNext >= -page->blockSize && page->freeNext <= (blockCount - 1) * page->blockSize);
*end = page->data + blockCount * page->blockSize;
char* data = page->data; // silences ubsan when indexing page->data
*start = data + page->freeNext + page->blockSize;
*end = data + blockCount * page->blockSize;
*busyBlocks = page->busyBlocks; *busyBlocks = page->busyBlocks;
*blockSize = page->blockSize; *blockSize = page->blockSize;
} }
@ -675,7 +736,7 @@ void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, l
for (lua_Page* curr = g->allgcopages; curr;) for (lua_Page* curr = g->allgcopages; curr;)
{ {
lua_Page* next = curr->gcolistnext; // page blockvisit might destroy the page lua_Page* next = curr->gcolistnext; // block visit might destroy the page
luaM_visitpage(curr, context, visitor); luaM_visitpage(curr, context, visitor);

View file

@ -131,7 +131,7 @@ void luaO_chunkid(char* out, const char* source, size_t bufflen)
{ {
size_t l; size_t l;
source++; /* skip the `@' */ source++; /* skip the `@' */
bufflen -= sizeof(" '...' "); bufflen -= sizeof("...");
l = strlen(source); l = strlen(source);
strcpy(out, ""); strcpy(out, "");
if (l > bufflen) if (l > bufflen)
@ -144,7 +144,7 @@ void luaO_chunkid(char* out, const char* source, size_t bufflen)
else else
{ /* out = [string "string"] */ { /* out = [string "string"] */
size_t len = strcspn(source, "\n\r"); /* stop at first newline */ size_t len = strcspn(source, "\n\r"); /* stop at first newline */
bufflen -= sizeof(" [string \"...\"] "); bufflen -= sizeof("[string \"...\"]");
if (len > bufflen) if (len > bufflen)
len = bufflen; len = bufflen;
strcpy(out, "[string \""); strcpy(out, "[string \"");

View file

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

View file

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

View file

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

View file

@ -39,7 +39,7 @@
// When calling luau_callTM, we usually push the arguments to the top of the stack. // When calling luau_callTM, we usually push the arguments to the top of the stack.
// This is safe to do for complicated reasons: // This is safe to do for complicated reasons:
// - stack guarantees 1 + EXTRA_STACK room beyond stack_last (see luaD_reallocstack) // - stack guarantees EXTRA_STACK room beyond stack_last (see luaD_reallocstack)
// - stack reallocation copies values past stack_last // - stack reallocation copies values past stack_last
// All external function calls that can cause stack realloc or Lua calls have to be wrapped in VM_PROTECT // All external function calls that can cause stack realloc or Lua calls have to be wrapped in VM_PROTECT
@ -609,7 +609,8 @@ static void luau_execute(lua_State* L)
if (unsigned(ic) < LUA_VECTOR_SIZE && name[1] == '\0') if (unsigned(ic) < LUA_VECTOR_SIZE && name[1] == '\0')
{ {
setnvalue(ra, rb->value.v[ic]); const float* v = rb->value.v; // silences ubsan when indexing v[]
setnvalue(ra, v[ic]);
VM_NEXT(); VM_NEXT();
} }

View file

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

View file

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

View file

@ -68,8 +68,8 @@ SingletonType = STRING | 'true' | 'false'
Type = Type =
SimpleType ['?'] | SimpleType ['?'] |
SimpleType ['|' Type] | Type ['|' Type] |
SimpleType ['&' Type] Type ['&' Type]
GenericTypePackParameter = NAME '...' ['=' (TypePack | VariadicTypePack | GenericTypePack)] GenericTypePackParameter = NAME '...' ['=' (TypePack | VariadicTypePack | GenericTypePack)]
GenericTypeParameterList = NAME ['=' Type] [',' GenericTypeParameterList] | GenericTypePackParameter {',' GenericTypePackParameter} GenericTypeParameterList = NAME ['=' Type] [',' GenericTypeParameterList] | GenericTypePackParameter {',' GenericTypePackParameter}

View file

@ -128,7 +128,8 @@ Returns the type of the object, which is one of `"nil"`, `"boolean"`, `"number"`
function typeof(obj: any): string function typeof(obj: any): string
``` ```
Returns the type of the object; for userdata objects that have a metatable with the `__type` field, returns the value for that key. Returns the type of the object; for userdata objects that have a metatable with the `__type` field *and* are defined by the host with an internal tag, returns the value for that key.
For custom userdata objects, such as ones returned by `newproxy`, this function returns `"userdata"` to make sure host-defined types can not be spoofed.
``` ```
function ipairs(t: table): <iterator> function ipairs(t: table): <iterator>

View file

@ -294,6 +294,7 @@ table.insert(t, 0, 42) -- table.insert uses index 0 but arrays are 1-based; did
table.insert(t, #t+1, 42) -- table.insert will append the value to the table; consider removing the second argument for efficiency table.insert(t, #t+1, 42) -- table.insert will append the value to the table; consider removing the second argument for efficiency
``` ```
## DuplicateCondition (24) ## DuplicateCondition (24)
When checking multiple conditions via `and/or` or `if/elseif`, a copy & paste error may result in checking the same condition redundantly. This almost always indicates a bug, so a warning is emitted when use of a duplicate condition is detected. When checking multiple conditions via `and/or` or `if/elseif`, a copy & paste error may result in checking the same condition redundantly. This almost always indicates a bug, so a warning is emitted when use of a duplicate condition is detected.
@ -301,3 +302,28 @@ When checking multiple conditions via `and/or` or `if/elseif`, a copy & paste er
```lua ```lua
assert(self._adorns[normID1] and self._adorns[normID1]) -- Condition has already been checked on column 8 assert(self._adorns[normID1] and self._adorns[normID1]) -- Condition has already been checked on column 8
``` ```
## MisleadingAndOr (25)
In Lua, there is no first-class ternary operator but it can be emulated via `a and b or c` pattern. However, due to how boolean evaluation works, if `b` is `false` or `nil`, the resulting expression evaluates to `c` regardless of the value of `a`. Luau solves this problem with the `if a then b else c` expression; a warning is emitted for and-or expressions where the first alternative is `false` or `nil` because it's almost always a bug.
```lua
-- The and-or expression always evaluates to the second alternative because the first alternative is false; consider using if-then-else expression instead
local x = flag and false or true
```
The code above can be rewritten as follows to avoid the warning and the associated bug:
```lua
local x = if flag then false else true
```
## CommentDirective (26)
Luau uses comments that start from `!` to control certain aspects of analysis, for example setting type checking mode via `--!strict` or disabling individual lints with `--!nolint`. Unknown directives are ignored, for example `--!nostrict` doesn't have any effect on the type checking process as the correct spelling is `--!nonstrict`. This warning flags comment directives that are ignored during processing:
```lua
--!nostrict
-- Unknown comment directive 'nostrict'; did you mean 'nonstrict'?"
```
```

View file

@ -167,3 +167,11 @@ While Luau uses an incremental garbage collector, once per each collector cycle
Normally objects that have been modified after the GC marked them in an incremental mark phase need to be rescanned during atomic phase, so frequent modifications of existing tables may result in a slow atomic step. To address this, we run a "remark" step where we traverse objects that have been modified after being marked once more (incrementally); additionally, the write barrier that triggers for object modifications changes the transition logic during remark phase to reduce the probability that the object will need to be rescanned. Normally objects that have been modified after the GC marked them in an incremental mark phase need to be rescanned during atomic phase, so frequent modifications of existing tables may result in a slow atomic step. To address this, we run a "remark" step where we traverse objects that have been modified after being marked once more (incrementally); additionally, the write barrier that triggers for object modifications changes the transition logic during remark phase to reduce the probability that the object will need to be rescanned.
Another source of scalability challenges is coroutines. Writes to coroutine stacks don't use a write barrier, since that's prohibitively expensive as they are too frequent. This means that coroutine stacks need to be traversed during atomic step, so applications with many coroutines suffer large atomic pauses. To address this, we implement incremental marking of coroutines: marking a coroutine makes it "inactive" and resuming a coroutine (or pushing extra objects on the coroutine stack via C API) makes it "active". Atomic step only needs to traverse active coroutines again, which reduces the cost of atomic step by effectively making coroutine collection incremental as well. Another source of scalability challenges is coroutines. Writes to coroutine stacks don't use a write barrier, since that's prohibitively expensive as they are too frequent. This means that coroutine stacks need to be traversed during atomic step, so applications with many coroutines suffer large atomic pauses. To address this, we implement incremental marking of coroutines: marking a coroutine makes it "inactive" and resuming a coroutine (or pushing extra objects on the coroutine stack via C API) makes it "active". Atomic step only needs to traverse active coroutines again, which reduces the cost of atomic step by effectively making coroutine collection incremental as well.
While large tables can be a problem for incremental GC in general since currently marking a single object is indivisible, large weak tables are a unique challenge because they also need to be processed during atomic phase, and the main use case for weak tables - object caches - may result in tables with large capacity but few live objects in long-running applications that exhibit bursts of activity. To address this, weak tables in Luau can be marked as "shrinkable" by including `s` as part of `__mode` string, which results in weak tables being resized to the optimal capacity during GC. This option may result in missing keys during table iteration if the table is resized while iteration is in progress and as such is only recommended for use in specific circumstances.
## Optimized garbage collector sweeping
The incremental garbage collector in Luau runs three phases for each cycle: mark, atomic and sweep. Mark incrementally traverses all live objects, atomic finishes various operations that need to happen without mutator intervention (see previous section), and sweep traverses all objects in the heap, reclaiming memory used by dead objects and performing minor fixup for live objects. While objects allocated during the mark phase are traversed in the same cycle and thus may get reclaimed, objects allocated during the sweep phase are considered live. Because of this, the faster the sweep phase completes, the less garbage will accumulate; and, of course, the less time sweeping takes the less overhead there is from this phase of garbage collection on the process.
Since sweeping traverses the whole heap, we maximize the efficiency of this traversal by allocating garbage-collected objects of the same size in 16 KB pages, and traversing each page at a time, which is otherwise known as a paged sweeper. This ensures good locality of reference as consecutively swept objects are contiugous in memory, and allows us to spend no memory for each object on sweep-related data or allocation metadata, since paged sweeper doesn't need to be able to free objects without knowing which page they are in. Compared to linked list based sweeping that Lua/LuaJIT implement, paged sweeper is 2-3x faster, and saves 16 bytes per object on 64-bit platforms.

View file

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

View file

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

View file

@ -7,3 +7,4 @@ Interpreter
!Tests/Interpreter !Tests/Interpreter
!Tests/PrettyPrinter !Tests/PrettyPrinter
.ghc.* .ghc.*
test-failures/

View file

@ -5,7 +5,7 @@ module Examples.OpSem where
open import Luau.OpSem using (_⊢_⟶ᴱ_⊣_; _⊢_⟶ᴮ_⊣_; subst) open import Luau.OpSem using (_⊢_⟶ᴱ_⊣_; _⊢_⟶ᴮ_⊣_; subst)
open import Luau.Syntax using (Block; var; nil; local_←_; _∙_; done; return; block_is_end) open import Luau.Syntax using (Block; var; nil; local_←_; _∙_; done; return; block_is_end)
open import Luau.Heap using () open import Luau.Heap using ()
open import Luau.Value using (nil)
ex1 : (local (var "x") nil return (var "x") done) ⟶ᴮ (return nil done) ex1 : (local (var "x") nil return (var "x") done) ⟶ᴮ (return nil done)
ex1 = subst ex1 = subst nil

View file

@ -1,13 +1,18 @@
{-# OPTIONS --rewriting #-} {-# OPTIONS --rewriting #-}
{-# OPTIONS --allow-unsolved-metas #-}
module Examples.Run where module Examples.Run where
open import Agda.Builtin.Equality using (_≡_; refl) open import Agda.Builtin.Equality using (_≡_; refl)
open import Luau.Syntax using (nil; var; _$_; function_is_end; return; _∙_; done; _⟨_⟩) open import Luau.Syntax using (nil; var; _$_; function_is_end; return; _∙_; done; _⟨_⟩; number; binexp; +)
open import Luau.Value using (nil) open import Luau.Value using (nil; number)
open import Luau.Run using (run; return) open import Luau.Run using (run; return)
import Agda.Builtin.Equality.Rewrite
ex1 : (run (function "id" var "x" is return (var "x") done end return (var "id" $ nil) done) return nil _) ex1 : (run (function "id" var "x" is return (var "x") done end return (var "id" $ nil) done) return nil _)
ex1 = refl ex1 = {!!}
ex2 : (run (function "fn" var "x" is return (number 123.0) done end return (var "fn" $ nil) done) return (number 123.0) _)
ex2 = {!!}
ex3 : (run (function "fn" var "x" is return (binexp (number 1.0) + (number 2.0)) done end return (var "fn" $ nil) done) return (number 3.0) _)
ex3 = {!!}

View file

@ -6,7 +6,7 @@ open import Luau.Syntax using (var; _$_; return; nil; function_is_end; local_←
open import Luau.Syntax.ToString using (exprToString; blockToString) open import Luau.Syntax.ToString using (exprToString; blockToString)
ex1 : exprToString(function "" var "x" is return (var "f" $ var "x") done end) ex1 : exprToString(function "" var "x" is return (var "f" $ var "x") done end)
"function (x)\n" ++ "function(x)\n" ++
" return f(x)\n" ++ " return f(x)\n" ++
"end" "end"
ex1 = refl ex1 = refl

View file

@ -1,3 +1,5 @@
{-# OPTIONS --allow-unsolved-metas #-}
module Examples.Type where module Examples.Type where
open import Agda.Builtin.Equality using (_≡_; refl) open import Agda.Builtin.Equality using (_≡_; refl)
@ -19,3 +21,10 @@ ex4 = refl
ex5 : typeToString(nil ((nil nil) nil)) "(nil) -> ((nil) -> nil)?" ex5 : typeToString(nil ((nil nil) nil)) "(nil) -> ((nil) -> nil)?"
ex5 = refl ex5 = refl
ex6 : typeToString((nil nil) (nil (nil nil))) "((nil) -> nil | (nil) -> (nil) -> nil)"
ex6 = refl
ex7 : typeToString((nil nil) ((nil (nil nil)) nil)) "((nil) -> nil | (nil) -> (nil) -> nil)?"
ex7 = {!!}

View file

@ -1,6 +1,21 @@
module FFI.Data.Scientific where module FFI.Data.Scientific where
open import Agda.Builtin.Float using (Float)
open import FFI.Data.String using (String)
open import FFI.Data.HaskellString using (HaskellString; pack; unpack)
{-# FOREIGN GHC import qualified Data.Scientific #-} {-# FOREIGN GHC import qualified Data.Scientific #-}
{-# FOREIGN GHC import qualified Text.Show #-}
postulate Scientific : Set postulate Scientific : Set
{-# COMPILE GHC Scientific = type Data.Scientific.Scientific #-} {-# COMPILE GHC Scientific = type Data.Scientific.Scientific #-}
postulate
showHaskell : Scientific HaskellString
toFloat : Scientific Float
{-# COMPILE GHC showHaskell = \x -> Text.Show.show x #-}
{-# COMPILE GHC toFloat = \x -> Data.Scientific.toRealFloat x #-}
show : Scientific String
show x = pack (showHaskell x)

View file

@ -38,4 +38,3 @@ runString txt | (Right value) = runJSON value
main : IO main : IO
main = getContents >>= runString main = getContents >>= runString

View file

@ -47,4 +47,3 @@ allocated = snoc
lookup-not-allocated : {a} {H H : Heap a} {b c O} (H H b O) (c b) (H [ c ] H [ c ]) lookup-not-allocated : {a} {H H : Heap a} {b c O} (H H b O) (c b) (H [ c ] H [ c ])
lookup-not-allocated {H = H} {O = O} defn p = lookup-snoc-not O H p lookup-not-allocated {H = H} {O = O} defn p = lookup-snoc-not O H p

View file

@ -3,19 +3,25 @@
module Luau.OpSem where module Luau.OpSem where
open import Agda.Builtin.Equality using (_≡_) open import Agda.Builtin.Equality using (_≡_)
open import Agda.Builtin.Float using (Float; primFloatPlus; primFloatMinus; primFloatTimes; primFloatDiv)
open import FFI.Data.Maybe using (just) open import FFI.Data.Maybe using (just)
open import Luau.Heap using (Heap; _≡_⊕_↦_; _[_]; function_is_end) open import Luau.Heap using (Heap; _≡_⊕_↦_; _[_]; function_is_end)
open import Luau.Substitution using (_[_/_]ᴮ) open import Luau.Substitution using (_[_/_]ᴮ)
open import Luau.Syntax using (Expr; Stat; Block; nil; addr; var; function_is_end; _$_; block_is_end; local_←_; _∙_; done; return; name; fun; arg) open import Luau.Syntax using (Expr; Stat; Block; nil; addr; var; function_is_end; _$_; block_is_end; local_←_; _∙_; done; return; name; fun; arg; binexp; BinaryOperator; +; -; *; /; number)
open import Luau.Value using (addr; val) open import Luau.Value using (addr; val; number)
open import Luau.Type using (Type)
evalBinOp : Float BinaryOperator Float Float
evalBinOp x + y = primFloatPlus x y
evalBinOp x - y = primFloatMinus x y
evalBinOp x * y = primFloatTimes x y
evalBinOp x / y = primFloatDiv x y
data _⊢_⟶ᴮ_⊣_ {a} : Heap a Block a Block a Heap a Set data _⊢_⟶ᴮ_⊣_ {a} : Heap a Block a Block a Heap a Set
data _⊢_⟶ᴱ_⊣_ {a} : Heap a Expr a Expr a Heap a Set data _⊢_⟶ᴱ_⊣_ {a} : Heap a Expr a Expr a Heap a Set
data _⊢_⟶ᴱ_⊣_ where data _⊢_⟶ᴱ_⊣_ where
function : {H H a F B} function : a {H H F B}
H H a (function F is B end) H H a (function F is B end)
------------------------------------------- -------------------------------------------
@ -27,19 +33,20 @@ data _⊢_⟶ᴱ_⊣_ where
----------------------------- -----------------------------
H (M $ N) ⟶ᴱ (M $ N) H H (M $ N) ⟶ᴱ (M $ N) H
app₂ : {H H M N N V} app₂ : {H H M V N N}
(M val V) M val V
H N ⟶ᴱ N H H N ⟶ᴱ N H
----------------------------- -----------------------------
H (M $ N) ⟶ᴱ (M $ N) H H (M $ N) ⟶ᴱ (M $ N) H
beta : {H a N F B V} beta : O v {H a N F B}
H [ a ] just(function F is B end) (O function F is B end)
N val V (N val v)
H [ a ] just(O)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
H (addr a $ N) ⟶ᴱ (block (fun F) is (B [ V / name(arg F) ]ᴮ) end) H H (addr a $ N) ⟶ᴱ (block (fun F) is (B [ v / name(arg F) ]ᴮ) end) H
block : {H H B B b} block : {H H B B b}
@ -47,10 +54,10 @@ data _⊢_⟶ᴱ_⊣_ where
---------------------------------------------------- ----------------------------------------------------
H (block b is B end) ⟶ᴱ (block b is B end) H H (block b is B end) ⟶ᴱ (block b is B end) H
return : {H M V B b} return : v {H M B b}
M val V (M val v)
-------------------------------------------- --------------------------------------------------------
H (block b is return M B end) ⟶ᴱ M H H (block b is return M B end) ⟶ᴱ M H
done : {H b} done : {H b}
@ -58,6 +65,23 @@ data _⊢_⟶ᴱ_⊣_ where
--------------------------------- ---------------------------------
H (block b is done end) ⟶ᴱ nil H H (block b is done end) ⟶ᴱ nil H
binOpEval : {H x op y}
--------------------------------------------------------------------------
H (binexp (number x) op (number y)) ⟶ᴱ (number (evalBinOp x op y)) H
binOp₁ : {H H x x op y}
H x ⟶ᴱ x H
---------------------------------------------
H (binexp x op y) ⟶ᴱ (binexp x op y) H
binOp₂ : {H H x op y y}
H y ⟶ᴱ y H
---------------------------------------------
H (binexp x op y) ⟶ᴱ (binexp x op y) H
data _⊢_⟶ᴮ_⊣_ where data _⊢_⟶ᴮ_⊣_ where
local : {H H x M M B} local : {H H x M M B}
@ -66,12 +90,12 @@ data _⊢_⟶ᴮ_⊣_ where
------------------------------------------------- -------------------------------------------------
H (local x M B) ⟶ᴮ (local x M B) H H (local x M B) ⟶ᴮ (local x M B) H
subst : {H x v B} subst : v {H x B}
------------------------------------------------------ ------------------------------------------------------
H (local x val v B) ⟶ᴮ (B [ v / name x ]ᴮ) H H (local x val v B) ⟶ᴮ (B [ v / name x ]ᴮ) H
function : {H H a F B C} function : a {H H F B C}
H H a (function F is C end) H H a (function F is C end)
-------------------------------------------------------------- --------------------------------------------------------------
@ -95,5 +119,3 @@ data _⊢_⟶*_⊣_ {a} : Heap a → Block a → Block a → Heap a → Set wher
H B ⟶* B″ H″ H B ⟶* B″ H″
------------------ ------------------
H B ⟶* B″ H″ H B ⟶* B″ H″

View file

@ -5,21 +5,27 @@ module Luau.RuntimeError where
open import Agda.Builtin.Equality using (_≡_) open import Agda.Builtin.Equality using (_≡_)
open import Luau.Heap using (Heap; _[_]) open import Luau.Heap using (Heap; _[_])
open import FFI.Data.Maybe using (just; nothing) open import FFI.Data.Maybe using (just; nothing)
open import Luau.Syntax using (Block; Expr; nil; var; addr; block_is_end; _$_; local_←_; return; done; _∙_) open import FFI.Data.String using (String)
open import Luau.Syntax using (Block; Expr; nil; var; addr; block_is_end; _$_; local_←_; return; done; _∙_; number; binexp)
open import Luau.RuntimeType using (RuntimeType; valueType; function; number)
open import Luau.Value using (val) open import Luau.Value using (val)
open import Properties.Equality using (_≢_)
data RuntimeErrorᴮ {a} (H : Heap a) : Block a Set data RuntimeErrorᴮ {a} (H : Heap a) : Block a Set
data RuntimeErrorᴱ {a} (H : Heap a) : Expr a Set data RuntimeErrorᴱ {a} (H : Heap a) : Expr a Set
data RuntimeErrorᴱ H where data RuntimeErrorᴱ H where
NilIsNotAFunction : {V} RuntimeErrorᴱ H (nil $ val V) FunctionMismatch : v w {M N} (M val v) (N val w) (function valueType v) RuntimeErrorᴱ H (M $ N)
BinopMismatch₁ : op v w {M N} (M val v) (N val w) (number valueType v) RuntimeErrorᴱ H (binexp M op N)
BinopMismatch₂ : op v w {M N} (M val v) (N val w) (number valueType w) RuntimeErrorᴱ H (binexp M op N)
UnboundVariable : x RuntimeErrorᴱ H (var x) UnboundVariable : x RuntimeErrorᴱ H (var x)
SEGV : a (H [ a ] nothing) RuntimeErrorᴱ H (addr a) SEGV : a (H [ a ] nothing) RuntimeErrorᴱ H (addr a)
app₁ : {M N} RuntimeErrorᴱ H M RuntimeErrorᴱ H (M $ N) app₁ : {M N} RuntimeErrorᴱ H M RuntimeErrorᴱ H (M $ N)
app₂ : {M N} RuntimeErrorᴱ H N RuntimeErrorᴱ H (M $ N) app₂ : {M N} RuntimeErrorᴱ H N RuntimeErrorᴱ H (M $ N)
block : b {B} RuntimeErrorᴮ H B RuntimeErrorᴱ H (block b is B end) block : b {B} RuntimeErrorᴮ H B RuntimeErrorᴱ H (block b is B end)
bin₁ : {M N op} RuntimeErrorᴱ H M RuntimeErrorᴱ H (binexp M op N)
bin₂ : {M N op} RuntimeErrorᴱ H N RuntimeErrorᴱ H (binexp M op N)
data RuntimeErrorᴮ H where data RuntimeErrorᴮ H where
local : x {M B} RuntimeErrorᴱ H M RuntimeErrorᴮ H (local x M B) local : x {M B} RuntimeErrorᴱ H M RuntimeErrorᴮ H (local x M B)
return : {M B} RuntimeErrorᴱ H M RuntimeErrorᴮ H (return M B) return : {M B} RuntimeErrorᴱ H M RuntimeErrorᴮ H (return M B)

View file

@ -1,22 +1,30 @@
{-# OPTIONS --rewriting #-} {-# OPTIONS --rewriting #-}
{-# OPTIONS --allow-unsolved-metas #-}
module Luau.RuntimeError.ToString where module Luau.RuntimeError.ToString where
open import Agda.Builtin.Float using (primShowFloat)
open import FFI.Data.String using (String; _++_) open import FFI.Data.String using (String; _++_)
open import Luau.RuntimeError using (RuntimeErrorᴮ; RuntimeErrorᴱ; local; return; NilIsNotAFunction; UnboundVariable; SEGV; app₁; app₂; block) open import Luau.RuntimeError using (RuntimeErrorᴮ; RuntimeErrorᴱ; local; return; TypeMismatch; UnboundVariable; SEGV; app₁; app₂; block; bin₁; bin₂)
open import Luau.RuntimeType.ToString using (runtimeTypeToString)
open import Luau.Addr.ToString using (addrToString) open import Luau.Addr.ToString using (addrToString)
open import Luau.Syntax.ToString using (exprToString)
open import Luau.Var.ToString using (varToString) open import Luau.Var.ToString using (varToString)
open import Luau.Syntax using (name) open import Luau.Value.ToString using (valueToString)
open import Luau.Syntax using (name; _$_)
errToStringᴱ : {a H B} RuntimeErrorᴱ {a} H B String errToStringᴱ : {a H B} RuntimeErrorᴱ {a} H B String
errToStringᴮ : {a H B} RuntimeErrorᴮ {a} H B String errToStringᴮ : {a H B} RuntimeErrorᴮ {a} H B String
errToStringᴱ (NilIsNotAFunction) = "nil is not a function" -- errToStringᴱ (UnboundVariable x) = "variable " ++ varToString x ++ " is unbound"
errToStringᴱ (UnboundVariable x) = "variable " ++ varToString x ++ " is unbound" -- errToStringᴱ (SEGV a x) = "address " ++ addrToString a ++ " is unallocated"
errToStringᴱ (SEGV a x) = "address " ++ addrToString a ++ " is unallocated" -- errToStringᴱ (app₁ E) = errToStringᴱ E
errToStringᴱ (app₁ E) = errToStringᴱ E -- errToStringᴱ (app₂ E) = errToStringᴱ E
errToStringᴱ (app₂ E) = errToStringᴱ E -- errToStringᴱ (bin₁ E) = errToStringᴱ E
errToStringᴱ (block b E) = errToStringᴮ E ++ "\n in call of function " ++ varToString (name b) -- errToStringᴱ (bin₂ E) = errToStringᴱ E
-- errToStringᴱ (block b E) = errToStringᴮ E ++ "\n in call of function " ++ varToString (name b)
-- errToStringᴱ (TypeMismatch t v _) = "value " ++ valueToString v ++ " is not a " ++ runtimeTypeToString t
errToStringᴱ err = {!!}
errToStringᴮ (local x E) = errToStringᴱ E ++ "\n in definition of " ++ varToString (name x) errToStringᴮ (local x E) = errToStringᴱ E ++ "\n in definition of " ++ varToString (name x)
errToStringᴮ (return E) = errToStringᴱ E ++ "\n in return statement" errToStringᴮ (return E) = errToStringᴱ E ++ "\n in return statement"

View file

@ -0,0 +1,13 @@
module Luau.RuntimeType where
open import Luau.Value using (Value; nil; addr; number)
data RuntimeType : Set where
function : RuntimeType
number : RuntimeType
nil : RuntimeType
valueType : Value RuntimeType
valueType nil = nil
valueType (addr x) = function
valueType (number x) = number

View file

@ -0,0 +1,9 @@
module Luau.RuntimeType.ToString where
open import FFI.Data.String using (String)
open import Luau.RuntimeType using (RuntimeType; function; number; nil)
runtimeTypeToString : RuntimeType String
runtimeTypeToString function = "function"
runtimeTypeToString number = "number"
runtimeTypeToString nil = "nil"

View file

@ -63,11 +63,17 @@ data Warningᴱ H {Γ} where
------------------------- -------------------------
Warningᴱ H (function f {U = U} D) Warningᴱ H (function f {U = U} D)
block : b {B T} {D : Γ ⊢ᴮ B T} block₀ : b {B T U} {D : Γ ⊢ᴮ B U}
(T U)
------------------------------
Warningᴱ H (block b {T = T} D)
block₁ : b {B T U} {D : Γ ⊢ᴮ B U}
Warningᴮ H D Warningᴮ H D
----------------- ------------------------------
Warningᴱ H (block b D) Warningᴱ H (block b {T = T} D)
data Warningᴮ H {Γ} where data Warningᴮ H {Γ} where

View file

@ -1,6 +1,6 @@
module Luau.Substitution where module Luau.Substitution where
open import Luau.Syntax using (Expr; Stat; Block; nil; addr; var; function_is_end; _$_; block_is_end; local_←_; _∙_; done; return; _⟨_⟩ ; name; fun; arg) open import Luau.Syntax using (Expr; Stat; Block; nil; addr; var; function_is_end; _$_; block_is_end; local_←_; _∙_; done; return; _⟨_⟩ ; name; fun; arg; number; binexp)
open import Luau.Value using (Value; val) open import Luau.Value using (Value; val)
open import Luau.Var using (Var; _≡ⱽ_) open import Luau.Var using (Var; _≡ⱽ_)
open import Properties.Dec using (Dec; yes; no) open import Properties.Dec using (Dec; yes; no)
@ -13,9 +13,11 @@ _[_/_]ᴮunless_ : ∀ {a P} → Block a → Value → Var → (Dec P) → Block
nil [ v / x ]ᴱ = nil nil [ v / x ]ᴱ = nil
var y [ v / x ]ᴱ = var y [ v / x ]ᴱwhenever (x ≡ⱽ y) var y [ v / x ]ᴱ = var y [ v / x ]ᴱwhenever (x ≡ⱽ y)
addr a [ v / x ]ᴱ = addr a addr a [ v / x ]ᴱ = addr a
(number y) [ v / x ]ᴱ = number y
(M $ N) [ v / x ]ᴱ = (M [ v / x ]ᴱ) $ (N [ v / x ]ᴱ) (M $ N) [ v / x ]ᴱ = (M [ v / x ]ᴱ) $ (N [ v / x ]ᴱ)
function F is C end [ v / x ]ᴱ = function F is C [ v / x ]ᴮunless (x ≡ⱽ name(arg F)) end function F is C end [ v / x ]ᴱ = function F is C [ v / x ]ᴮunless (x ≡ⱽ name(arg F)) end
block b is C end [ v / x ]ᴱ = block b is C [ v / x ]ᴮ end block b is C end [ v / x ]ᴱ = block b is C [ v / x ]ᴮ end
(binexp e₁ op e₂) [ v / x ]ᴱ = binexp (e₁ [ v / x ]ᴱ) op (e₂ [ v / x ]ᴱ)
(function F is C end B) [ v / x ]ᴮ = function F is (C [ v / x ]ᴮunless (x ≡ⱽ name(arg F))) end (B [ v / x ]ᴮunless (x ≡ⱽ name(fun F))) (function F is C end B) [ v / x ]ᴮ = function F is (C [ v / x ]ᴮunless (x ≡ⱽ name(arg F))) end (B [ v / x ]ᴮunless (x ≡ⱽ name(fun F)))
(local y M B) [ v / x ]ᴮ = local y (M [ v / x ]ᴱ) (B [ v / x ]ᴮunless (x ≡ⱽ name y)) (local y M B) [ v / x ]ᴮ = local y (M [ v / x ]ᴱ) (B [ v / x ]ᴮunless (x ≡ⱽ name y))
@ -27,4 +29,3 @@ var y [ v / x ]ᴱwhenever no p = var y
B [ v / x ]ᴮunless yes p = B B [ v / x ]ᴮunless yes p = B
B [ v / x ]ᴮunless no p = B [ v / x ]ᴮ B [ v / x ]ᴮunless no p = B [ v / x ]ᴮ

View file

@ -1,6 +1,7 @@
module Luau.Syntax where module Luau.Syntax where
open import Agda.Builtin.Equality using (_≡_) open import Agda.Builtin.Equality using (_≡_)
open import Agda.Builtin.Float using (Float)
open import Luau.Var using (Var) open import Luau.Var using (Var)
open import Luau.Addr using (Addr) open import Luau.Addr using (Addr)
open import Luau.Type using (Type) open import Luau.Type using (Type)
@ -31,6 +32,12 @@ arg : ∀ {a} → FunDec a → VarDec a
arg (f x ⟩∈ T) = x arg (f x ⟩∈ T) = x
arg (f x ) = x arg (f x ) = x
data BinaryOperator : Set where
+ : BinaryOperator
- : BinaryOperator
* : BinaryOperator
/ : BinaryOperator
data Block (a : Annotated) : Set data Block (a : Annotated) : Set
data Stat (a : Annotated) : Set data Stat (a : Annotated) : Set
data Expr (a : Annotated) : Set data Expr (a : Annotated) : Set
@ -51,4 +58,5 @@ data Expr a where
_$_ : Expr a Expr a Expr a _$_ : Expr a Expr a Expr a
function_is_end : FunDec a Block a Expr a function_is_end : FunDec a Block a Expr a
block_is_end : VarDec a Block a Expr a block_is_end : VarDec a Block a Expr a
number : Float Expr a
binexp : Expr a BinaryOperator Expr a Expr a

View file

@ -2,7 +2,8 @@
module Luau.Syntax.FromJSON where module Luau.Syntax.FromJSON where
open import Luau.Syntax using (Block; Stat ; Expr; nil; _$_; var; function_is_end; _⟨_⟩; local_←_; return; done; _∙_; maybe) open import Luau.Syntax using (Block; Stat ; Expr; nil; _$_; var; var_∈_; function_is_end; _⟨_⟩; local_←_; return; done; _∙_; maybe; VarDec; number; binexp; BinaryOperator; +; -; *; /)
open import Luau.Type.FromJSON using (typeFromJSON)
open import Agda.Builtin.List using (List; _∷_; []) open import Agda.Builtin.List using (List; _∷_; [])
@ -10,6 +11,7 @@ open import FFI.Data.Aeson using (Value; Array; Object; object; array; string; f
open import FFI.Data.Bool using (true; false) open import FFI.Data.Bool using (true; false)
open import FFI.Data.Either using (Either; Left; Right) open import FFI.Data.Either using (Either; Left; Right)
open import FFI.Data.Maybe using (Maybe; nothing; just) open import FFI.Data.Maybe using (Maybe; nothing; just)
open import FFI.Data.Scientific using (toFloat)
open import FFI.Data.String using (String; _++_) open import FFI.Data.String using (String; _++_)
open import FFI.Data.Vector using (head; tail; null; empty) open import FFI.Data.Vector using (head; tail; null; empty)
@ -20,8 +22,12 @@ lokal = fromString "local"
list = fromString "list" list = fromString "list"
name = fromString "name" name = fromString "name"
type = fromString "type" type = fromString "type"
value = fromString "value"
values = fromString "values" values = fromString "values"
vars = fromString "vars" vars = fromString "vars"
op = fromString "op"
left = fromString "left"
right = fromString "right"
data Lookup : Set where data Lookup : Set where
_,_ : String Value Lookup _,_ : String Value Lookup
@ -33,6 +39,10 @@ lookupIn (key ∷ keys) obj with lookup (fromString key) obj
lookupIn (key keys) obj | nothing = lookupIn keys obj lookupIn (key keys) obj | nothing = lookupIn keys obj
lookupIn (key keys) obj | just value = (key , value) lookupIn (key keys) obj | just value = (key , value)
binOpFromJSON : Value Either String BinaryOperator
binOpFromString : String Either String BinaryOperator
varDecFromJSON : Value Either String (VarDec maybe)
varDecFromObject : Object Either String (VarDec maybe)
exprFromJSON : Value Either String (Expr maybe) exprFromJSON : Value Either String (Expr maybe)
exprFromObject : Object Either String (Expr maybe) exprFromObject : Object Either String (Expr maybe)
statFromJSON : Value Either String (Stat maybe) statFromJSON : Value Either String (Stat maybe)
@ -40,6 +50,27 @@ statFromObject : Object → Either String (Stat maybe)
blockFromJSON : Value Either String (Block maybe) blockFromJSON : Value Either String (Block maybe)
blockFromArray : Array Either String (Block maybe) blockFromArray : Array Either String (Block maybe)
binOpFromJSON (string s) = binOpFromString s
binOpFromJSON val = Left "Binary operator not a string"
binOpFromString "Add" = Right +
binOpFromString "Sub" = Right -
binOpFromString "Mul" = Right *
binOpFromString "Div" = Right /
binOpFromString s = Left ("'" ++ s ++ "' is not a valid operator")
varDecFromJSON (object arg) = varDecFromObject arg
varDecFromJSON val = Left "VarDec not an object"
varDecFromObject obj with lookup name obj | lookup type obj
varDecFromObject obj | just (string name) | nothing = Right (var name)
varDecFromObject obj | just (string name) | just Value.null = Right (var name)
varDecFromObject obj | just (string name) | just tyValue with typeFromJSON tyValue
varDecFromObject obj | just (string name) | just tyValue | Right ty = Right (var name ty)
varDecFromObject obj | just (string name) | just tyValue | Left err = Left err
varDecFromObject obj | just _ | _ = Left "AstLocal name is not a string"
varDecFromObject obj | nothing | _ = Left "AstLocal missing name"
exprFromJSON (object obj) = exprFromObject obj exprFromJSON (object obj) = exprFromObject obj
exprFromJSON val = Left "AstExpr not an object" exprFromJSON val = Left "AstExpr not an object"
@ -56,18 +87,33 @@ exprFromObject obj | just (string "AstExprCall") | nothing | _ = Left ("AstExpr
exprFromObject obj | just (string "AstExprCall") | _ | nothing = Left ("AstExprCall missing args") exprFromObject obj | just (string "AstExprCall") | _ | nothing = Left ("AstExprCall missing args")
exprFromObject obj | just (string "AstExprConstantNil") = Right nil exprFromObject obj | just (string "AstExprConstantNil") = Right nil
exprFromObject obj | just (string "AstExprFunction") with lookup args obj | lookup body obj exprFromObject obj | just (string "AstExprFunction") with lookup args obj | lookup body obj
exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just value with head arr | blockFromJSON value exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue with head arr | blockFromJSON blockValue
exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just value | just (string x) | Right B = Right (function "" var x is B end) exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | just argValue | Right B with varDecFromJSON argValue
exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just value | just _ | Right B = Left "AstExprFunction args not a string array" exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | just argValue | Right B | Right arg = Right (function "" arg is B end)
exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just value | nothing | Right B = Left "Unsupported AstExprFunction empty args" exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | just argValue | Right B | Left err = Left err
exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just value | _ | Left err = Left err exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | nothing | Right B = Left "Unsupported AstExprFunction empty args"
exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | _ | Left err = Left err
exprFromObject obj | just (string "AstExprFunction") | just _ | just _ = Left "AstExprFunction args not an array" exprFromObject obj | just (string "AstExprFunction") | just _ | just _ = Left "AstExprFunction args not an array"
exprFromObject obj | just (string "AstExprFunction") | nothing | _ = Left "AstExprFunction missing args" exprFromObject obj | just (string "AstExprFunction") | nothing | _ = Left "AstExprFunction missing args"
exprFromObject obj | just (string "AstExprFunction") | _ | nothing = Left "AstExprFunction missing body" exprFromObject obj | just (string "AstExprFunction") | _ | nothing = Left "AstExprFunction missing body"
exprFromObject obj | just (string "AstExprLocal") with lookup lokal obj exprFromObject obj | just (string "AstExprLocal") with lookup lokal obj
exprFromObject obj | just (string "AstExprLocal") | just (string x) = Right (var x) exprFromObject obj | just (string "AstExprLocal") | just x with varDecFromJSON x
exprFromObject obj | just (string "AstExprLocal") | just (_) = Left "AstExprLocal local not a string" exprFromObject obj | just (string "AstExprLocal") | just x | Right x = Right (var (Luau.Syntax.name x))
exprFromObject obj | just (string "AstExprLocal") | just x | Left err = Left err
exprFromObject obj | just (string "AstExprLocal") | nothing = Left "AstExprLocal missing local" exprFromObject obj | just (string "AstExprLocal") | nothing = Left "AstExprLocal missing local"
exprFromObject obj | just (string "AstExprConstantNumber") with lookup value obj
exprFromObject obj | just (string "AstExprConstantNumber") | just (FFI.Data.Aeson.Value.number x) = Right (number (toFloat x))
exprFromObject obj | just (string "AstExprConstantNumber") | just _ = Left "AstExprConstantNumber value is not a number"
exprFromObject obj | just (string "AstExprConstantNumber") | nothing = Left "AstExprConstantNumber missing value"
exprFromObject obj | just (string "AstExprBinary") with lookup op obj | lookup left obj | lookup right obj
exprFromObject obj | just (string "AstExprBinary") | just o | just l | just r with binOpFromJSON o | exprFromJSON l | exprFromJSON r
exprFromObject obj | just (string "AstExprBinary") | just o | just l | just r | Right o | Right l | Right r = Right (binexp l o r)
exprFromObject obj | just (string "AstExprBinary") | just o | just l | just r | Left err | _ | _ = Left err
exprFromObject obj | just (string "AstExprBinary") | just o | just l | just r | _ | Left err | _ = Left err
exprFromObject obj | just (string "AstExprBinary") | just o | just l | just r | _ | _ | Left err = Left err
exprFromObject obj | just (string "AstExprBinary") | nothing | _ | _ = Left "Missing 'op' in AstExprBinary"
exprFromObject obj | just (string "AstExprBinary") | _ | nothing | _ = Left "Missing 'left' in AstExprBinary"
exprFromObject obj | just (string "AstExprBinary") | _ | _ | nothing = Left "Missing 'right' in AstExprBinary"
exprFromObject obj | just (string ty) = Left ("TODO: Unsupported AstExpr " ++ ty) exprFromObject obj | just (string ty) = Left ("TODO: Unsupported AstExpr " ++ ty)
exprFromObject obj | just _ = Left "AstExpr type not a string" exprFromObject obj | just _ = Left "AstExpr type not a string"
exprFromObject obj | nothing = Left "AstExpr missing type" exprFromObject obj | nothing = Left "AstExpr missing type"
@ -79,11 +125,11 @@ statFromJSON _ = Left "AstStat not an object"
statFromObject obj with lookup type obj statFromObject obj with lookup type obj
statFromObject obj | just(string "AstStatLocal") with lookup vars obj | lookup values obj statFromObject obj | just(string "AstStatLocal") with lookup vars obj | lookup values obj
statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(array arr2) with head(arr1) | head(arr2) statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(array arr2) with head(arr1) | head(arr2)
statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(array arr2) | just(string x) | just(value) with exprFromJSON(value) statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(array arr2) | just(x) | just(value) with varDecFromJSON(x) | exprFromJSON(value)
statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(array arr2) | just(string x) | just(value) | Right M = Right (local (var x) M) statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(array arr2) | just(x) | just(value) | Right x | Right M = Right (local x M)
statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(array arr2) | just(string x) | just(value) | Left err = Left err statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(array arr2) | just(x) | just(value) | Left err | _ = Left err
statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(array arr2) | just(string x) | nothing = Left "AstStatLocal empty values" statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(array arr2) | just(x) | just(value) | _ | Left err = Left err
statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(array arr2) | just(_) | _ = Left "AstStatLocal vars not a string array" statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(array arr2) | just(x) | nothing = Left "AstStatLocal empty values"
statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(array arr2) | nothing | _ = Left "AstStatLocal empty vars" statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(array arr2) | nothing | _ = Left "AstStatLocal empty vars"
statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(_) = Left "AstStatLocal values not an array" statFromObject obj | just(string "AstStatLocal") | just(array arr1) | just(_) = Left "AstStatLocal values not an array"
statFromObject obj | just(string "AstStatLocal") | just(_) | just(_) = Left "AstStatLocal vars not an array" statFromObject obj | just(string "AstStatLocal") | just(_) | just(_) = Left "AstStatLocal vars not an array"
@ -95,6 +141,11 @@ statFromObject obj | just(string "AstStatLocalFunction") | just (string f) | jus
statFromObject obj | just(string "AstStatLocalFunction") | just (string f) | just value | Left err = Left err statFromObject obj | just(string "AstStatLocalFunction") | just (string f) | just value | Left err = Left err
statFromObject obj | just(string "AstStatLocalFunction") | just _ | just _ | Right _ = Left "AstStatLocalFunction func is not an AstExprFunction" statFromObject obj | just(string "AstStatLocalFunction") | just _ | just _ | Right _ = Left "AstStatLocalFunction func is not an AstExprFunction"
statFromObject obj | just(string "AstStatLocalFunction") | just _ | just _ = Left "AstStatLocalFunction name is not a string" statFromObject obj | just(string "AstStatLocalFunction") | just _ | just _ = Left "AstStatLocalFunction name is not a string"
statFromObject obj | just(string "AstStatLocalFunction") | just fnName | just value with varDecFromJSON fnName | exprFromJSON value
statFromObject obj | just(string "AstStatLocalFunction") | just fnName | just value | Right fnVar | Right (function "" x is B end) = Right (function (Luau.Syntax.name fnVar) x is B end)
statFromObject obj | just(string "AstStatLocalFunction") | just fnName | just value | Left err | _ = Left err
statFromObject obj | just(string "AstStatLocalFunction") | just fnName | just value | _ | Left err = Left err
statFromObject obj | just(string "AstStatLocalFunction") | just _ | just _ | Right _ | Right _ = Left "AstStatLocalFunction func is not an AstExprFunction"
statFromObject obj | just(string "AstStatLocalFunction") | nothing | _ = Left "AstStatFunction missing name" statFromObject obj | just(string "AstStatLocalFunction") | nothing | _ = Left "AstStatFunction missing name"
statFromObject obj | just(string "AstStatLocalFunction") | _ | nothing = Left "AstStatFunction missing func" statFromObject obj | just(string "AstStatLocalFunction") | _ | nothing = Left "AstStatFunction missing func"
statFromObject obj | just(string "AstStatReturn") with lookup list obj statFromObject obj | just(string "AstStatReturn") with lookup list obj

View file

@ -1,6 +1,7 @@
module Luau.Syntax.ToString where module Luau.Syntax.ToString where
open import Luau.Syntax using (Block; Stat; Expr; VarDec; FunDec; nil; var; var_∈_; addr; _$_; function_is_end; return; local_←_; _∙_; done; block_is_end; _⟨_⟩; _⟨_⟩∈_) open import Agda.Builtin.Float using (primShowFloat)
open import Luau.Syntax using (Block; Stat; Expr; VarDec; FunDec; nil; var; var_∈_; addr; _$_; function_is_end; return; local_←_; _∙_; done; block_is_end; _⟨_⟩; _⟨_⟩∈_; number; BinaryOperator; +; -; *; /; binexp)
open import FFI.Data.String using (String; _++_) open import FFI.Data.String using (String; _++_)
open import Luau.Addr.ToString using (addrToString) open import Luau.Addr.ToString using (addrToString)
open import Luau.Type.ToString using (typeToString) open import Luau.Type.ToString using (typeToString)
@ -11,8 +12,16 @@ varDecToString (var x) = varToString x
varDecToString (var x T) = varToString x ++ " : " ++ typeToString T varDecToString (var x T) = varToString x ++ " : " ++ typeToString T
funDecToString : {a} FunDec a String funDecToString : {a} FunDec a String
funDecToString (f x ⟩∈ T) = varToString f ++ "(" ++ varDecToString x ++ "): " ++ typeToString T funDecToString ("" x ⟩∈ T) = "function(" ++ varDecToString x ++ "): " ++ typeToString T
funDecToString (f x ) = varToString f ++ "(" ++ varDecToString x ++ ")" funDecToString ("" x ) = "function(" ++ varDecToString x ++ ")"
funDecToString (f x ⟩∈ T) = "function " ++ varToString f ++ "(" ++ varDecToString x ++ "): " ++ typeToString T
funDecToString (f x ) = "function " ++ varToString f ++ "(" ++ varDecToString x ++ ")"
binOpToString : BinaryOperator String
binOpToString + = "+"
binOpToString - = "-"
binOpToString * = "*"
binOpToString / = "/"
exprToString : {a} String Expr a String exprToString : {a} String Expr a String
statToString : {a} String Stat a String statToString : {a} String Stat a String
@ -27,20 +36,18 @@ exprToString lb (var x) =
exprToString lb (M $ N) = exprToString lb (M $ N) =
(exprToString lb M) ++ "(" ++ (exprToString lb N) ++ ")" (exprToString lb M) ++ "(" ++ (exprToString lb N) ++ ")"
exprToString lb (function F is B end) = exprToString lb (function F is B end) =
"function " ++ funDecToString F ++ lb ++ funDecToString F ++ lb ++
" " ++ (blockToString (lb ++ " ") B) ++ lb ++ " " ++ (blockToString (lb ++ " ") B) ++ lb ++
"end" "end"
exprToString lb (block (var b) is B end) = exprToString lb (block b is B end) =
"(function " ++ b ++ "()" ++ lb ++ "(" ++ varDecToString b ++ "()" ++ lb ++
" " ++ (blockToString (lb ++ " ") B) ++ lb ++
"end)()"
exprToString lb (block (var b T) is B end) =
"(function " ++ b ++ "(): " ++ typeToString(T) ++ lb ++
" " ++ (blockToString (lb ++ " ") B) ++ lb ++ " " ++ (blockToString (lb ++ " ") B) ++ lb ++
"end)()" "end)()"
exprToString lb (number x) = primShowFloat x
exprToString lb (binexp x op y) = exprToString lb x ++ " " ++ binOpToString op ++ " " ++ exprToString lb y
statToString lb (function F is B end) = statToString lb (function F is B end) =
"local function " ++ funDecToString F ++ lb ++ "local " ++ funDecToString F ++ lb ++
" " ++ (blockToString (lb ++ " ") B) ++ lb ++ " " ++ (blockToString (lb ++ " ") B) ++ lb ++
"end" "end"
statToString lb (local x M) = statToString lb (local x M) =

View file

@ -4,12 +4,14 @@ open import FFI.Data.Maybe using (Maybe; just; nothing; just-inv)
open import Agda.Builtin.Equality using (_≡_; refl) open import Agda.Builtin.Equality using (_≡_; refl)
open import Properties.Dec using (Dec; yes; no) open import Properties.Dec using (Dec; yes; no)
open import Properties.Equality using (cong) open import Properties.Equality using (cong)
open import FFI.Data.Maybe using (Maybe; just; nothing)
data Type : Set where data Type : Set where
nil : Type nil : Type
_⇒_ : Type Type Type _⇒_ : Type Type Type
bot : Type bot : Type
top : Type top : Type
number : Type
__ : Type Type Type __ : Type Type Type
_∩_ : Type Type Type _∩_ : Type Type Type
@ -20,6 +22,7 @@ lhs (T ∩ _) = T
lhs nil = nil lhs nil = nil
lhs bot = bot lhs bot = bot
lhs top = top lhs top = top
lhs number = number
rhs : Type Type rhs : Type Type
rhs (_ T) = T rhs (_ T) = T
@ -28,12 +31,14 @@ rhs (_ ∩ T) = T
rhs nil = nil rhs nil = nil
rhs bot = bot rhs bot = bot
rhs top = top rhs top = top
rhs number = number
_≡ᵀ_ : (T U : Type) Dec(T U) _≡ᵀ_ : (T U : Type) Dec(T U)
nil ≡ᵀ nil = yes refl nil ≡ᵀ nil = yes refl
nil ≡ᵀ (S T) = no (λ ()) nil ≡ᵀ (S T) = no (λ ())
nil ≡ᵀ bot = no (λ ()) nil ≡ᵀ bot = no (λ ())
nil ≡ᵀ top = no (λ ()) nil ≡ᵀ top = no (λ ())
nil ≡ᵀ number = no (λ ())
nil ≡ᵀ (S T) = no (λ ()) nil ≡ᵀ (S T) = no (λ ())
nil ≡ᵀ (S T) = no (λ ()) nil ≡ᵀ (S T) = no (λ ())
(S T) ≡ᵀ nil = no (λ ()) (S T) ≡ᵀ nil = no (λ ())
@ -43,24 +48,35 @@ nil ≡ᵀ (S ∩ T) = no (λ ())
(S T) ≡ᵀ (U V) | no p | _ = no (λ q p (cong lhs q)) (S T) ≡ᵀ (U V) | no p | _ = no (λ q p (cong lhs q))
(S T) ≡ᵀ bot = no (λ ()) (S T) ≡ᵀ bot = no (λ ())
(S T) ≡ᵀ top = no (λ ()) (S T) ≡ᵀ top = no (λ ())
(S T) ≡ᵀ number = no (λ ())
(S T) ≡ᵀ (U V) = no (λ ()) (S T) ≡ᵀ (U V) = no (λ ())
(S T) ≡ᵀ (U V) = no (λ ()) (S T) ≡ᵀ (U V) = no (λ ())
bot ≡ᵀ nil = no (λ ()) bot ≡ᵀ nil = no (λ ())
bot ≡ᵀ (U V) = no (λ ()) bot ≡ᵀ (U V) = no (λ ())
bot ≡ᵀ bot = yes refl bot ≡ᵀ bot = yes refl
bot ≡ᵀ top = no (λ ()) bot ≡ᵀ top = no (λ ())
bot ≡ᵀ number = no (λ ())
bot ≡ᵀ (U V) = no (λ ()) bot ≡ᵀ (U V) = no (λ ())
bot ≡ᵀ (U V) = no (λ ()) bot ≡ᵀ (U V) = no (λ ())
top ≡ᵀ nil = no (λ ()) top ≡ᵀ nil = no (λ ())
top ≡ᵀ (U V) = no (λ ()) top ≡ᵀ (U V) = no (λ ())
top ≡ᵀ bot = no (λ ()) top ≡ᵀ bot = no (λ ())
top ≡ᵀ top = yes refl top ≡ᵀ top = yes refl
top ≡ᵀ number = no (λ ())
top ≡ᵀ (U V) = no (λ ()) top ≡ᵀ (U V) = no (λ ())
top ≡ᵀ (U V) = no (λ ()) top ≡ᵀ (U V) = no (λ ())
number ≡ᵀ nil = no (λ ())
number ≡ᵀ (U U₁) = no (λ ())
number ≡ᵀ bot = no (λ ())
number ≡ᵀ top = no (λ ())
number ≡ᵀ number = yes refl
number ≡ᵀ (U U₁) = no (λ ())
number ≡ᵀ (U U₁) = no (λ ())
(S T) ≡ᵀ nil = no (λ ()) (S T) ≡ᵀ nil = no (λ ())
(S T) ≡ᵀ (U V) = no (λ ()) (S T) ≡ᵀ (U V) = no (λ ())
(S T) ≡ᵀ bot = no (λ ()) (S T) ≡ᵀ bot = no (λ ())
(S T) ≡ᵀ top = no (λ ()) (S T) ≡ᵀ top = no (λ ())
(S T) ≡ᵀ number = no (λ ())
(S T) ≡ᵀ (U V) with (S ≡ᵀ U) | (T ≡ᵀ V) (S T) ≡ᵀ (U V) with (S ≡ᵀ U) | (T ≡ᵀ V)
(S T) ≡ᵀ (S T) | yes refl | yes refl = yes refl (S T) ≡ᵀ (S T) | yes refl | yes refl = yes refl
(S T) ≡ᵀ (U V) | _ | no p = no (λ q p (cong rhs q)) (S T) ≡ᵀ (U V) | _ | no p = no (λ q p (cong rhs q))
@ -70,6 +86,7 @@ top ≡ᵀ (U ∩ V) = no (λ ())
(S T) ≡ᵀ (U V) = no (λ ()) (S T) ≡ᵀ (U V) = no (λ ())
(S T) ≡ᵀ bot = no (λ ()) (S T) ≡ᵀ bot = no (λ ())
(S T) ≡ᵀ top = no (λ ()) (S T) ≡ᵀ top = no (λ ())
(S T) ≡ᵀ number = no (λ ())
(S T) ≡ᵀ (U V) = no (λ ()) (S T) ≡ᵀ (U V) = no (λ ())
(S T) ≡ᵀ (U V) with (S ≡ᵀ U) | (T ≡ᵀ V) (S T) ≡ᵀ (U V) with (S ≡ᵀ U) | (T ≡ᵀ V)
(S T) ≡ᵀ (U V) | yes refl | yes refl = yes refl (S T) ≡ᵀ (U V) | yes refl | yes refl = yes refl
@ -90,6 +107,7 @@ data Mode : Set where
src : Mode Type Type src : Mode Type Type
src m nil = bot src m nil = bot
src m number = bot
src m (S T) = S src m (S T) = S
-- In nonstrict mode, functions are covaraiant, in strict mode they're contravariant -- In nonstrict mode, functions are covaraiant, in strict mode they're contravariant
src strict (S T) = (src strict S) (src strict T) src strict (S T) = (src strict S) (src strict T)
@ -106,6 +124,7 @@ tgt nil = bot
tgt (S T) = T tgt (S T) = T
tgt bot = bot tgt bot = bot
tgt top = top tgt top = top
tgt number = bot
tgt (S T) = (tgt S) (tgt T) tgt (S T) = (tgt S) (tgt T)
tgt (S T) = (tgt S) (tgt T) tgt (S T) = (tgt S) (tgt T)
@ -117,8 +136,9 @@ optional T = (T nil)
normalizeOptional : Type Type normalizeOptional : Type Type
normalizeOptional (S T) with normalizeOptional S | normalizeOptional T normalizeOptional (S T) with normalizeOptional S | normalizeOptional T
normalizeOptional (S T) | (S nil) | (T nil) = (S T) nil normalizeOptional (S T) | (S nil) | (T nil) = (S T) nil
normalizeOptional (S T) | S | nil = optional S normalizeOptional (S T) | S | (T nil) = (S T) nil
normalizeOptional (S T) | nil | T = optional T normalizeOptional (S T) | (S nil) | T = (S T) nil
normalizeOptional (S T) | S | T = S T normalizeOptional (S T) | S | nil = optional S
normalizeOptional (S T) | nil | T = optional T
normalizeOptional (S T) | S | T = S T
normalizeOptional T = T normalizeOptional T = T

View file

@ -0,0 +1,69 @@
{-# OPTIONS --rewriting #-}
module Luau.Type.FromJSON where
open import Luau.Type using (Type; nil; _⇒_; __; _∩_; top; number)
open import Agda.Builtin.List using (List; _∷_; [])
open import FFI.Data.Aeson using (Value; Array; Object; object; array; string; fromString; lookup)
open import FFI.Data.Bool using (true; false)
open import FFI.Data.Either using (Either; Left; Right)
open import FFI.Data.Maybe using (Maybe; nothing; just)
open import FFI.Data.String using (String; _++_)
open import FFI.Data.Vector using (head; tail; null; empty)
name = fromString "name"
type = fromString "type"
argTypes = fromString "argTypes"
returnTypes = fromString "returnTypes"
types = fromString "types"
{-# TERMINATING #-}
typeFromJSON : Value Either String Type
compoundFromArray : (Type Type Type) Array Either String Type
typeFromJSON (object o) with lookup type o
typeFromJSON (object o) | just (string "AstTypeFunction") with lookup argTypes o | lookup returnTypes o
typeFromJSON (object o) | just (string "AstTypeFunction") | just (object argsSet) | just (object retsSet) with lookup types argsSet | lookup types retsSet
typeFromJSON (object o) | just (string "AstTypeFunction") | just (object argsSet) | just (object retsSet) | just (array args) | just (array rets) with head args | head rets
typeFromJSON (object o) | just (string "AstTypeFunction") | just (object argsSet) | just (object retsSet) | just (array args) | just (array rets) | just argValue | just retValue with typeFromJSON argValue | typeFromJSON retValue
typeFromJSON (object o) | just (string "AstTypeFunction") | just (object argsSet) | just (object retsSet) | just (array args) | just (array rets) | just argValue | just retValue | Right arg | Right ret = Right (arg ret)
typeFromJSON (object o) | just (string "AstTypeFunction") | just (object argsSet) | just (object retsSet) | just (array args) | just (array rets) | just argValue | just retValue | Left err | _ = Left err
typeFromJSON (object o) | just (string "AstTypeFunction") | just (object argsSet) | just (object retsSet) | just (array args) | just (array rets) | just argValue | just retValue | _ | Left err = Left err
typeFromJSON (object o) | just (string "AstTypeFunction") | just (object argsSet) | just (object retsSet) | just (array args) | just (array rets) | _ | nothing = Left "No return type"
typeFromJSON (object o) | just (string "AstTypeFunction") | just (object argsSet) | just (object retsSet) | just (array args) | just (array rets) | nothing | _ = Left "No argument type"
typeFromJSON (object o) | just (string "AstTypeFunction") | just (object argsSet) | just (object retsSet) | just _ | _ = Left "argTypes.types is not an array"
typeFromJSON (object o) | just (string "AstTypeFunction") | just (object argsSet) | just (object retsSet) | _ | just _ = Left "retTypes.types is not an array"
typeFromJSON (object o) | just (string "AstTypeFunction") | just (object argsSet) | just (object retsSet) | nothing | _ = Left "argTypes.types does not exist"
typeFromJSON (object o) | just (string "AstTypeFunction") | _ | just _ = Left "argTypes is not an object"
typeFromJSON (object o) | just (string "AstTypeFunction") | just _ | _ = Left "returnTypes is not an object"
typeFromJSON (object o) | just (string "AstTypeFunction") | nothing | nothing = Left "Missing argTypes and returnTypes"
typeFromJSON (object o) | just (string "AstTypeReference") with lookup name o
typeFromJSON (object o) | just (string "AstTypeReference") | just (string "nil") = Right nil
typeFromJSON (object o) | just (string "AstTypeReference") | just (string "any") = Right top
typeFromJSON (object o) | just (string "AstTypeReference") | just (string "number") = Right number
typeFromJSON (object o) | just (string "AstTypeReference") | _ = Left "Unknown referenced type"
typeFromJSON (object o) | just (string "AstTypeUnion") with lookup types o
typeFromJSON (object o) | just (string "AstTypeUnion") | just (array types) = compoundFromArray __ types
typeFromJSON (object o) | just (string "AstTypeUnion") | _ = Left "`types` field must be an array"
typeFromJSON (object o) | just (string "AstTypeIntersection") with lookup types o
typeFromJSON (object o) | just (string "AstTypeIntersection") | just (array types) = compoundFromArray _∩_ types
typeFromJSON (object o) | just (string "AstTypeIntersection") | _ = Left "`types` field must be an array"
typeFromJSON (object o) | just (string ty) = Left ("Unsupported type " ++ ty)
typeFromJSON (object o) | just _ = Left "`type` field must be a string"
typeFromJSON (object o) | nothing = Left "No `type` field"
typeFromJSON _ = Left "Unsupported JSON type"
compoundFromArray ctor ts with head ts | tail ts
compoundFromArray ctor ts | just hd | tl with null tl
compoundFromArray ctor ts | just hd | tl | true = typeFromJSON hd
compoundFromArray ctor ts | just hd | tl | false with typeFromJSON hd | compoundFromArray ctor tl
compoundFromArray ctor ts | just hd | tl | false | Right hdTy | Right tlTy = Right (ctor hdTy tlTy)
compoundFromArray ctor ts | just hd | tl | false | Left err | _ = Left err
compoundFromArray ctor ts | just hd | tl | false | _ | Left Err = Left Err
compoundFromArray ctor ts | nothing | empty = Left "Empty types array?"

View file

@ -1,7 +1,7 @@
module Luau.Type.ToString where module Luau.Type.ToString where
open import FFI.Data.String using (String; _++_) open import FFI.Data.String using (String; _++_)
open import Luau.Type using (Type; nil; _⇒_; bot; top; __; _∩_; normalizeOptional) open import Luau.Type using (Type; nil; _⇒_; bot; top; number; __; _∩_; normalizeOptional)
{-# TERMINATING #-} {-# TERMINATING #-}
typeToString : Type String typeToString : Type String
@ -12,6 +12,7 @@ typeToString nil = "nil"
typeToString (S T) = "(" ++ (typeToString S) ++ ") -> " ++ (typeToString T) typeToString (S T) = "(" ++ (typeToString S) ++ ") -> " ++ (typeToString T)
typeToString bot = "bot" typeToString bot = "bot"
typeToString top = "top" typeToString top = "top"
typeToString number = "number"
typeToString (S T) with normalizeOptional(S T) typeToString (S T) with normalizeOptional(S T)
typeToString (S T) | ((S T) nil) = "(" ++ typeToString (S T) ++ ")?" typeToString (S T) | ((S T) nil) = "(" ++ typeToString (S T) ++ ")?"
typeToString (S T) | (S nil) = "(" ++ typeToString S ++ "?" typeToString (S T) | (S nil) = "(" ++ typeToString S ++ "?"
@ -19,10 +20,7 @@ typeToString (S T) | (S T) = "(" ++ typeToStringᵁ (S T) ++ "
typeToString (S T) | T = typeToString T typeToString (S T) | T = typeToString T
typeToString (S T) = "(" ++ typeToStringᴵ (S T) ++ ")" typeToString (S T) = "(" ++ typeToStringᴵ (S T) ++ ")"
typeToStringᵁ (S T) = "(" ++ typeToString (S T) ++ ")"
typeToStringᵁ (S T) = (typeToStringᵁ S) ++ " | " ++ (typeToStringᵁ T) typeToStringᵁ (S T) = (typeToStringᵁ S) ++ " | " ++ (typeToStringᵁ T)
typeToStringᵁ T = typeToString T typeToStringᵁ T = typeToString T
typeToStringᴵ (S T) = "(" ++ typeToString (S T) ++ ")"
typeToStringᴵ (S T) = (typeToStringᴵ S) ++ " & " ++ (typeToStringᴵ T) typeToStringᴵ (S T) = (typeToStringᴵ S) ++ " & " ++ (typeToStringᴵ T)
typeToStringᴵ T = typeToString T typeToStringᴵ T = typeToString T

View file

@ -6,12 +6,12 @@ module Luau.TypeCheck (m : Mode) where
open import Agda.Builtin.Equality using (_≡_) open import Agda.Builtin.Equality using (_≡_)
open import FFI.Data.Maybe using (Maybe; just) open import FFI.Data.Maybe using (Maybe; just)
open import Luau.Syntax using (Expr; Stat; Block; yes; nil; addr; var; var_∈_; _⟨_⟩∈_; function_is_end; _$_; block_is_end; local_←_; _∙_; done; return; name) open import Luau.Syntax using (Expr; Stat; Block; yes; nil; addr; number; var; var_∈_; _⟨_⟩∈_; function_is_end; _$_; block_is_end; binexp; local_←_; _∙_; done; return; name)
open import Luau.Var using (Var) open import Luau.Var using (Var)
open import Luau.Addr using (Addr) open import Luau.Addr using (Addr)
open import Luau.Heap using (Heap; Object; function_is_end) renaming (_[_] to _[_]ᴴ) open import Luau.Heap using (Heap; Object; function_is_end) renaming (_[_] to _[_]ᴴ)
open import Luau.Value using (addr; val) open import Luau.Value using (addr; val)
open import Luau.Type using (Type; Mode; nil; bot; top; _⇒_; tgt) open import Luau.Type using (Type; Mode; nil; bot; top; number; _⇒_; tgt)
open import Luau.VarCtxt using (VarCtxt; ∅; _⋒_; _↦_; _⊕_↦_; _⊝_) renaming (_[_] to _[_]ⱽ) open import Luau.VarCtxt using (VarCtxt; ∅; _⋒_; _↦_; _⊕_↦_; _⊝_) renaming (_[_] to _[_]ⱽ)
open import FFI.Data.Vector using (Vector) open import FFI.Data.Vector using (Vector)
open import FFI.Data.Maybe using (Maybe; just; nothing) open import FFI.Data.Maybe using (Maybe; just; nothing)
@ -73,6 +73,11 @@ data _⊢ᴱ_∈_ where
----------------- -----------------
Γ ⊢ᴱ (addr a) T Γ ⊢ᴱ (addr a) T
number : n {Γ}
------------------------
Γ ⊢ᴱ (number n) number
app : {M N T U Γ} app : {M N T U Γ}
Γ ⊢ᴱ M T Γ ⊢ᴱ M T
@ -86,11 +91,18 @@ data _⊢ᴱ_∈_ where
----------------------------------------------------- -----------------------------------------------------
Γ ⊢ᴱ (function f var x T ⟩∈ U is B end) (T U) Γ ⊢ᴱ (function f var x T ⟩∈ U is B end) (T U)
block : b {B T Γ} block : b {B T U Γ}
Γ ⊢ᴮ B T Γ ⊢ᴮ B U
--------------------------- ------------------------------------
Γ ⊢ᴱ (block b is B end) T Γ ⊢ᴱ (block var b T is B end) T
binexp : op {Γ M N T U}
Γ ⊢ᴱ M T
Γ ⊢ᴱ N U
----------------------------
Γ ⊢ᴱ (binexp M op N) number
data ⊢ᴼ_ : Maybe(Object yes) Set where data ⊢ᴼ_ : Maybe(Object yes) Set where

View file

@ -1,15 +1,16 @@
module Luau.Value where module Luau.Value where
open import Agda.Builtin.Float using (Float)
open import Luau.Addr using (Addr) open import Luau.Addr using (Addr)
open import Luau.Syntax using (Block; Expr; nil; addr) open import Luau.Syntax using (Block; Expr; nil; addr; number)
open import Luau.Var using (Var) open import Luau.Var using (Var)
data Value : Set where data Value : Set where
nil : Value nil : Value
addr : Addr Value addr : Addr Value
number : Float Value
val : {a} Value Expr a val : {a} Value Expr a
val nil = nil val nil = nil
val (addr a) = addr a val (addr a) = addr a
val (number x) = number x

View file

@ -1,10 +1,11 @@
module Luau.Value.ToString where module Luau.Value.ToString where
open import Agda.Builtin.String using (String) open import Agda.Builtin.String using (String)
open import Luau.Value using (Value; nil; addr) open import Agda.Builtin.Float using (primShowFloat)
open import Luau.Value using (Value; nil; addr; number)
open import Luau.Addr.ToString using (addrToString) open import Luau.Addr.ToString using (addrToString)
valueToString : Value String valueToString : Value String
valueToString nil = "nil" valueToString nil = "nil"
valueToString (addr a) = addrToString a valueToString (addr a) = addrToString a
valueToString (number x) = primShowFloat x

View file

@ -32,4 +32,3 @@ runString txt | (Right value) = runJSON value
main : IO main : IO
main = getContents >>= runString main = getContents >>= runString

View file

@ -9,5 +9,3 @@ import Properties.Remember
import Properties.Step import Properties.Step
import Properties.StrictMode import Properties.StrictMode
import Properties.TypeCheck import Properties.TypeCheck

View file

@ -5,4 +5,3 @@ open import Properties.Contradiction using (¬)
data Dec(A : Set) : Set where data Dec(A : Set) : Set where
yes : A Dec A yes : A Dec A
no : ¬ A Dec A no : ¬ A Dec A

View file

@ -0,0 +1,25 @@
open import Luau.Type using (Mode)
module Properties.Preservation (m : Mode) where
open import Agda.Builtin.Equality using (_≡_; refl)
open import FFI.Data.Maybe using (Maybe; just; nothing)
open import FFI.Data.Either using (Either)
open import Luau.TypeCheck(m) using (_▷_⊢ᴱ_∋_∈_⊣_; _▷_⊢ᴮ_∋_∈_⊣_; _▷_✓; nil; var; addr; app; function; block; done; return; local)
open import Luau.Syntax using (Block; Expr; yes; nil; var; addr; _$_; function_is_end; block_is_end; _∙_; return; done; local_←_; _⟨_⟩; _⟨_⟩∈_; var_∈_; name; fun; arg)
open import Luau.Type using (Type; nil; top; _⇒_; tgt)
open import Luau.VarCtxt using (VarCtxt; ∅; _↦_; _⊕_↦_; _⋒_; _⊝_; ⊕-[]) renaming (_[_] to _[_]ⱽ)
open import Luau.Addr using (Addr)
open import Luau.Var using (Var; _≡ⱽ_)
open import Luau.AddrCtxt using (AddrCtxt) renaming (_[_] to _[_]ᴬ)
open import Luau.Heap using (Heap)
open import Luau.OpSem using (_⊢_⟶ᴱ_⊣_)
open import Properties.Dec using (yes; no)
open import Properties.Equality using (_≢_; sym; trans; cong)
open import Properties.Remember using (remember; _,_)
src : Type Type
src = Luau.Type.src m
preservationᴱ : {Σ Γ S Δ T H H M M} (Σ H ) (Σ Γ ⊢ᴱ S M T Δ) (H M ⟶ᴱ M H) (Σ Γ ⊢ᴱ S M T Δ)
preservationᴱ = ?

View file

@ -1,15 +1,18 @@
{-# OPTIONS --rewriting #-} {-# OPTIONS --rewriting #-}
{-# OPTIONS --allow-unsolved-metas #-}
module Properties.Step where module Properties.Step where
open import Agda.Builtin.Equality using (_≡_; refl) open import Agda.Builtin.Equality using (_≡_; refl)
open import Agda.Builtin.Float using (primFloatPlus; primFloatMinus; primFloatTimes; primFloatDiv)
open import FFI.Data.Maybe using (just; nothing) open import FFI.Data.Maybe using (just; nothing)
open import Luau.Heap using (Heap; _[_]; alloc; ok; function_is_end) open import Luau.Heap using (Heap; _[_]; alloc; ok; function_is_end)
open import Luau.Syntax using (Block; Expr; nil; var; addr; function_is_end; block_is_end; _$_; local_←_; return; done; _∙_; name; fun; arg) open import Luau.Syntax using (Block; Expr; nil; var; addr; function_is_end; block_is_end; _$_; local_←_; return; done; _∙_; name; fun; arg; number; binexp; +)
open import Luau.OpSem using (_⊢_⟶ᴱ_⊣_; _⊢_⟶ᴮ_⊣_; app₁; app₂; beta; function; block; return; done; local; subst) open import Luau.OpSem using (_⊢_⟶ᴱ_⊣_; _⊢_⟶ᴮ_⊣_; app₁ ; app₂ ; beta; function; block; return; done; local; subst; binOpEval; evalBinOp; binOp₁; binOp₂)
open import Luau.RuntimeError using (RuntimeErrorᴱ; RuntimeErrorᴮ; NilIsNotAFunction; UnboundVariable; SEGV; app₁; app₂; block; local; return) open import Luau.RuntimeError using (RuntimeErrorᴱ; RuntimeErrorᴮ; TypeMismatch; UnboundVariable; SEGV; app₁; app₂; block; local; return; bin₁; bin₂)
open import Luau.RuntimeType using (function; number)
open import Luau.Substitution using (_[_/_]ᴮ) open import Luau.Substitution using (_[_/_]ᴮ)
open import Luau.Value using (nil; addr; val) open import Luau.Value using (nil; addr; val; number)
open import Properties.Remember using (remember; _,_) open import Properties.Remember using (remember; _,_)
data StepResultᴮ {a} (H : Heap a) (B : Block a) : Set data StepResultᴮ {a} (H : Heap a) (B : Block a) : Set
@ -32,29 +35,42 @@ stepᴮ : ∀ {a} H B → StepResultᴮ {a} H B
stepᴱ H nil = value nil refl stepᴱ H nil = value nil refl
stepᴱ H (var x) = error (UnboundVariable x) stepᴱ H (var x) = error (UnboundVariable x)
stepᴱ H (addr a) = value (addr a) refl stepᴱ H (addr a) = value (addr a) refl
stepᴱ H (number x) = value (number x) refl
stepᴱ H (M $ N) with stepᴱ H M stepᴱ H (M $ N) with stepᴱ H M
stepᴱ H (M $ N) | step H M D = step H (M $ N) (app₁ D) stepᴱ H (M $ N) | step H M D = step H (M $ N) (app₁ D)
stepᴱ H (M $ N) | value V p with stepᴱ H N stepᴱ H (_ $ N) | value V refl with stepᴱ H N
stepᴱ H (M $ N) | value V p | step H N D = step H (M $ N) (app₂ p D) stepᴱ H (_ $ N) | value V refl | step H N s = step H (val V $ N) (app₂ refl s)
stepᴱ H (nil $ _) | value nil refl | value W refl = error NilIsNotAFunction stepᴱ H (_ $ _) | value nil refl | value W refl = {!!} -- error (app₁ (TypeMismatch function nil λ()))
stepᴱ H (addr a $ N) | value (addr a) refl | value W q with remember (H [ a ]) stepᴱ H (_ $ _) | value (number n) refl | value W refl = {!!} -- error (app₁ (TypeMismatch function (number n) λ()))
stepᴱ H (addr a $ N) | value (addr a) refl | value W q | (nothing , p) = error (app₁ (SEGV a p)) stepᴱ H (_ $ _) | value (addr a) refl | value W refl with remember (H [ a ])
stepᴱ H (addr a $ N) | value (addr a) refl | value W q | (just(function F is B end) , p) = step H (block fun F is (B [ W / name(arg F) ]ᴮ) end) (beta p q) stepᴱ H (_ $ _) | value (addr a) refl | value W refl | (nothing , p) = error (app₁ (SEGV a p))
stepᴱ H (_ $ _) | value (addr a) refl | value W refl | (just(function F is B end) , p) = {!!} -- step H (block fun F is B [ W / name (arg F) ]ᴮ end) (beta refl p)
stepᴱ H (M $ N) | value V p | error E = error (app₂ E) stepᴱ H (M $ N) | value V p | error E = error (app₂ E)
stepᴱ H (M $ N) | error E = error (app₁ E) stepᴱ H (M $ N) | error E = error (app₁ E)
stepᴱ H (block b is B end) with stepᴮ H B stepᴱ H (block b is B end) with stepᴮ H B
stepᴱ H (block b is B end) | step H B D = step H (block b is B end) (block D) stepᴱ H (block b is B end) | step H B D = step H (block b is B end) (block D)
stepᴱ H (block b is (return _ B) end) | return V refl = step H (val V) (return refl) stepᴱ H (block b is (return _ B) end) | return V refl = {!!} -- step H (val V) (return refl)
stepᴱ H (block b is done end) | done refl = step H nil done stepᴱ H (block b is done end) | done refl = step H nil done
stepᴱ H (block b is B end) | error E = error (block b E) stepᴱ H (block b is B end) | error E = error (block b E)
stepᴱ H (function F is C end) with alloc H (function F is C end) stepᴱ H (function F is C end) with alloc H (function F is C end)
stepᴱ H function F is C end | ok a H p = step H (addr a) (function p) stepᴱ H function F is C end | ok a H p = {!!} -- step H (addr a) (function p)
stepᴱ H (binexp x op y) with stepᴱ H x
stepᴱ H (binexp x op y) | value x refl with stepᴱ H y
stepᴱ H (binexp x op y) | value (number x) refl | value (number y) refl = step H (number (evalBinOp x op y)) binOpEval
stepᴱ H (binexp x op y) | value (number x) refl | step H y s = step H (binexp (number x) op y) (binOp₂ s)
stepᴱ H (binexp x op y) | value (number x) refl | error E = error (bin₂ E)
stepᴱ H (binexp x op y) | value nil refl | _ = {!!} -- error (bin₁ (TypeMismatch number nil λ()))
stepᴱ H (binexp x op y) | _ | value nil refl = {!!} -- error (bin₂ (TypeMismatch number nil λ()))
stepᴱ H (binexp x op y) | value (addr a) refl | _ = {!!} -- error (bin₁ (TypeMismatch number (addr a) λ()))
stepᴱ H (binexp x op y) | _ | value (addr a) refl = {!!} -- error (bin₂ (TypeMismatch number (addr a) λ()))
stepᴱ H (binexp x op y) | step H x s = step H (binexp x op y) (binOp₁ s)
stepᴱ H (binexp x op y) | error E = error (bin₁ E)
stepᴮ H (function F is C end B) with alloc H (function F is C end) stepᴮ H (function F is C end B) with alloc H (function F is C end)
stepᴮ H (function F is C end B) | ok a H p = step H (B [ addr a / name(fun F) ]ᴮ) (function p) stepᴮ H (function F is C end B) | ok a H p = {!!} -- step H (B [ addr a / name(fun F) ]ᴮ) (function p)
stepᴮ H (local x M B) with stepᴱ H M stepᴮ H (local x M B) with stepᴱ H M
stepᴮ H (local x M B) | step H M D = step H (local x M B) (local D) stepᴮ H (local x M B) | step H M D = step H (local x M B) (local D)
stepᴮ H (local x _ B) | value V refl = step H (B [ V / name x ]ᴮ) subst stepᴮ H (local x _ B) | value V refl = {!!} -- step H (B [ V / name x ]ᴮ) subst
stepᴮ H (local x M B) | error E = error (local x E) stepᴮ H (local x M B) | error E = error (local x E)
stepᴮ H (return M B) with stepᴱ H M stepᴮ H (return M B) with stepᴱ H M
stepᴮ H (return M B) | step H M D = step H (return M B) (return D) stepᴮ H (return M B) | step H M D = step H (return M B) (return D)

Some files were not shown because too many files have changed in this diff Show more