Compare commits

..

No commits in common. "master" and "0.642" have entirely different histories.

544 changed files with 12763 additions and 54296 deletions

View file

@ -63,10 +63,10 @@ jobs:
} }
valgrind --tool=callgrind ./luau-analyze --mode=nonstrict bench/other/LuauPolyfillMap.lua 2>&1 | filter map-nonstrict | tee -a analyze-output.txt valgrind --tool=callgrind ./luau-analyze --mode=nonstrict bench/other/LuauPolyfillMap.lua 2>&1 | filter map-nonstrict | tee -a analyze-output.txt
valgrind --tool=callgrind ./luau-analyze --mode=strict bench/other/LuauPolyfillMap.lua 2>&1 | filter map-strict | tee -a analyze-output.txt valgrind --tool=callgrind ./luau-analyze --mode=strict bench/other/LuauPolyfillMap.lua 2>&1 | filter map-strict | tee -a analyze-output.txt
valgrind --tool=callgrind ./luau-analyze --mode=strict --fflags=LuauSolverV2 bench/other/LuauPolyfillMap.lua 2>&1 | filter map-dcr | tee -a analyze-output.txt valgrind --tool=callgrind ./luau-analyze --mode=strict --fflags=DebugLuauDeferredConstraintResolution bench/other/LuauPolyfillMap.lua 2>&1 | filter map-dcr | tee -a analyze-output.txt
valgrind --tool=callgrind ./luau-analyze --mode=nonstrict bench/other/regex.lua 2>&1 | filter regex-nonstrict | tee -a analyze-output.txt valgrind --tool=callgrind ./luau-analyze --mode=nonstrict bench/other/regex.lua 2>&1 | filter regex-nonstrict | tee -a analyze-output.txt
valgrind --tool=callgrind ./luau-analyze --mode=strict bench/other/regex.lua 2>&1 | filter regex-strict | tee -a analyze-output.txt valgrind --tool=callgrind ./luau-analyze --mode=strict bench/other/regex.lua 2>&1 | filter regex-strict | tee -a analyze-output.txt
valgrind --tool=callgrind ./luau-analyze --mode=strict --fflags=LuauSolverV2 bench/other/regex.lua 2>&1 | filter regex-dcr | tee -a analyze-output.txt valgrind --tool=callgrind ./luau-analyze --mode=strict --fflags=DebugLuauDeferredConstraintResolution bench/other/regex.lua 2>&1 | filter regex-dcr | tee -a analyze-output.txt
- name: Run benchmark (compile) - name: Run benchmark (compile)
run: | run: |

View file

@ -46,9 +46,9 @@ jobs:
- name: make cli - name: make cli
run: | run: |
make -j2 config=sanitize werror=1 luau luau-analyze luau-compile # match config with tests to improve build time make -j2 config=sanitize werror=1 luau luau-analyze luau-compile # match config with tests to improve build time
./luau tests/conformance/assert.luau ./luau tests/conformance/assert.lua
./luau-analyze tests/conformance/assert.luau ./luau-analyze tests/conformance/assert.lua
./luau-compile tests/conformance/assert.luau ./luau-compile tests/conformance/assert.lua
windows: windows:
runs-on: windows-latest runs-on: windows-latest
@ -81,12 +81,12 @@ jobs:
shell: bash # necessary for fail-fast shell: bash # necessary for fail-fast
run: | run: |
cmake --build . --target Luau.Repl.CLI Luau.Analyze.CLI Luau.Compile.CLI --config Debug # match config with tests to improve build time cmake --build . --target Luau.Repl.CLI Luau.Analyze.CLI Luau.Compile.CLI --config Debug # match config with tests to improve build time
Debug/luau tests/conformance/assert.luau Debug/luau tests/conformance/assert.lua
Debug/luau-analyze tests/conformance/assert.luau Debug/luau-analyze tests/conformance/assert.lua
Debug/luau-compile tests/conformance/assert.luau Debug/luau-compile tests/conformance/assert.lua
coverage: coverage:
runs-on: ubuntu-22.04 runs-on: ubuntu-20.04 # needed for clang++-10 to avoid gcov compatibility issues
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: install - name: install
@ -94,7 +94,7 @@ jobs:
sudo apt install llvm sudo apt install llvm
- name: make coverage - name: make coverage
run: | run: |
CXX=clang++ make -j2 config=coverage native=1 coverage CXX=clang++-10 make -j2 config=coverage native=1 coverage
- name: upload coverage - name: upload coverage
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3
with: with:

View file

@ -29,8 +29,8 @@ jobs:
build: build:
needs: ["create-release"] needs: ["create-release"]
strategy: strategy:
matrix: # not using ubuntu-latest to improve compatibility matrix: # using ubuntu-20.04 to build a Linux binary targeting older glibc to improve compatibility
os: [{name: ubuntu, version: ubuntu-22.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}] os: [{name: ubuntu, version: ubuntu-20.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}]
name: ${{matrix.os.name}} name: ${{matrix.os.name}}
runs-on: ${{matrix.os.version}} runs-on: ${{matrix.os.version}}
steps: steps:
@ -38,7 +38,7 @@ jobs:
- name: configure - name: configure
run: cmake . -DCMAKE_BUILD_TYPE=Release run: cmake . -DCMAKE_BUILD_TYPE=Release
- name: build - name: build
run: cmake --build . --target Luau.Repl.CLI Luau.Analyze.CLI Luau.Compile.CLI Luau.Ast.CLI --config Release -j 2 run: cmake --build . --target Luau.Repl.CLI Luau.Analyze.CLI Luau.Compile.CLI --config Release -j 2
- name: pack - name: pack
if: matrix.os.name != 'windows' if: matrix.os.name != 'windows'
run: zip luau-${{matrix.os.name}}.zip luau* run: zip luau-${{matrix.os.name}}.zip luau*

View file

@ -13,8 +13,8 @@ on:
jobs: jobs:
build: build:
strategy: strategy:
matrix: # not using ubuntu-latest to improve compatibility matrix: # using ubuntu-20.04 to build a Linux binary targeting older glibc to improve compatibility
os: [{name: ubuntu, version: ubuntu-22.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}] os: [{name: ubuntu, version: ubuntu-20.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}]
name: ${{matrix.os.name}} name: ${{matrix.os.name}}
runs-on: ${{matrix.os.version}} runs-on: ${{matrix.os.version}}
steps: steps:
@ -23,18 +23,17 @@ jobs:
run: cmake . -DCMAKE_BUILD_TYPE=Release run: cmake . -DCMAKE_BUILD_TYPE=Release
- name: build - name: build
run: cmake --build . --target Luau.Repl.CLI Luau.Analyze.CLI Luau.Compile.CLI --config Release -j 2 run: cmake --build . --target Luau.Repl.CLI Luau.Analyze.CLI Luau.Compile.CLI --config Release -j 2
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v2
if: matrix.os.name != 'windows' if: matrix.os.name != 'windows'
with: with:
name: luau-${{matrix.os.name}} name: luau-${{matrix.os.name}}
path: luau* path: luau*
overwrite: true - uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v4
if: matrix.os.name == 'windows' if: matrix.os.name == 'windows'
with: with:
name: luau-${{matrix.os.name}} name: luau-${{matrix.os.name}}
path: Release\luau*.exe path: Release\luau*.exe
overwrite: true
web: web:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -53,8 +52,7 @@ jobs:
source emsdk/emsdk_env.sh source emsdk/emsdk_env.sh
emcmake cmake . -DLUAU_BUILD_WEB=ON -DCMAKE_BUILD_TYPE=Release emcmake cmake . -DLUAU_BUILD_WEB=ON -DCMAKE_BUILD_TYPE=Release
make -j2 Luau.Web make -j2 Luau.Web
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v2
with: with:
name: Luau.Web.js name: Luau.Web.js
path: Luau.Web.js path: Luau.Web.js
overwrite: true

2
.gitignore vendored
View file

@ -1,6 +1,5 @@
/build/ /build/
/build[.-]*/ /build[.-]*/
/out
/cmake/ /cmake/
/cmake[.-]*/ /cmake[.-]*/
/coverage/ /coverage/
@ -13,7 +12,6 @@
/luau /luau
/luau-tests /luau-tests
/luau-analyze /luau-analyze
/luau-bytecode
/luau-compile /luau-compile
__pycache__ __pycache__
.cache .cache

View file

@ -0,0 +1,148 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/AstQuery.h"
#include "Luau/Config.h"
#include "Luau/ModuleResolver.h"
#include "Luau/Scope.h"
#include "Luau/Variant.h"
#include "Luau/Normalize.h"
#include "Luau/TypePack.h"
#include "Luau/TypeArena.h"
#include <mutex>
#include <string>
#include <vector>
#include <optional>
namespace Luau
{
class AstStat;
class ParseError;
struct TypeError;
struct LintWarning;
struct GlobalTypes;
struct ModuleResolver;
struct ParseResult;
struct DcrLogger;
struct TelemetryTypePair
{
std::string annotatedType;
std::string inferredType;
};
struct AnyTypeSummary
{
TypeArena arena;
AstStatBlock* rootSrc = nullptr;
DenseHashSet<TypeId> seenTypeFamilyInstances{nullptr};
int recursionCount = 0;
std::string root;
int strictCount = 0;
DenseHashMap<const void*, bool> seen{nullptr};
AnyTypeSummary();
void traverse(const Module* module, AstStat* src, NotNull<BuiltinTypes> builtinTypes);
std::pair<bool, TypeId> checkForAnyCast(const Scope* scope, AstExprTypeAssertion* expr);
bool containsAny(TypePackId typ);
bool containsAny(TypeId typ);
bool isAnyCast(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
bool isAnyCall(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
bool hasVariadicAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
bool hasArgAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
bool hasAnyReturns(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
TypeId checkForFamilyInhabitance(const TypeId instance, Location location);
TypeId lookupType(const AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
TypePackId reconstructTypePack(const AstArray<AstExpr*> exprs, const Module* module, NotNull<BuiltinTypes> builtinTypes);
DenseHashSet<TypeId> seenTypeFunctionInstances{nullptr};
TypeId lookupAnnotation(AstType* annotation, const Module* module, NotNull<BuiltinTypes> builtintypes);
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation, const Module* module);
TypeId checkForTypeFunctionInhabitance(const TypeId instance, const Location location);
enum Pattern : uint64_t
{
Casts,
FuncArg,
FuncRet,
FuncApp,
VarAnnot,
VarAny,
TableProp,
Alias,
Assign,
TypePk
};
struct TypeInfo
{
Pattern code;
std::string node;
TelemetryTypePair type;
explicit TypeInfo(Pattern code, std::string node, TelemetryTypePair type);
};
struct FindReturnAncestry final : public AstVisitor
{
AstNode* currNode{nullptr};
AstNode* stat{nullptr};
Position rootEnd;
bool found = false;
explicit FindReturnAncestry(AstNode* stat, Position rootEnd);
bool visit(AstType* node) override;
bool visit(AstNode* node) override;
bool visit(AstStatFunction* node) override;
bool visit(AstStatLocalFunction* node) override;
};
std::vector<TypeInfo> typeInfo;
/**
* Fabricates a scope that is a child of another scope.
* @param node the lexical node that the scope belongs to.
* @param parent the parent scope of the new scope. Must not be null.
*/
const Scope* childScope(const AstNode* node, const Scope* parent);
std::optional<AstExpr*> matchRequire(const AstExprCall& call);
AstNode* getNode(AstStatBlock* root, AstNode* node);
const Scope* findInnerMostScope(const Location location, const Module* module);
const AstNode* findAstAncestryAtLocation(const AstStatBlock* root, AstNode* node);
void visit(const Scope* scope, AstStat* stat, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatBlock* block, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatIf* ifStatement, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatWhile* while_, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatRepeat* repeat, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatReturn* ret, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatLocal* local, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatFor* for_, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatForIn* forIn, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatCompoundAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatLocalFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatTypeAlias* alias, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatDeclareGlobal* declareGlobal, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatDeclareClass* declareClass, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatDeclareFunction* declareFunction, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatError* error, const Module* module, NotNull<BuiltinTypes> builtinTypes);
};
} // namespace Luau

View file

@ -1,10 +1,10 @@
// 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
#pragma once #pragma once
#include "Luau/AutocompleteTypes.h"
#include "Luau/Location.h" #include "Luau/Location.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include <unordered_map>
#include <string> #include <string>
#include <memory> #include <memory>
#include <optional> #include <optional>
@ -16,8 +16,89 @@ struct Frontend;
struct SourceModule; struct SourceModule;
struct Module; struct Module;
struct TypeChecker; struct TypeChecker;
struct FileResolver;
using ModulePtr = std::shared_ptr<Module>;
enum class AutocompleteContext
{
Unknown,
Expression,
Statement,
Property,
Type,
Keyword,
String,
};
enum class AutocompleteEntryKind
{
Property,
Binding,
Keyword,
String,
Type,
Module,
GeneratedFunction,
};
enum class ParenthesesRecommendation
{
None,
CursorAfter,
CursorInside,
};
enum class TypeCorrectKind
{
None,
Correct,
CorrectFunctionResult,
};
struct AutocompleteEntry
{
AutocompleteEntryKind kind = AutocompleteEntryKind::Property;
// Nullopt if kind is Keyword
std::optional<TypeId> type = std::nullopt;
bool deprecated = false;
// Only meaningful if kind is Property.
bool wrongIndexType = false;
// Set if this suggestion matches the type expected in the context
TypeCorrectKind typeCorrect = TypeCorrectKind::None;
std::optional<const ClassType*> containingClass = std::nullopt;
std::optional<const Property*> prop = std::nullopt;
std::optional<std::string> documentationSymbol = std::nullopt;
Tags tags;
ParenthesesRecommendation parens = ParenthesesRecommendation::None;
std::optional<std::string> insertText;
// Only meaningful if kind is Property.
bool indexedWithSelf = false;
};
using AutocompleteEntryMap = std::unordered_map<std::string, AutocompleteEntry>;
struct AutocompleteResult
{
AutocompleteEntryMap entryMap;
std::vector<AstNode*> ancestry;
AutocompleteContext context = AutocompleteContext::Unknown;
AutocompleteResult() = default;
AutocompleteResult(AutocompleteEntryMap entryMap, std::vector<AstNode*> ancestry, AutocompleteContext context)
: entryMap(std::move(entryMap))
, ancestry(std::move(ancestry))
, context(context)
{
}
};
using ModuleName = std::string;
using StringCompletionCallback =
std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ClassType*> ctx, std::optional<std::string> contents)>;
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback); AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback);
constexpr char kGeneratedAnonymousFunctionEntryName[] = "function (anonymous autofilled)";
} // namespace Luau } // namespace Luau

View file

@ -1,92 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Ast.h"
#include "Luau/Type.h"
#include <unordered_map>
namespace Luau
{
enum class AutocompleteContext
{
Unknown,
Expression,
Statement,
Property,
Type,
Keyword,
String,
};
enum class AutocompleteEntryKind
{
Property,
Binding,
Keyword,
String,
Type,
Module,
GeneratedFunction,
RequirePath,
};
enum class ParenthesesRecommendation
{
None,
CursorAfter,
CursorInside,
};
enum class TypeCorrectKind
{
None,
Correct,
CorrectFunctionResult,
};
struct AutocompleteEntry
{
AutocompleteEntryKind kind = AutocompleteEntryKind::Property;
// Nullopt if kind is Keyword
std::optional<TypeId> type = std::nullopt;
bool deprecated = false;
// Only meaningful if kind is Property.
bool wrongIndexType = false;
// Set if this suggestion matches the type expected in the context
TypeCorrectKind typeCorrect = TypeCorrectKind::None;
std::optional<const ExternType*> containingExternType = std::nullopt;
std::optional<const Property*> prop = std::nullopt;
std::optional<std::string> documentationSymbol = std::nullopt;
Tags tags;
ParenthesesRecommendation parens = ParenthesesRecommendation::None;
std::optional<std::string> insertText;
// Only meaningful if kind is Property.
bool indexedWithSelf = false;
};
using AutocompleteEntryMap = std::unordered_map<std::string, AutocompleteEntry>;
struct AutocompleteResult
{
AutocompleteEntryMap entryMap;
std::vector<AstNode*> ancestry;
AutocompleteContext context = AutocompleteContext::Unknown;
AutocompleteResult() = default;
AutocompleteResult(AutocompleteEntryMap entryMap, std::vector<AstNode*> ancestry, AutocompleteContext context)
: entryMap(std::move(entryMap))
, ancestry(std::move(ancestry))
, context(context)
{
}
};
using StringCompletionCallback =
std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ExternType*> ctx, std::optional<std::string> contents)>;
constexpr char kGeneratedAnonymousFunctionEntryName[] = "function (anonymous autofilled)";
} // namespace Luau

View file

@ -9,8 +9,6 @@
namespace Luau namespace Luau
{ {
static constexpr char kRequireTagName[] = "require";
struct Frontend; struct Frontend;
struct GlobalTypes; struct GlobalTypes;
struct TypeChecker; struct TypeChecker;
@ -65,12 +63,14 @@ TypeId makeFunction( // Polymorphic
bool checked = false bool checked = false
); );
void attachMagicFunction(TypeId ty, std::shared_ptr<MagicFunction> fn); void attachMagicFunction(TypeId ty, MagicFunction fn);
void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn);
void attachDcrMagicRefinement(TypeId ty, DcrMagicRefinement fn);
void attachDcrMagicFunctionTypeCheck(TypeId ty, DcrMagicFunctionTypeCheck fn);
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol = std::nullopt); Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol = std::nullopt);
void assignPropDocumentationSymbols(TableType::Props& props, const std::string& baseName); void assignPropDocumentationSymbols(TableType::Props& props, const std::string& baseName);
std::string getBuiltinDefinitionSource(); std::string getBuiltinDefinitionSource();
std::string getTypeFunctionDefinitionSource();
void addGlobalBinding(GlobalTypes& globals, const std::string& name, TypeId ty, const std::string& packageName); void addGlobalBinding(GlobalTypes& globals, const std::string& name, TypeId ty, const std::string& packageName);
void addGlobalBinding(GlobalTypes& globals, const std::string& name, Binding binding); void addGlobalBinding(GlobalTypes& globals, const std::string& name, Binding binding);
@ -80,16 +80,4 @@ std::optional<Binding> tryGetGlobalBinding(GlobalTypes& globals, const std::stri
Binding* tryGetGlobalBindingRef(GlobalTypes& globals, const std::string& name); Binding* tryGetGlobalBindingRef(GlobalTypes& globals, const std::string& name);
TypeId getGlobalBinding(GlobalTypes& globals, const std::string& name); TypeId getGlobalBinding(GlobalTypes& globals, const std::string& name);
/** A number of built-in functions are magical enough that we need to match on them specifically by
* name when they are called. These are listed here to be used whenever necessary, instead of duplicating this logic repeatedly.
*/
bool matchSetMetatable(const AstExprCall& call);
bool matchTableFreeze(const AstExprCall& call);
bool matchAssert(const AstExprCall& call);
// Returns `true` if the function should introduce typestate for its first argument.
bool shouldTypestateForFirstArgument(const AstExprCall& call);
} // namespace Luau } // namespace Luau

View file

@ -4,7 +4,6 @@
#include <Luau/NotNull.h> #include <Luau/NotNull.h>
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/Scope.h"
#include <unordered_map> #include <unordered_map>
@ -23,26 +22,8 @@ struct CloneState
SeenTypePacks seenTypePacks; SeenTypePacks seenTypePacks;
}; };
/** `shallowClone` will make a copy of only the _top level_ constructor of the type,
* while `clone` will make a deep copy of the entire type and its every component.
*
* Be mindful about which behavior you actually _want_.
*
* Persistent types are not cloned as an optimization.
* If a type is cloned in order to mutate it, 'ignorePersistent' has to be set
*/
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent = false);
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent = false);
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState); TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState); TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState);
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState); TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState);
Binding clone(const Binding& binding, TypeArena& dest, CloneState& cloneState);
TypePackId cloneIncremental(TypePackId tp, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
TypeId cloneIncremental(TypeId typeId, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
TypeFun cloneIncremental(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
Binding cloneIncremental(const Binding& binding, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
} // namespace Luau } // namespace Luau

View file

@ -50,7 +50,6 @@ struct GeneralizationConstraint
TypeId sourceType; TypeId sourceType;
std::vector<TypeId> interiorTypes; std::vector<TypeId> interiorTypes;
bool hasDeprecatedAttribute = false;
}; };
// variables ~ iterate iterator // variables ~ iterate iterator
@ -110,21 +109,6 @@ struct FunctionCheckConstraint
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes; NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes;
}; };
// table_check expectedType exprType
//
// If `expectedType` is a table type and `exprType` is _also_ a table type,
// propogate the member types of `expectedType` into the types of `exprType`.
// This is used to implement bidirectional inference on table assignment.
// Also see: FunctionCheckConstraint.
struct TableCheckConstraint
{
TypeId expectedType;
TypeId exprType;
AstExprTable* table = nullptr;
NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes;
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes;
};
// prim FreeType ExpectedType PrimitiveType // prim FreeType ExpectedType PrimitiveType
// //
// FreeType is bounded below by the singleton type and above by PrimitiveType // FreeType is bounded below by the singleton type and above by PrimitiveType
@ -289,8 +273,7 @@ using ConstraintV = Variant<
UnpackConstraint, UnpackConstraint,
ReduceConstraint, ReduceConstraint,
ReducePackConstraint, ReducePackConstraint,
EqualityConstraint, EqualityConstraint>;
TableCheckConstraint>;
struct Constraint struct Constraint
{ {

View file

@ -3,23 +3,23 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Constraint.h" #include "Luau/Constraint.h"
#include "Luau/ConstraintSet.h"
#include "Luau/ControlFlow.h" #include "Luau/ControlFlow.h"
#include "Luau/DataFlowGraph.h" #include "Luau/DataFlowGraph.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/InsertionOrderedMap.h" #include "Luau/InsertionOrderedMap.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/ModuleResolver.h" #include "Luau/ModuleResolver.h"
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Polarity.h"
#include "Luau/Refinement.h" #include "Luau/Refinement.h"
#include "Luau/Symbol.h" #include "Luau/Symbol.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/Variant.h"
#include "Luau/Normalize.h"
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <unordered_map>
namespace Luau namespace Luau
{ {
@ -28,7 +28,6 @@ struct Scope;
using ScopePtr = std::shared_ptr<Scope>; using ScopePtr = std::shared_ptr<Scope>;
struct DcrLogger; struct DcrLogger;
struct TypeFunctionRuntime;
struct Inference struct Inference
{ {
@ -92,11 +91,9 @@ struct ConstraintGenerator
// Constraints that go straight to the solver. // Constraints that go straight to the solver.
std::vector<ConstraintPtr> constraints; std::vector<ConstraintPtr> constraints;
// The set of all free types introduced during constraint generation. // Constraints that do not go to the solver right away. Other constraints
DenseHashSet<TypeId> freeTypes{nullptr}; // will enqueue them during solving.
std::vector<ConstraintPtr> unqueuedConstraints;
// Map a function's signature scope back to its signature type.
DenseHashMap<Scope*, TypeId> scopeToFunction{nullptr};
// The private scope of type aliases for which the type parameters belong to. // The private scope of type aliases for which the type parameters belong to.
DenseHashMap<const AstStatTypeAlias*, ScopePtr> astTypeAliasDefiningScopes{nullptr}; DenseHashMap<const AstStatTypeAlias*, ScopePtr> astTypeAliasDefiningScopes{nullptr};
@ -111,49 +108,33 @@ struct ConstraintGenerator
// Needed to be able to enable error-suppression preservation for immediate refinements. // Needed to be able to enable error-suppression preservation for immediate refinements.
NotNull<Normalizer> normalizer; NotNull<Normalizer> normalizer;
NotNull<Simplifier> simplifier;
// Needed to register all available type functions for execution at later stages.
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
DenseHashMap<const AstStatTypeFunction*, ScopePtr> astTypeFunctionEnvironmentScopes{nullptr};
// Needed to resolve modules to make 'require' import types properly. // Needed to resolve modules to make 'require' import types properly.
NotNull<ModuleResolver> moduleResolver; NotNull<ModuleResolver> moduleResolver;
// Occasionally constraint generation needs to produce an ICE. // Occasionally constraint generation needs to produce an ICE.
const NotNull<InternalErrorReporter> ice; const NotNull<InternalErrorReporter> ice;
ScopePtr globalScope; ScopePtr globalScope;
ScopePtr typeFunctionScope;
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope; std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
std::vector<RequireCycle> requireCycles; std::vector<RequireCycle> requireCycles;
DenseHashMap<TypeId, TypeIds> localTypes{nullptr}; DenseHashMap<TypeId, TypeIds> localTypes{nullptr};
DenseHashMap<AstExpr*, Inference> inferredExprCache{nullptr};
DcrLogger* logger; DcrLogger* logger;
ConstraintGenerator( ConstraintGenerator(
ModulePtr module, ModulePtr module,
NotNull<Normalizer> normalizer, NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> ice, NotNull<InternalErrorReporter> ice,
const ScopePtr& globalScope, const ScopePtr& globalScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
DcrLogger* logger, DcrLogger* logger,
NotNull<DataFlowGraph> dfg, NotNull<DataFlowGraph> dfg,
std::vector<RequireCycle> requireCycles std::vector<RequireCycle> requireCycles
); );
ConstraintSet run(AstStatBlock* block);
ConstraintSet runOnFragment(const ScopePtr& resumeScope, AstStatBlock* block);
/** /**
* The entry point to the ConstraintGenerator. This will construct a set * The entry point to the ConstraintGenerator. This will construct a set
* of scopes, constraints, and free types that can be solved later. * of scopes, constraints, and free types that can be solved later.
@ -161,29 +142,20 @@ struct ConstraintGenerator
*/ */
void visitModuleRoot(AstStatBlock* block); void visitModuleRoot(AstStatBlock* block);
void visitFragmentRoot(const ScopePtr& resumeScope, AstStatBlock* block);
private: private:
struct InteriorFreeTypes std::vector<std::vector<TypeId>> interiorTypes;
{
std::vector<TypeId> types;
std::vector<TypePackId> typePacks;
};
std::vector<std::vector<TypeId>> DEPRECATED_interiorTypes;
std::vector<InteriorFreeTypes> interiorFreeTypes;
/** /**
* Fabricates a new free type belonging to a given scope. * Fabricates a new free type belonging to a given scope.
* @param scope the scope the free type belongs to. * @param scope the scope the free type belongs to.
*/ */
TypeId freshType(const ScopePtr& scope, Polarity polarity = Polarity::Unknown); TypeId freshType(const ScopePtr& scope);
/** /**
* Fabricates a new free type pack belonging to a given scope. * Fabricates a new free type pack belonging to a given scope.
* @param scope the scope the free type pack belongs to. * @param scope the scope the free type pack belongs to.
*/ */
TypePackId freshTypePack(const ScopePtr& scope, Polarity polarity = Polarity::Unknown); TypePackId freshTypePack(const ScopePtr& scope);
/** /**
* Allocate a new TypePack with the given head and tail. * Allocate a new TypePack with the given head and tail.
@ -251,10 +223,7 @@ private:
); );
void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement); void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement);
LUAU_NOINLINE void checkAliases(const ScopePtr& scope, AstStatBlock* block);
ControlFlow visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block); ControlFlow visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block);
ControlFlow visitBlockWithoutChildScope_DEPRECATED(const ScopePtr& scope, AstStatBlock* block);
ControlFlow visit(const ScopePtr& scope, AstStat* stat); ControlFlow visit(const ScopePtr& scope, AstStat* stat);
ControlFlow visit(const ScopePtr& scope, AstStatBlock* block); ControlFlow visit(const ScopePtr& scope, AstStatBlock* block);
@ -272,7 +241,7 @@ private:
ControlFlow visit(const ScopePtr& scope, AstStatTypeAlias* alias); ControlFlow visit(const ScopePtr& scope, AstStatTypeAlias* alias);
ControlFlow visit(const ScopePtr& scope, AstStatTypeFunction* function); ControlFlow visit(const ScopePtr& scope, AstStatTypeFunction* function);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareGlobal* declareGlobal); ControlFlow visit(const ScopePtr& scope, AstStatDeclareGlobal* declareGlobal);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareExternType* declareExternType); ControlFlow visit(const ScopePtr& scope, AstStatDeclareClass* declareClass);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction); ControlFlow visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction);
ControlFlow visit(const ScopePtr& scope, AstStatError* error); ControlFlow visit(const ScopePtr& scope, AstStatError* error);
@ -304,7 +273,7 @@ private:
); );
Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton); Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton);
Inference check(const ScopePtr& scope, AstExprConstantBool* boolExpr, std::optional<TypeId> expectedType, bool forceSingleton); Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional<TypeId> expectedType, bool forceSingleton);
Inference check(const ScopePtr& scope, AstExprLocal* local); Inference check(const ScopePtr& scope, AstExprLocal* local);
Inference check(const ScopePtr& scope, AstExprGlobal* global); Inference check(const ScopePtr& scope, AstExprGlobal* global);
Inference checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation); Inference checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation);
@ -313,25 +282,11 @@ private:
Inference check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType, bool generalize); Inference check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType, bool generalize);
Inference check(const ScopePtr& scope, AstExprUnary* unary); Inference check(const ScopePtr& scope, AstExprUnary* unary);
Inference check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType); Inference check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
Inference checkAstExprBinary(
const ScopePtr& scope,
const Location& location,
AstExprBinary::Op op,
AstExpr* left,
AstExpr* right,
std::optional<TypeId> expectedType
);
Inference check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType); Inference check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType);
Inference check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert); Inference check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert);
Inference check(const ScopePtr& scope, AstExprInterpString* interpString); Inference check(const ScopePtr& scope, AstExprInterpString* interpString);
Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType); Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType);
std::tuple<TypeId, TypeId, RefinementId> checkBinary( std::tuple<TypeId, TypeId, RefinementId> checkBinary(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
const ScopePtr& scope,
AstExprBinary::Op op,
AstExpr* left,
AstExpr* right,
std::optional<TypeId> expectedType
);
void visitLValue(const ScopePtr& scope, AstExpr* expr, TypeId rhsType); void visitLValue(const ScopePtr& scope, AstExpr* expr, TypeId rhsType);
void visitLValue(const ScopePtr& scope, AstExprLocal* local, TypeId rhsType); void visitLValue(const ScopePtr& scope, AstExprLocal* local, TypeId rhsType);
@ -366,11 +321,6 @@ private:
*/ */
void checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn); void checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn);
// Specializations of 'resolveType' below
TypeId resolveReferenceType(const ScopePtr& scope, AstType* ty, AstTypeReference* ref, bool inTypeArguments, bool replaceErrorWithFresh);
TypeId resolveTableType(const ScopePtr& scope, AstType* ty, AstTypeTable* tab, bool inTypeArguments, bool replaceErrorWithFresh);
TypeId resolveFunctionType(const ScopePtr& scope, AstType* ty, AstTypeFunction* fn, bool inTypeArguments, bool replaceErrorWithFresh);
/** /**
* Resolves a type from its AST annotation. * Resolves a type from its AST annotation.
* @param scope the scope that the type annotation appears within. * @param scope the scope that the type annotation appears within.
@ -380,11 +330,6 @@ private:
**/ **/
TypeId resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh = false); TypeId resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh = false);
// resolveType() is recursive, but we only want to invoke
// inferGenericPolarities() once at the very end. We thus isolate the
// recursive part of the algorithm to this internal helper.
TypeId resolveType_(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh = false);
/** /**
* Resolves a type pack from its AST annotation. * Resolves a type pack from its AST annotation.
* @param scope the scope that the type annotation appears within. * @param scope the scope that the type annotation appears within.
@ -394,9 +339,6 @@ private:
**/ **/
TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments, bool replaceErrorWithFresh = false); TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments, bool replaceErrorWithFresh = false);
// Inner hepler for resolveTypePack
TypePackId resolveTypePack_(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments, bool replaceErrorWithFresh = false);
/** /**
* Resolves a type pack from its AST annotation. * Resolves a type pack from its AST annotation.
* @param scope the scope that the type annotation appears within. * @param scope the scope that the type annotation appears within.
@ -418,7 +360,7 @@ private:
**/ **/
std::vector<std::pair<Name, GenericTypeDefinition>> createGenerics( std::vector<std::pair<Name, GenericTypeDefinition>> createGenerics(
const ScopePtr& scope, const ScopePtr& scope,
AstArray<AstGenericType*> generics, AstArray<AstGenericType> generics,
bool useCache = false, bool useCache = false,
bool addTypes = true bool addTypes = true
); );
@ -435,7 +377,7 @@ private:
**/ **/
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks( std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(
const ScopePtr& scope, const ScopePtr& scope,
AstArray<AstGenericTypePack*> generics, AstArray<AstGenericTypePack> packs,
bool useCache = false, bool useCache = false,
bool addTypes = true bool addTypes = true
); );
@ -449,7 +391,6 @@ private:
TypeId makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs); TypeId makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs);
// make an intersect type function of these two types // make an intersect type function of these two types
TypeId makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs); TypeId makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs);
void prepopulateGlobalScopeForFragmentTypecheck(const ScopePtr& globalScope, const ScopePtr& resumeScope, AstStatBlock* program);
/** Scan the program for global definitions. /** Scan the program for global definitions.
* *
@ -480,8 +421,11 @@ private:
const ScopePtr& scope, const ScopePtr& scope,
Location location Location location
); );
TypeId simplifyUnion(const ScopePtr& scope, Location location, TypeId left, TypeId right);
}; };
/** Borrow a vector of pointers from a vector of owning pointers to constraints.
*/
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints);
} // namespace Luau } // namespace Luau

View file

@ -1,32 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Constraint.h"
#include "Luau/DenseHash.h"
#include "Luau/Error.h"
#include <vector>
namespace Luau
{
struct ConstraintSet
{
NotNull<Scope> rootScope;
std::vector<ConstraintPtr> constraints;
// The set of all free types created during constraint generation
DenseHashSet<TypeId> freeTypes{nullptr};
// Map a function's signature scope back to its signature type. Once we've
// dispatched all of the constraints pertaining to a particular free type,
// we use this mapping to generalize that free type.
DenseHashMap<Scope*, TypeId> scopeToFunction{nullptr};
// It is pretty uncommon for constraint generation to itself produce errors, but it can happen.
std::vector<TypeError> errors;
};
}

View file

@ -3,10 +3,7 @@
#pragma once #pragma once
#include "Luau/Constraint.h" #include "Luau/Constraint.h"
#include "Luau/ConstraintSet.h"
#include "Luau/DataFlowGraph.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Location.h" #include "Luau/Location.h"
#include "Luau/Module.h" #include "Luau/Module.h"
@ -15,7 +12,6 @@
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeCheckLimits.h" #include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFunction.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
@ -60,43 +56,17 @@ struct HashInstantiationSignature
size_t operator()(const InstantiationSignature& signature) const; size_t operator()(const InstantiationSignature& signature) const;
}; };
struct TablePropLookupResult
{
// What types are we blocked on for determining this type?
std::vector<TypeId> blockedTypes;
// The type of the property (if we were able to determine it).
std::optional<TypeId> propType;
// Whether or not this is _definitely_ derived as the result of an indexer.
// We use this to determine whether or not code like:
//
// t.lol = nil;
//
// ... is legal. If `t: { [string]: ~nil }` then this is legal as
// there's no guarantee on whether "lol" specifically exists.
// However, if `t: { lol: ~nil }`, then we cannot allow assignment as
// that would remove "lol" from the table entirely.
bool isIndex = false;
};
struct ConstraintSolver struct ConstraintSolver
{ {
NotNull<TypeArena> arena; NotNull<TypeArena> arena;
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
InternalErrorReporter iceReporter; InternalErrorReporter iceReporter;
NotNull<Normalizer> normalizer; NotNull<Normalizer> normalizer;
NotNull<Simplifier> simplifier;
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
// The entire set of constraints that the solver is trying to resolve. // The entire set of constraints that the solver is trying to resolve.
ConstraintSet constraintSet;
std::vector<NotNull<Constraint>> constraints; std::vector<NotNull<Constraint>> constraints;
NotNull<DenseHashMap<Scope*, TypeId>> scopeToFunction;
NotNull<Scope> rootScope; NotNull<Scope> rootScope;
ModuleName currentModuleName; ModuleName currentModuleName;
// The dataflow graph of the program, used in constraint generation and for magic functions.
NotNull<const DataFlowGraph> dfg;
// Constraints that the solver has generated, rather than sourcing from the // Constraints that the solver has generated, rather than sourcing from the
// scope tree. // scope tree.
std::vector<std::unique_ptr<Constraint>> solverConstraints; std::vector<std::unique_ptr<Constraint>> solverConstraints;
@ -121,9 +91,6 @@ struct ConstraintSolver
// A mapping from free types to the number of unresolved constraints that mention them. // A mapping from free types to the number of unresolved constraints that mention them.
DenseHashMap<TypeId, size_t> unresolvedConstraints{{}}; DenseHashMap<TypeId, size_t> unresolvedConstraints{{}};
std::unordered_map<NotNull<const Constraint>, DenseHashSet<TypeId>> maybeMutatedFreeTypes;
std::unordered_map<TypeId, DenseHashSet<const Constraint*>> mutatedFreeTypeToConstraint;
// Irreducible/uninhabited type functions or type pack functions. // Irreducible/uninhabited type functions or type pack functions.
DenseHashSet<const void*> uninhabitedTypeFunctions{{}}; DenseHashSet<const void*> uninhabitedTypeFunctions{{}};
@ -144,29 +111,12 @@ struct ConstraintSolver
explicit ConstraintSolver( explicit ConstraintSolver(
NotNull<Normalizer> normalizer, NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
ModuleName moduleName,
NotNull<ModuleResolver> moduleResolver,
std::vector<RequireCycle> requireCycles,
DcrLogger* logger,
NotNull<const DataFlowGraph> dfg,
TypeCheckLimits limits,
ConstraintSet constraintSet
);
explicit ConstraintSolver(
NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<Scope> rootScope, NotNull<Scope> rootScope,
std::vector<NotNull<Constraint>> constraints, std::vector<NotNull<Constraint>> constraints,
NotNull<DenseHashMap<Scope*, TypeId>> scopeToFunction,
ModuleName moduleName, ModuleName moduleName,
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
std::vector<RequireCycle> requireCycles, std::vector<RequireCycle> requireCycles,
DcrLogger* logger, DcrLogger* logger,
NotNull<const DataFlowGraph> dfg,
TypeCheckLimits limits TypeCheckLimits limits
); );
@ -186,14 +136,9 @@ struct ConstraintSolver
**/ **/
void finalizeTypeFunctions(); void finalizeTypeFunctions();
bool isDone() const; bool isDone();
private: private:
/// A helper that does most of the setup work that is shared between the two constructors.
void initFreeTypeTracking();
void generalizeOneType(TypeId ty);
/** /**
* Bind a type variable to another type. * Bind a type variable to another type.
* *
@ -219,14 +164,13 @@ public:
*/ */
bool tryDispatch(NotNull<const Constraint> c, bool force); bool tryDispatch(NotNull<const Constraint> c, bool force);
bool tryDispatch(const SubtypeConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const SubtypeConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const GeneralizationConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const GeneralizationConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const TableCheckConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
@ -247,16 +191,16 @@ public:
bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint, bool force);
// for a, ... in some_table do // for a, ... in some_table do
// also handles __iter metamethod // also handles __iter metamethod
bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
// for a, ... in next_function, t, ... do // for a, ... in next_function, t, ... do
bool tryDispatchIterableFunction(TypeId nextTy, TypeId tableTy, const IterableConstraint& c, NotNull<const Constraint> constraint); bool tryDispatchIterableFunction(TypeId nextTy, TypeId tableTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
TablePropLookupResult lookupTableProp( std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
NotNull<const Constraint> constraint, NotNull<const Constraint> constraint,
TypeId subjectType, TypeId subjectType,
const std::string& propName, const std::string& propName,
@ -264,8 +208,7 @@ public:
bool inConditional = false, bool inConditional = false,
bool suppressSimplification = false bool suppressSimplification = false
); );
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
TablePropLookupResult lookupTableProp(
NotNull<const Constraint> constraint, NotNull<const Constraint> constraint,
TypeId subjectType, TypeId subjectType,
const std::string& propName, const std::string& propName,
@ -324,10 +267,10 @@ public:
// FIXME: This use of a boolean for the return result is an appalling // FIXME: This use of a boolean for the return result is an appalling
// interface. // interface.
bool blockOnPendingTypes(TypeId target, NotNull<const Constraint> constraint); bool blockOnPendingTypes(TypeId target, NotNull<const Constraint> constraint);
bool blockOnPendingTypes(TypePackId targetPack, NotNull<const Constraint> constraint); bool blockOnPendingTypes(TypePackId target, NotNull<const Constraint> constraint);
void unblock(NotNull<const Constraint> progressed); void unblock(NotNull<const Constraint> progressed);
void unblock(TypeId ty, Location location); void unblock(TypeId progressed, Location location);
void unblock(TypePackId progressed, Location location); void unblock(TypePackId progressed, Location location);
void unblock(const std::vector<TypeId>& types, Location location); void unblock(const std::vector<TypeId>& types, Location location);
void unblock(const std::vector<TypePackId>& packs, Location location); void unblock(const std::vector<TypePackId>& packs, Location location);
@ -335,18 +278,18 @@ public:
/** /**
* @returns true if the TypeId is in a blocked state. * @returns true if the TypeId is in a blocked state.
*/ */
bool isBlocked(TypeId ty) const; bool isBlocked(TypeId ty);
/** /**
* @returns true if the TypePackId is in a blocked state. * @returns true if the TypePackId is in a blocked state.
*/ */
bool isBlocked(TypePackId tp) const; bool isBlocked(TypePackId tp);
/** /**
* Returns whether the constraint is blocked on anything. * Returns whether the constraint is blocked on anything.
* @param constraint the constraint to check. * @param constraint the constraint to check.
*/ */
bool isBlocked(NotNull<const Constraint> constraint) const; bool isBlocked(NotNull<const Constraint> constraint);
/** Pushes a new solver constraint to the solver. /** Pushes a new solver constraint to the solver.
* @param cv the body of the constraint. * @param cv the body of the constraint.
@ -362,7 +305,7 @@ public:
* @param location the location where the require is taking place; used for * @param location the location where the require is taking place; used for
* error locations. * error locations.
**/ **/
TypeId resolveModule(const ModuleInfo& info, const Location& location); TypeId resolveModule(const ModuleInfo& module, const Location& location);
void reportError(TypeErrorData&& data, const Location& location); void reportError(TypeErrorData&& data, const Location& location);
void reportError(TypeError e); void reportError(TypeError e);
@ -383,7 +326,7 @@ public:
* @returns a non-free type that generalizes the argument, or `std::nullopt` if one * @returns a non-free type that generalizes the argument, or `std::nullopt` if one
* does not exist * does not exist
*/ */
std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type); std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type, bool avoidSealingTables = false);
/** /**
* Checks the existing set of constraints to see if there exist any that contain * Checks the existing set of constraints to see if there exist any that contain
@ -433,27 +376,17 @@ public:
**/ **/
void reproduceConstraints(NotNull<Scope> scope, const Location& location, const Substitution& subst); void reproduceConstraints(NotNull<Scope> scope, const Location& location, const Substitution& subst);
TypeId simplifyIntersection(NotNull<Scope> scope, Location location, TypeId left, TypeId right);
TypeId simplifyIntersection(NotNull<Scope> scope, Location location, std::set<TypeId> parts);
TypeId simplifyUnion(NotNull<Scope> scope, Location location, TypeId left, TypeId right);
TypeId errorRecoveryType() const; TypeId errorRecoveryType() const;
TypePackId errorRecoveryTypePack() const; TypePackId errorRecoveryTypePack() const;
TypePackId anyifyModuleReturnTypePackGenerics(TypePackId tp); TypePackId anyifyModuleReturnTypePackGenerics(TypePackId tp);
void throwTimeLimitError() const; void throwTimeLimitError();
void throwUserCancelError() const; void throwUserCancelError();
ToStringOptions opts; ToStringOptions opts;
void fillInDiscriminantTypes(NotNull<const Constraint> constraint, const std::vector<std::optional<TypeId>>& discriminantTypes);
}; };
/** Borrow a vector of pointers from a vector of owning pointers to constraints.
*/
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints);
void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts); void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts);
} // namespace Luau } // namespace Luau

View file

@ -6,7 +6,6 @@
#include "Luau/ControlFlow.h" #include "Luau/ControlFlow.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/Def.h" #include "Luau/Def.h"
#include "Luau/NotNull.h"
#include "Luau/Symbol.h" #include "Luau/Symbol.h"
#include "Luau/TypedAllocator.h" #include "Luau/TypedAllocator.h"
@ -36,8 +35,8 @@ struct DataFlowGraph
DataFlowGraph& operator=(DataFlowGraph&&) = default; DataFlowGraph& operator=(DataFlowGraph&&) = default;
DefId getDef(const AstExpr* expr) const; DefId getDef(const AstExpr* expr) const;
// Look up the definition optionally, knowing it may not be present. // Look up for the rvalue def for a compound assignment.
std::optional<DefId> getDefOptional(const AstExpr* expr) const; std::optional<DefId> getRValueDefForCompoundAssign(const AstExpr* expr) const;
DefId getDef(const AstLocal* local) const; DefId getDef(const AstLocal* local) const;
@ -47,13 +46,13 @@ struct DataFlowGraph
const RefinementKey* getRefinementKey(const AstExpr* expr) const; const RefinementKey* getRefinementKey(const AstExpr* expr) const;
private: private:
DataFlowGraph(NotNull<DefArena> defArena, NotNull<RefinementKeyArena> keyArena); DataFlowGraph() = default;
DataFlowGraph(const DataFlowGraph&) = delete; DataFlowGraph(const DataFlowGraph&) = delete;
DataFlowGraph& operator=(const DataFlowGraph&) = delete; DataFlowGraph& operator=(const DataFlowGraph&) = delete;
NotNull<DefArena> defArena; DefArena defArena;
NotNull<RefinementKeyArena> keyArena; RefinementKeyArena keyArena;
DenseHashMap<const AstExpr*, const Def*> astDefs{nullptr}; DenseHashMap<const AstExpr*, const Def*> astDefs{nullptr};
@ -64,7 +63,12 @@ private:
// All keys in this maps are really only statements that ambiently declares a symbol. // All keys in this maps are really only statements that ambiently declares a symbol.
DenseHashMap<const AstStat*, const Def*> declaredDefs{nullptr}; DenseHashMap<const AstStat*, const Def*> declaredDefs{nullptr};
// Compound assignments are in a weird situation where the local being assigned to is also being used at its
// previous type implicitly in an rvalue position. This map provides the previous binding.
DenseHashMap<const AstExpr*, const Def*> compoundAssignDefs{nullptr};
DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr}; DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr};
friend struct DataFlowGraphBuilder; friend struct DataFlowGraphBuilder;
}; };
@ -101,37 +105,25 @@ struct DataFlowResult
const RefinementKey* parent = nullptr; const RefinementKey* parent = nullptr;
}; };
using ScopeStack = std::vector<DfgScope*>;
struct DataFlowGraphBuilder struct DataFlowGraphBuilder
{ {
static DataFlowGraph build( static DataFlowGraph build(AstStatBlock* root, NotNull<struct InternalErrorReporter> handle);
AstStatBlock* block,
NotNull<DefArena> defArena,
NotNull<RefinementKeyArena> keyArena,
NotNull<struct InternalErrorReporter> handle
);
private: private:
DataFlowGraphBuilder(NotNull<DefArena> defArena, NotNull<RefinementKeyArena> keyArena); DataFlowGraphBuilder() = default;
DataFlowGraphBuilder(const DataFlowGraphBuilder&) = delete; DataFlowGraphBuilder(const DataFlowGraphBuilder&) = delete;
DataFlowGraphBuilder& operator=(const DataFlowGraphBuilder&) = delete; DataFlowGraphBuilder& operator=(const DataFlowGraphBuilder&) = delete;
DataFlowGraph graph; DataFlowGraph graph;
NotNull<DefArena> defArena; NotNull<DefArena> defArena{&graph.defArena};
NotNull<RefinementKeyArena> keyArena; NotNull<RefinementKeyArena> keyArena{&graph.keyArena};
struct InternalErrorReporter* handle = nullptr; struct InternalErrorReporter* handle = nullptr;
DfgScope* moduleScope = nullptr;
/// The arena owning all of the scope allocations for the dataflow graph being built.
std::vector<std::unique_ptr<DfgScope>> scopes; std::vector<std::unique_ptr<DfgScope>> scopes;
/// A stack of scopes used by the visitor to see where we are.
ScopeStack scopeStack;
NotNull<DfgScope> currentScope();
DfgScope* currentScope_DEPRECATED();
struct FunctionCapture struct FunctionCapture
{ {
std::vector<DefId> captureDefs; std::vector<DefId> captureDefs;
@ -142,81 +134,81 @@ private:
DenseHashMap<Symbol, FunctionCapture> captures{Symbol{}}; DenseHashMap<Symbol, FunctionCapture> captures{Symbol{}};
void resolveCaptures(); void resolveCaptures();
DfgScope* makeChildScope(DfgScope::ScopeType scopeType = DfgScope::Linear); DfgScope* childScope(DfgScope* scope, DfgScope::ScopeType scopeType = DfgScope::Linear);
void join(DfgScope* p, DfgScope* a, DfgScope* b); void join(DfgScope* p, DfgScope* a, DfgScope* b);
void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b); void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b);
void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b); void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b);
DefId lookup(Symbol symbol, Location location); DefId lookup(DfgScope* scope, Symbol symbol);
DefId lookup(DefId def, const std::string& key, Location location); DefId lookup(DfgScope* scope, DefId def, const std::string& key);
ControlFlow visit(AstStatBlock* b); ControlFlow visit(DfgScope* scope, AstStatBlock* b);
ControlFlow visitBlockWithoutChildScope(AstStatBlock* b); ControlFlow visitBlockWithoutChildScope(DfgScope* scope, AstStatBlock* b);
ControlFlow visit(AstStat* s); ControlFlow visit(DfgScope* scope, AstStat* s);
ControlFlow visit(AstStatIf* i); ControlFlow visit(DfgScope* scope, AstStatIf* i);
ControlFlow visit(AstStatWhile* w); ControlFlow visit(DfgScope* scope, AstStatWhile* w);
ControlFlow visit(AstStatRepeat* r); ControlFlow visit(DfgScope* scope, AstStatRepeat* r);
ControlFlow visit(AstStatBreak* b); ControlFlow visit(DfgScope* scope, AstStatBreak* b);
ControlFlow visit(AstStatContinue* c); ControlFlow visit(DfgScope* scope, AstStatContinue* c);
ControlFlow visit(AstStatReturn* r); ControlFlow visit(DfgScope* scope, AstStatReturn* r);
ControlFlow visit(AstStatExpr* e); ControlFlow visit(DfgScope* scope, AstStatExpr* e);
ControlFlow visit(AstStatLocal* l); ControlFlow visit(DfgScope* scope, AstStatLocal* l);
ControlFlow visit(AstStatFor* f); ControlFlow visit(DfgScope* scope, AstStatFor* f);
ControlFlow visit(AstStatForIn* f); ControlFlow visit(DfgScope* scope, AstStatForIn* f);
ControlFlow visit(AstStatAssign* a); ControlFlow visit(DfgScope* scope, AstStatAssign* a);
ControlFlow visit(AstStatCompoundAssign* c); ControlFlow visit(DfgScope* scope, AstStatCompoundAssign* c);
ControlFlow visit(AstStatFunction* f); ControlFlow visit(DfgScope* scope, AstStatFunction* f);
ControlFlow visit(AstStatLocalFunction* l); ControlFlow visit(DfgScope* scope, AstStatLocalFunction* l);
ControlFlow visit(AstStatTypeAlias* t); ControlFlow visit(DfgScope* scope, AstStatTypeAlias* t);
ControlFlow visit(AstStatTypeFunction* f); ControlFlow visit(DfgScope* scope, AstStatTypeFunction* f);
ControlFlow visit(AstStatDeclareGlobal* d); ControlFlow visit(DfgScope* scope, AstStatDeclareGlobal* d);
ControlFlow visit(AstStatDeclareFunction* d); ControlFlow visit(DfgScope* scope, AstStatDeclareFunction* d);
ControlFlow visit(AstStatDeclareExternType* d); ControlFlow visit(DfgScope* scope, AstStatDeclareClass* d);
ControlFlow visit(AstStatError* error); ControlFlow visit(DfgScope* scope, AstStatError* error);
DataFlowResult visitExpr(AstExpr* e); DataFlowResult visitExpr(DfgScope* scope, AstExpr* e);
DataFlowResult visitExpr(AstExprGroup* group); DataFlowResult visitExpr(DfgScope* scope, AstExprGroup* group);
DataFlowResult visitExpr(AstExprLocal* l); DataFlowResult visitExpr(DfgScope* scope, AstExprLocal* l);
DataFlowResult visitExpr(AstExprGlobal* g); DataFlowResult visitExpr(DfgScope* scope, AstExprGlobal* g);
DataFlowResult visitExpr(AstExprCall* c); DataFlowResult visitExpr(DfgScope* scope, AstExprCall* c);
DataFlowResult visitExpr(AstExprIndexName* i); DataFlowResult visitExpr(DfgScope* scope, AstExprIndexName* i);
DataFlowResult visitExpr(AstExprIndexExpr* i); DataFlowResult visitExpr(DfgScope* scope, AstExprIndexExpr* i);
DataFlowResult visitExpr(AstExprFunction* f); DataFlowResult visitExpr(DfgScope* scope, AstExprFunction* f);
DataFlowResult visitExpr(AstExprTable* t); DataFlowResult visitExpr(DfgScope* scope, AstExprTable* t);
DataFlowResult visitExpr(AstExprUnary* u); DataFlowResult visitExpr(DfgScope* scope, AstExprUnary* u);
DataFlowResult visitExpr(AstExprBinary* b); DataFlowResult visitExpr(DfgScope* scope, AstExprBinary* b);
DataFlowResult visitExpr(AstExprTypeAssertion* t); DataFlowResult visitExpr(DfgScope* scope, AstExprTypeAssertion* t);
DataFlowResult visitExpr(AstExprIfElse* i); DataFlowResult visitExpr(DfgScope* scope, AstExprIfElse* i);
DataFlowResult visitExpr(AstExprInterpString* i); DataFlowResult visitExpr(DfgScope* scope, AstExprInterpString* i);
DataFlowResult visitExpr(AstExprError* error); DataFlowResult visitExpr(DfgScope* scope, AstExprError* error);
void visitLValue(AstExpr* e, DefId incomingDef); void visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef);
DefId visitLValue(AstExprLocal* l, DefId incomingDef); DefId visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef);
DefId visitLValue(AstExprGlobal* g, DefId incomingDef); DefId visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef);
DefId visitLValue(AstExprIndexName* i, DefId incomingDef); DefId visitLValue(DfgScope* scope, AstExprIndexName* i, DefId incomingDef);
DefId visitLValue(AstExprIndexExpr* i, DefId incomingDef); DefId visitLValue(DfgScope* scope, AstExprIndexExpr* i, DefId incomingDef);
DefId visitLValue(AstExprError* e, DefId incomingDef); DefId visitLValue(DfgScope* scope, AstExprError* e, DefId incomingDef);
void visitType(AstType* t); void visitType(DfgScope* scope, AstType* t);
void visitType(AstTypeReference* r); void visitType(DfgScope* scope, AstTypeReference* r);
void visitType(AstTypeTable* t); void visitType(DfgScope* scope, AstTypeTable* t);
void visitType(AstTypeFunction* f); void visitType(DfgScope* scope, AstTypeFunction* f);
void visitType(AstTypeTypeof* t); void visitType(DfgScope* scope, AstTypeTypeof* t);
void visitType(AstTypeUnion* u); void visitType(DfgScope* scope, AstTypeUnion* u);
void visitType(AstTypeIntersection* i); void visitType(DfgScope* scope, AstTypeIntersection* i);
void visitType(AstTypeError* error); void visitType(DfgScope* scope, AstTypeError* error);
void visitTypePack(AstTypePack* p); void visitTypePack(DfgScope* scope, AstTypePack* p);
void visitTypePack(AstTypePackExplicit* e); void visitTypePack(DfgScope* scope, AstTypePackExplicit* e);
void visitTypePack(AstTypePackVariadic* v); void visitTypePack(DfgScope* scope, AstTypePackVariadic* v);
void visitTypePack(AstTypePackGeneric* g); void visitTypePack(DfgScope* scope, AstTypePackGeneric* g);
void visitTypeList(AstTypeList l); void visitTypeList(DfgScope* scope, AstTypeList l);
void visitGenerics(AstArray<AstGenericType*> g); void visitGenerics(DfgScope* scope, AstArray<AstGenericType> g);
void visitGenericPacks(AstArray<AstGenericTypePack*> g); void visitGenericPacks(DfgScope* scope, AstArray<AstGenericTypePack> g);
}; };
} // namespace Luau } // namespace Luau

View file

@ -4,8 +4,7 @@
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/TypedAllocator.h" #include "Luau/TypedAllocator.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/Location.h"
#include "Luau/Symbol.h"
#include <string> #include <string>
#include <optional> #include <optional>
@ -14,7 +13,6 @@ namespace Luau
struct Def; struct Def;
using DefId = NotNull<const Def>; using DefId = NotNull<const Def>;
struct AstLocal;
/** /**
* A cell is a "single-object" value. * A cell is a "single-object" value.
@ -66,8 +64,6 @@ struct Def
using V = Variant<struct Cell, struct Phi>; using V = Variant<struct Cell, struct Phi>;
V v; V v;
Symbol name;
Location location;
}; };
template<typename T> template<typename T>
@ -83,7 +79,7 @@ struct DefArena
{ {
TypedAllocator<Def> allocator; TypedAllocator<Def> allocator;
DefId freshCell(Symbol sym, Location location, bool subscripted = false); DefId freshCell(bool subscripted = false);
DefId phi(DefId a, DefId b); DefId phi(DefId a, DefId b);
DefId phi(const std::vector<DefId>& defs); DefId phi(const std::vector<DefId>& defs);
}; };

View file

@ -1,50 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/TypeFwd.h"
#include "Luau/NotNull.h"
#include "Luau/DenseHash.h"
#include <memory>
#include <optional>
#include <vector>
namespace Luau
{
struct TypeArena;
}
// The EqSat stuff is pretty template heavy, so we go to some lengths to prevent
// the complexity from leaking outside its implementation sources.
namespace Luau::EqSatSimplification
{
struct Simplifier;
using SimplifierPtr = std::unique_ptr<Simplifier, void (*)(Simplifier*)>;
SimplifierPtr newSimplifier(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes);
} // namespace Luau::EqSatSimplification
namespace Luau
{
struct EqSatSimplificationResult
{
TypeId result;
// New type function applications that were created by the reduction phase.
// We return these so that the ConstraintSolver can know to try to reduce
// them.
std::vector<TypeId> newTypeFunctions;
};
using EqSatSimplification::newSimplifier; // NOLINT: clang-tidy thinks these are unused. It is incorrect.
using Luau::EqSatSimplification::Simplifier; // NOLINT
using Luau::EqSatSimplification::SimplifierPtr;
std::optional<EqSatSimplificationResult> eqSatSimplify(NotNull<Simplifier> simplifier, TypeId ty);
} // namespace Luau

View file

@ -1,376 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/EGraph.h"
#include "Luau/Id.h"
#include "Luau/Language.h"
#include "Luau/Lexer.h" // For Allocator
#include "Luau/NotNull.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeFwd.h"
namespace Luau
{
struct TypeFunction;
}
namespace Luau::EqSatSimplification
{
using StringId = uint32_t;
using Id = Luau::EqSat::Id;
LUAU_EQSAT_UNIT(TNil);
LUAU_EQSAT_UNIT(TBoolean);
LUAU_EQSAT_UNIT(TNumber);
LUAU_EQSAT_UNIT(TString);
LUAU_EQSAT_UNIT(TThread);
LUAU_EQSAT_UNIT(TTopFunction);
LUAU_EQSAT_UNIT(TTopTable);
LUAU_EQSAT_UNIT(TTopClass);
LUAU_EQSAT_UNIT(TBuffer);
// Used for any type that eqsat can't do anything interesting with.
LUAU_EQSAT_ATOM(TOpaque, TypeId);
LUAU_EQSAT_ATOM(SBoolean, bool);
LUAU_EQSAT_ATOM(SString, StringId);
LUAU_EQSAT_ATOM(TFunction, TypeId);
LUAU_EQSAT_ATOM(TImportedTable, TypeId);
LUAU_EQSAT_ATOM(TClass, TypeId);
LUAU_EQSAT_UNIT(TAny);
LUAU_EQSAT_UNIT(TError);
LUAU_EQSAT_UNIT(TUnknown);
LUAU_EQSAT_UNIT(TNever);
LUAU_EQSAT_NODE_SET(Union);
LUAU_EQSAT_NODE_SET(Intersection);
LUAU_EQSAT_NODE_ARRAY(Negation, 1);
LUAU_EQSAT_NODE_ATOM_WITH_VECTOR(TTypeFun, std::shared_ptr<const TypeFunctionInstanceType>);
LUAU_EQSAT_UNIT(TNoRefine);
LUAU_EQSAT_UNIT(Invalid);
// enodes are immutable, but types are cyclic. We need a way to tie the knot.
// We handle this by generating TBound nodes at points where we encounter cycles.
// Each TBound has an ordinal that we later map onto the type.
// We use a substitution rule to replace all TBound nodes with their referrent.
LUAU_EQSAT_ATOM(TBound, size_t);
// Tables are sufficiently unlike other enodes that the Language.h macros won't cut it.
struct TTable
{
explicit TTable(Id basis);
TTable(Id basis, std::vector<StringId> propNames_, std::vector<Id> propTypes_);
// All TTables extend some other table. This may be TTopTable.
//
// It will frequently be a TImportedTable, in which case we can reuse things
// like source location and documentation info.
Id getBasis() const;
EqSat::Slice<const Id> propTypes() const;
// TODO: Also support read-only table props
// TODO: Indexer type, index result type.
std::vector<StringId> propNames;
// The enode interface
EqSat::Slice<Id> mutableOperands();
EqSat::Slice<const Id> operands() const;
bool operator==(const TTable& rhs) const;
bool operator!=(const TTable& rhs) const
{
return !(*this == rhs);
}
struct Hash
{
size_t operator()(const TTable& value) const;
};
private:
// The first element of this vector is the basis. Subsequent elements are
// property types. As we add other things like read-only properties and
// indexers, the structure of this array is likely to change.
//
// We encode our data in this way so that the operands() method can properly
// return a Slice<Id>.
std::vector<Id> storage;
};
template<typename L>
using Node = EqSat::Node<L>;
using EType = EqSat::Language<
TNil,
TBoolean,
TNumber,
TString,
TThread,
TTopFunction,
TTopTable,
TTopClass,
TBuffer,
TOpaque,
SBoolean,
SString,
TFunction,
TTable,
TImportedTable,
TClass,
TAny,
TError,
TUnknown,
TNever,
Union,
Intersection,
Negation,
TTypeFun,
Invalid,
TNoRefine,
TBound>;
struct StringCache
{
Allocator allocator;
DenseHashMap<std::string_view, StringId> strings{{}};
std::vector<std::string_view> views;
StringId add(std::string_view s);
std::string_view asStringView(StringId id) const;
std::string asString(StringId id) const;
};
using EGraph = Luau::EqSat::EGraph<EType, struct Simplify>;
struct Simplify
{
using Data = bool;
template<typename T>
Data make(const EGraph&, const T&) const;
void join(Data& left, const Data& right) const;
};
struct Subst
{
Id eclass;
Id newClass;
// The node into eclass which is boring, if any
std::optional<size_t> boringIndex;
std::string desc;
Subst(Id eclass, Id newClass, std::string desc = "");
};
struct Simplifier
{
NotNull<TypeArena> arena;
NotNull<BuiltinTypes> builtinTypes;
EGraph egraph;
StringCache stringCache;
// enodes are immutable but types can be cyclic, so we need some way to
// encode the cycle. This map is used to connect TBound nodes to the right
// eclass.
//
// The cyclicIntersection rewrite rule uses this to sense when a cycle can
// be deleted from an intersection or union.
std::unordered_map<size_t, Id> mappingIdToClass;
std::vector<Subst> substs;
using RewriteRuleFn = void (Simplifier::*)(Id id);
Simplifier(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes);
// Utilities
const EqSat::EClass<EType, Simplify::Data>& get(Id id) const;
Id find(Id id) const;
Id add(EType enode);
template<typename Tag>
const Tag* isTag(Id id) const;
template<typename Tag>
const Tag* isTag(const EType& enode) const;
void subst(Id from, Id to);
void subst(Id from, Id to, const std::string& ruleName);
void subst(Id from, Id to, const std::string& ruleName, const std::unordered_map<Id, size_t>& forceNodes);
void subst(Id from, size_t boringIndex, Id to, const std::string& ruleName, const std::unordered_map<Id, size_t>& forceNodes);
void unionClasses(std::vector<Id>& hereParts, Id there);
// Rewrite rules
void simplifyUnion(Id id);
void uninhabitedIntersection(Id id);
void intersectWithNegatedClass(Id id);
void intersectWithNegatedAtom(Id id);
void intersectWithNoRefine(Id id);
void cyclicIntersectionOfUnion(Id id);
void cyclicUnionOfIntersection(Id id);
void expandNegation(Id id);
void intersectionOfUnion(Id id);
void intersectTableProperty(Id id);
void uninhabitedTable(Id id);
void unneededTableModification(Id id);
void builtinTypeFunctions(Id id);
void iffyTypeFunctions(Id id);
void strictMetamethods(Id id);
};
template<typename Tag>
struct QueryIterator
{
QueryIterator();
QueryIterator(EGraph* egraph, Id eclass);
bool operator==(const QueryIterator& other) const;
bool operator!=(const QueryIterator& other) const;
std::pair<const Tag*, size_t> operator*() const;
QueryIterator& operator++();
QueryIterator& operator++(int);
private:
EGraph* egraph = nullptr;
Id eclass;
size_t index = 0;
};
template<typename Tag>
struct Query
{
EGraph* egraph;
Id eclass;
Query(EGraph* egraph, Id eclass)
: egraph(egraph)
, eclass(eclass)
{
}
QueryIterator<Tag> begin()
{
return QueryIterator<Tag>{egraph, eclass};
}
QueryIterator<Tag> end()
{
return QueryIterator<Tag>{};
}
};
template<typename Tag>
QueryIterator<Tag>::QueryIterator()
: egraph(nullptr)
, eclass(Id{0})
, index(0)
{
}
template<typename Tag>
QueryIterator<Tag>::QueryIterator(EGraph* egraph_, Id eclass)
: egraph(egraph_)
, eclass(eclass)
, index(0)
{
const auto& ecl = (*egraph)[eclass];
static constexpr const int idx = EType::VariantTy::getTypeId<Tag>();
for (const auto& enode : ecl.nodes)
{
if (enode.node.index() < idx)
++index;
else
break;
}
if (index >= ecl.nodes.size() || ecl.nodes[index].node.index() != idx)
{
egraph = nullptr;
index = 0;
}
}
template<typename Tag>
bool QueryIterator<Tag>::operator==(const QueryIterator<Tag>& rhs) const
{
if (egraph == nullptr && rhs.egraph == nullptr)
return true;
return egraph == rhs.egraph && eclass == rhs.eclass && index == rhs.index;
}
template<typename Tag>
bool QueryIterator<Tag>::operator!=(const QueryIterator<Tag>& rhs) const
{
return !(*this == rhs);
}
template<typename Tag>
std::pair<const Tag*, size_t> QueryIterator<Tag>::operator*() const
{
LUAU_ASSERT(egraph != nullptr);
EGraph::EClassT& ecl = (*egraph)[eclass];
LUAU_ASSERT(index < ecl.nodes.size());
auto& enode = ecl.nodes[index].node;
Tag* result = enode.template get<Tag>();
LUAU_ASSERT(result);
return {result, index};
}
// pre-increment
template<typename Tag>
QueryIterator<Tag>& QueryIterator<Tag>::operator++()
{
const auto& ecl = (*egraph)[eclass];
do
{
++index;
if (index >= ecl.nodes.size() || ecl.nodes[index].node.index() != EType::VariantTy::getTypeId<Tag>())
{
egraph = nullptr;
index = 0;
break;
}
} while (ecl.nodes[index].boring);
return *this;
}
// post-increment
template<typename Tag>
QueryIterator<Tag>& QueryIterator<Tag>::operator++(int)
{
QueryIterator<Tag> res = *this;
++res;
return res;
}
} // namespace Luau::EqSatSimplification

View file

@ -332,11 +332,11 @@ struct TypePackMismatch
bool operator==(const TypePackMismatch& rhs) const; bool operator==(const TypePackMismatch& rhs) const;
}; };
struct DynamicPropertyLookupOnExternTypesUnsafe struct DynamicPropertyLookupOnClassesUnsafe
{ {
TypeId ty; TypeId ty;
bool operator==(const DynamicPropertyLookupOnExternTypesUnsafe& rhs) const; bool operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const;
}; };
struct UninhabitedTypeFunction struct UninhabitedTypeFunction
@ -448,20 +448,6 @@ struct UnexpectedTypePackInSubtyping
bool operator==(const UnexpectedTypePackInSubtyping& rhs) const; bool operator==(const UnexpectedTypePackInSubtyping& rhs) const;
}; };
struct UserDefinedTypeFunctionError
{
std::string message;
bool operator==(const UserDefinedTypeFunctionError& rhs) const;
};
struct ReservedIdentifier
{
std::string name;
bool operator==(const ReservedIdentifier& rhs) const;
};
using TypeErrorData = Variant< using TypeErrorData = Variant<
TypeMismatch, TypeMismatch,
UnknownSymbol, UnknownSymbol,
@ -499,7 +485,7 @@ using TypeErrorData = Variant<
TypesAreUnrelated, TypesAreUnrelated,
NormalizationTooComplex, NormalizationTooComplex,
TypePackMismatch, TypePackMismatch,
DynamicPropertyLookupOnExternTypesUnsafe, DynamicPropertyLookupOnClassesUnsafe,
UninhabitedTypeFunction, UninhabitedTypeFunction,
UninhabitedTypePackFunction, UninhabitedTypePackFunction,
WhereClauseNeeded, WhereClauseNeeded,
@ -510,9 +496,7 @@ using TypeErrorData = Variant<
CheckedFunctionIncorrectArgs, CheckedFunctionIncorrectArgs,
UnexpectedTypeInSubtyping, UnexpectedTypeInSubtyping,
UnexpectedTypePackInSubtyping, UnexpectedTypePackInSubtyping,
ExplicitFunctionAnnotationRecommended, ExplicitFunctionAnnotationRecommended>;
UserDefinedTypeFunctionError,
ReservedIdentifier>;
struct TypeErrorSummary struct TypeErrorSummary
{ {

View file

@ -1,10 +1,8 @@
// 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
#pragma once #pragma once
#include <memory>
#include <optional>
#include <string> #include <string>
#include <vector> #include <optional>
namespace Luau namespace Luau
{ {
@ -20,7 +18,7 @@ struct SourceCode
None, None,
Module, Module,
Script, Script,
Local_DEPRECATED Local
}; };
std::string source; std::string source;
@ -33,71 +31,8 @@ struct ModuleInfo
bool optional = false; bool optional = false;
}; };
struct RequireAlias
{
std::string alias; // Unprefixed alias name (no leading `@`).
std::vector<std::string> tags = {};
};
struct RequireNode
{
virtual ~RequireNode() {}
// Get the path component representing this node.
virtual std::string getPathComponent() const = 0;
// Get the displayed user-facing label for this node, defaults to getPathComponent()
virtual std::string getLabel() const
{
return getPathComponent();
}
// Get tags to attach to this node's RequireSuggestion (defaults to none).
virtual std::vector<std::string> getTags() const
{
return {};
}
// TODO: resolvePathToNode() can ultimately be replaced with a call into
// require-by-string's path resolution algorithm. This will first require
// generalizing that algorithm to work with a virtual file system.
virtual std::unique_ptr<RequireNode> resolvePathToNode(const std::string& path) const = 0;
// Get children of this node, if any (if this node represents a directory).
virtual std::vector<std::unique_ptr<RequireNode>> getChildren() const = 0;
// A list of the aliases available to this node.
virtual std::vector<RequireAlias> getAvailableAliases() const = 0;
};
struct RequireSuggestion
{
std::string label;
std::string fullPath;
std::vector<std::string> tags;
};
using RequireSuggestions = std::vector<RequireSuggestion>;
struct RequireSuggester
{
virtual ~RequireSuggester() {}
std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
protected:
virtual std::unique_ptr<RequireNode> getNode(const ModuleName& name) const = 0;
private:
std::optional<RequireSuggestions> getRequireSuggestionsImpl(const ModuleName& requirer, const std::optional<std::string>& path) const;
};
struct FileResolver struct FileResolver
{ {
FileResolver() = default;
FileResolver(std::shared_ptr<RequireSuggester> requireSuggester)
: requireSuggester(std::move(requireSuggester))
{
}
virtual ~FileResolver() {} virtual ~FileResolver() {}
virtual std::optional<SourceCode> readSource(const ModuleName& name) = 0; virtual std::optional<SourceCode> readSource(const ModuleName& name) = 0;
@ -116,10 +51,6 @@ struct FileResolver
{ {
return std::nullopt; return std::nullopt;
} }
std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
std::shared_ptr<RequireSuggester> requireSuggester;
}; };
struct NullFileResolver : FileResolver struct NullFileResolver : FileResolver

View file

@ -1,196 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Ast.h"
#include "Luau/Parser.h"
#include "Luau/AutocompleteTypes.h"
#include "Luau/DenseHash.h"
#include "Luau/Module.h"
#include "Luau/Frontend.h"
#include <memory>
#include <vector>
namespace Luau
{
struct FrontendOptions;
enum class FragmentAutocompleteWaypoint
{
ParseFragmentEnd,
CloneModuleStart,
CloneModuleEnd,
DfgBuildEnd,
CloneAndSquashScopeStart,
CloneAndSquashScopeEnd,
ConstraintSolverStart,
ConstraintSolverEnd,
TypecheckFragmentEnd,
AutocompleteEnd,
COUNT,
};
class IFragmentAutocompleteReporter
{
public:
virtual void reportWaypoint(FragmentAutocompleteWaypoint) = 0;
virtual void reportFragmentString(std::string_view) = 0;
};
enum class FragmentTypeCheckStatus
{
SkipAutocomplete,
Success,
};
struct FragmentAutocompleteAncestryResult
{
DenseHashMap<AstName, AstLocal*> localMap{AstName()};
std::vector<AstLocal*> localStack;
std::vector<AstNode*> ancestry;
AstStat* nearestStatement = nullptr;
AstStatBlock* parentBlock = nullptr;
Location fragmentSelectionRegion;
};
struct FragmentParseResult
{
std::string fragmentToParse;
AstStatBlock* root = nullptr;
std::vector<AstNode*> ancestry;
AstStat* nearestStatement = nullptr;
std::vector<Comment> commentLocations;
std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>();
Position scopePos{0, 0};
};
struct FragmentTypeCheckResult
{
ModulePtr incrementalModule = nullptr;
ScopePtr freshScope;
std::vector<AstNode*> ancestry;
};
struct FragmentAutocompleteResult
{
ModulePtr incrementalModule;
Scope* freshScope;
TypeArena arenaForAutocomplete_DEPRECATED;
AutocompleteResult acResults;
};
struct FragmentRegion
{
Location fragmentLocation;
AstStat* nearestStatement = nullptr; // used for tests
AstStatBlock* parentBlock = nullptr; // used for scope detection
};
std::optional<Position> blockDiffStart(AstStatBlock* blockOld, AstStatBlock* blockNew, AstStat* nearestStatementNewAst);
FragmentRegion getFragmentRegion(AstStatBlock* root, const Position& cursorPosition);
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* stale, const Position& cursorPos, AstStatBlock* lastGoodParse);
FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstStatBlock* root, const Position& cursorPos);
std::optional<FragmentParseResult> parseFragment_DEPRECATED(
AstStatBlock* root,
AstNameTable* names,
std::string_view src,
const Position& cursorPos,
std::optional<Position> fragmentEndPosition
);
std::optional<FragmentParseResult> parseFragment(
AstStatBlock* stale,
AstStatBlock* mostRecentParse,
AstNameTable* names,
std::string_view src,
const Position& cursorPos,
std::optional<Position> fragmentEndPosition
);
std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
Frontend& frontend,
const ModuleName& moduleName,
const Position& cursorPos,
std::optional<FrontendOptions> opts,
std::string_view src,
std::optional<Position> fragmentEndPosition,
AstStatBlock* recentParse = nullptr,
IFragmentAutocompleteReporter* reporter = nullptr
);
FragmentAutocompleteResult fragmentAutocomplete(
Frontend& frontend,
std::string_view src,
const ModuleName& moduleName,
Position cursorPosition,
std::optional<FrontendOptions> opts,
StringCompletionCallback callback,
std::optional<Position> fragmentEndPosition = std::nullopt,
AstStatBlock* recentParse = nullptr,
IFragmentAutocompleteReporter* reporter = nullptr
);
enum class FragmentAutocompleteStatus
{
Success,
FragmentTypeCheckFail,
InternalIce
};
struct FragmentAutocompleteStatusResult
{
FragmentAutocompleteStatus status;
std::optional<FragmentAutocompleteResult> result;
};
struct FragmentContext
{
std::string_view newSrc;
const ParseResult& freshParse;
std::optional<FrontendOptions> opts;
std::optional<Position> DEPRECATED_fragmentEndPosition;
IFragmentAutocompleteReporter* reporter = nullptr;
};
/**
* @brief Attempts to compute autocomplete suggestions from the fragment context.
*
* This function computes autocomplete suggestions using outdated frontend typechecking data
* by patching the fragment context of the new script source content.
*
* @param frontend The Luau Frontend data structure, which may contain outdated typechecking data.
*
* @param moduleName The name of the target module, specifying which script the caller wants to request autocomplete for.
*
* @param cursorPosition The position in the script where the caller wants to trigger autocomplete.
*
* @param context The fragment context that this API will use to patch the outdated typechecking data.
*
* @param stringCompletionCB A callback function that provides autocomplete suggestions for string contexts.
*
* @return
* The status indicating whether `fragmentAutocomplete` ran successfully or failed, along with the reason for failure.
* Also includes autocomplete suggestions if the status is successful.
*
* @usage
* FragmentAutocompleteStatusResult acStatusResult;
* if (shouldFragmentAC)
* acStatusResult = Luau::tryFragmentAutocomplete(...);
*
* if (acStatusResult.status != Successful)
* {
* frontend.check(moduleName, options);
* acStatusResult.acResult = Luau::autocomplete(...);
* }
* return convertResultWithContext(acStatusResult.acResult);
*/
FragmentAutocompleteStatusResult tryFragmentAutocomplete(
Frontend& frontend,
const ModuleName& moduleName,
Position cursorPosition,
FragmentContext context,
StringCompletionCallback stringCompletionCB
);
} // namespace Luau

View file

@ -7,9 +7,9 @@
#include "Luau/ModuleResolver.h" #include "Luau/ModuleResolver.h"
#include "Luau/RequireTracer.h" #include "Luau/RequireTracer.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/Set.h"
#include "Luau/TypeCheckLimits.h" #include "Luau/TypeCheckLimits.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/AnyTypeSummary.h"
#include <mutex> #include <mutex>
#include <string> #include <string>
@ -31,8 +31,8 @@ struct ModuleResolver;
struct ParseResult; struct ParseResult;
struct HotComment; struct HotComment;
struct BuildQueueItem; struct BuildQueueItem;
struct BuildQueueWorkState;
struct FrontendCancellationToken; struct FrontendCancellationToken;
struct AnyTypeSummary;
struct LoadDefinitionFileResult struct LoadDefinitionFileResult
{ {
@ -44,6 +44,21 @@ struct LoadDefinitionFileResult
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments); std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments);
std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr);
// Exported only for convenient testing.
std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& expr);
/** Try to convert an AST fragment into a ModuleName.
* Returns std::nullopt if the expression cannot be resolved. This will most likely happen in cases where
* the import path involves some dynamic computation that we cannot see into at typechecking time.
*
* Unintuitively, weirdly-formulated modules (like game.Parent.Parent.Parent.Foo) will successfully produce a ModuleName
* as long as it falls within the permitted syntax. This is ok because we will fail to find the module and produce an
* error when we try during typechecking.
*/
std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& expr);
struct SourceNode struct SourceNode
{ {
bool hasDirtySourceModule() const bool hasDirtySourceModule() const
@ -56,32 +71,13 @@ struct SourceNode
return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule; return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule;
} }
bool hasInvalidModuleDependency(bool forAutocomplete) const
{
return forAutocomplete ? invalidModuleDependencyForAutocomplete : invalidModuleDependency;
}
void setInvalidModuleDependency(bool value, bool forAutocomplete)
{
if (forAutocomplete)
invalidModuleDependencyForAutocomplete = value;
else
invalidModuleDependency = value;
}
ModuleName name; ModuleName name;
std::string humanReadableName; std::string humanReadableName;
DenseHashSet<ModuleName> requireSet{{}}; DenseHashSet<ModuleName> requireSet{{}};
std::vector<std::pair<ModuleName, Location>> requireLocations; std::vector<std::pair<ModuleName, Location>> requireLocations;
Set<ModuleName> dependents{{}};
bool dirtySourceModule = true; bool dirtySourceModule = true;
bool dirtyModule = true; bool dirtyModule = true;
bool dirtyModuleForAutocomplete = true; bool dirtyModuleForAutocomplete = true;
bool invalidModuleDependency = true;
bool invalidModuleDependencyForAutocomplete = true;
double autocompleteLimitsMult = 1.0; double autocompleteLimitsMult = 1.0;
}; };
@ -136,7 +132,7 @@ struct FrontendModuleResolver : ModuleResolver
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override; std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override;
std::string getHumanReadableModuleName(const ModuleName& moduleName) const override; std::string getHumanReadableModuleName(const ModuleName& moduleName) const override;
bool setModule(const ModuleName& moduleName, ModulePtr module); void setModule(const ModuleName& moduleName, ModulePtr module);
void clearModules(); void clearModules();
private: private:
@ -170,13 +166,9 @@ struct Frontend
// Parse and typecheck module graph // Parse and typecheck module graph
CheckResult check(const ModuleName& name, std::optional<FrontendOptions> optionOverride = {}); // new shininess CheckResult check(const ModuleName& name, std::optional<FrontendOptions> optionOverride = {}); // new shininess
bool allModuleDependenciesValid(const ModuleName& name, bool forAutocomplete = false) const;
bool isDirty(const ModuleName& name, bool forAutocomplete = false) const; bool isDirty(const ModuleName& name, bool forAutocomplete = false) const;
void markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty = nullptr); void markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty = nullptr);
void traverseDependents(const ModuleName& name, std::function<bool(SourceNode&)> processSubtree);
/** Borrow a pointer into the SourceModule cache. /** Borrow a pointer into the SourceModule cache.
* *
* Returns nullptr if we don't have it. This could mean that the script * Returns nullptr if we don't have it. This could mean that the script
@ -217,7 +209,6 @@ struct Frontend
); );
std::optional<CheckResult> getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete = false); std::optional<CheckResult> getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete = false);
std::vector<ModuleName> getRequiredScripts(const ModuleName& name);
private: private:
ModulePtr check( ModulePtr check(
@ -250,9 +241,6 @@ private:
void checkBuildQueueItem(BuildQueueItem& item); void checkBuildQueueItem(BuildQueueItem& item);
void checkBuildQueueItems(std::vector<BuildQueueItem>& items); void checkBuildQueueItems(std::vector<BuildQueueItem>& items);
void recordItemResult(const BuildQueueItem& item); void recordItemResult(const BuildQueueItem& item);
void performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos);
void sendQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos);
void sendQueueCycleItemTask(std::shared_ptr<BuildQueueWorkState> state);
static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config); static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config);
@ -298,7 +286,6 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& globalScope, const ScopePtr& globalScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options, FrontendOptions options,
TypeCheckLimits limits TypeCheckLimits limits
@ -313,7 +300,6 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& globalScope, const ScopePtr& globalScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options, FrontendOptions options,
TypeCheckLimits limits, TypeCheckLimits limits,

View file

@ -8,75 +8,12 @@
namespace Luau namespace Luau
{ {
template<typename TID>
struct GeneralizationParams
{
bool foundOutsideFunctions = false;
size_t useCount = 0;
Polarity polarity = Polarity::None;
};
template<typename TID>
struct GeneralizationResult
{
std::optional<TID> result;
// True if the provided type was replaced with a generic.
bool wasReplacedByGeneric = false;
bool resourceLimitsExceeded = false;
explicit operator bool() const
{
return bool(result);
}
};
// Replace a single free type by its bounds according to the polarity provided.
GeneralizationResult<TypeId> generalizeType(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
TypeId freeTy,
const GeneralizationParams<TypeId>& params
);
// Generalize one type pack
GeneralizationResult<TypePackId> generalizeTypePack(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
TypePackId tp,
const GeneralizationParams<TypePackId>& params
);
void sealTable(NotNull<Scope> scope, TypeId ty);
/** Attempt to generalize a type.
*
* If generalizationTarget is set, then only that type will be replaced by its
* bounds. The way this is intended to be used is that ty is some function that
* is not fully generalized, and generalizationTarget is a type within its
* signature. There should be no further constraints that could affect the
* bounds of generalizationTarget.
*
* Returns nullopt if generalization failed due to resources limits.
*/
std::optional<TypeId> generalize( std::optional<TypeId> generalize(
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope, NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes, NotNull<DenseHashSet<TypeId>> bakedTypes,
TypeId ty, TypeId ty,
std::optional<TypeId> generalizationTarget = {} /* avoid sealing tables*/ bool avoidSealingTables = false
); );
}
void pruneUnnecessaryGenerics(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty
);
} // namespace Luau

View file

@ -19,9 +19,7 @@ struct GlobalTypes
TypeArena globalTypes; TypeArena globalTypes;
SourceModule globalNames; // names for symbols entered into globalScope SourceModule globalNames; // names for symbols entered into globalScope
ScopePtr globalScope; // shared by all modules ScopePtr globalScope; // shared by all modules
ScopePtr globalTypeFunctionScope; // shared by all modules
}; };
} // namespace Luau } // namespace Luau

View file

@ -1,16 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/NotNull.h"
#include "Luau/TypeFwd.h"
namespace Luau
{
struct Scope;
struct TypeArena;
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypeId ty);
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypePackId tp);
} // namespace Luau

View file

@ -67,19 +67,6 @@ public:
return &pairs.at(it->second).second; return &pairs.at(it->second).second;
} }
V& operator[](const K& k)
{
auto it = indices.find(k);
if (it == indices.end())
{
pairs.push_back(std::make_pair(k, V()));
indices[k] = pairs.size() - 1;
return pairs.back().second;
}
else
return pairs.at(it->second).second;
}
const_iterator begin() const const_iterator begin() const
{ {
return pairs.begin(); return pairs.begin();

View file

@ -60,7 +60,7 @@ struct ReplaceGenerics : Substitution
}; };
// A substitution which replaces generic functions by monomorphic functions // A substitution which replaces generic functions by monomorphic functions
struct Instantiation final : Substitution struct Instantiation : Substitution
{ {
Instantiation(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope) Instantiation(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope)
: Substitution(log, arena) : Substitution(log, arena)
@ -133,9 +133,9 @@ struct GenericTypeFinder : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId ty, const Luau::ExternType&) override bool visit(TypeId ty, const Luau::ClassType&) override
{ {
// During function instantiation, extern types are not traversed even if they have generics // During function instantiation, classes are not traversed even if they have generics
return false; return false;
} }
}; };

View file

@ -53,7 +53,7 @@ struct Replacer : Substitution
}; };
// A substitution which replaces generic functions by monomorphic functions // A substitution which replaces generic functions by monomorphic functions
struct Instantiation2 final : Substitution struct Instantiation2 : Substitution
{ {
// Mapping from generic types to free types to be used in instantiation. // Mapping from generic types to free types to be used in instantiation.
DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr}; DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr};

View file

@ -8,7 +8,7 @@
#include "Luau/ParseResult.h" #include "Luau/ParseResult.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/DataFlowGraph.h" #include "Luau/AnyTypeSummary.h"
#include <memory> #include <memory>
#include <vector> #include <vector>
@ -18,13 +18,8 @@
namespace Luau namespace Luau
{ {
using LogLuauProc = void (*)(std::string_view, std::string_view);
extern LogLuauProc logLuau;
void setLogLuau(LogLuauProc ll);
void resetLogLuauProc();
struct Module; struct Module;
struct AnyTypeSummary;
using ScopePtr = std::shared_ptr<struct Scope>; using ScopePtr = std::shared_ptr<struct Scope>;
using ModulePtr = std::shared_ptr<Module>; using ModulePtr = std::shared_ptr<Module>;
@ -59,7 +54,6 @@ struct SourceModule
} }
}; };
bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos);
bool isWithinComment(const SourceModule& sourceModule, Position pos); bool isWithinComment(const SourceModule& sourceModule, Position pos);
bool isWithinComment(const ParseResult& result, Position pos); bool isWithinComment(const ParseResult& result, Position pos);
@ -73,19 +67,19 @@ struct Module
{ {
~Module(); ~Module();
// TODO: Clip this when we clip FFlagLuauSolverV2
bool checkedInNewSolver = false;
ModuleName name; ModuleName name;
std::string humanReadableName; std::string humanReadableName;
TypeArena interfaceTypes; TypeArena interfaceTypes;
TypeArena internalTypes; TypeArena internalTypes;
// Summary of Ast Nodes that either contain
// user annotated anys or typechecker inferred anys
AnyTypeSummary ats{};
// Scopes and AST types refer to parse data, so we need to keep that alive // Scopes and AST types refer to parse data, so we need to keep that alive
std::shared_ptr<Allocator> allocator; std::shared_ptr<Allocator> allocator;
std::shared_ptr<AstNameTable> names; std::shared_ptr<AstNameTable> names;
AstStatBlock* root = nullptr;
std::vector<std::pair<Location, ScopePtr>> scopes; // never empty std::vector<std::pair<Location, ScopePtr>> scopes; // never empty
@ -138,11 +132,6 @@ struct Module
TypePackId returnType = nullptr; TypePackId returnType = nullptr;
std::unordered_map<Name, TypeFun> exportedTypeBindings; std::unordered_map<Name, TypeFun> exportedTypeBindings;
// Arenas related to the DFG must persist after the DFG no longer exists, as
// Module objects maintain raw pointers to objects in these arenas.
DefArena defArena;
RefinementKeyArena keyArena;
bool hasModuleScope() const; bool hasModuleScope() const;
ScopePtr getModuleScope() const; ScopePtr getModuleScope() const;

View file

@ -20,6 +20,8 @@ struct ModuleResolver
virtual ~ModuleResolver() {} virtual ~ModuleResolver() {}
/** Compute a ModuleName from an AST fragment. This AST fragment is generally the argument to the require() function. /** Compute a ModuleName from an AST fragment. This AST fragment is generally the argument to the require() function.
*
* You probably want to implement this with some variation of pathExprToModuleName.
* *
* @returns The ModuleInfo if the expression is a syntactically legal path. * @returns The ModuleInfo if the expression is a syntactically legal path.
* @returns std::nullopt if we are unable to determine whether or not the expression is a valid path. Type inference will * @returns std::nullopt if we are unable to determine whether or not the expression is a valid path. Type inference will

View file

@ -1,23 +1,19 @@
// 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
#pragma once #pragma once
#include "Luau/DataFlowGraph.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/DataFlowGraph.h"
namespace Luau namespace Luau
{ {
struct BuiltinTypes; struct BuiltinTypes;
struct TypeFunctionRuntime;
struct UnifierSharedState; struct UnifierSharedState;
struct TypeCheckLimits; struct TypeCheckLimits;
void checkNonStrict( void checkNonStrict(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> ice, NotNull<InternalErrorReporter> ice,
NotNull<UnifierSharedState> unifierState, NotNull<UnifierSharedState> unifierState,
NotNull<const DataFlowGraph> dfg, NotNull<const DataFlowGraph> dfg,

View file

@ -1,7 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once #pragma once
#include "Luau/EqSatSimplification.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Set.h" #include "Luau/Set.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
@ -22,22 +21,10 @@ struct Scope;
using ModulePtr = std::shared_ptr<Module>; using ModulePtr = std::shared_ptr<Module>;
bool isSubtype( bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice);
TypeId subTy, bool isSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice);
TypeId superTy, bool isConsistentSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice);
NotNull<Scope> scope, bool isConsistentSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice);
NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
InternalErrorReporter& ice
);
bool isSubtype(
TypePackId subPack,
TypePackId superPack,
NotNull<Scope> scope,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
InternalErrorReporter& ice
);
class TypeIds class TypeIds
{ {
@ -181,7 +168,7 @@ struct NormalizedStringType
bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr); bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr);
struct NormalizedExternType struct NormalizedClassType
{ {
/** Has the following structure: /** Has the following structure:
* *
@ -192,7 +179,7 @@ struct NormalizedExternType
* *
* Each TypeId is a class type. * Each TypeId is a class type.
*/ */
std::unordered_map<TypeId, TypeIds> externTypes; std::unordered_map<TypeId, TypeIds> classes;
/** /**
* In order to maintain a consistent insertion order, we use this vector to * In order to maintain a consistent insertion order, we use this vector to
@ -245,7 +232,7 @@ enum class NormalizationResult
}; };
// A normalized type is either any, unknown, or one of the form P | T | F | G where // A normalized type is either any, unknown, or one of the form P | T | F | G where
// * P is a union of primitive types (including singletons, extern types and the error type) // * P is a union of primitive types (including singletons, classes and the error type)
// * T is a union of table types // * T is a union of table types
// * F is a union of an intersection of function types // * F is a union of an intersection of function types
// * G is a union of generic/free/blocked types, intersected with a normalized type // * G is a union of generic/free/blocked types, intersected with a normalized type
@ -260,7 +247,7 @@ struct NormalizedType
// This type is either never, boolean type, or a boolean singleton. // This type is either never, boolean type, or a boolean singleton.
TypeId booleans; TypeId booleans;
NormalizedExternType externTypes; NormalizedClassType classes;
// The error part of the type. // The error part of the type.
// This type is either never or the error type. // This type is either never or the error type.
@ -333,7 +320,7 @@ struct NormalizedType
// Helpers that improve readability of the above (they just say if the component is present) // Helpers that improve readability of the above (they just say if the component is present)
bool hasTops() const; bool hasTops() const;
bool hasBooleans() const; bool hasBooleans() const;
bool hasExternTypes() const; bool hasClasses() const;
bool hasErrors() const; bool hasErrors() const;
bool hasNils() const; bool hasNils() const;
bool hasNumbers() const; bool hasNumbers() const;
@ -349,7 +336,6 @@ struct NormalizedType
}; };
using SeenTablePropPairs = Set<std::pair<TypeId, TypeId>, TypeIdPairHash>;
class Normalizer class Normalizer
{ {
@ -391,10 +377,10 @@ public:
void unionTysWithTy(TypeIds& here, TypeId there); void unionTysWithTy(TypeIds& here, TypeId there);
TypeId unionOfTops(TypeId here, TypeId there); TypeId unionOfTops(TypeId here, TypeId there);
TypeId unionOfBools(TypeId here, TypeId there); TypeId unionOfBools(TypeId here, TypeId there);
void unionExternTypesWithExternType(TypeIds& heres, TypeId there); void unionClassesWithClass(TypeIds& heres, TypeId there);
void unionExternTypes(TypeIds& heres, const TypeIds& theres); void unionClasses(TypeIds& heres, const TypeIds& theres);
void unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there); void unionClassesWithClass(NormalizedClassType& heres, TypeId there);
void unionExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres); void unionClasses(NormalizedClassType& heres, const NormalizedClassType& theres);
void unionStrings(NormalizedStringType& here, const NormalizedStringType& there); void unionStrings(NormalizedStringType& here, const NormalizedStringType& there);
std::optional<TypePackId> unionOfTypePacks(TypePackId here, TypePackId there); std::optional<TypePackId> unionOfTypePacks(TypePackId here, TypePackId there);
std::optional<TypeId> unionOfFunctions(TypeId here, TypeId there); std::optional<TypeId> unionOfFunctions(TypeId here, TypeId there);
@ -404,13 +390,7 @@ public:
void unionTablesWithTable(TypeIds& heres, TypeId there); void unionTablesWithTable(TypeIds& heres, TypeId there);
void unionTables(TypeIds& heres, const TypeIds& theres); void unionTables(TypeIds& heres, const TypeIds& theres);
NormalizationResult unionNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars = -1); NormalizationResult unionNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars = -1);
NormalizationResult unionNormalWithTy( NormalizationResult unionNormalWithTy(NormalizedType& here, TypeId there, Set<TypeId>& seenSetTypes, int ignoreSmallerTyvars = -1);
NormalizedType& here,
TypeId there,
SeenTablePropPairs& seenTablePropPairs,
Set<TypeId>& seenSetTypes,
int ignoreSmallerTyvars = -1
);
// ------- Negations // ------- Negations
std::optional<NormalizedType> negateNormal(const NormalizedType& here); std::optional<NormalizedType> negateNormal(const NormalizedType& here);
@ -423,30 +403,20 @@ public:
// ------- Normalizing intersections // ------- Normalizing intersections
TypeId intersectionOfTops(TypeId here, TypeId there); TypeId intersectionOfTops(TypeId here, TypeId there);
TypeId intersectionOfBools(TypeId here, TypeId there); TypeId intersectionOfBools(TypeId here, TypeId there);
void intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres); void intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres);
void intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there); void intersectClassesWithClass(NormalizedClassType& heres, TypeId there);
void intersectStrings(NormalizedStringType& here, const NormalizedStringType& there); void intersectStrings(NormalizedStringType& here, const NormalizedStringType& there);
std::optional<TypePackId> intersectionOfTypePacks(TypePackId here, TypePackId there); std::optional<TypePackId> intersectionOfTypePacks(TypePackId here, TypePackId there);
std::optional<TypeId> intersectionOfTables(TypeId here, TypeId there, SeenTablePropPairs& seenTablePropPairs, Set<TypeId>& seenSet); std::optional<TypeId> intersectionOfTables(TypeId here, TypeId there, Set<TypeId>& seenSet);
void intersectTablesWithTable(TypeIds& heres, TypeId there, SeenTablePropPairs& seenTablePropPairs, Set<TypeId>& seenSetTypes); void intersectTablesWithTable(TypeIds& heres, TypeId there, Set<TypeId>& seenSetTypes);
void intersectTables(TypeIds& heres, const TypeIds& theres); void intersectTables(TypeIds& heres, const TypeIds& theres);
std::optional<TypeId> intersectionOfFunctions(TypeId here, TypeId there); std::optional<TypeId> intersectionOfFunctions(TypeId here, TypeId there);
void intersectFunctionsWithFunction(NormalizedFunctionType& heress, TypeId there); void intersectFunctionsWithFunction(NormalizedFunctionType& heress, TypeId there);
void intersectFunctions(NormalizedFunctionType& heress, const NormalizedFunctionType& theress); void intersectFunctions(NormalizedFunctionType& heress, const NormalizedFunctionType& theress);
NormalizationResult intersectTyvarsWithTy( NormalizationResult intersectTyvarsWithTy(NormalizedTyvars& here, TypeId there, Set<TypeId>& seenSetTypes);
NormalizedTyvars& here,
TypeId there,
SeenTablePropPairs& seenTablePropPairs,
Set<TypeId>& seenSetTypes
);
NormalizationResult intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars = -1); NormalizationResult intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars = -1);
NormalizationResult intersectNormalWithTy(NormalizedType& here, TypeId there, SeenTablePropPairs& seenTablePropPairs, Set<TypeId>& seenSetTypes); NormalizationResult intersectNormalWithTy(NormalizedType& here, TypeId there, Set<TypeId>& seenSetTypes);
NormalizationResult normalizeIntersections( NormalizationResult normalizeIntersections(const std::vector<TypeId>& intersections, NormalizedType& outType, Set<TypeId>& seenSet);
const std::vector<TypeId>& intersections,
NormalizedType& outType,
SeenTablePropPairs& seenTablePropPairs,
Set<TypeId>& seenSet
);
// Check for inhabitance // Check for inhabitance
NormalizationResult isInhabited(TypeId ty); NormalizationResult isInhabited(TypeId ty);
@ -456,7 +426,7 @@ public:
// Check for intersections being inhabited // Check for intersections being inhabited
NormalizationResult isIntersectionInhabited(TypeId left, TypeId right); NormalizationResult isIntersectionInhabited(TypeId left, TypeId right);
NormalizationResult isIntersectionInhabited(TypeId left, TypeId right, SeenTablePropPairs& seenTablePropPairs, Set<TypeId>& seenSet); NormalizationResult isIntersectionInhabited(TypeId left, TypeId right, Set<TypeId>& seenSet);
// -------- Convert back from a normalized type to a type // -------- Convert back from a normalized type to a type
TypeId typeFromNormal(const NormalizedType& norm); TypeId typeFromNormal(const NormalizedType& norm);

View file

@ -2,13 +2,12 @@
#pragma once #pragma once
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Error.h"
#include "Luau/InsertionOrderedMap.h" #include "Luau/InsertionOrderedMap.h"
#include "Luau/Location.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Subtyping.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include "Luau/Location.h"
#include "Luau/Error.h"
#include "Luau/Subtyping.h"
namespace Luau namespace Luau
{ {
@ -35,9 +34,7 @@ struct OverloadResolver
OverloadResolver( OverloadResolver(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<Simplifier> simplifier,
NotNull<Normalizer> normalizer, NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<Scope> scope, NotNull<Scope> scope,
NotNull<InternalErrorReporter> reporter, NotNull<InternalErrorReporter> reporter,
NotNull<TypeCheckLimits> limits, NotNull<TypeCheckLimits> limits,
@ -46,9 +43,7 @@ struct OverloadResolver
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
NotNull<TypeArena> arena; NotNull<TypeArena> arena;
NotNull<Simplifier> simplifier;
NotNull<Normalizer> normalizer; NotNull<Normalizer> normalizer;
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
NotNull<Scope> scope; NotNull<Scope> scope;
NotNull<InternalErrorReporter> ice; NotNull<InternalErrorReporter> ice;
NotNull<TypeCheckLimits> limits; NotNull<TypeCheckLimits> limits;
@ -113,9 +108,7 @@ struct SolveResult
SolveResult solveFunctionCall( SolveResult solveFunctionCall(
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
NotNull<Normalizer> normalizer, NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> iceReporter, NotNull<InternalErrorReporter> iceReporter,
NotNull<TypeCheckLimits> limits, NotNull<TypeCheckLimits> limits,
NotNull<Scope> scope, NotNull<Scope> scope,

View file

@ -1,68 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <cstdint>
namespace Luau
{
enum struct Polarity : uint8_t
{
None = 0b000,
Positive = 0b001,
Negative = 0b010,
Mixed = 0b011,
Unknown = 0b100,
};
inline Polarity operator|(Polarity lhs, Polarity rhs)
{
return Polarity(uint8_t(lhs) | uint8_t(rhs));
}
inline Polarity& operator|=(Polarity& lhs, Polarity rhs)
{
lhs = lhs | rhs;
return lhs;
}
inline Polarity operator&(Polarity lhs, Polarity rhs)
{
return Polarity(uint8_t(lhs) & uint8_t(rhs));
}
inline Polarity& operator&=(Polarity& lhs, Polarity rhs)
{
lhs = lhs & rhs;
return lhs;
}
inline bool isPositive(Polarity p)
{
return bool(p & Polarity::Positive);
}
inline bool isNegative(Polarity p)
{
return bool(p & Polarity::Negative);
}
inline bool isKnown(Polarity p)
{
return p != Polarity::Unknown;
}
inline Polarity invert(Polarity p)
{
switch (p)
{
case Polarity::Positive:
return Polarity::Negative;
case Polarity::Negative:
return Polarity::Positive;
default:
return p;
}
}
} // namespace Luau

View file

@ -16,7 +16,7 @@ struct Scope;
void quantify(TypeId ty, TypeLevel level); void quantify(TypeId ty, TypeLevel level);
// TODO: This is eerily similar to the pattern that NormalizedExternType // TODO: This is eerily similar to the pattern that NormalizedClassType
// implements. We could, and perhaps should, merge them together. // implements. We could, and perhaps should, merge them together.
template<typename K, typename V> template<typename K, typename V>
struct OrderedMap struct OrderedMap
@ -31,4 +31,13 @@ struct OrderedMap
} }
}; };
struct QuantifierResult
{
TypeId result;
OrderedMap<TypeId, TypeId> insertedGenerics;
OrderedMap<TypePackId, TypePackId> insertedGenericPacks;
};
std::optional<QuantifierResult> quantify(TypeArena* arena, TypeId ty, Scope* scope);
} // namespace Luau } // namespace Luau

View file

@ -53,7 +53,6 @@ struct Proposition
{ {
const RefinementKey* key; const RefinementKey* key;
TypeId discriminantTy; TypeId discriminantTy;
bool implicitFromCall;
}; };
template<typename T> template<typename T>
@ -70,7 +69,6 @@ struct RefinementArena
RefinementId disjunction(RefinementId lhs, RefinementId rhs); RefinementId disjunction(RefinementId lhs, RefinementId rhs);
RefinementId equivalence(RefinementId lhs, RefinementId rhs); RefinementId equivalence(RefinementId lhs, RefinementId rhs);
RefinementId proposition(const RefinementKey* key, TypeId discriminantTy); RefinementId proposition(const RefinementKey* key, TypeId discriminantTy);
RefinementId implicitProposition(const RefinementKey* key, TypeId discriminantTy);
private: private:
TypedAllocator<Refinement> allocator; TypedAllocator<Refinement> allocator;

View file

@ -11,12 +11,14 @@
namespace Luau namespace Luau
{ {
class AstNode; class AstStat;
class AstExpr;
class AstStatBlock; class AstStatBlock;
struct AstLocal;
struct RequireTraceResult struct RequireTraceResult
{ {
DenseHashMap<const AstNode*, ModuleInfo> exprs{nullptr}; DenseHashMap<const AstExpr*, ModuleInfo> exprs{nullptr};
std::vector<std::pair<ModuleName, Location>> requireList; std::vector<std::pair<ModuleName, Location>> requireList;
}; };

View file

@ -35,12 +35,12 @@ struct Scope
explicit Scope(TypePackId returnType); // root scope explicit Scope(TypePackId returnType); // root scope
explicit Scope(const ScopePtr& parent, int subLevel = 0); // child scope. Parent must not be nullptr. explicit Scope(const ScopePtr& parent, int subLevel = 0); // child scope. Parent must not be nullptr.
ScopePtr parent; // null for the root const ScopePtr parent; // null for the root
// All the children of this scope. // All the children of this scope.
std::vector<NotNull<Scope>> children; std::vector<NotNull<Scope>> children;
std::unordered_map<Symbol, Binding> bindings; std::unordered_map<Symbol, Binding> bindings;
TypePackId returnType = nullptr; TypePackId returnType;
std::optional<TypePackId> varargPack; std::optional<TypePackId> varargPack;
TypeLevel level; TypeLevel level;
@ -59,8 +59,6 @@ struct Scope
std::optional<TypeId> lookup(Symbol sym) const; std::optional<TypeId> lookup(Symbol sym) const;
std::optional<TypeId> lookupUnrefinedType(DefId def) const; std::optional<TypeId> lookupUnrefinedType(DefId def) const;
std::optional<TypeId> lookupRValueRefinementType(DefId def) const;
std::optional<TypeId> lookup(DefId def) const; std::optional<TypeId> lookup(DefId def) const;
std::optional<std::pair<TypeId, Scope*>> lookupEx(DefId def); std::optional<std::pair<TypeId, Scope*>> lookupEx(DefId def);
std::optional<std::pair<Binding*, Scope*>> lookupEx(Symbol sym); std::optional<std::pair<Binding*, Scope*>> lookupEx(Symbol sym);
@ -73,7 +71,6 @@ struct Scope
// WARNING: This function linearly scans for a string key of equal value! It is thus O(n**2) // WARNING: This function linearly scans for a string key of equal value! It is thus O(n**2)
std::optional<Binding> linearSearchForBinding(const std::string& name, bool traverseScopeChain = true) const; std::optional<Binding> linearSearchForBinding(const std::string& name, bool traverseScopeChain = true) const;
std::optional<std::pair<Symbol, Binding>> linearSearchForBindingPair(const std::string& name, bool traverseScopeChain) const;
RefinementMap refinements; RefinementMap refinements;
@ -88,19 +85,12 @@ struct Scope
void inheritAssignments(const ScopePtr& childScope); void inheritAssignments(const ScopePtr& childScope);
void inheritRefinements(const ScopePtr& childScope); void inheritRefinements(const ScopePtr& childScope);
// Track globals that should emit warnings during type checking.
DenseHashSet<std::string> globalsToWarn{""};
bool shouldWarnGlobal(std::string name) const;
// For mutually recursive type aliases, it's important that // For mutually recursive type aliases, it's important that
// they use the same types for the same names. // they use the same types for the same names.
// For instance, in `type Tree<T> { data: T, children: Forest<T> } type Forest<T> = {Tree<T>}` // For instance, in `type Tree<T> { data: T, children: Forest<T> } type Forest<T> = {Tree<T>}`
// we need that the generic type `T` in both cases is the same, so we use a cache. // we need that the generic type `T` in both cases is the same, so we use a cache.
std::unordered_map<Name, TypeId> typeAliasTypeParameters; std::unordered_map<Name, TypeId> typeAliasTypeParameters;
std::unordered_map<Name, TypePackId> typeAliasTypePackParameters; std::unordered_map<Name, TypePackId> typeAliasTypePackParameters;
std::optional<std::vector<TypeId>> interiorFreeTypes;
std::optional<std::vector<TypePackId>> interiorFreeTypePacks;
}; };
// Returns true iff the left scope encloses the right scope. A Scope* equal to // Returns true iff the left scope encloses the right scope. A Scope* equal to

View file

@ -19,13 +19,10 @@ struct SimplifyResult
DenseHashSet<TypeId> blockedTypes; DenseHashSet<TypeId> blockedTypes;
}; };
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right); SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty, TypeId discriminant);
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, std::set<TypeId> parts); SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, std::set<TypeId> parts);
SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right); SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty, TypeId discriminant);
SimplifyResult simplifyIntersectWithTruthy(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId target);
SimplifyResult simplifyIntersectWithFalsy(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId target);
enum class Relation enum class Relation
{ {

View file

@ -86,7 +86,6 @@ struct TarjanNode
struct Tarjan struct Tarjan
{ {
Tarjan(); Tarjan();
virtual ~Tarjan() = default;
// Vertices (types and type packs) are indexed, using pre-order traversal. // Vertices (types and type packs) are indexed, using pre-order traversal.
DenseHashMap<TypeId, int> typeToIndex{nullptr}; DenseHashMap<TypeId, int> typeToIndex{nullptr};
@ -122,7 +121,7 @@ struct Tarjan
void visitChildren(TypePackId tp, int index); void visitChildren(TypePackId tp, int index);
void visitChild(TypeId ty); void visitChild(TypeId ty);
void visitChild(TypePackId tp); void visitChild(TypePackId ty);
template<typename Ty> template<typename Ty>
void visitChild(std::optional<Ty> ty) void visitChild(std::optional<Ty> ty)
@ -133,7 +132,7 @@ struct Tarjan
// Visit the root vertex. // Visit the root vertex.
TarjanResult visitRoot(TypeId ty); TarjanResult visitRoot(TypeId ty);
TarjanResult visitRoot(TypePackId tp); TarjanResult visitRoot(TypePackId ty);
// Used to reuse the object for a new operation // Used to reuse the object for a new operation
void clearTarjan(const TxnLog* log); void clearTarjan(const TxnLog* log);
@ -151,12 +150,26 @@ struct Tarjan
void visitSCC(int index); void visitSCC(int index);
// Each subclass can decide to ignore some nodes. // Each subclass can decide to ignore some nodes.
virtual bool ignoreChildren(TypeId ty); virtual bool ignoreChildren(TypeId ty)
virtual bool ignoreChildren(TypePackId ty); {
return false;
}
virtual bool ignoreChildren(TypePackId ty)
{
return false;
}
// Some subclasses might ignore children visit, but not other actions like replacing the children // Some subclasses might ignore children visit, but not other actions like replacing the children
virtual bool ignoreChildrenVisit(TypeId ty); virtual bool ignoreChildrenVisit(TypeId ty)
virtual bool ignoreChildrenVisit(TypePackId ty); {
return ignoreChildren(ty);
}
virtual bool ignoreChildrenVisit(TypePackId ty)
{
return ignoreChildren(ty);
}
// Subclasses should say which vertices are dirty, // Subclasses should say which vertices are dirty,
// and what to do with dirty vertices. // and what to do with dirty vertices.
@ -171,7 +184,6 @@ struct Tarjan
struct Substitution : Tarjan struct Substitution : Tarjan
{ {
protected: protected:
explicit Substitution(TypeArena* arena);
Substitution(const TxnLog* log_, TypeArena* arena); Substitution(const TxnLog* log_, TypeArena* arena);
/* /*
@ -220,23 +232,28 @@ public:
virtual TypeId clean(TypeId ty) = 0; virtual TypeId clean(TypeId ty) = 0;
virtual TypePackId clean(TypePackId tp) = 0; virtual TypePackId clean(TypePackId tp) = 0;
protected:
// Helper functions to create new types (used by subclasses) // Helper functions to create new types (used by subclasses)
template<typename T> template<typename T>
TypeId addType(T tv) TypeId addType(const T& tv)
{ {
return arena->addType(std::move(tv)); return arena->addType(tv);
} }
template<typename T> template<typename T>
TypePackId addTypePack(T tp) TypePackId addTypePack(const T& tp)
{ {
return arena->addTypePack(TypePackVar{std::move(tp)}); return arena->addTypePack(TypePackVar{tp});
} }
private: private:
template<typename Ty> template<typename Ty>
std::optional<Ty> replace(std::optional<Ty> ty); std::optional<Ty> replace(std::optional<Ty> ty)
{
if (ty)
return replace(*ty);
else
return std::nullopt;
}
}; };
} // namespace Luau } // namespace Luau

View file

@ -1,14 +1,13 @@
// 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
#pragma once #pragma once
#include "Luau/DenseHash.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Set.h" #include "Luau/Set.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFunction.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include "Luau/TypePairHash.h" #include "Luau/TypePairHash.h"
#include "Luau/TypePath.h" #include "Luau/TypePath.h"
#include "Luau/TypeFunction.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/DenseHash.h"
#include <vector> #include <vector>
#include <optional> #include <optional>
@ -22,7 +21,7 @@ struct InternalErrorReporter;
class TypeIds; class TypeIds;
class Normalizer; class Normalizer;
struct NormalizedExternType; struct NormalizedClassType;
struct NormalizedFunctionType; struct NormalizedFunctionType;
struct NormalizedStringType; struct NormalizedStringType;
struct NormalizedType; struct NormalizedType;
@ -97,22 +96,6 @@ struct SubtypingEnvironment
DenseHashSet<TypeId> upperBound{nullptr}; DenseHashSet<TypeId> upperBound{nullptr};
}; };
/* For nested subtyping relationship tests of mapped generic bounds, we keep the outer environment immutable */
SubtypingEnvironment* parent = nullptr;
/// Applies `mappedGenerics` to the given type.
/// This is used specifically to substitute for generics in type function instances.
std::optional<TypeId> applyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty);
const TypeId* tryFindSubstitution(TypeId ty) const;
const SubtypingResult* tryFindSubtypingResult(std::pair<TypeId, TypeId> subAndSuper) const;
bool containsMappedType(TypeId ty) const;
bool containsMappedPack(TypePackId tp) const;
GenericBounds& getMappedTypeBounds(TypeId ty);
TypePackId* getMappedPackBounds(TypePackId tp);
/* /*
* When we encounter a generic over the course of a subtyping test, we need * When we encounter a generic over the course of a subtyping test, we need
* to tentatively map that generic onto a type on the other side. * to tentatively map that generic onto a type on the other side.
@ -121,7 +104,7 @@ struct SubtypingEnvironment
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr}; DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
/* /*
* See the test cyclic_tables_are_assumed_to_be_compatible_with_extern_types for * See the test cyclic_tables_are_assumed_to_be_compatible_with_classes for
* details. * details.
* *
* An empty value is equivalent to a nonexistent key. * An empty value is equivalent to a nonexistent key.
@ -129,15 +112,17 @@ struct SubtypingEnvironment
DenseHashMap<TypeId, TypeId> substitutions{nullptr}; DenseHashMap<TypeId, TypeId> substitutions{nullptr};
DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash> ephemeralCache{{}}; DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash> ephemeralCache{{}};
/// Applies `mappedGenerics` to the given type.
/// This is used specifically to substitute for generics in type function instances.
std::optional<TypeId> applyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty);
}; };
struct Subtyping struct Subtyping
{ {
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
NotNull<TypeArena> arena; NotNull<TypeArena> arena;
NotNull<Simplifier> simplifier;
NotNull<Normalizer> normalizer; NotNull<Normalizer> normalizer;
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
NotNull<InternalErrorReporter> iceReporter; NotNull<InternalErrorReporter> iceReporter;
TypeCheckLimits limits; TypeCheckLimits limits;
@ -157,9 +142,7 @@ struct Subtyping
Subtyping( Subtyping(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> typeArena, NotNull<TypeArena> typeArena,
NotNull<Simplifier> simplifier,
NotNull<Normalizer> normalizer, NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> iceReporter NotNull<InternalErrorReporter> iceReporter
); );
@ -229,8 +212,9 @@ private:
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull<Scope> scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull<Scope> scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ExternType* subExternType, TypeId superTy, const TableType* superTable, NotNull<Scope>); SubtypingResult
isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable, NotNull<Scope>);
SubtypingResult isCovariantWith( SubtypingResult isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const FunctionType* subFunction, const FunctionType* subFunction,
@ -258,11 +242,11 @@ private:
); );
SubtypingResult isCovariantWith( SubtypingResult isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const NormalizedExternType& subExternType, const NormalizedClassType& subClass,
const NormalizedExternType& superExternType, const NormalizedClassType& superClass,
NotNull<Scope> scope NotNull<Scope> scope
); );
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedExternType& subExternType, const TypeIds& superTables, NotNull<Scope> scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables, NotNull<Scope> scope);
SubtypingResult isCovariantWith( SubtypingResult isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const NormalizedStringType& subString, const NormalizedStringType& subString,

View file

@ -6,15 +6,12 @@
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include <vector>
namespace Luau namespace Luau
{ {
struct TypeArena; struct TypeArena;
struct BuiltinTypes; struct BuiltinTypes;
struct Unifier2; struct Unifier2;
struct Subtyping;
class AstExpr; class AstExpr;
TypeId matchLiteralType( TypeId matchLiteralType(
@ -23,7 +20,6 @@ TypeId matchLiteralType(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<Unifier2> unifier, NotNull<Unifier2> unifier,
NotNull<Subtyping> subtyping,
TypeId expectedType, TypeId expectedType,
TypeId exprType, TypeId exprType,
const AstExpr* expr, const AstExpr* expr,

View file

@ -44,8 +44,6 @@ struct ToStringOptions
bool hideTableKind = false; // If true, all tables will be surrounded with plain '{}' bool hideTableKind = false; // If true, all tables will be surrounded with plain '{}'
bool hideNamedFunctionTypeParameters = false; // If true, type parameters of functions will be hidden at top-level. bool hideNamedFunctionTypeParameters = false; // If true, type parameters of functions will be hidden at top-level.
bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self
bool hideTableAliasExpansions = false; // If true, all table aliases will not be expanded
bool useQuestionMarks = true; // If true, use a postfix ? for options, else write them out as unions that include nil.
size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypes size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypes
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength); size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);
size_t compositeTypesSingleLineLimit = 5; // The number of type elements permitted on a single line when printing type unions/intersections size_t compositeTypesSingleLineLimit = 5; // The number of type elements permitted on a single line when printing type unions/intersections

View file

@ -65,10 +65,11 @@ T* getMutable(PendingTypePack* pending)
// Log of what TypeIds we are rebinding, to be committed later. // Log of what TypeIds we are rebinding, to be committed later.
struct TxnLog struct TxnLog
{ {
explicit TxnLog() explicit TxnLog(bool useScopes = false)
: typeVarChanges(nullptr) : typeVarChanges(nullptr)
, typePackChanges(nullptr) , typePackChanges(nullptr)
, ownedSeen() , ownedSeen()
, useScopes(useScopes)
, sharedSeen(&ownedSeen) , sharedSeen(&ownedSeen)
{ {
} }
@ -192,6 +193,16 @@ struct TxnLog
// The pointer returned lives until `commit` or `clear` is called. // The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel); PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel);
// Queues the replacement of a type's scope with the provided scope.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* changeScope(TypeId ty, NotNull<Scope> scope);
// Queues the replacement of a type pack's scope with the provided scope.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* changeScope(TypePackId tp, NotNull<Scope> scope);
// Queues a replacement of a table type with another table type with a new // Queues a replacement of a table type with another table type with a new
// indexer. // indexer.
// //

View file

@ -5,11 +5,10 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Refinement.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Polarity.h"
#include "Luau/Predicate.h" #include "Luau/Predicate.h"
#include "Luau/Refinement.h"
#include "Luau/Unifiable.h" #include "Luau/Unifiable.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/VecDeque.h" #include "Luau/VecDeque.h"
@ -20,6 +19,7 @@
#include <optional> #include <optional>
#include <set> #include <set>
#include <string> #include <string>
#include <unordered_map>
#include <vector> #include <vector>
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength) LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
@ -31,7 +31,6 @@ namespace Luau
struct TypeArena; struct TypeArena;
struct Scope; struct Scope;
using ScopePtr = std::shared_ptr<Scope>; using ScopePtr = std::shared_ptr<Scope>;
struct Module;
struct TypeFunction; struct TypeFunction;
struct Constraint; struct Constraint;
@ -69,11 +68,11 @@ using Name = std::string;
// A free type is one whose exact shape has yet to be fully determined. // A free type is one whose exact shape has yet to be fully determined.
struct FreeType struct FreeType
{ {
// New constructors explicit FreeType(TypeLevel level);
explicit FreeType(TypeLevel level, TypeId lowerBound, TypeId upperBound); explicit FreeType(Scope* scope);
// This one got promoted to explicit FreeType(Scope* scope, TypeLevel level);
explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound, Polarity polarity = Polarity::Unknown);
explicit FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId upperBound); FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound);
int index; int index;
TypeLevel level; TypeLevel level;
@ -87,8 +86,6 @@ struct FreeType
// Only used under local type inference // Only used under local type inference
TypeId lowerBound = nullptr; TypeId lowerBound = nullptr;
TypeId upperBound = nullptr; TypeId upperBound = nullptr;
Polarity polarity = Polarity::Unknown;
}; };
struct GenericType struct GenericType
@ -97,8 +94,8 @@ struct GenericType
GenericType(); GenericType();
explicit GenericType(TypeLevel level); explicit GenericType(TypeLevel level);
explicit GenericType(const Name& name, Polarity polarity = Polarity::Unknown); explicit GenericType(const Name& name);
explicit GenericType(Scope* scope, Polarity polarity = Polarity::Unknown); explicit GenericType(Scope* scope);
GenericType(TypeLevel level, const Name& name); GenericType(TypeLevel level, const Name& name);
GenericType(Scope* scope, const Name& name); GenericType(Scope* scope, const Name& name);
@ -108,8 +105,6 @@ struct GenericType
Scope* scope = nullptr; Scope* scope = nullptr;
Name name; Name name;
bool explicitName = false; bool explicitName = false;
Polarity polarity = Polarity::Unknown;
}; };
// When an equality constraint is found, it is then "bound" to that type, // When an equality constraint is found, it is then "bound" to that type,
@ -135,14 +130,14 @@ struct BlockedType
BlockedType(); BlockedType();
int index; int index;
const Constraint* getOwner() const; Constraint* getOwner() const;
void setOwner(const Constraint* newOwner); void setOwner(Constraint* newOwner);
void replaceOwner(const Constraint* newOwner); void replaceOwner(Constraint* newOwner);
private: private:
// The constraint that is intended to unblock this type. Other constraints // The constraint that is intended to unblock this type. Other constraints
// should block on this constraint if present. // should block on this constraint if present.
const Constraint* owner = nullptr; Constraint* owner = nullptr;
}; };
struct PrimitiveType struct PrimitiveType
@ -283,15 +278,19 @@ struct WithPredicate
} }
}; };
using MagicFunction = std::function<std::optional<
WithPredicate<TypePackId>>(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>)>;
struct MagicFunctionCallContext struct MagicFunctionCallContext
{ {
NotNull<struct ConstraintSolver> solver; NotNull<struct ConstraintSolver> solver;
NotNull<const Constraint> constraint; NotNull<const Constraint> constraint;
NotNull<const AstExprCall> callSite; const class AstExprCall* callSite;
TypePackId arguments; TypePackId arguments;
TypePackId result; TypePackId result;
}; };
using DcrMagicFunction = std::function<bool(MagicFunctionCallContext)>;
struct MagicRefinementContext struct MagicRefinementContext
{ {
NotNull<Scope> scope; NotNull<Scope> scope;
@ -308,30 +307,8 @@ struct MagicFunctionTypeCheckContext
NotNull<Scope> checkScope; NotNull<Scope> checkScope;
}; };
struct MagicFunction using DcrMagicRefinement = void (*)(const MagicRefinementContext&);
{ using DcrMagicFunctionTypeCheck = std::function<void(const MagicFunctionTypeCheckContext&)>;
virtual std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) = 0;
// Callback to allow custom typechecking of builtin function calls whose argument types
// will only be resolved after constraint solving. For example, the arguments to string.format
// have types that can only be decided after parsing the format string and unifying
// with the passed in values, but the correctness of the call can only be decided after
// all the types have been finalized.
virtual bool infer(const MagicFunctionCallContext&) = 0;
virtual void refine(const MagicRefinementContext&) {}
// If a magic function needs to do its own special typechecking, do it here.
// Returns true if magic typechecking was performed. Return false if the
// default typechecking logic should run.
virtual bool typeCheck(const MagicFunctionTypeCheckContext&)
{
return false;
}
virtual ~MagicFunction() {}
};
struct FunctionType struct FunctionType
{ {
// Global monomorphic function // Global monomorphic function
@ -348,8 +325,10 @@ struct FunctionType
); );
// Local monomorphic function // Local monomorphic function
FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
FunctionType( FunctionType(
TypeLevel level, TypeLevel level,
Scope* scope,
TypePackId argTypes, TypePackId argTypes,
TypePackId retTypes, TypePackId retTypes,
std::optional<FunctionDefinition> defn = {}, std::optional<FunctionDefinition> defn = {},
@ -366,6 +345,16 @@ struct FunctionType
std::optional<FunctionDefinition> defn = {}, std::optional<FunctionDefinition> defn = {},
bool hasSelf = false bool hasSelf = false
); );
FunctionType(
TypeLevel level,
Scope* scope,
std::vector<TypeId> generics,
std::vector<TypePackId> genericPacks,
TypePackId argTypes,
TypePackId retTypes,
std::optional<FunctionDefinition> defn = {},
bool hasSelf = false
);
std::optional<FunctionDefinition> definition; std::optional<FunctionDefinition> definition;
/// These should all be generic /// These should all be generic
@ -374,16 +363,25 @@ struct FunctionType
std::vector<std::optional<FunctionArgument>> argNames; std::vector<std::optional<FunctionArgument>> argNames;
Tags tags; Tags tags;
TypeLevel level; TypeLevel level;
Scope* scope = nullptr;
TypePackId argTypes; TypePackId argTypes;
TypePackId retTypes; TypePackId retTypes;
std::shared_ptr<MagicFunction> magic = nullptr; MagicFunction magicFunction = nullptr;
DcrMagicFunction dcrMagicFunction = nullptr;
DcrMagicRefinement dcrMagicRefinement = nullptr;
// Callback to allow custom typechecking of builtin function calls whose argument types
// will only be resolved after constraint solving. For example, the arguments to string.format
// have types that can only be decided after parsing the format string and unifying
// with the passed in values, but the correctness of the call can only be decided after
// all the types have been finalized.
DcrMagicFunctionTypeCheck dcrMagicTypeCheck = nullptr;
bool hasSelf; bool hasSelf;
// `hasNoFreeOrGenericTypes` should be true if and only if the type does not have any free or generic types present inside it. // `hasNoFreeOrGenericTypes` should be true if and only if the type does not have any free or generic types present inside it.
// this flag is used as an optimization to exit early from procedures that manipulate free or generic types. // this flag is used as an optimization to exit early from procedures that manipulate free or generic types.
bool hasNoFreeOrGenericTypes = false; bool hasNoFreeOrGenericTypes = false;
bool isCheckedFunction = false; bool isCheckedFunction = false;
bool isDeprecatedFunction = false;
}; };
enum class TableState enum class TableState
@ -460,9 +458,7 @@ struct Property
TypeId type() const; TypeId type() const;
void setType(TypeId ty); void setType(TypeId ty);
// If this property has a present `writeTy`, set it equal to the `readTy`. // Sets the write type of this property to the read type.
// This is to ensure that if we normalize a property that has divergent
// read and write types, we make them converge (for now).
void makeShared(); void makeShared();
bool isShared() const; bool isShared() const;
@ -507,6 +503,9 @@ struct TableType
std::optional<TypeId> boundTo; std::optional<TypeId> boundTo;
Tags tags; Tags tags;
// Methods of this table that have an untyped self will use the same shared self type.
std::optional<TypeId> selfTy;
// We track the number of as-yet-unadded properties to unsealed tables. // We track the number of as-yet-unadded properties to unsealed tables.
// Some constraints will use this information to decide whether or not they // Some constraints will use this information to decide whether or not they
// are able to dispatch. // are able to dispatch.
@ -532,15 +531,15 @@ struct ClassUserData
virtual ~ClassUserData() {} virtual ~ClassUserData() {}
}; };
/** The type of an external userdata exposed to Luau. /** The type of a class.
* *
* Extern types behave like tables in many ways, but there are some important differences: * Classes behave like tables in many ways, but there are some important differences:
* *
* The properties of a class are always exactly known. * The properties of a class are always exactly known.
* Extern types optionally have a parent type. * Classes optionally have a parent class.
* Two different extern types that share the same properties are nevertheless distinct and mutually incompatible. * Two different classes that share the same properties are nevertheless distinct and mutually incompatible.
*/ */
struct ExternType struct ClassType
{ {
using Props = TableType::Props; using Props = TableType::Props;
@ -554,7 +553,7 @@ struct ExternType
std::optional<Location> definitionLocation; std::optional<Location> definitionLocation;
std::optional<TableIndexer> indexer; std::optional<TableIndexer> indexer;
ExternType( ClassType(
Name name, Name name,
Props props, Props props,
std::optional<TypeId> parent, std::optional<TypeId> parent,
@ -575,7 +574,7 @@ struct ExternType
{ {
} }
ExternType( ClassType(
Name name, Name name,
Props props, Props props,
std::optional<TypeId> parent, std::optional<TypeId> parent,
@ -599,18 +598,6 @@ struct ExternType
} }
}; };
// Data required to initialize a user-defined function and its environment
struct UserDefinedFunctionData
{
// Store a weak module reference to ensure the lifetime requirements are preserved
std::weak_ptr<Module> owner;
// References to AST elements are owned by the Module allocator which also stores this type
AstStatTypeFunction* definition = nullptr;
DenseHashMap<Name, std::pair<AstStatTypeFunction*, size_t>> environment{""};
};
/** /**
* An instance of a type function that has not yet been reduced to a more concrete * An instance of a type function that has not yet been reduced to a more concrete
* type. The constraint solver receives a constraint to reduce each * type. The constraint solver receives a constraint to reduce each
@ -625,21 +612,21 @@ struct TypeFunctionInstanceType
std::vector<TypeId> typeArguments; std::vector<TypeId> typeArguments;
std::vector<TypePackId> packArguments; std::vector<TypePackId> packArguments;
std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs
UserDefinedFunctionData userFuncData; std::optional<AstExprFunction*> userFuncBody; // Body of the user-defined type function; only available for UDTFs
TypeFunctionInstanceType( TypeFunctionInstanceType(
NotNull<const TypeFunction> function, NotNull<const TypeFunction> function,
std::vector<TypeId> typeArguments, std::vector<TypeId> typeArguments,
std::vector<TypePackId> packArguments, std::vector<TypePackId> packArguments,
std::optional<AstName> userFuncName, std::optional<AstName> userFuncName = std::nullopt,
UserDefinedFunctionData userFuncData std::optional<AstExprFunction*> userFuncBody = std::nullopt
) )
: function(function) : function(function)
, typeArguments(typeArguments) , typeArguments(typeArguments)
, packArguments(packArguments) , packArguments(packArguments)
, userFuncName(userFuncName) , userFuncName(userFuncName)
, userFuncData(userFuncData) , userFuncBody(userFuncBody)
{ {
} }
@ -656,13 +643,6 @@ struct TypeFunctionInstanceType
, packArguments(packArguments) , packArguments(packArguments)
{ {
} }
TypeFunctionInstanceType(NotNull<const TypeFunction> function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
: function{function}
, typeArguments(typeArguments)
, packArguments(packArguments)
{
}
}; };
/** Represents a pending type alias instantiation. /** Represents a pending type alias instantiation.
@ -690,11 +670,6 @@ struct AnyType
{ {
}; };
// A special, trivial type for the refinement system that is always eliminated from intersections.
struct NoRefineType
{
};
// `T | U` // `T | U`
struct UnionType struct UnionType
{ {
@ -762,7 +737,7 @@ struct NegationType
TypeId ty; TypeId ty;
}; };
using ErrorType = Unifiable::Error<TypeId>; using ErrorType = Unifiable::Error;
using TypeVariant = Unifiable::Variant< using TypeVariant = Unifiable::Variant<
TypeId, TypeId,
@ -775,7 +750,7 @@ using TypeVariant = Unifiable::Variant<
FunctionType, FunctionType,
TableType, TableType,
MetatableType, MetatableType,
ExternType, ClassType,
AnyType, AnyType,
UnionType, UnionType,
IntersectionType, IntersectionType,
@ -783,7 +758,6 @@ using TypeVariant = Unifiable::Variant<
UnknownType, UnknownType,
NeverType, NeverType,
NegationType, NegationType,
NoRefineType,
TypeFunctionInstanceType>; TypeFunctionInstanceType>;
struct Type final struct Type final
@ -829,13 +803,6 @@ struct Type final
Type& operator=(const TypeVariant& rhs); Type& operator=(const TypeVariant& rhs);
Type& operator=(TypeVariant&& rhs); Type& operator=(TypeVariant&& rhs);
Type(Type&&) = default;
Type& operator=(Type&&) = default;
Type clone() const;
private:
Type(const Type&) = default;
Type& operator=(const Type& rhs); Type& operator=(const Type& rhs);
}; };
@ -868,9 +835,6 @@ struct TypeFun
*/ */
TypeId type; TypeId type;
// The location of where this TypeFun was defined, if available
std::optional<Location> definitionLocation;
TypeFun() = default; TypeFun() = default;
explicit TypeFun(TypeId ty) explicit TypeFun(TypeId ty)
@ -878,23 +842,16 @@ struct TypeFun
{ {
} }
TypeFun(std::vector<GenericTypeDefinition> typeParams, TypeId type, std::optional<Location> definitionLocation = std::nullopt) TypeFun(std::vector<GenericTypeDefinition> typeParams, TypeId type)
: typeParams(std::move(typeParams)) : typeParams(std::move(typeParams))
, type(type) , type(type)
, definitionLocation(definitionLocation)
{ {
} }
TypeFun( TypeFun(std::vector<GenericTypeDefinition> typeParams, std::vector<GenericTypePackDefinition> typePackParams, TypeId type)
std::vector<GenericTypeDefinition> typeParams,
std::vector<GenericTypePackDefinition> typePackParams,
TypeId type,
std::optional<Location> definitionLocation = std::nullopt
)
: typeParams(std::move(typeParams)) : typeParams(std::move(typeParams))
, typePackParams(std::move(typePackParams)) , typePackParams(std::move(typePackParams))
, type(type) , type(type)
, definitionLocation(definitionLocation)
{ {
} }
@ -986,7 +943,7 @@ public:
const TypeId threadType; const TypeId threadType;
const TypeId bufferType; const TypeId bufferType;
const TypeId functionType; const TypeId functionType;
const TypeId externType; const TypeId classType;
const TypeId tableType; const TypeId tableType;
const TypeId emptyTableType; const TypeId emptyTableType;
const TypeId trueType; const TypeId trueType;
@ -995,10 +952,8 @@ public:
const TypeId unknownType; const TypeId unknownType;
const TypeId neverType; const TypeId neverType;
const TypeId errorType; const TypeId errorType;
const TypeId noRefineType;
const TypeId falsyType; const TypeId falsyType;
const TypeId truthyType; const TypeId truthyType;
const TypeId notNilType;
const TypeId optionalNumberType; const TypeId optionalNumberType;
const TypeId optionalStringType; const TypeId optionalStringType;
@ -1019,10 +974,10 @@ TypeLevel* getMutableLevel(TypeId ty);
std::optional<TypeLevel> getLevel(TypePackId tp); std::optional<TypeLevel> getLevel(TypePackId tp);
const Property* lookupExternTypeProp(const ExternType* cls, const Name& name); const Property* lookupClassProp(const ClassType* cls, const Name& name);
// Whether `cls` is a subclass of `parent` // Whether `cls` is a subclass of `parent`
bool isSubclass(const ExternType* cls, const ExternType* parent); bool isSubclass(const ClassType* cls, const ClassType* parent);
Type* asMutable(TypeId ty); Type* asMutable(TypeId ty);
@ -1199,15 +1154,11 @@ private:
} }
}; };
TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope, Polarity polarity = Polarity::Unknown); TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope);
using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>; using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>;
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate); std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
// A tag to mark a type which doesn't derive directly from the root type as overriding the return of `typeof`.
// Any classes which derive from this type will have typeof return this type.
static constexpr char kTypeofRootTag[] = "typeofRoot";
void attachTag(TypeId ty, const std::string& tagName); void attachTag(TypeId ty, const std::string& tagName);
void attachTag(Property& prop, const std::string& tagName); void attachTag(Property& prop, const std::string& tagName);

View file

@ -1,7 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once #pragma once
#include "Luau/Polarity.h"
#include "Luau/TypedAllocator.h" #include "Luau/TypedAllocator.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
@ -33,11 +32,11 @@ struct TypeArena
TypeId addTV(Type&& tv); TypeId addTV(Type&& tv);
TypeId freshType(NotNull<BuiltinTypes> builtins, TypeLevel level); TypeId freshType(TypeLevel level);
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope); TypeId freshType(Scope* scope);
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLevel level); TypeId freshType(Scope* scope, TypeLevel level);
TypePackId freshTypePack(Scope* scope, Polarity polarity = Polarity::Unknown); TypePackId freshTypePack(Scope* scope);
TypePackId addTypePack(std::initializer_list<TypeId> types); TypePackId addTypePack(std::initializer_list<TypeId> types);
TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {}); TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {});

View file

@ -11,7 +11,7 @@ namespace Luau
struct TypeRehydrationOptions struct TypeRehydrationOptions
{ {
std::unordered_set<std::string> bannedNames; std::unordered_set<std::string> bannedNames;
bool expandExternTypeProps = false; bool expandClassProps = false;
}; };
void attachTypeData(SourceModule& source, Module& result); void attachTypeData(SourceModule& source, Module& result);

View file

@ -2,18 +2,15 @@
#pragma once #pragma once
#include "Luau/Common.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Normalize.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Subtyping.h" #include "Luau/Common.h"
#include "Luau/TypeUtils.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include "Luau/TypeOrPack.h" #include "Luau/TypeOrPack.h"
#include "Luau/TypeUtils.h" #include "Luau/Normalize.h"
#include "Luau/Subtyping.h"
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
namespace Luau namespace Luau
{ {
@ -40,29 +37,18 @@ struct Reasonings
std::string toString() std::string toString()
{ {
if (FFlag::LuauImproveTypePathsInErrors && reasons.empty())
return "";
// DenseHashSet ordering is entirely undefined, so we want to // DenseHashSet ordering is entirely undefined, so we want to
// sort the reasons here to achieve a stable error // sort the reasons here to achieve a stable error
// stringification. // stringification.
std::sort(reasons.begin(), reasons.end()); std::sort(reasons.begin(), reasons.end());
std::string allReasons = FFlag::LuauImproveTypePathsInErrors ? "\nthis is because " : ""; std::string allReasons;
bool first = true; bool first = true;
for (const std::string& reason : reasons) for (const std::string& reason : reasons)
{ {
if (FFlag::LuauImproveTypePathsInErrors) if (first)
{ first = false;
if (reasons.size() > 1)
allReasons += "\n\t * ";
}
else else
{ allReasons += "\n\t";
if (first)
first = false;
else
allReasons += "\n\t";
}
allReasons += reason; allReasons += reason;
} }
@ -74,9 +60,7 @@ struct Reasonings
void check( void check(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier, NotNull<UnifierSharedState> sharedState,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<UnifierSharedState> unifierState,
NotNull<TypeCheckLimits> limits, NotNull<TypeCheckLimits> limits,
DcrLogger* logger, DcrLogger* logger,
const SourceModule& sourceModule, const SourceModule& sourceModule,
@ -86,8 +70,6 @@ void check(
struct TypeChecker2 struct TypeChecker2
{ {
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
NotNull<Simplifier> simplifier;
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
DcrLogger* logger; DcrLogger* logger;
const NotNull<TypeCheckLimits> limits; const NotNull<TypeCheckLimits> limits;
const NotNull<InternalErrorReporter> ice; const NotNull<InternalErrorReporter> ice;
@ -106,8 +88,6 @@ struct TypeChecker2
TypeChecker2( TypeChecker2(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<UnifierSharedState> unifierState, NotNull<UnifierSharedState> unifierState,
NotNull<TypeCheckLimits> limits, NotNull<TypeCheckLimits> limits,
DcrLogger* logger, DcrLogger* logger,
@ -129,14 +109,14 @@ private:
std::optional<StackPusher> pushStack(AstNode* node); std::optional<StackPusher> pushStack(AstNode* node);
void checkForInternalTypeFunction(TypeId ty, Location location); void checkForInternalTypeFunction(TypeId ty, Location location);
TypeId checkForTypeFunctionInhabitance(TypeId instance, Location location); TypeId checkForTypeFunctionInhabitance(TypeId instance, Location location);
TypePackId lookupPack(AstExpr* expr) const; TypePackId lookupPack(AstExpr* expr);
TypeId lookupType(AstExpr* expr); TypeId lookupType(AstExpr* expr);
TypeId lookupAnnotation(AstType* annotation); TypeId lookupAnnotation(AstType* annotation);
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation) const; std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation);
TypeId lookupExpectedType(AstExpr* expr) const; TypeId lookupExpectedType(AstExpr* expr);
TypePackId lookupExpectedPack(AstExpr* expr, TypeArena& arena) const; TypePackId lookupExpectedPack(AstExpr* expr, TypeArena& arena);
TypePackId reconstructPack(AstArray<AstExpr*> exprs, TypeArena& arena); TypePackId reconstructPack(AstArray<AstExpr*> exprs, TypeArena& arena);
Scope* findInnermostScope(Location location) const; Scope* findInnermostScope(Location location);
void visit(AstStat* stat); void visit(AstStat* stat);
void visit(AstStatIf* ifStatement); void visit(AstStatIf* ifStatement);
void visit(AstStatWhile* whileStatement); void visit(AstStatWhile* whileStatement);
@ -160,7 +140,7 @@ private:
void visit(AstTypeList types); void visit(AstTypeList types);
void visit(AstStatDeclareFunction* stat); void visit(AstStatDeclareFunction* stat);
void visit(AstStatDeclareGlobal* stat); void visit(AstStatDeclareGlobal* stat);
void visit(AstStatDeclareExternType* stat); void visit(AstStatDeclareClass* stat);
void visit(AstStatError* stat); void visit(AstStatError* stat);
void visit(AstExpr* expr, ValueContext context); void visit(AstExpr* expr, ValueContext context);
void visit(AstExprGroup* expr, ValueContext context); void visit(AstExprGroup* expr, ValueContext context);
@ -173,7 +153,7 @@ private:
void visit(AstExprVarargs* expr); void visit(AstExprVarargs* expr);
void visitCall(AstExprCall* call); void visitCall(AstExprCall* call);
void visit(AstExprCall* call); void visit(AstExprCall* call);
std::optional<TypeId> tryStripUnionFromNil(TypeId ty) const; std::optional<TypeId> tryStripUnionFromNil(TypeId ty);
TypeId stripFromNilAndReport(TypeId ty, const Location& location); TypeId stripFromNilAndReport(TypeId ty, const Location& location);
void visitExprName(AstExpr* expr, Location location, const std::string& propName, ValueContext context, TypeId astIndexExprTy); void visitExprName(AstExpr* expr, Location location, const std::string& propName, ValueContext context, TypeId astIndexExprTy);
void visit(AstExprIndexName* indexName, ValueContext context); void visit(AstExprIndexName* indexName, ValueContext context);
@ -188,7 +168,7 @@ private:
void visit(AstExprInterpString* interpString); void visit(AstExprInterpString* interpString);
void visit(AstExprError* expr); void visit(AstExprError* expr);
TypeId flattenPack(TypePackId pack); TypeId flattenPack(TypePackId pack);
void visitGenerics(AstArray<AstGenericType*> generics, AstArray<AstGenericTypePack*> genericPacks); void visitGenerics(AstArray<AstGenericType> generics, AstArray<AstGenericTypePack> genericPacks);
void visit(AstType* ty); void visit(AstType* ty);
void visit(AstTypeReference* ty); void visit(AstTypeReference* ty);
void visit(AstTypeTable* table); void visit(AstTypeTable* table);
@ -230,9 +210,6 @@ private:
std::vector<TypeError>& errors std::vector<TypeError>& errors
); );
// Avoid duplicate warnings being emitted for the same global variable.
DenseHashSet<std::string> warnedGlobals{""};
void diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& data) const; void diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& data) const;
bool isErrorSuppressing(Location loc, TypeId ty); bool isErrorSuppressing(Location loc, TypeId ty);
bool isErrorSuppressing(Location loc1, TypeId ty1, Location loc2, TypeId ty2); bool isErrorSuppressing(Location loc1, TypeId ty1, Location loc2, TypeId ty2);

View file

@ -1,71 +1,29 @@
// 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
#pragma once #pragma once
#include "Luau/Constraint.h" #include "Luau/ConstraintSolver.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/TypeCheckLimits.h" #include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFunctionRuntime.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include <functional> #include <functional>
#include <string> #include <string>
#include <optional> #include <optional>
struct lua_State;
namespace Luau namespace Luau
{ {
struct TypeArena; struct TypeArena;
struct TxnLog; struct TxnLog;
struct ConstraintSolver;
class Normalizer; class Normalizer;
using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>;
struct TypeFunctionRuntime
{
TypeFunctionRuntime(NotNull<InternalErrorReporter> ice, NotNull<TypeCheckLimits> limits);
~TypeFunctionRuntime();
// Return value is an error message if registration failed
std::optional<std::string> registerFunction(AstStatTypeFunction* function);
// For user-defined type functions, we store all generated types and packs for the duration of the typecheck
TypedAllocator<TypeFunctionType> typeArena;
TypedAllocator<TypeFunctionTypePackVar> typePackArena;
NotNull<InternalErrorReporter> ice;
NotNull<TypeCheckLimits> limits;
StateRef state;
// Set of functions which have their environment table initialized
DenseHashSet<AstStatTypeFunction*> initialized{nullptr};
// Evaluation of type functions should only be performed in the absence of parse errors in the source module
bool allowEvaluation = true;
// Root scope in which the type function operates in, set up by ConstraintGenerator
ScopePtr rootScope;
// Output created by 'print' function
std::vector<std::string> messages;
private:
void prepareState();
};
struct TypeFunctionContext struct TypeFunctionContext
{ {
NotNull<TypeArena> arena; NotNull<TypeArena> arena;
NotNull<BuiltinTypes> builtins; NotNull<BuiltinTypes> builtins;
NotNull<Scope> scope; NotNull<Scope> scope;
NotNull<Simplifier> simplifier;
NotNull<Normalizer> normalizer; NotNull<Normalizer> normalizer;
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
NotNull<InternalErrorReporter> ice; NotNull<InternalErrorReporter> ice;
NotNull<TypeCheckLimits> limits; NotNull<TypeCheckLimits> limits;
@ -74,26 +32,33 @@ struct TypeFunctionContext
// The constraint being reduced in this run of the reduction // The constraint being reduced in this run of the reduction
const Constraint* constraint; const Constraint* constraint;
std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs
std::optional<AstExprFunction*> userFuncBody; // Body of the user-defined type function; only available for UDTFs
TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint); TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint)
: arena(cs->arena)
, builtins(cs->builtinTypes)
, scope(scope)
, normalizer(cs->normalizer)
, ice(NotNull{&cs->iceReporter})
, limits(NotNull{&cs->limits})
, solver(cs.get())
, constraint(constraint.get())
{
}
TypeFunctionContext( TypeFunctionContext(
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtins, NotNull<BuiltinTypes> builtins,
NotNull<Scope> scope, NotNull<Scope> scope,
NotNull<Simplifier> simplifier,
NotNull<Normalizer> normalizer, NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> ice, NotNull<InternalErrorReporter> ice,
NotNull<TypeCheckLimits> limits NotNull<TypeCheckLimits> limits
) )
: arena(arena) : arena(arena)
, builtins(builtins) , builtins(builtins)
, scope(scope) , scope(scope)
, simplifier(simplifier)
, normalizer(normalizer) , normalizer(normalizer)
, typeFunctionRuntime(typeFunctionRuntime)
, ice(ice) , ice(ice)
, limits(limits) , limits(limits)
, solver(nullptr) , solver(nullptr)
@ -101,17 +66,7 @@ struct TypeFunctionContext
{ {
} }
NotNull<Constraint> pushConstraint(ConstraintV&& c) const; NotNull<Constraint> pushConstraint(ConstraintV&& c);
};
enum class Reduction
{
// The type function is either known to be reducible or the determination is blocked.
MaybeOk,
// The type function is known to be irreducible, but maybe not be erroneous, e.g. when it's over generics or free types.
Irreducible,
// The type function is known to be irreducible, and is definitely erroneous.
Erroneous,
}; };
/// Represents a reduction result, which may have successfully reduced the type, /// Represents a reduction result, which may have successfully reduced the type,
@ -120,25 +75,19 @@ enum class Reduction
template<typename Ty> template<typename Ty>
struct TypeFunctionReductionResult struct TypeFunctionReductionResult
{ {
/// The result of the reduction, if any. If this is nullopt, the type function /// The result of the reduction, if any. If this is nullopt, the type function
/// could not be reduced. /// could not be reduced.
std::optional<Ty> result; std::optional<Ty> result;
/// Indicates the status of this reduction: is `Reduction::Irreducible` if /// Whether the result is uninhabited: whether we know, unambiguously and
/// the this result indicates the type function is irreducible, and /// permanently, whether this type function reduction results in an
/// `Reduction::Erroneous` if this result indicates the type function is /// uninhabitable type. This will trigger an error to be reported.
/// erroneous. `Reduction::MaybeOk` otherwise. bool uninhabited;
Reduction reductionStatus;
/// Any types that need to be progressed or mutated before the reduction may /// Any types that need to be progressed or mutated before the reduction may
/// proceed. /// proceed.
std::vector<TypeId> blockedTypes; std::vector<TypeId> blockedTypes;
/// Any type packs that need to be progressed or mutated before the /// Any type packs that need to be progressed or mutated before the
/// reduction may proceed. /// reduction may proceed.
std::vector<TypePackId> blockedPacks; std::vector<TypePackId> blockedPacks;
/// A runtime error message from user-defined type functions
std::optional<std::string> error;
/// Messages printed out from user-defined type functions
std::vector<std::string> messages;
}; };
template<typename T> template<typename T>
@ -155,9 +104,6 @@ struct TypeFunction
/// The reducer function for the type function. /// The reducer function for the type function.
ReducerFunction<TypeId> reducer; ReducerFunction<TypeId> reducer;
/// If true, this type function can reduce even if it is parameterized on a generic.
bool canReduceGenerics = false;
}; };
/// Represents a type function that may be applied to map a series of types and /// Represents a type function that may be applied to map a series of types and
@ -170,20 +116,15 @@ struct TypePackFunction
/// The reducer function for the type pack function. /// The reducer function for the type pack function.
ReducerFunction<TypePackId> reducer; ReducerFunction<TypePackId> reducer;
/// If true, this type function can reduce even if it is parameterized on a generic.
bool canReduceGenerics = false;
}; };
struct FunctionGraphReductionResult struct FunctionGraphReductionResult
{ {
ErrorVec errors; ErrorVec errors;
ErrorVec messages;
DenseHashSet<TypeId> blockedTypes{nullptr}; DenseHashSet<TypeId> blockedTypes{nullptr};
DenseHashSet<TypePackId> blockedPacks{nullptr}; DenseHashSet<TypePackId> blockedPacks{nullptr};
DenseHashSet<TypeId> reducedTypes{nullptr}; DenseHashSet<TypeId> reducedTypes{nullptr};
DenseHashSet<TypePackId> reducedPacks{nullptr}; DenseHashSet<TypePackId> reducedPacks{nullptr};
DenseHashSet<TypeId> irreducibleTypes{nullptr};
}; };
/** /**
@ -251,11 +192,6 @@ struct BuiltinTypeFunctions
TypeFunction indexFunc; TypeFunction indexFunc;
TypeFunction rawgetFunc; TypeFunction rawgetFunc;
TypeFunction setmetatableFunc;
TypeFunction getmetatableFunc;
TypeFunction weakoptionalFunc;
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const; void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
}; };

View file

@ -1,296 +0,0 @@
// 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/Variant.h"
#include "Luau/TypeFwd.h"
#include <optional>
#include <string>
#include <map>
#include <vector>
using lua_State = struct lua_State;
namespace Luau
{
void* typeFunctionAlloc(void* ud, void* ptr, size_t osize, size_t nsize);
// Replica of types from Type.h
struct TypeFunctionType;
using TypeFunctionTypeId = const TypeFunctionType*;
struct TypeFunctionTypePackVar;
using TypeFunctionTypePackId = const TypeFunctionTypePackVar*;
struct TypeFunctionPrimitiveType
{
enum Type
{
NilType,
Boolean,
Number,
String,
Thread,
Buffer,
};
Type type;
TypeFunctionPrimitiveType(Type type)
: type(type)
{
}
};
struct TypeFunctionBooleanSingleton
{
bool value = false;
};
struct TypeFunctionStringSingleton
{
std::string value;
};
using TypeFunctionSingletonVariant = Variant<TypeFunctionBooleanSingleton, TypeFunctionStringSingleton>;
struct TypeFunctionSingletonType
{
TypeFunctionSingletonVariant variant;
explicit TypeFunctionSingletonType(TypeFunctionSingletonVariant variant)
: variant(std::move(variant))
{
}
};
template<typename T>
const T* get(const TypeFunctionSingletonType* tv)
{
LUAU_ASSERT(tv);
return tv ? get_if<T>(&tv->variant) : nullptr;
}
template<typename T>
T* getMutable(const TypeFunctionSingletonType* tv)
{
LUAU_ASSERT(tv);
return tv ? get_if<T>(&const_cast<TypeFunctionSingletonType*>(tv)->variant) : nullptr;
}
struct TypeFunctionUnionType
{
std::vector<TypeFunctionTypeId> components;
};
struct TypeFunctionIntersectionType
{
std::vector<TypeFunctionTypeId> components;
};
struct TypeFunctionAnyType
{
};
struct TypeFunctionUnknownType
{
};
struct TypeFunctionNeverType
{
};
struct TypeFunctionNegationType
{
TypeFunctionTypeId type;
};
struct TypeFunctionTypePack
{
std::vector<TypeFunctionTypeId> head;
std::optional<TypeFunctionTypePackId> tail;
};
struct TypeFunctionVariadicTypePack
{
TypeFunctionTypeId type;
};
struct TypeFunctionGenericTypePack
{
bool isNamed = false;
std::string name;
};
using TypeFunctionTypePackVariant = Variant<TypeFunctionTypePack, TypeFunctionVariadicTypePack, TypeFunctionGenericTypePack>;
struct TypeFunctionTypePackVar
{
TypeFunctionTypePackVariant type;
TypeFunctionTypePackVar(TypeFunctionTypePackVariant type)
: type(std::move(type))
{
}
bool operator==(const TypeFunctionTypePackVar& rhs) const;
};
struct TypeFunctionFunctionType
{
std::vector<TypeFunctionTypeId> generics;
std::vector<TypeFunctionTypePackId> genericPacks;
TypeFunctionTypePackId argTypes;
TypeFunctionTypePackId retTypes;
};
template<typename T>
const T* get(TypeFunctionTypePackId tv)
{
LUAU_ASSERT(tv);
return tv ? get_if<T>(&tv->type) : nullptr;
}
template<typename T>
T* getMutable(TypeFunctionTypePackId tv)
{
LUAU_ASSERT(tv);
return tv ? get_if<T>(&const_cast<TypeFunctionTypePackVar*>(tv)->type) : nullptr;
}
struct TypeFunctionTableIndexer
{
TypeFunctionTableIndexer(TypeFunctionTypeId keyType, TypeFunctionTypeId valueType)
: keyType(keyType)
, valueType(valueType)
{
}
TypeFunctionTypeId keyType;
TypeFunctionTypeId valueType;
};
struct TypeFunctionProperty
{
static TypeFunctionProperty readonly(TypeFunctionTypeId ty);
static TypeFunctionProperty writeonly(TypeFunctionTypeId ty);
static TypeFunctionProperty rw(TypeFunctionTypeId ty); // Shared read-write type.
static TypeFunctionProperty rw(TypeFunctionTypeId read, TypeFunctionTypeId write); // Separate read-write type.
bool isReadOnly() const;
bool isWriteOnly() const;
std::optional<TypeFunctionTypeId> readTy;
std::optional<TypeFunctionTypeId> writeTy;
};
struct TypeFunctionTableType
{
using Name = std::string;
using Props = std::map<Name, TypeFunctionProperty>;
Props props;
std::optional<TypeFunctionTableIndexer> indexer;
// Should always be a TypeFunctionTableType
std::optional<TypeFunctionTypeId> metatable;
};
struct TypeFunctionExternType
{
using Name = std::string;
using Props = std::map<Name, TypeFunctionProperty>;
Props props;
std::optional<TypeFunctionTableIndexer> indexer;
std::optional<TypeFunctionTypeId> metatable; // metaclass?
// this was mistaken, and we should actually be keeping separate read/write types here.
std::optional<TypeFunctionTypeId> parent_DEPRECATED;
std::optional<TypeFunctionTypeId> readParent;
std::optional<TypeFunctionTypeId> writeParent;
TypeId externTy;
};
struct TypeFunctionGenericType
{
bool isNamed = false;
bool isPack = false;
std::string name;
};
using TypeFunctionTypeVariant = Luau::Variant<
TypeFunctionPrimitiveType,
TypeFunctionAnyType,
TypeFunctionUnknownType,
TypeFunctionNeverType,
TypeFunctionSingletonType,
TypeFunctionUnionType,
TypeFunctionIntersectionType,
TypeFunctionNegationType,
TypeFunctionFunctionType,
TypeFunctionTableType,
TypeFunctionExternType,
TypeFunctionGenericType>;
struct TypeFunctionType
{
TypeFunctionTypeVariant type;
TypeFunctionType(TypeFunctionTypeVariant type)
: type(std::move(type))
{
}
bool operator==(const TypeFunctionType& rhs) const;
};
template<typename T>
const T* get(TypeFunctionTypeId tv)
{
LUAU_ASSERT(tv);
return tv ? Luau::get_if<T>(&tv->type) : nullptr;
}
template<typename T>
T* getMutable(TypeFunctionTypeId tv)
{
LUAU_ASSERT(tv);
return tv ? Luau::get_if<T>(&const_cast<TypeFunctionType*>(tv)->type) : nullptr;
}
std::optional<std::string> checkResultForError(lua_State* L, const char* typeFunctionName, int luaResult);
TypeFunctionType* allocateTypeFunctionType(lua_State* L, TypeFunctionTypeVariant type);
TypeFunctionTypePackVar* allocateTypeFunctionTypePack(lua_State* L, TypeFunctionTypePackVariant type);
void allocTypeUserData(lua_State* L, TypeFunctionTypeVariant type);
bool isTypeUserData(lua_State* L, int idx);
TypeFunctionTypeId getTypeUserData(lua_State* L, int idx);
std::optional<TypeFunctionTypeId> optionalTypeUserData(lua_State* L, int idx);
void registerTypesLibrary(lua_State* L);
void registerTypeUserData(lua_State* L);
void setTypeFunctionEnvironment(lua_State* L);
void resetTypeFunctionState(lua_State* L);
} // namespace Luau

View file

@ -1,44 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Type.h"
#include "Luau/TypeFunction.h"
#include "Luau/TypeFunctionRuntime.h"
namespace Luau
{
using Kind = Variant<TypeId, TypePackId>;
template<typename T>
const T* get(const Kind& kind)
{
return get_if<T>(&kind);
}
using TypeFunctionKind = Variant<TypeFunctionTypeId, TypeFunctionTypePackId>;
template<typename T>
const T* get(const TypeFunctionKind& tfkind)
{
return get_if<T>(&tfkind);
}
struct TypeFunctionRuntimeBuilderState
{
NotNull<TypeFunctionContext> ctx;
// List of errors that occur during serialization/deserialization
// At every iteration of serialization/deserialization, if this list.size() != 0, we halt the process
std::vector<std::string> errors{};
TypeFunctionRuntimeBuilderState(NotNull<TypeFunctionContext> ctx)
: ctx(ctx)
{
}
};
TypeFunctionTypeId serialize(TypeId ty, TypeFunctionRuntimeBuilderState* state);
TypeId deserialize(TypeFunctionTypeId ty, TypeFunctionRuntimeBuilderState* state);
} // namespace Luau

View file

@ -29,7 +29,7 @@ struct SingletonType;
struct FunctionType; struct FunctionType;
struct TableType; struct TableType;
struct MetatableType; struct MetatableType;
struct ExternType; struct ClassType;
struct AnyType; struct AnyType;
struct UnionType; struct UnionType;
struct IntersectionType; struct IntersectionType;

View file

@ -90,11 +90,11 @@ struct TypeChecker
ControlFlow check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatLocalFunction& function); ControlFlow check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatLocalFunction& function);
ControlFlow check(const ScopePtr& scope, const AstStatTypeAlias& typealias); ControlFlow check(const ScopePtr& scope, const AstStatTypeAlias& typealias);
ControlFlow check(const ScopePtr& scope, const AstStatTypeFunction& typefunction); ControlFlow check(const ScopePtr& scope, const AstStatTypeFunction& typefunction);
ControlFlow check(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType); ControlFlow check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
ControlFlow check(const ScopePtr& scope, const AstStatDeclareFunction& declaredFunction); ControlFlow check(const ScopePtr& scope, const AstStatDeclareFunction& declaredFunction);
void prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel = 0); void prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel = 0);
void prototype(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType); void prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
ControlFlow checkBlock(const ScopePtr& scope, const AstStatBlock& statement); ControlFlow checkBlock(const ScopePtr& scope, const AstStatBlock& statement);
ControlFlow checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement); ControlFlow checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement);
@ -130,7 +130,6 @@ struct TypeChecker
const PredicateVec& predicates = {} const PredicateVec& predicates = {}
); );
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt); WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt);
WithPredicate<TypeId> checkExpr_DEPRECATED(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr); WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr); WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType = std::nullopt); WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType = std::nullopt);
@ -400,8 +399,8 @@ private:
const ScopePtr& scope, const ScopePtr& scope,
std::optional<TypeLevel> levelOpt, std::optional<TypeLevel> levelOpt,
const AstNode& node, const AstNode& node,
const AstArray<AstGenericType*>& genericNames, const AstArray<AstGenericType>& genericNames,
const AstArray<AstGenericTypePack*>& genericPackNames, const AstArray<AstGenericTypePack>& genericPackNames,
bool useCache = false bool useCache = false
); );
@ -487,7 +486,7 @@ private:
/** /**
* A set of incorrect class definitions which is used to avoid a second-pass analysis. * A set of incorrect class definitions which is used to avoid a second-pass analysis.
*/ */
DenseHashSet<const AstStatDeclareExternType*> incorrectExternTypeDefinitions{nullptr}; DenseHashSet<const AstStatDeclareClass*> incorrectClassDefinitions{nullptr};
std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification; std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification;
}; };

View file

@ -1,12 +1,11 @@
// 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
#pragma once #pragma once
#include "Luau/Common.h"
#include "Luau/NotNull.h"
#include "Luau/Polarity.h"
#include "Luau/TypeFwd.h"
#include "Luau/Unifiable.h" #include "Luau/Unifiable.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/TypeFwd.h"
#include "Luau/NotNull.h"
#include "Luau/Common.h"
#include <optional> #include <optional>
#include <set> #include <set>
@ -27,14 +26,12 @@ struct TypeFunctionInstanceTypePack;
struct FreeTypePack struct FreeTypePack
{ {
explicit FreeTypePack(TypeLevel level); explicit FreeTypePack(TypeLevel level);
explicit FreeTypePack(Scope* scope, Polarity polarity = Polarity::Unknown); explicit FreeTypePack(Scope* scope);
FreeTypePack(Scope* scope, TypeLevel level); FreeTypePack(Scope* scope, TypeLevel level);
int index; int index;
TypeLevel level; TypeLevel level;
Scope* scope = nullptr; Scope* scope = nullptr;
Polarity polarity = Polarity::Unknown;
}; };
struct GenericTypePack struct GenericTypePack
@ -43,7 +40,7 @@ struct GenericTypePack
GenericTypePack(); GenericTypePack();
explicit GenericTypePack(TypeLevel level); explicit GenericTypePack(TypeLevel level);
explicit GenericTypePack(const Name& name); explicit GenericTypePack(const Name& name);
explicit GenericTypePack(Scope* scope, Polarity polarity = Polarity::Unknown); explicit GenericTypePack(Scope* scope);
GenericTypePack(TypeLevel level, const Name& name); GenericTypePack(TypeLevel level, const Name& name);
GenericTypePack(Scope* scope, const Name& name); GenericTypePack(Scope* scope, const Name& name);
@ -52,12 +49,10 @@ struct GenericTypePack
Scope* scope = nullptr; Scope* scope = nullptr;
Name name; Name name;
bool explicitName = false; bool explicitName = false;
Polarity polarity = Polarity::Unknown;
}; };
using BoundTypePack = Unifiable::Bound<TypePackId>; using BoundTypePack = Unifiable::Bound<TypePackId>;
using ErrorTypePack = Unifiable::Error<TypePackId>; using ErrorTypePack = Unifiable::Error;
using TypePackVariant = using TypePackVariant =
Unifiable::Variant<TypePackId, FreeTypePack, GenericTypePack, TypePack, VariadicTypePack, BlockedTypePack, TypeFunctionInstanceTypePack>; Unifiable::Variant<TypePackId, FreeTypePack, GenericTypePack, TypePack, VariadicTypePack, BlockedTypePack, TypeFunctionInstanceTypePack>;
@ -105,9 +100,9 @@ struct TypeFunctionInstanceTypePack
struct TypePackVar struct TypePackVar
{ {
explicit TypePackVar(const TypePackVariant& tp); explicit TypePackVar(const TypePackVariant& ty);
explicit TypePackVar(TypePackVariant&& tp); explicit TypePackVar(TypePackVariant&& ty);
TypePackVar(TypePackVariant&& tp, bool persistent); TypePackVar(TypePackVariant&& ty, bool persistent);
bool operator==(const TypePackVar& rhs) const; bool operator==(const TypePackVar& rhs) const;
@ -174,7 +169,6 @@ struct TypePackIterator
private: private:
TypePackId currentTypePack = nullptr; TypePackId currentTypePack = nullptr;
TypePackId tailCycleCheck = nullptr;
const TypePack* tp = nullptr; const TypePack* tp = nullptr;
size_t currentIndex = 0; size_t currentIndex = 0;
@ -185,8 +179,6 @@ TypePackIterator begin(TypePackId tp);
TypePackIterator begin(TypePackId tp, const TxnLog* log); TypePackIterator begin(TypePackId tp, const TxnLog* log);
TypePackIterator end(TypePackId tp); TypePackIterator end(TypePackId tp);
TypePackId getTail(TypePackId tp);
using SeenSet = std::set<std::pair<const void*, const void*>>; using SeenSet = std::set<std::pair<const void*, const void*>>;
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs); bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);

View file

@ -42,27 +42,15 @@ struct Property
/// element. /// element.
struct Index struct Index
{ {
enum class Variant
{
Pack,
Union,
Intersection
};
/// The 0-based index to use for the lookup. /// The 0-based index to use for the lookup.
size_t index; size_t index;
/// The sort of thing we're indexing from, this is used in stringifying the type path for errors.
Variant variant;
bool operator==(const Index& other) const; bool operator==(const Index& other) const;
}; };
/// Represents fields of a type or pack that contain a type. /// Represents fields of a type or pack that contain a type.
enum class TypeField enum class TypeField
{ {
/// The table of a metatable type.
Table,
/// The metatable of a type. This could be a metatable type, a primitive /// The metatable of a type. This could be a metatable type, a primitive
/// type, a class type, or perhaps even a string singleton type. /// type, a class type, or perhaps even a string singleton type.
Metatable, Metatable,
@ -215,9 +203,6 @@ using Path = TypePath::Path;
/// terribly clear to end users of the Luau type system. /// terribly clear to end users of the Luau type system.
std::string toString(const TypePath::Path& path, bool prefixDot = false); std::string toString(const TypePath::Path& path, bool prefixDot = false);
/// Converts a Path to a human readable string for error reporting.
std::string toStringHuman(const TypePath::Path& path);
std::optional<TypeOrPack> traverse(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes); std::optional<TypeOrPack> traverse(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
std::optional<TypeOrPack> traverse(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes); std::optional<TypeOrPack> traverse(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);

View file

@ -40,7 +40,7 @@ struct InConditionalContext
TypeContext* typeContext; TypeContext* typeContext;
TypeContext oldValue; TypeContext oldValue;
explicit InConditionalContext(TypeContext* c) InConditionalContext(TypeContext* c)
: typeContext(c) : typeContext(c)
, oldValue(*c) , oldValue(*c)
{ {
@ -248,47 +248,4 @@ std::optional<Ty> follow(std::optional<Ty> ty)
return std::nullopt; return std::nullopt;
} }
/**
* Returns whether or not expr is a literal expression, for example:
* - Scalar literals (numbers, booleans, strings, nil)
* - Table literals
* - Lambdas (a "function literal")
*/
bool isLiteral(const AstExpr* expr);
/**
* Given a table literal and a mapping from expression to type, determine
* whether any literal expression in this table depends on any blocked types.
* This is used as a precondition for bidirectional inference: be warned that
* the behavior of this algorithm is tightly coupled to that of bidirectional
* inference.
* @param expr Expression to search
* @param astTypes Mapping from AST node to TypeID
* @returns A vector of blocked types
*/
std::vector<TypeId> findBlockedTypesIn(AstExprTable* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes);
/**
* Given a function call and a mapping from expression to type, determine
* whether the type of any argument in said call in depends on a blocked types.
* This is used as a precondition for bidirectional inference: be warned that
* the behavior of this algorithm is tightly coupled to that of bidirectional
* inference.
* @param expr Expression to search
* @param astTypes Mapping from AST node to TypeID
* @returns A vector of blocked types
*/
std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes);
/**
* Given a scope and a free type, find the closest parent that has a present
* `interiorFreeTypes` and append the given type to said list. This list will
* be generalized when the requiste `GeneralizationConstraint` is resolved.
* @param scope Initial scope this free type was attached to
* @param ty Free type to track.
*/
void trackInteriorFreeType(Scope* scope, TypeId ty);
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp);
} // namespace Luau } // namespace Luau

View file

@ -3,7 +3,6 @@
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include <optional>
#include <string> #include <string>
namespace Luau namespace Luau
@ -95,29 +94,19 @@ struct Bound
Id boundTo; Id boundTo;
}; };
template<typename Id>
struct Error struct Error
{ {
// This constructor has to be public, since it's used in Type and TypePack, // This constructor has to be public, since it's used in Type and TypePack,
// but shouldn't be called directly. Please use errorRecoveryType() instead. // but shouldn't be called directly. Please use errorRecoveryType() instead.
explicit Error(); Error();
explicit Error(Id synthetic)
: synthetic{synthetic}
{
}
int index; int index;
// This is used to create an error that can be rendered out using this field
// as appropriate metadata for communicating it to the user.
std::optional<Id> synthetic;
private: private:
static int nextIndex; static int nextIndex;
}; };
template<typename Id, typename... Value> template<typename Id, typename... Value>
using Variant = Luau::Variant<Bound<Id>, Error<Id>, Value...>; using Variant = Luau::Variant<Bound<Id>, Error, Value...>;
} // namespace Luau::Unifiable } // namespace Luau::Unifiable

View file

@ -93,6 +93,10 @@ struct Unifier
Unifier(NotNull<Normalizer> normalizer, NotNull<Scope> scope, const Location& location, Variance variance, TxnLog* parentLog = nullptr); Unifier(NotNull<Normalizer> normalizer, NotNull<Scope> scope, const Location& location, Variance variance, TxnLog* parentLog = nullptr);
// Configure the Unifier to test for scope subsumption via embedded Scope
// pointers rather than TypeLevels.
void enableNewSolver();
// Test whether the two type vars unify. Never commits the result. // Test whether the two type vars unify. Never commits the result.
ErrorVec canUnify(TypeId subTy, TypeId superTy); ErrorVec canUnify(TypeId subTy, TypeId superTy);
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false); ErrorVec canUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
@ -140,7 +144,7 @@ private:
void tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr); void tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed); void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed); void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithExternType(TypeId subTy, TypeId superTy, bool reversed); void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyNegations(TypeId subTy, TypeId superTy); void tryUnifyNegations(TypeId subTy, TypeId superTy);
TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args); TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args);
@ -165,6 +169,7 @@ private:
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name); std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name);
TxnLog combineLogsIntoIntersection(std::vector<TxnLog> logs);
TxnLog combineLogsIntoUnion(std::vector<TxnLog> logs); TxnLog combineLogsIntoUnion(std::vector<TxnLog> logs);
public: public:
@ -174,7 +179,7 @@ public:
bool occursCheck(TypePackId needle, TypePackId haystack, bool reversed); bool occursCheck(TypePackId needle, TypePackId haystack, bool reversed);
bool occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack); bool occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
std::unique_ptr<Unifier> makeChildUnifier(); Unifier makeChildUnifier();
void reportError(TypeError err); void reportError(TypeError err);
LUAU_NOINLINE void reportError(Location location, TypeErrorData data); LUAU_NOINLINE void reportError(Location location, TypeErrorData data);
@ -190,6 +195,11 @@ private:
// Available after regular type pack unification errors // Available after regular type pack unification errors
std::optional<int> firstPackErrorPos; std::optional<int> firstPackErrorPos;
// If true, we do a bunch of small things differently to work better with
// the new type inference engine. Most notably, we use the Scope hierarchy
// directly rather than using TypeLevels.
bool useNewSolver = false;
}; };
void promoteTypeLevels(TxnLog& log, const TypeArena* arena, TypeLevel minLevel, Scope* outerScope, bool useScope, TypePackId tp); void promoteTypeLevels(TxnLog& log, const TypeArena* arena, TypeLevel minLevel, Scope* outerScope, bool useScope, TypePackId tp);

View file

@ -44,12 +44,6 @@ struct Unifier2
// Mapping from generic type packs to `TypePack`s of free types to be used in instantiation. // Mapping from generic type packs to `TypePack`s of free types to be used in instantiation.
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr}; DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};
// Unification sometimes results in the creation of new free types.
// We collect them here so that other systems can perform necessary
// bookkeeping.
std::vector<TypeId> newFreshTypes;
std::vector<TypePackId> newFreshTypePacks;
int recursionCount = 0; int recursionCount = 0;
int recursionLimit = 0; int recursionLimit = 0;
@ -93,9 +87,6 @@ struct Unifier2
bool unify(const AnyType* subAny, const TableType* superTable); bool unify(const AnyType* subAny, const TableType* superTable);
bool unify(const TableType* subTable, const AnyType* superAny); bool unify(const TableType* subTable, const AnyType* superAny);
bool unify(const MetatableType* subMetatable, const AnyType*);
bool unify(const AnyType*, const MetatableType* superMetatable);
// TODO think about this one carefully. We don't do unions or intersections of type packs // TODO think about this one carefully. We don't do unions or intersections of type packs
bool unify(TypePackId subTp, TypePackId superTp); bool unify(TypePackId subTp, TypePackId superTp);
@ -119,9 +110,6 @@ private:
// Returns true if needle occurs within haystack already. ie if we bound // Returns true if needle occurs within haystack already. ie if we bound
// needle to haystack, would a cyclic TypePack result? // needle to haystack, would a cyclic TypePack result?
OccursCheckResult occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack); OccursCheckResult occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
TypeId freshType(NotNull<Scope> scope, Polarity polarity);
TypePackId freshTypePack(NotNull<Scope> scope, Polarity polarity);
}; };
} // namespace Luau } // namespace Luau

View file

@ -49,26 +49,6 @@ struct UnifierSharedState
DenseHashSet<TypePackId> tempSeenTp{nullptr}; DenseHashSet<TypePackId> tempSeenTp{nullptr};
UnifierCounters counters; UnifierCounters counters;
bool reentrantTypeReduction = false;
};
struct TypeReductionRentrancyGuard final
{
explicit TypeReductionRentrancyGuard(NotNull<UnifierSharedState> sharedState)
: sharedState{sharedState}
{
sharedState->reentrantTypeReduction = true;
}
~TypeReductionRentrancyGuard()
{
sharedState->reentrantTypeReduction = false;
}
TypeReductionRentrancyGuard(const TypeReductionRentrancyGuard&) = delete;
TypeReductionRentrancyGuard(TypeReductionRentrancyGuard&&) = delete;
private:
NotNull<UnifierSharedState> sharedState;
}; };
} // namespace Luau } // namespace Luau

View file

@ -10,6 +10,7 @@
#include "Type.h" #include "Type.h"
LUAU_FASTINT(LuauVisitRecursionLimit) LUAU_FASTINT(LuauVisitRecursionLimit)
LUAU_FASTFLAG(LuauBoundLazyTypes2)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
namespace Luau namespace Luau
@ -85,8 +86,6 @@ struct GenericTypeVisitor
{ {
} }
virtual ~GenericTypeVisitor() {}
virtual void cycle(TypeId) {} virtual void cycle(TypeId) {}
virtual void cycle(TypePackId) {} virtual void cycle(TypePackId) {}
@ -126,7 +125,7 @@ struct GenericTypeVisitor
{ {
return visit(ty); return visit(ty);
} }
virtual bool visit(TypeId ty, const ExternType& etv) virtual bool visit(TypeId ty, const ClassType& ctv)
{ {
return visit(ty); return visit(ty);
} }
@ -134,10 +133,6 @@ struct GenericTypeVisitor
{ {
return visit(ty); return visit(ty);
} }
virtual bool visit(TypeId ty, const NoRefineType& nrt)
{
return visit(ty);
}
virtual bool visit(TypeId ty, const UnknownType& utv) virtual bool visit(TypeId ty, const UnknownType& utv)
{ {
return visit(ty); return visit(ty);
@ -191,7 +186,7 @@ struct GenericTypeVisitor
{ {
return visit(tp); return visit(tp);
} }
virtual bool visit(TypePackId tp, const ErrorTypePack& etp) virtual bool visit(TypePackId tp, const Unifiable::Error& etp)
{ {
return visit(tp); return visit(tp);
} }
@ -313,11 +308,11 @@ struct GenericTypeVisitor
traverse(mtv->metatable); traverse(mtv->metatable);
} }
} }
else if (auto etv = get<ExternType>(ty)) else if (auto ctv = get<ClassType>(ty))
{ {
if (visit(ty, *etv)) if (visit(ty, *ctv))
{ {
for (const auto& [name, prop] : etv->props) for (const auto& [name, prop] : ctv->props)
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
@ -335,23 +330,21 @@ struct GenericTypeVisitor
traverse(prop.type()); traverse(prop.type());
} }
if (etv->parent) if (ctv->parent)
traverse(*etv->parent); traverse(*ctv->parent);
if (etv->metatable) if (ctv->metatable)
traverse(*etv->metatable); traverse(*ctv->metatable);
if (etv->indexer) if (ctv->indexer)
{ {
traverse(etv->indexer->indexType); traverse(ctv->indexer->indexType);
traverse(etv->indexer->indexResultType); traverse(ctv->indexer->indexResultType);
} }
} }
} }
else if (auto atv = get<AnyType>(ty)) else if (auto atv = get<AnyType>(ty))
visit(ty, *atv); visit(ty, *atv);
else if (auto nrt = get<NoRefineType>(ty))
visit(ty, *nrt);
else if (auto utv = get<UnionType>(ty)) else if (auto utv = get<UnionType>(ty))
{ {
if (visit(ty, *utv)) if (visit(ty, *utv))
@ -396,7 +389,7 @@ struct GenericTypeVisitor
traverse(unwrapped); traverse(unwrapped);
// Visiting into LazyType that hasn't been unwrapped may necessarily cause infinite expansion, so we don't do that on purpose. // Visiting into LazyType that hasn't been unwrapped may necessarily cause infinite expansion, so we don't do that on purpose.
// Asserting also makes no sense, because the type _will_ happen here, most likely as a property of some ExternType // Asserting also makes no sense, because the type _will_ happen here, most likely as a property of some ClassType
// that doesn't need to be expanded. // that doesn't need to be expanded.
} }
else if (auto stv = get<SingletonType>(ty)) else if (auto stv = get<SingletonType>(ty))
@ -462,7 +455,7 @@ struct GenericTypeVisitor
else if (auto gtv = get<GenericTypePack>(tp)) else if (auto gtv = get<GenericTypePack>(tp))
visit(tp, *gtv); visit(tp, *gtv);
else if (auto etv = get<ErrorTypePack>(tp)) else if (auto etv = get<Unifiable::Error>(tp))
visit(tp, *etv); visit(tp, *etv);
else if (auto pack = get<TypePack>(tp)) else if (auto pack = get<TypePack>(tp))

View file

@ -0,0 +1,903 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/AnyTypeSummary.h"
#include "Luau/BuiltinDefinitions.h"
#include "Luau/Clone.h"
#include "Luau/Common.h"
#include "Luau/Config.h"
#include "Luau/ConstraintGenerator.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/DataFlowGraph.h"
#include "Luau/DcrLogger.h"
#include "Luau/Module.h"
#include "Luau/Parser.h"
#include "Luau/Scope.h"
#include "Luau/StringUtils.h"
#include "Luau/TimeTrace.h"
#include "Luau/ToString.h"
#include "Luau/Transpiler.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeChecker2.h"
#include "Luau/NonStrictTypeChecker.h"
#include "Luau/TypeInfer.h"
#include "Luau/Variant.h"
#include "Luau/VisitType.h"
#include "Luau/TypePack.h"
#include "Luau/TypeOrPack.h"
#include <algorithm>
#include <memory>
#include <chrono>
#include <condition_variable>
#include <exception>
#include <mutex>
#include <stdexcept>
#include <string>
#include <iostream>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(StudioReportLuauAny2, false);
LUAU_FASTINTVARIABLE(LuauAnySummaryRecursionLimit, 300);
LUAU_FASTFLAG(DebugLuauMagicTypes);
namespace Luau
{
void AnyTypeSummary::traverse(const Module* module, AstStat* src, NotNull<BuiltinTypes> builtinTypes)
{
visit(findInnerMostScope(src->location, module), src, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStat* stat, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
RecursionLimiter limiter{&recursionCount, FInt::LuauAnySummaryRecursionLimit};
if (auto s = stat->as<AstStatBlock>())
return visit(scope, s, module, builtinTypes);
else if (auto i = stat->as<AstStatIf>())
return visit(scope, i, module, builtinTypes);
else if (auto s = stat->as<AstStatWhile>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatRepeat>())
return visit(scope, s, module, builtinTypes);
else if (auto r = stat->as<AstStatReturn>())
return visit(scope, r, module, builtinTypes);
else if (auto e = stat->as<AstStatExpr>())
return visit(scope, e, module, builtinTypes);
else if (auto s = stat->as<AstStatLocal>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatFor>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatForIn>())
return visit(scope, s, module, builtinTypes);
else if (auto a = stat->as<AstStatAssign>())
return visit(scope, a, module, builtinTypes);
else if (auto a = stat->as<AstStatCompoundAssign>())
return visit(scope, a, module, builtinTypes);
else if (auto f = stat->as<AstStatFunction>())
return visit(scope, f, module, builtinTypes);
else if (auto f = stat->as<AstStatLocalFunction>())
return visit(scope, f, module, builtinTypes);
else if (auto a = stat->as<AstStatTypeAlias>())
return visit(scope, a, module, builtinTypes);
else if (auto s = stat->as<AstStatDeclareGlobal>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatDeclareFunction>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatDeclareClass>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatError>())
return visit(scope, s, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatBlock* block, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauAnySummaryRecursionLimit)
return; // don't report
for (AstStat* stat : block->body)
visit(scope, stat, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatIf* ifStatement, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (ifStatement->thenbody)
{
const Scope* thenScope = findInnerMostScope(ifStatement->thenbody->location, module);
visit(thenScope, ifStatement->thenbody, module, builtinTypes);
}
if (ifStatement->elsebody)
{
const Scope* elseScope = findInnerMostScope(ifStatement->elsebody->location, module);
visit(elseScope, ifStatement->elsebody, module, builtinTypes);
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatWhile* while_, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const Scope* whileScope = findInnerMostScope(while_->location, module);
visit(whileScope, while_->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatRepeat* repeat, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const Scope* repeatScope = findInnerMostScope(repeat->location, module);
visit(repeatScope, repeat->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatReturn* ret, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const Scope* retScope = findInnerMostScope(ret->location, module);
auto ctxNode = getNode(rootSrc, ret);
bool seenTP = false;
for (auto val : ret->list)
{
if (isAnyCall(retScope, val, module, builtinTypes))
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(val, module, builtinTypes));
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
typeInfo.push_back(ti);
}
if (isAnyCast(retScope, val, module, builtinTypes))
{
if (auto cast = val->as<AstExprTypeAssertion>())
{
TelemetryTypePair types;
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
types.inferredType = toString(lookupType(cast->expr, module, builtinTypes));
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
if (ret->list.size > 1 && !seenTP)
{
if (containsAny(retScope->returnType))
{
seenTP = true;
TelemetryTypePair types;
types.inferredType = toString(retScope->returnType);
TypeInfo ti{Pattern::TypePk, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatLocal* local, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto ctxNode = getNode(rootSrc, local);
TypePackId values = reconstructTypePack(local->values, module, builtinTypes);
auto [head, tail] = flatten(values);
size_t posn = 0;
for (AstLocal* loc : local->vars)
{
if (local->vars.data[0] == loc && posn < local->values.size)
{
if (loc->annotation)
{
auto annot = lookupAnnotation(loc->annotation, module, builtinTypes);
if (containsAny(annot))
{
TelemetryTypePair types;
types.annotatedType = toString(annot);
types.inferredType = toString(lookupType(local->values.data[posn], module, builtinTypes));
TypeInfo ti{Pattern::VarAnnot, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
const AstExprTypeAssertion* maybeRequire = local->values.data[posn]->as<AstExprTypeAssertion>();
if (!maybeRequire)
continue;
if (std::min(local->values.size - 1, posn) < head.size())
{
if (isAnyCast(scope, local->values.data[posn], module, builtinTypes))
{
TelemetryTypePair types;
types.inferredType = toString(head[std::min(local->values.size - 1, posn)]);
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
else
{
if (std::min(local->values.size - 1, posn) < head.size())
{
if (loc->annotation)
{
auto annot = lookupAnnotation(loc->annotation, module, builtinTypes);
if (containsAny(annot))
{
TelemetryTypePair types;
types.annotatedType = toString(annot);
types.inferredType = toString(head[std::min(local->values.size - 1, posn)]);
TypeInfo ti{Pattern::VarAnnot, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
else
{
if (tail)
{
if (containsAny(*tail))
{
TelemetryTypePair types;
types.inferredType = toString(*tail);
TypeInfo ti{Pattern::VarAny, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
}
++posn;
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatFor* for_, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const Scope* forScope = findInnerMostScope(for_->location, module);
visit(forScope, for_->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatForIn* forIn, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const Scope* loopScope = findInnerMostScope(forIn->location, module);
visit(loopScope, forIn->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto ctxNode = getNode(rootSrc, assign);
TypePackId values = reconstructTypePack(assign->values, module, builtinTypes);
auto [head, tail] = flatten(values);
size_t posn = 0;
for (AstExpr* var : assign->vars)
{
TypeId tp = lookupType(var, module, builtinTypes);
if (containsAny(tp))
{
TelemetryTypePair types;
types.annotatedType = toString(tp);
auto loc = std::min(assign->vars.size - 1, posn);
if (head.size() >= assign->vars.size && posn < head.size())
{
types.inferredType = toString(head[posn]);
}
else if (loc < head.size())
types.inferredType = toString(head[loc]);
else
types.inferredType = toString(builtinTypes->nilType);
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
typeInfo.push_back(ti);
}
++posn;
}
for (AstExpr* val : assign->values)
{
if (isAnyCall(scope, val, module, builtinTypes))
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(val, module, builtinTypes));
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
typeInfo.push_back(ti);
}
if (isAnyCast(scope, val, module, builtinTypes))
{
if (auto cast = val->as<AstExprTypeAssertion>())
{
TelemetryTypePair types;
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
types.inferredType = toString(lookupType(val, module, builtinTypes));
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
if (tail)
{
if (containsAny(*tail))
{
TelemetryTypePair types;
types.inferredType = toString(*tail);
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatCompoundAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto ctxNode = getNode(rootSrc, assign);
TelemetryTypePair types;
types.inferredType = toString(lookupType(assign->value, module, builtinTypes));
types.annotatedType = toString(lookupType(assign->var, module, builtinTypes));
if (module->astTypes.contains(assign->var))
{
if (containsAny(*module->astTypes.find(assign->var)))
{
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
else if (module->astTypePacks.contains(assign->var))
{
if (containsAny(*module->astTypePacks.find(assign->var)))
{
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
if (isAnyCall(scope, assign->value, module, builtinTypes))
{
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
typeInfo.push_back(ti);
}
if (isAnyCast(scope, assign->value, module, builtinTypes))
{
if (auto cast = assign->value->as<AstExprTypeAssertion>())
{
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
types.inferredType = toString(lookupType(cast->expr, module, builtinTypes));
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
if (hasVariadicAnys(scope, function->func, module, builtinTypes))
{
TypeInfo ti{Pattern::VarAny, toString(function), types};
typeInfo.push_back(ti);
}
if (hasArgAnys(scope, function->func, module, builtinTypes))
{
TypeInfo ti{Pattern::FuncArg, toString(function), types};
typeInfo.push_back(ti);
}
if (hasAnyReturns(scope, function->func, module, builtinTypes))
{
TypeInfo ti{Pattern::FuncRet, toString(function), types};
typeInfo.push_back(ti);
}
if (function->func->body->body.size > 0)
visit(scope, function->func->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatLocalFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
TelemetryTypePair types;
if (hasVariadicAnys(scope, function->func, module, builtinTypes))
{
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
TypeInfo ti{Pattern::VarAny, toString(function), types};
typeInfo.push_back(ti);
}
if (hasArgAnys(scope, function->func, module, builtinTypes))
{
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
TypeInfo ti{Pattern::FuncArg, toString(function), types};
typeInfo.push_back(ti);
}
if (hasAnyReturns(scope, function->func, module, builtinTypes))
{
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
TypeInfo ti{Pattern::FuncRet, toString(function), types};
typeInfo.push_back(ti);
}
if (function->func->body->body.size > 0)
visit(scope, function->func->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatTypeAlias* alias, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto ctxNode = getNode(rootSrc, alias);
auto annot = lookupAnnotation(alias->type, module, builtinTypes);
if (containsAny(annot))
{
// no expr => no inference for aliases
TelemetryTypePair types;
types.annotatedType = toString(annot);
TypeInfo ti{Pattern::Alias, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto ctxNode = getNode(rootSrc, expr);
if (isAnyCall(scope, expr->expr, module, builtinTypes))
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(expr->expr, module, builtinTypes));
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatDeclareGlobal* declareGlobal, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
void AnyTypeSummary::visit(const Scope* scope, AstStatDeclareClass* declareClass, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
void AnyTypeSummary::visit(const Scope* scope, AstStatDeclareFunction* declareFunction, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
void AnyTypeSummary::visit(const Scope* scope, AstStatError* error, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
TypeId AnyTypeSummary::checkForFamilyInhabitance(const TypeId instance, const Location location)
{
if (seenTypeFamilyInstances.find(instance))
return instance;
seenTypeFamilyInstances.insert(instance);
return instance;
}
TypeId AnyTypeSummary::lookupType(const AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const TypeId* ty = module->astTypes.find(expr);
if (ty)
return checkForFamilyInhabitance(follow(*ty), expr->location);
const TypePackId* tp = module->astTypePacks.find(expr);
if (tp)
{
if (auto fst = first(*tp, /*ignoreHiddenVariadics*/ false))
return checkForFamilyInhabitance(*fst, expr->location);
else if (finite(*tp) && size(*tp) == 0)
return checkForFamilyInhabitance(builtinTypes->nilType, expr->location);
}
return builtinTypes->errorRecoveryType();
}
TypePackId AnyTypeSummary::reconstructTypePack(AstArray<AstExpr*> exprs, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (exprs.size == 0)
return arena.addTypePack(TypePack{{}, std::nullopt});
std::vector<TypeId> head;
for (size_t i = 0; i < exprs.size - 1; ++i)
{
head.push_back(lookupType(exprs.data[i], module, builtinTypes));
}
const TypePackId* tail = module->astTypePacks.find(exprs.data[exprs.size - 1]);
if (tail)
return arena.addTypePack(TypePack{std::move(head), follow(*tail)});
else
return arena.addTypePack(TypePack{std::move(head), builtinTypes->errorRecoveryTypePack()});
}
bool AnyTypeSummary::isAnyCall(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (auto call = expr->as<AstExprCall>())
{
TypePackId args = reconstructTypePack(call->args, module, builtinTypes);
if (containsAny(args))
return true;
TypeId func = lookupType(call->func, module, builtinTypes);
if (containsAny(func))
return true;
}
return false;
}
bool AnyTypeSummary::hasVariadicAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (expr->vararg && expr->varargAnnotation)
{
auto annot = lookupPackAnnotation(expr->varargAnnotation, module);
if (annot && containsAny(*annot))
{
return true;
}
}
return false;
}
bool AnyTypeSummary::hasArgAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (expr->args.size > 0)
{
for (const AstLocal* arg : expr->args)
{
if (arg->annotation)
{
auto annot = lookupAnnotation(arg->annotation, module, builtinTypes);
if (containsAny(annot))
{
return true;
}
}
}
}
return false;
}
bool AnyTypeSummary::hasAnyReturns(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (!expr->returnAnnotation)
{
return false;
}
for (AstType* ret : expr->returnAnnotation->types)
{
if (containsAny(lookupAnnotation(ret, module, builtinTypes)))
{
return true;
}
}
if (expr->returnAnnotation->tailType)
{
auto annot = lookupPackAnnotation(expr->returnAnnotation->tailType, module);
if (annot && containsAny(*annot))
{
return true;
}
}
return false;
}
bool AnyTypeSummary::isAnyCast(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (auto cast = expr->as<AstExprTypeAssertion>())
{
auto annot = lookupAnnotation(cast->annotation, module, builtinTypes);
if (containsAny(annot))
{
return true;
}
}
return false;
}
TypeId AnyTypeSummary::lookupAnnotation(AstType* annotation, const Module* module, NotNull<BuiltinTypes> builtintypes)
{
if (FFlag::DebugLuauMagicTypes)
{
if (auto ref = annotation->as<AstTypeReference>(); ref && ref->parameters.size > 0)
{
if (auto ann = ref->parameters.data[0].type)
{
TypeId argTy = lookupAnnotation(ref->parameters.data[0].type, module, builtintypes);
return follow(argTy);
}
}
}
const TypeId* ty = module->astResolvedTypes.find(annotation);
if (ty)
return checkForTypeFunctionInhabitance(follow(*ty), annotation->location);
else
return checkForTypeFunctionInhabitance(builtintypes->errorRecoveryType(), annotation->location);
}
TypeId AnyTypeSummary::checkForTypeFunctionInhabitance(const TypeId instance, const Location location)
{
if (seenTypeFunctionInstances.find(instance))
return instance;
seenTypeFunctionInstances.insert(instance);
return instance;
}
std::optional<TypePackId> AnyTypeSummary::lookupPackAnnotation(AstTypePack* annotation, const Module* module)
{
const TypePackId* tp = module->astResolvedTypePacks.find(annotation);
if (tp != nullptr)
return {follow(*tp)};
return {};
}
bool AnyTypeSummary::containsAny(TypeId typ)
{
typ = follow(typ);
if (auto t = seen.find(typ); t && !*t)
{
return false;
}
seen[typ] = false;
RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauAnySummaryRecursionLimit)
{
return false;
}
bool found = false;
if (auto ty = get<AnyType>(typ))
{
found = true;
}
else if (auto ty = get<UnknownType>(typ))
{
found = true;
}
else if (auto ty = get<TableType>(typ))
{
for (auto& [_name, prop] : ty->props)
{
if (FFlag::LuauSolverV2)
{
if (auto newT = follow(prop.readTy))
{
if (containsAny(*newT))
found = true;
}
else if (auto newT = follow(prop.writeTy))
{
if (containsAny(*newT))
found = true;
}
}
else
{
if (containsAny(prop.type()))
found = true;
}
}
}
else if (auto ty = get<IntersectionType>(typ))
{
for (auto part : ty->parts)
{
if (containsAny(part))
{
found = true;
}
}
}
else if (auto ty = get<UnionType>(typ))
{
for (auto option : ty->options)
{
if (containsAny(option))
{
found = true;
}
}
}
else if (auto ty = get<FunctionType>(typ))
{
if (containsAny(ty->argTypes))
found = true;
else if (containsAny(ty->retTypes))
found = true;
}
seen[typ] = found;
return found;
}
bool AnyTypeSummary::containsAny(TypePackId typ)
{
typ = follow(typ);
if (auto t = seen.find(typ); t && !*t)
{
return false;
}
seen[typ] = false;
auto [head, tail] = flatten(typ);
bool found = false;
for (auto tp : head)
{
if (containsAny(tp))
found = true;
}
if (tail)
{
if (auto vtp = get<VariadicTypePack>(tail))
{
if (auto ty = get<AnyType>(follow(vtp->ty)))
{
found = true;
}
}
else if (auto tftp = get<TypeFunctionInstanceTypePack>(tail))
{
for (TypePackId tp : tftp->packArguments)
{
if (containsAny(tp))
{
found = true;
}
}
for (TypeId t : tftp->typeArguments)
{
if (containsAny(t))
{
found = true;
}
}
}
}
seen[typ] = found;
return found;
}
const Scope* AnyTypeSummary::findInnerMostScope(const Location location, const Module* module)
{
const Scope* bestScope = module->getModuleScope().get();
bool didNarrow = false;
do
{
didNarrow = false;
for (auto scope : bestScope->children)
{
if (scope->location.encloses(location))
{
bestScope = scope.get();
didNarrow = true;
break;
}
}
} while (didNarrow && bestScope->children.size() > 0);
return bestScope;
}
std::optional<AstExpr*> AnyTypeSummary::matchRequire(const AstExprCall& call)
{
const char* require = "require";
if (call.args.size != 1)
return std::nullopt;
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
if (!funcAsGlobal || funcAsGlobal->name != require)
return std::nullopt;
if (call.args.size != 1)
return std::nullopt;
return call.args.data[0];
}
AstNode* AnyTypeSummary::getNode(AstStatBlock* root, AstNode* node)
{
FindReturnAncestry finder(node, root->location.end);
root->visit(&finder);
if (!finder.currNode)
finder.currNode = node;
LUAU_ASSERT(finder.found && finder.currNode);
return finder.currNode;
}
bool AnyTypeSummary::FindReturnAncestry::visit(AstStatLocalFunction* node)
{
currNode = node;
return !found;
}
bool AnyTypeSummary::FindReturnAncestry::visit(AstStatFunction* node)
{
currNode = node;
return !found;
}
bool AnyTypeSummary::FindReturnAncestry::visit(AstType* node)
{
return !found;
}
bool AnyTypeSummary::FindReturnAncestry::visit(AstNode* node)
{
if (node == stat)
{
found = true;
}
if (node->location.end == rootEnd && stat->location.end >= rootEnd)
{
currNode = node;
found = true;
}
return !found;
}
AnyTypeSummary::TypeInfo::TypeInfo(Pattern code, std::string node, TelemetryTypePair type)
: code(code)
, node(node)
, type(type)
{
}
AnyTypeSummary::FindReturnAncestry::FindReturnAncestry(AstNode* stat, Position rootEnd)
: stat(stat)
, rootEnd(rootEnd)
{
}
AnyTypeSummary::AnyTypeSummary() {}
} // namespace Luau

View file

@ -88,7 +88,7 @@ TypePackId Anyification::clean(TypePackId tp)
bool Anyification::ignoreChildren(TypeId ty) bool Anyification::ignoreChildren(TypeId ty)
{ {
if (get<ExternType>(ty)) if (get<ClassType>(ty))
return true; return true;
return ty->persistent; return ty->persistent;

View file

@ -31,7 +31,7 @@ bool ApplyTypeFunction::ignoreChildren(TypeId ty)
{ {
if (get<GenericType>(ty)) if (get<GenericType>(ty))
return true; return true;
else if (get<ExternType>(ty)) else if (get<ClassType>(ty))
return true; return true;
else else
return false; return false;

View file

@ -8,8 +8,6 @@
#include <math.h> #include <math.h>
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -427,22 +425,13 @@ struct AstJsonEncoder : public AstVisitor
"AstExprFunction", "AstExprFunction",
[&]() [&]()
{ {
PROP(attributes);
PROP(generics); PROP(generics);
PROP(genericPacks); PROP(genericPacks);
if (node->self) if (node->self)
PROP(self); PROP(self);
PROP(args); PROP(args);
if (FFlag::LuauStoreReturnTypesAsPackOnAst) if (node->returnAnnotation)
{ PROP(returnAnnotation);
if (node->returnAnnotation)
PROP(returnAnnotation);
}
else
{
if (node->returnAnnotation_DEPRECATED)
write("returnAnnotation", node->returnAnnotation_DEPRECATED);
}
PROP(vararg); PROP(vararg);
PROP(varargLocation); PROP(varargLocation);
if (node->varargAnnotation) if (node->varargAnnotation)
@ -475,26 +464,26 @@ struct AstJsonEncoder : public AstVisitor
writeRaw("}"); writeRaw("}");
} }
void write(class AstGenericType* genericType) void write(const AstGenericType& genericType)
{ {
writeRaw("{"); writeRaw("{");
bool c = pushComma(); bool c = pushComma();
writeType("AstGenericType"); writeType("AstGenericType");
write("name", genericType->name); write("name", genericType.name);
if (genericType->defaultValue) if (genericType.defaultValue)
write("luauType", genericType->defaultValue); write("luauType", genericType.defaultValue);
popComma(c); popComma(c);
writeRaw("}"); writeRaw("}");
} }
void write(class AstGenericTypePack* genericTypePack) void write(const AstGenericTypePack& genericTypePack)
{ {
writeRaw("{"); writeRaw("{");
bool c = pushComma(); bool c = pushComma();
writeType("AstGenericTypePack"); writeType("AstGenericTypePack");
write("name", genericTypePack->name); write("name", genericTypePack.name);
if (genericTypePack->defaultValue) if (genericTypePack.defaultValue)
write("luauType", genericTypePack->defaultValue); write("luauType", genericTypePack.defaultValue);
popComma(c); popComma(c);
writeRaw("}"); writeRaw("}");
} }
@ -892,7 +881,7 @@ struct AstJsonEncoder : public AstVisitor
PROP(name); PROP(name);
PROP(generics); PROP(generics);
PROP(genericPacks); PROP(genericPacks);
write("value", node->type); PROP(type);
PROP(exported); PROP(exported);
} }
); );
@ -905,17 +894,14 @@ struct AstJsonEncoder : public AstVisitor
"AstStatDeclareFunction", "AstStatDeclareFunction",
[&]() [&]()
{ {
PROP(attributes); // TODO: attributes
PROP(name); PROP(name);
PROP(nameLocation); PROP(nameLocation);
PROP(params); PROP(params);
PROP(paramNames); PROP(paramNames);
PROP(vararg); PROP(vararg);
PROP(varargLocation); PROP(varargLocation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst) PROP(retTypes);
PROP(retTypes);
else
write("retTypes", node->retTypes_DEPRECATED);
PROP(generics); PROP(generics);
PROP(genericPacks); PROP(genericPacks);
} }
@ -936,7 +922,7 @@ struct AstJsonEncoder : public AstVisitor
); );
} }
void write(const AstDeclaredExternTypeProperty& prop) void write(const AstDeclaredClassProp& prop)
{ {
writeRaw("{"); writeRaw("{");
bool c = pushComma(); bool c = pushComma();
@ -949,7 +935,7 @@ struct AstJsonEncoder : public AstVisitor
writeRaw("}"); writeRaw("}");
} }
void write(class AstStatDeclareExternType* node) void write(class AstStatDeclareClass* node)
{ {
writeNode( writeNode(
node, node,
@ -1056,15 +1042,11 @@ struct AstJsonEncoder : public AstVisitor
"AstTypeFunction", "AstTypeFunction",
[&]() [&]()
{ {
PROP(attributes);
PROP(generics); PROP(generics);
PROP(genericPacks); PROP(genericPacks);
PROP(argTypes); PROP(argTypes);
PROP(argNames); PROP(argNames);
if (FFlag::LuauStoreReturnTypesAsPackOnAst) PROP(returnTypes);
PROP(returnTypes);
else
write("returnTypes", node->returnTypes_DEPRECATED);
} }
); );
} }
@ -1081,11 +1063,6 @@ struct AstJsonEncoder : public AstVisitor
); );
} }
void write(class AstTypeOptional* node)
{
writeNode(node, "AstTypeOptional", [&]() {});
}
void write(class AstTypeUnion* node) void write(class AstTypeUnion* node)
{ {
writeNode( writeNode(
@ -1159,44 +1136,6 @@ struct AstJsonEncoder : public AstVisitor
); );
} }
void write(AstAttr::Type type)
{
switch (type)
{
case AstAttr::Type::Checked:
return writeString("checked");
case AstAttr::Type::Native:
return writeString("native");
case AstAttr::Type::Deprecated:
return writeString("deprecated");
}
}
void write(class AstAttr* node)
{
writeNode(
node,
"AstAttr",
[&]()
{
write("name", node->type);
}
);
}
bool visit(class AstTypeGroup* node) override
{
writeNode(
node,
"AstTypeGroup",
[&]()
{
write("inner", node->type);
}
);
return false;
}
bool visit(class AstTypeSingletonBool* node) override bool visit(class AstTypeSingletonBool* node) override
{ {
writeNode( writeNode(
@ -1445,7 +1384,7 @@ struct AstJsonEncoder : public AstVisitor
return false; return false;
} }
bool visit(class AstStatDeclareExternType* node) override bool visit(class AstStatDeclareClass* node) override
{ {
write(node); write(node);
return false; return false;

View file

@ -41,15 +41,11 @@ struct AutocompleteNodeFinder : public AstVisitor
bool visit(AstStat* stat) override bool visit(AstStat* stat) override
{ {
// Consider 'local myLocal = 4;|' and 'local myLocal = 4', where '|' is the cursor position. In both cases, the cursor position is equal if (stat->location.begin < pos && pos <= stat->location.end)
// to `AstStatLocal.location.end`. However, in the first case (semicolon), we are starting a new statement, whilst in the second case
// (no semicolon) we are still part of the AstStatLocal, hence the different comparison check.
if (stat->location.begin < pos && (stat->hasSemicolon ? pos < stat->location.end : pos <= stat->location.end))
{ {
ancestry.push_back(stat); ancestry.push_back(stat);
return true; return true;
} }
return false; return false;
} }
@ -513,37 +509,6 @@ static std::optional<DocumentationSymbol> checkOverloadedDocumentationSymbol(
return documentationSymbol; return documentationSymbol;
} }
static std::optional<DocumentationSymbol> getMetatableDocumentation(
const Module& module,
AstExpr* parentExpr,
const TableType* mtable,
const AstName& index
)
{
auto indexIt = mtable->props.find("__index");
if (indexIt == mtable->props.end())
return std::nullopt;
TypeId followed = follow(indexIt->second.type());
const TableType* ttv = get<TableType>(followed);
if (!ttv)
return std::nullopt;
auto propIt = ttv->props.find(index.value);
if (propIt == ttv->props.end())
return std::nullopt;
if (FFlag::LuauSolverV2)
{
if (auto ty = propIt->second.readTy)
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
}
else
return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol);
return std::nullopt;
}
std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const SourceModule& source, const Module& module, Position position) std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const SourceModule& source, const Module& module, Position position)
{ {
std::vector<AstNode*> ancestry = findAstAncestryOfPosition(source, position); std::vector<AstNode*> ancestry = findAstAncestryOfPosition(source, position);
@ -574,31 +539,17 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol); return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol);
} }
} }
else if (const ExternType* etv = get<ExternType>(parentTy)) else if (const ClassType* ctv = get<ClassType>(parentTy))
{ {
while (etv) if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
{ {
if (auto propIt = etv->props.find(indexName->index.value); propIt != etv->props.end()) if (FFlag::LuauSolverV2)
{ {
if (FFlag::LuauSolverV2) if (auto ty = propIt->second.readTy)
{ return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
if (auto ty = propIt->second.readTy)
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
}
else
return checkOverloadedDocumentationSymbol(
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol
);
} }
etv = etv->parent ? Luau::get<Luau::ExternType>(*etv->parent) : nullptr; else
} return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol);
}
else if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable)
{
if (auto mtable = get<TableType>(*ptv->metatable))
{
if (std::optional<std::string> docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index))
return docSymbol;
} }
} }
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,27 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/AutocompleteTypes.h"
namespace Luau
{
struct Module;
struct FileResolver;
using ModulePtr = std::shared_ptr<Module>;
using ModuleName = std::string;
AutocompleteResult autocomplete_(
const ModulePtr& module,
NotNull<BuiltinTypes> builtinTypes,
TypeArena* typeArena,
std::vector<AstNode*>& ancestry,
Scope* globalScope,
const ScopePtr& scopeAtPosition,
Position position,
FileResolver* fileResolver,
StringCompletionCallback callback
);
} // namespace Luau

View file

@ -2,24 +2,20 @@
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Clone.h"
#include "Luau/Common.h"
#include "Luau/ConstraintGenerator.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/DenseHash.h"
#include "Luau/Error.h"
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
#include "Luau/InferPolarity.h"
#include "Luau/NotNull.h"
#include "Luau/Subtyping.h"
#include "Luau/Symbol.h" #include "Luau/Symbol.h"
#include "Luau/Common.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/Type.h" #include "Luau/ConstraintSolver.h"
#include "Luau/ConstraintGenerator.h"
#include "Luau/NotNull.h"
#include "Luau/TypeInfer.h"
#include "Luau/TypeChecker2.h" #include "Luau/TypeChecker2.h"
#include "Luau/TypeFunction.h" #include "Luau/TypeFunction.h"
#include "Luau/TypeInfer.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/Type.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/Subtyping.h"
#include <algorithm> #include <algorithm>
@ -29,93 +25,47 @@
* about a function that takes any number of values, but where each value must have some specific type. * about a function that takes any number of values, but where each value must have some specific type.
*/ */
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAGVARIABLE(LuauDCRMagicFunctionTypeChecker, false);
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked)
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
namespace Luau namespace Luau
{ {
struct MagicSelect final : MagicFunction static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
{ TypeChecker& typechecker,
std::optional<WithPredicate<TypePackId>> const ScopePtr& scope,
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override; const AstExprCall& expr,
bool infer(const MagicFunctionCallContext& ctx) override; WithPredicate<TypePackId> withPredicate
}; );
static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
WithPredicate<TypePackId> withPredicate
);
static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
WithPredicate<TypePackId> withPredicate
);
static std::optional<WithPredicate<TypePackId>> magicFunctionPack(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
WithPredicate<TypePackId> withPredicate
);
static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
WithPredicate<TypePackId> withPredicate
);
struct MagicSetMetatable final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicAssert final : MagicFunction static bool dcrMagicFunctionSelect(MagicFunctionCallContext context);
{ static bool dcrMagicFunctionRequire(MagicFunctionCallContext context);
std::optional<WithPredicate<TypePackId>> static bool dcrMagicFunctionPack(MagicFunctionCallContext context);
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicPack final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicRequire final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicClone final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicFreeze final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicFormat final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
bool typeCheck(const MagicFunctionTypeCheckContext& ctx) override;
};
struct MagicMatch final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicGmatch final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicFind final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types) TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
{ {
@ -210,10 +160,34 @@ TypeId makeFunction(
return arena.addType(std::move(ftv)); return arena.addType(std::move(ftv));
} }
void attachMagicFunction(TypeId ty, std::shared_ptr<MagicFunction> magic) void attachMagicFunction(TypeId ty, MagicFunction fn)
{ {
if (auto ftv = getMutable<FunctionType>(ty)) if (auto ftv = getMutable<FunctionType>(ty))
ftv->magic = std::move(magic); ftv->magicFunction = fn;
else
LUAU_ASSERT(!"Got a non functional type");
}
void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn)
{
if (auto ftv = getMutable<FunctionType>(ty))
ftv->dcrMagicFunction = fn;
else
LUAU_ASSERT(!"Got a non functional type");
}
void attachDcrMagicRefinement(TypeId ty, DcrMagicRefinement fn)
{
if (auto ftv = getMutable<FunctionType>(ty))
ftv->dcrMagicRefinement = fn;
else
LUAU_ASSERT(!"Got a non functional type");
}
void attachDcrMagicFunctionTypeCheck(TypeId ty, DcrMagicFunctionTypeCheck fn)
{
if (auto ftv = getMutable<FunctionType>(ty))
ftv->dcrMagicTypeCheck = fn;
else else
LUAU_ASSERT(!"Got a non functional type"); LUAU_ASSERT(!"Got a non functional type");
} }
@ -248,7 +222,6 @@ void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::st
void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, Binding binding) void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, Binding binding)
{ {
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{scope.get()}, binding.typeId);
scope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = binding; scope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = binding;
} }
@ -290,22 +263,6 @@ void assignPropDocumentationSymbols(TableType::Props& props, const std::string&
} }
} }
static void finalizeGlobalBindings(ScopePtr scope)
{
LUAU_ASSERT(FFlag::LuauUserTypeFunTypecheck);
for (const auto& pair : scope->bindings)
{
persist(pair.second.typeId);
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
{
if (!ttv->name)
ttv->name = "typeof(" + toString(pair.first) + ")";
}
}
}
void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete) void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete)
{ {
LUAU_ASSERT(!globals.globalTypes.types.isFrozen()); LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
@ -313,9 +270,6 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeArena& arena = globals.globalTypes; TypeArena& arena = globals.globalTypes;
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes; NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauNonReentrantGeneralization2
if (FFlag::LuauNonReentrantGeneralization2)
globalScope = globals.globalScope.get();
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()}); builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()});
@ -325,8 +279,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
); );
LUAU_ASSERT(loadResult.success); LUAU_ASSERT(loadResult.success);
TypeId genericK = arena.addType(GenericType{globalScope, "K"}); TypeId genericK = arena.addType(GenericType{"K"});
TypeId genericV = arena.addType(GenericType{globalScope, "V"}); TypeId genericV = arena.addType(GenericType{"V"});
TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), globals.globalScope->level, TableState::Generic}); TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), globals.globalScope->level, TableState::Generic});
std::optional<TypeId> stringMetatableTy = getMetatable(builtinTypes->stringType, builtinTypes); std::optional<TypeId> stringMetatableTy = getMetatable(builtinTypes->stringType, builtinTypes);
@ -339,28 +293,6 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
addGlobalBinding(globals, "string", it->second.type(), "@luau"); addGlobalBinding(globals, "string", it->second.type(), "@luau");
// Setup 'vector' metatable
if (auto it = globals.globalScope->exportedTypeBindings.find("vector"); it != globals.globalScope->exportedTypeBindings.end())
{
TypeId vectorTy = it->second.type;
ExternType* vectorCls = getMutable<ExternType>(vectorTy);
vectorCls->metatable = arena.addType(TableType{{}, std::nullopt, TypeLevel{}, TableState::Sealed});
TableType* metatableTy = Luau::getMutable<TableType>(vectorCls->metatable);
metatableTy->props["__add"] = {makeFunction(arena, vectorTy, {vectorTy}, {vectorTy})};
metatableTy->props["__sub"] = {makeFunction(arena, vectorTy, {vectorTy}, {vectorTy})};
metatableTy->props["__unm"] = {makeFunction(arena, vectorTy, {}, {vectorTy})};
std::initializer_list<TypeId> mulOverloads{
makeFunction(arena, vectorTy, {vectorTy}, {vectorTy}),
makeFunction(arena, vectorTy, {builtinTypes->numberType}, {vectorTy}),
};
metatableTy->props["__mul"] = {makeIntersection(arena, mulOverloads)};
metatableTy->props["__div"] = {makeIntersection(arena, mulOverloads)};
metatableTy->props["__idiv"] = {makeIntersection(arena, mulOverloads)};
}
// next<K, V>(t: Table<K, V>, i: K?) -> (K?, V) // next<K, V>(t: Table<K, V>, i: K?) -> (K?, V)
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(builtinTypes, arena, genericK)}}); TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(builtinTypes, arena, genericK)}});
TypePackId nextRetsTypePack = arena.addTypePack(TypePack{{makeOption(builtinTypes, arena, genericK), genericV}}); TypePackId nextRetsTypePack = arena.addTypePack(TypePack{{makeOption(builtinTypes, arena, genericK), genericV}});
@ -374,7 +306,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil) // pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
addGlobalBinding(globals, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau"); addGlobalBinding(globals, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
TypeId genericMT = arena.addType(GenericType{globalScope, "MT"}); TypeId genericMT = arena.addType(GenericType{"MT"});
TableType tab{TableState::Generic, globals.globalScope->level}; TableType tab{TableState::Generic, globals.globalScope->level};
TypeId tabTy = arena.addType(tab); TypeId tabTy = arena.addType(tab);
@ -386,7 +318,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
TypeId genericT = arena.addType(GenericType{globalScope, "T"}); TypeId genericT = arena.addType(GenericType{"T"});
TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT}); TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT});
// clang-format off // clang-format off
@ -420,30 +352,23 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
// clang-format on // clang-format on
} }
if (FFlag::LuauUserTypeFunTypecheck) for (const auto& pair : globals.globalScope->bindings)
{ {
finalizeGlobalBindings(globals.globalScope); persist(pair.second.typeId);
}
else
{
for (const auto& pair : globals.globalScope->bindings)
{
persist(pair.second.typeId);
if (TableType* ttv = getMutable<TableType>(pair.second.typeId)) if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
{ {
if (!ttv->name) if (!ttv->name)
ttv->name = "typeof(" + toString(pair.first) + ")"; ttv->name = "typeof(" + toString(pair.first) + ")";
}
} }
} }
attachMagicFunction(getGlobalBinding(globals, "assert"), std::make_shared<MagicAssert>()); attachMagicFunction(getGlobalBinding(globals, "assert"), magicFunctionAssert);
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
// declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)> // declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)>
TypeId genericT = arena.addType(GenericType{globalScope, "T"}); TypeId genericT = arena.addType(GenericType{"T"});
TypeId refinedTy = arena.addType(TypeFunctionInstanceType{ TypeId refinedTy = arena.addType(TypeFunctionInstanceType{
NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {} NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {}
}); });
@ -454,8 +379,9 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
addGlobalBinding(globals, "assert", assertTy, "@luau"); addGlobalBinding(globals, "assert", assertTy, "@luau");
} }
attachMagicFunction(getGlobalBinding(globals, "setmetatable"), std::make_shared<MagicSetMetatable>()); attachMagicFunction(getGlobalBinding(globals, "setmetatable"), magicFunctionSetMetaTable);
attachMagicFunction(getGlobalBinding(globals, "select"), std::make_shared<MagicSelect>()); attachMagicFunction(getGlobalBinding(globals, "select"), magicFunctionSelect);
attachDcrMagicFunction(getGlobalBinding(globals, "select"), dcrMagicFunctionSelect);
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(globals, "table"))) if (TableType* ttv = getMutable<TableType>(getGlobalBinding(globals, "table")))
{ {
@ -466,16 +392,10 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
// the top table type. We do the best we can by modelling these // the top table type. We do the best we can by modelling these
// functions using unconstrained generics. It's not quite right, // functions using unconstrained generics. It's not quite right,
// but it'll be ok for now. // but it'll be ok for now.
TypeId genericTy = arena.addType(GenericType{globalScope, "T"}); TypeId genericTy = arena.addType(GenericType{"T"});
TypePackId thePack = arena.addTypePack({genericTy}); TypePackId thePack = arena.addTypePack({genericTy});
TypeId idTyWithMagic = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
ttv->props["freeze"] = makeProperty(idTyWithMagic, "@luau/global/table.freeze");
if (globalScope)
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{globalScope}, idTyWithMagic);
TypeId idTy = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack}); TypeId idTy = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
if (globalScope) ttv->props["freeze"] = makeProperty(idTy, "@luau/global/table.freeze");
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{globalScope}, idTy);
ttv->props["clone"] = makeProperty(idTy, "@luau/global/table.clone"); ttv->props["clone"] = makeProperty(idTy, "@luau/global/table.clone");
} }
else else
@ -490,68 +410,12 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
ttv->props["foreach"].deprecated = true; ttv->props["foreach"].deprecated = true;
ttv->props["foreachi"].deprecated = true; ttv->props["foreachi"].deprecated = true;
attachMagicFunction(ttv->props["pack"].type(), std::make_shared<MagicPack>()); attachMagicFunction(ttv->props["pack"].type(), magicFunctionPack);
if (FFlag::LuauTableCloneClonesType3) attachDcrMagicFunction(ttv->props["pack"].type(), dcrMagicFunctionPack);
attachMagicFunction(ttv->props["clone"].type(), std::make_shared<MagicClone>());
attachMagicFunction(ttv->props["freeze"].type(), std::make_shared<MagicFreeze>());
} }
TypeId requireTy = getGlobalBinding(globals, "require"); attachMagicFunction(getGlobalBinding(globals, "require"), magicFunctionRequire);
attachTag(requireTy, kRequireTagName); attachDcrMagicFunction(getGlobalBinding(globals, "require"), dcrMagicFunctionRequire);
attachMagicFunction(requireTy, std::make_shared<MagicRequire>());
if (FFlag::LuauUserTypeFunTypecheck)
{
// Global scope cannot be the parent of the type checking environment because it can be changed by the embedder
globals.globalTypeFunctionScope->exportedTypeBindings = globals.globalScope->exportedTypeBindings;
globals.globalTypeFunctionScope->builtinTypeNames = globals.globalScope->builtinTypeNames;
// Type function runtime also removes a few standard libraries and globals, so we will take only the ones that are defined
static const char* typeFunctionRuntimeBindings[] = {
// Libraries
"math",
"table",
"string",
"bit32",
"utf8",
"buffer",
// Globals
"assert",
"error",
"print",
"next",
"ipairs",
"pairs",
"select",
"unpack",
"getmetatable",
"setmetatable",
"rawget",
"rawset",
"rawlen",
"rawequal",
"tonumber",
"tostring",
"type",
"typeof",
};
for (auto& name : typeFunctionRuntimeBindings)
{
AstName astName = globals.globalNames.names->get(name);
LUAU_ASSERT(astName.value);
globals.globalTypeFunctionScope->bindings[astName] = globals.globalScope->bindings[astName];
}
LoadDefinitionFileResult typeFunctionLoadResult = frontend.loadDefinitionFile(
globals, globals.globalTypeFunctionScope, getTypeFunctionDefinitionSource(), "@luau", /* captureComments */ false, false
);
LUAU_ASSERT(typeFunctionLoadResult.success);
finalizeGlobalBindings(globals.globalTypeFunctionScope);
}
} }
static std::vector<TypeId> parseFormatString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size) static std::vector<TypeId> parseFormatString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size)
@ -590,7 +454,7 @@ static std::vector<TypeId> parseFormatString(NotNull<BuiltinTypes> builtinTypes,
return result; return result;
} }
std::optional<WithPredicate<TypePackId>> MagicFormat::handleOldSolver( std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
TypeChecker& typechecker, TypeChecker& typechecker,
const ScopePtr& scope, const ScopePtr& scope,
const AstExprCall& expr, const AstExprCall& expr,
@ -640,7 +504,7 @@ std::optional<WithPredicate<TypePackId>> MagicFormat::handleOldSolver(
return WithPredicate<TypePackId>{arena.addTypePack({typechecker.stringType})}; return WithPredicate<TypePackId>{arena.addTypePack({typechecker.stringType})};
} }
bool MagicFormat::infer(const MagicFunctionCallContext& context) static bool dcrMagicFunctionFormat(MagicFunctionCallContext context)
{ {
TypeArena* arena = context.solver->arena; TypeArena* arena = context.solver->arena;
@ -684,7 +548,7 @@ bool MagicFormat::infer(const MagicFunctionCallContext& context)
return true; return true;
} }
bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context) static void dcrMagicFunctionTypeCheckFormat(MagicFunctionTypeCheckContext context)
{ {
AstExprConstantString* fmt = nullptr; AstExprConstantString* fmt = nullptr;
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self) if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
@ -699,18 +563,7 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
fmt = context.callSite->args.data[0]->as<AstExprConstantString>(); fmt = context.callSite->args.data[0]->as<AstExprConstantString>();
if (!fmt) if (!fmt)
{ return;
context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location);
return true;
}
// CLI-150726: The block below effectively constructs a type pack and then type checks it by going parameter-by-parameter.
// This does _not_ handle cases like:
//
// local foo : () -> (...string) = (nil :: any)
// print(string.format("%s %d %s", foo()))
//
// ... which should be disallowed.
std::vector<TypeId> expected = parseFormatString(context.builtinTypes, fmt->value.data, fmt->value.size); std::vector<TypeId> expected = parseFormatString(context.builtinTypes, fmt->value.data, fmt->value.size);
const auto& [params, tail] = flatten(context.arguments); const auto& [params, tail] = flatten(context.arguments);
@ -723,30 +576,15 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
{ {
TypeId actualTy = params[i + paramOffset]; TypeId actualTy = params[i + paramOffset];
TypeId expectedTy = expected[i]; TypeId expectedTy = expected[i];
Location location = FFlag::LuauFormatUseLastPosition Location location = context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location;
? context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location
: context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location;
// use subtyping instead here // use subtyping instead here
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope); SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);
if (!result.isSubtype) if (!result.isSubtype)
{ {
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy)) Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
{ context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
case ErrorSuppression::Suppress:
break;
case ErrorSuppression::NormalizationFailed:
break;
case ErrorSuppression::DoNotSuppress:
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
if (!reasonings.suppressed)
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
}
} }
} }
return true;
} }
static std::vector<TypeId> parsePatternString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size) static std::vector<TypeId> parsePatternString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size)
@ -809,7 +647,7 @@ static std::vector<TypeId> parsePatternString(NotNull<BuiltinTypes> builtinTypes
return result; return result;
} }
std::optional<WithPredicate<TypePackId>> MagicGmatch::handleOldSolver( static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
TypeChecker& typechecker, TypeChecker& typechecker,
const ScopePtr& scope, const ScopePtr& scope,
const AstExprCall& expr, const AstExprCall& expr,
@ -845,7 +683,7 @@ std::optional<WithPredicate<TypePackId>> MagicGmatch::handleOldSolver(
return WithPredicate<TypePackId>{arena.addTypePack({iteratorType})}; return WithPredicate<TypePackId>{arena.addTypePack({iteratorType})};
} }
bool MagicGmatch::infer(const MagicFunctionCallContext& context) static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context)
{ {
const auto& [params, tail] = flatten(context.arguments); const auto& [params, tail] = flatten(context.arguments);
@ -878,7 +716,7 @@ bool MagicGmatch::infer(const MagicFunctionCallContext& context)
return true; return true;
} }
std::optional<WithPredicate<TypePackId>> MagicMatch::handleOldSolver( static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
TypeChecker& typechecker, TypeChecker& typechecker,
const ScopePtr& scope, const ScopePtr& scope,
const AstExprCall& expr, const AstExprCall& expr,
@ -918,7 +756,7 @@ std::optional<WithPredicate<TypePackId>> MagicMatch::handleOldSolver(
return WithPredicate<TypePackId>{returnList}; return WithPredicate<TypePackId>{returnList};
} }
bool MagicMatch::infer(const MagicFunctionCallContext& context) static bool dcrMagicFunctionMatch(MagicFunctionCallContext context)
{ {
const auto& [params, tail] = flatten(context.arguments); const auto& [params, tail] = flatten(context.arguments);
@ -954,7 +792,7 @@ bool MagicMatch::infer(const MagicFunctionCallContext& context)
return true; return true;
} }
std::optional<WithPredicate<TypePackId>> MagicFind::handleOldSolver( static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
TypeChecker& typechecker, TypeChecker& typechecker,
const ScopePtr& scope, const ScopePtr& scope,
const AstExprCall& expr, const AstExprCall& expr,
@ -1012,7 +850,7 @@ std::optional<WithPredicate<TypePackId>> MagicFind::handleOldSolver(
return WithPredicate<TypePackId>{returnList}; return WithPredicate<TypePackId>{returnList};
} }
bool MagicFind::infer(const MagicFunctionCallContext& context) static bool dcrMagicFunctionFind(MagicFunctionCallContext context)
{ {
const auto& [params, tail] = flatten(context.arguments); const auto& [params, tail] = flatten(context.arguments);
@ -1089,9 +927,12 @@ TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack}; FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack};
formatFTV.magicFunction = &magicFunctionFormat;
formatFTV.isCheckedFunction = true; formatFTV.isCheckedFunction = true;
const TypeId formatFn = arena->addType(formatFTV); const TypeId formatFn = arena->addType(formatFTV);
attachMagicFunction(formatFn, std::make_shared<MagicFormat>()); attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat);
if (FFlag::LuauDCRMagicFunctionTypeChecker)
attachDcrMagicFunctionTypeCheck(formatFn, dcrMagicFunctionTypeCheckFormat);
const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ true); const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ true);
@ -1105,14 +946,16 @@ TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}, /* checked */ false); makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}, /* checked */ false);
const TypeId gmatchFunc = const TypeId gmatchFunc =
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}, /* checked */ true); makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}, /* checked */ true);
attachMagicFunction(gmatchFunc, std::make_shared<MagicGmatch>()); attachMagicFunction(gmatchFunc, magicFunctionGmatch);
attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch);
FunctionType matchFuncTy{ FunctionType matchFuncTy{
arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}}) arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})
}; };
matchFuncTy.isCheckedFunction = true; matchFuncTy.isCheckedFunction = true;
const TypeId matchFunc = arena->addType(matchFuncTy); const TypeId matchFunc = arena->addType(matchFuncTy);
attachMagicFunction(matchFunc, std::make_shared<MagicMatch>()); attachMagicFunction(matchFunc, magicFunctionMatch);
attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch);
FunctionType findFuncTy{ FunctionType findFuncTy{
arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}), arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
@ -1120,7 +963,8 @@ TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
}; };
findFuncTy.isCheckedFunction = true; findFuncTy.isCheckedFunction = true;
const TypeId findFunc = arena->addType(findFuncTy); const TypeId findFunc = arena->addType(findFuncTy);
attachMagicFunction(findFunc, std::make_shared<MagicFind>()); attachMagicFunction(findFunc, magicFunctionFind);
attachDcrMagicFunction(findFunc, dcrMagicFunctionFind);
// string.byte : string -> number? -> number? -> ...number // string.byte : string -> number? -> number? -> ...number
FunctionType stringDotByte{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList}; FunctionType stringDotByte{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList};
@ -1181,7 +1025,7 @@ TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed}); return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
} }
std::optional<WithPredicate<TypePackId>> MagicSelect::handleOldSolver( static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
TypeChecker& typechecker, TypeChecker& typechecker,
const ScopePtr& scope, const ScopePtr& scope,
const AstExprCall& expr, const AstExprCall& expr,
@ -1226,7 +1070,7 @@ std::optional<WithPredicate<TypePackId>> MagicSelect::handleOldSolver(
return std::nullopt; return std::nullopt;
} }
bool MagicSelect::infer(const MagicFunctionCallContext& context) static bool dcrMagicFunctionSelect(MagicFunctionCallContext context)
{ {
if (context.callSite->args.size <= 0) if (context.callSite->args.size <= 0)
{ {
@ -1271,7 +1115,7 @@ bool MagicSelect::infer(const MagicFunctionCallContext& context)
return false; return false;
} }
std::optional<WithPredicate<TypePackId>> MagicSetMetatable::handleOldSolver( static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
TypeChecker& typechecker, TypeChecker& typechecker,
const ScopePtr& scope, const ScopePtr& scope,
const AstExprCall& expr, const AstExprCall& expr,
@ -1353,12 +1197,7 @@ std::optional<WithPredicate<TypePackId>> MagicSetMetatable::handleOldSolver(
return WithPredicate<TypePackId>{arena.addTypePack({target})}; return WithPredicate<TypePackId>{arena.addTypePack({target})};
} }
bool MagicSetMetatable::infer(const MagicFunctionCallContext&) static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
{
return false;
}
std::optional<WithPredicate<TypePackId>> MagicAssert::handleOldSolver(
TypeChecker& typechecker, TypeChecker& typechecker,
const ScopePtr& scope, const ScopePtr& scope,
const AstExprCall& expr, const AstExprCall& expr,
@ -1392,12 +1231,7 @@ std::optional<WithPredicate<TypePackId>> MagicAssert::handleOldSolver(
return WithPredicate<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})}; return WithPredicate<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
} }
bool MagicAssert::infer(const MagicFunctionCallContext&) static std::optional<WithPredicate<TypePackId>> magicFunctionPack(
{
return false;
}
std::optional<WithPredicate<TypePackId>> MagicPack::handleOldSolver(
TypeChecker& typechecker, TypeChecker& typechecker,
const ScopePtr& scope, const ScopePtr& scope,
const AstExprCall& expr, const AstExprCall& expr,
@ -1440,7 +1274,7 @@ std::optional<WithPredicate<TypePackId>> MagicPack::handleOldSolver(
return WithPredicate<TypePackId>{arena.addTypePack({packedTable})}; return WithPredicate<TypePackId>{arena.addTypePack({packedTable})};
} }
bool MagicPack::infer(const MagicFunctionCallContext& context) static bool dcrMagicFunctionPack(MagicFunctionCallContext context)
{ {
TypeArena* arena = context.solver->arena; TypeArena* arena = context.solver->arena;
@ -1480,171 +1314,6 @@ bool MagicPack::infer(const MagicFunctionCallContext& context)
return true; return true;
} }
std::optional<WithPredicate<TypePackId>> MagicClone::handleOldSolver(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
WithPredicate<TypePackId> withPredicate
)
{
LUAU_ASSERT(FFlag::LuauTableCloneClonesType3);
auto [paramPack, _predicates] = withPredicate;
TypeArena& arena = typechecker.currentModule->internalTypes;
const auto& [paramTypes, paramTail] = flatten(paramPack);
if (paramTypes.empty() || expr.args.size == 0)
{
typechecker.reportError(expr.argLocation, CountMismatch{1, std::nullopt, 0});
return std::nullopt;
}
TypeId inputType = follow(paramTypes[0]);
if (!get<TableType>(inputType))
return std::nullopt;
CloneState cloneState{typechecker.builtinTypes};
TypeId resultType = shallowClone(inputType, arena, cloneState);
TypePackId clonedTypePack = arena.addTypePack({resultType});
return WithPredicate<TypePackId>{clonedTypePack};
}
bool MagicClone::infer(const MagicFunctionCallContext& context)
{
LUAU_ASSERT(FFlag::LuauTableCloneClonesType3);
TypeArena* arena = context.solver->arena;
const auto& [paramTypes, paramTail] = flatten(context.arguments);
if (paramTypes.empty() || context.callSite->args.size == 0)
{
context.solver->reportError(CountMismatch{1, std::nullopt, 0}, context.callSite->argLocation);
return false;
}
TypeId inputType = follow(paramTypes[0]);
if (!get<TableType>(inputType))
return false;
CloneState cloneState{context.solver->builtinTypes};
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
if (auto tableType = getMutable<TableType>(resultType))
{
tableType->scope = context.constraint->scope.get();
}
trackInteriorFreeType(context.constraint->scope.get(), resultType);
TypePackId clonedTypePack = arena->addTypePack({resultType});
asMutable(context.result)->ty.emplace<BoundTypePack>(clonedTypePack);
return true;
}
static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCallContext& context)
{
TypeArena* arena = context.solver->arena;
inputType = follow(inputType);
if (auto mt = get<MetatableType>(inputType))
{
std::optional<TypeId> frozenTable = freezeTable(mt->table, context);
if (!frozenTable)
return std::nullopt;
TypeId resultType = arena->addType(MetatableType{*frozenTable, mt->metatable, mt->syntheticName});
return resultType;
}
if (get<TableType>(inputType))
{
// Clone the input type, this will become our final result type after we mutate it.
CloneState cloneState{context.solver->builtinTypes};
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
auto tableTy = getMutable<TableType>(resultType);
// `clone` should not break this.
LUAU_ASSERT(tableTy);
tableTy->state = TableState::Sealed;
// We'll mutate the table to make every property type read-only.
for (auto iter = tableTy->props.begin(); iter != tableTy->props.end();)
{
if (iter->second.isWriteOnly())
iter = tableTy->props.erase(iter);
else
{
iter->second.writeTy = std::nullopt;
iter++;
}
}
return resultType;
}
context.solver->reportError(TypeMismatch{context.solver->builtinTypes->tableType, inputType}, context.callSite->argLocation);
return std::nullopt;
}
std::optional<WithPredicate<TypePackId>> MagicFreeze::
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>)
{
return std::nullopt;
}
bool MagicFreeze::infer(const MagicFunctionCallContext& context)
{
TypeArena* arena = context.solver->arena;
const DataFlowGraph* dfg = context.solver->dfg.get();
Scope* scope = context.constraint->scope.get();
const auto& [paramTypes, paramTail] = extendTypePack(*arena, context.solver->builtinTypes, context.arguments, 1);
if (paramTypes.empty() || context.callSite->args.size == 0)
{
context.solver->reportError(CountMismatch{1, std::nullopt, 0}, context.callSite->argLocation);
return false;
}
TypeId inputType = follow(paramTypes[0]);
AstExpr* targetExpr = context.callSite->args.data[0];
std::optional<DefId> resultDef = dfg->getDefOptional(targetExpr);
std::optional<TypeId> resultTy = resultDef ? scope->lookup(*resultDef) : std::nullopt;
if (FFlag::LuauMagicFreezeCheckBlocked)
{
if (resultTy && !get<BlockedType>(resultTy))
{
// If there's an existing result type but it's _not_ blocked, then
// we aren't type stating this builtin and should fall back to
// regular inference.
return false;
}
}
std::optional<TypeId> frozenType = freezeTable(inputType, context);
if (!frozenType)
{
if (resultTy)
asMutable(*resultTy)->ty.emplace<BoundType>(context.solver->builtinTypes->errorType);
asMutable(context.result)->ty.emplace<BoundTypePack>(context.solver->builtinTypes->errorTypePack);
return true;
}
if (resultTy)
asMutable(*resultTy)->ty.emplace<BoundType>(*frozenType);
asMutable(context.result)->ty.emplace<BoundTypePack>(arena->addTypePack({*frozenType}));
return true;
}
static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr) static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr)
{ {
// require(foo.parent.bar) will technically work, but it depends on legacy goop that // require(foo.parent.bar) will technically work, but it depends on legacy goop that
@ -1667,7 +1336,7 @@ static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr)
return good; return good;
} }
std::optional<WithPredicate<TypePackId>> MagicRequire::handleOldSolver( static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
TypeChecker& typechecker, TypeChecker& typechecker,
const ScopePtr& scope, const ScopePtr& scope,
const AstExprCall& expr, const AstExprCall& expr,
@ -1713,7 +1382,7 @@ static bool checkRequirePathDcr(NotNull<ConstraintSolver> solver, AstExpr* expr)
return good; return good;
} }
bool MagicRequire::infer(const MagicFunctionCallContext& context) static bool dcrMagicFunctionRequire(MagicFunctionCallContext context)
{ {
if (context.callSite->args.size != 1) if (context.callSite->args.size != 1)
{ {
@ -1736,52 +1405,4 @@ bool MagicRequire::infer(const MagicFunctionCallContext& context)
return false; return false;
} }
bool matchSetMetatable(const AstExprCall& call)
{
const char* smt = "setmetatable";
if (call.args.size != 2)
return false;
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
if (!funcAsGlobal || funcAsGlobal->name != smt)
return false;
return true;
}
bool matchTableFreeze(const AstExprCall& call)
{
if (call.args.size < 1)
return false;
const AstExprIndexName* index = call.func->as<AstExprIndexName>();
if (!index || index->index != "freeze")
return false;
const AstExprGlobal* global = index->expr->as<AstExprGlobal>();
if (!global || global->name != "table")
return false;
return true;
}
bool matchAssert(const AstExprCall& call)
{
if (call.args.size < 1)
return false;
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
if (!funcAsGlobal || funcAsGlobal->name != "assert")
return false;
return true;
}
bool shouldTypestateForFirstArgument(const AstExprCall& call)
{
// TODO: magic function for setmetatable and assert and then add them
return matchTableFreeze(call);
}
} // namespace Luau } // namespace Luau

View file

@ -1,20 +1,16 @@
// 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/Clone.h" #include "Luau/Clone.h"
#include "Luau/Common.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/Unifiable.h" #include "Luau/Unifiable.h"
#include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
// For each `Luau::clone` call, we will clone only up to N amount of types _and_ packs, as controlled by this limit. // For each `Luau::clone` call, we will clone only up to N amount of types _and_ packs, as controlled by this limit.
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000) LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
LUAU_FASTFLAGVARIABLE(LuauClonedTableAndFunctionTypesMustHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings)
LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning)
namespace Luau namespace Luau
{ {
@ -31,8 +27,6 @@ const T* get(const Kind& kind)
class TypeCloner class TypeCloner
{ {
protected:
NotNull<TypeArena> arena; NotNull<TypeArena> arena;
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
@ -44,31 +38,17 @@ protected:
NotNull<SeenTypes> types; NotNull<SeenTypes> types;
NotNull<SeenTypePacks> packs; NotNull<SeenTypePacks> packs;
TypeId forceTy = nullptr;
TypePackId forceTp = nullptr;
int steps = 0; int steps = 0;
public: public:
TypeCloner( TypeCloner(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<SeenTypes> types, NotNull<SeenTypePacks> packs)
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<SeenTypes> types,
NotNull<SeenTypePacks> packs,
TypeId forceTy,
TypePackId forceTp
)
: arena(arena) : arena(arena)
, builtinTypes(builtinTypes) , builtinTypes(builtinTypes)
, types(types) , types(types)
, packs(packs) , packs(packs)
, forceTy(forceTy)
, forceTp(forceTp)
{ {
} }
virtual ~TypeCloner() = default;
TypeId clone(TypeId ty) TypeId clone(TypeId ty)
{ {
shallowClone(ty); shallowClone(ty);
@ -127,13 +107,12 @@ private:
} }
} }
protected:
std::optional<TypeId> find(TypeId ty) const std::optional<TypeId> find(TypeId ty) const
{ {
ty = follow(ty, FollowOption::DisableLazyTypeThunks); ty = follow(ty, FollowOption::DisableLazyTypeThunks);
if (auto it = types->find(ty); it != types->end()) if (auto it = types->find(ty); it != types->end())
return it->second; return it->second;
else if (ty->persistent && ty != forceTy) else if (ty->persistent)
return ty; return ty;
return std::nullopt; return std::nullopt;
} }
@ -143,7 +122,7 @@ protected:
tp = follow(tp); tp = follow(tp);
if (auto it = packs->find(tp); it != packs->end()) if (auto it = packs->find(tp); it != packs->end())
return it->second; return it->second;
else if (tp->persistent && tp != forceTp) else if (tp->persistent)
return tp; return tp;
return std::nullopt; return std::nullopt;
} }
@ -161,15 +140,15 @@ protected:
} }
} }
public: private:
virtual TypeId shallowClone(TypeId ty) TypeId shallowClone(TypeId ty)
{ {
// We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s. // We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s.
ty = follow(ty, FollowOption::DisableLazyTypeThunks); ty = follow(ty, FollowOption::DisableLazyTypeThunks);
if (auto clone = find(ty)) if (auto clone = find(ty))
return *clone; return *clone;
else if (ty->persistent && ty != forceTy) else if (ty->persistent)
return ty; return ty;
TypeId target = arena->addType(ty->ty); TypeId target = arena->addType(ty->ty);
@ -179,6 +158,8 @@ public:
generic->scope = nullptr; generic->scope = nullptr;
else if (auto free = getMutable<FreeType>(target)) else if (auto free = getMutable<FreeType>(target))
free->scope = nullptr; free->scope = nullptr;
else if (auto fn = getMutable<FunctionType>(target))
fn->scope = nullptr;
else if (auto table = getMutable<TableType>(target)) else if (auto table = getMutable<TableType>(target))
table->scope = nullptr; table->scope = nullptr;
@ -187,13 +168,13 @@ public:
return target; return target;
} }
virtual TypePackId shallowClone(TypePackId tp) TypePackId shallowClone(TypePackId tp)
{ {
tp = follow(tp); tp = follow(tp);
if (auto clone = find(tp)) if (auto clone = find(tp))
return *clone; return *clone;
else if (tp->persistent && tp != forceTp) else if (tp->persistent)
return tp; return tp;
TypePackId target = arena->addTypePack(tp->ty); TypePackId target = arena->addTypePack(tp->ty);
@ -208,7 +189,6 @@ public:
return target; return target;
} }
private:
Property shallowClone(const Property& p) Property shallowClone(const Property& p)
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -276,7 +256,8 @@ private:
LUAU_ASSERT(!"Item holds neither TypeId nor TypePackId when enqueuing its children?"); LUAU_ASSERT(!"Item holds neither TypeId nor TypePackId when enqueuing its children?");
} }
void cloneChildren(ErrorType* t) // ErrorType and ErrorTypePack is an alias to this type.
void cloneChildren(Unifiable::Error* t)
{ {
// noop. // noop.
} }
@ -355,7 +336,7 @@ private:
t->metatable = shallowClone(t->metatable); t->metatable = shallowClone(t->metatable);
} }
void cloneChildren(ExternType* t) void cloneChildren(ClassType* t)
{ {
for (auto& [_, p] : t->props) for (auto& [_, p] : t->props)
p = shallowClone(p); p = shallowClone(p);
@ -378,11 +359,6 @@ private:
// noop. // noop.
} }
void cloneChildren(NoRefineType* t)
{
// noop.
}
void cloneChildren(UnionType* t) void cloneChildren(UnionType* t)
{ {
for (TypeId& ty : t->options) for (TypeId& ty : t->options)
@ -395,7 +371,7 @@ private:
ty = shallowClone(ty); ty = shallowClone(ty);
} }
virtual void cloneChildren(LazyType* t) void cloneChildren(LazyType* t)
{ {
if (auto unwrapped = t->unwrapped.load()) if (auto unwrapped = t->unwrapped.load())
t->unwrapped.store(shallowClone(unwrapped)); t->unwrapped.store(shallowClone(unwrapped));
@ -446,11 +422,6 @@ private:
t->boundTo = shallowClone(t->boundTo); t->boundTo = shallowClone(t->boundTo);
} }
void cloneChildren(ErrorTypePack* t)
{
// noop.
}
void cloneChildren(VariadicTypePack* t) void cloneChildren(VariadicTypePack* t)
{ {
t->ty = shallowClone(t->ty); t->ty = shallowClone(t->ty);
@ -475,131 +446,14 @@ private:
} }
}; };
class FragmentAutocompleteTypeCloner final : public TypeCloner
{
Scope* replacementForNullScope = nullptr;
public:
FragmentAutocompleteTypeCloner(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<SeenTypes> types,
NotNull<SeenTypePacks> packs,
TypeId forceTy,
TypePackId forceTp,
Scope* replacementForNullScope
)
: TypeCloner(arena, builtinTypes, types, packs, forceTy, forceTp)
, replacementForNullScope(replacementForNullScope)
{
LUAU_ASSERT(replacementForNullScope);
}
TypeId shallowClone(TypeId ty) override
{
// We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s.
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
if (auto clone = find(ty))
return *clone;
else if (ty->persistent && ty != forceTy)
return ty;
TypeId target = arena->addType(ty->ty);
asMutable(target)->documentationSymbol = ty->documentationSymbol;
if (auto generic = getMutable<GenericType>(target))
generic->scope = nullptr;
else if (auto free = getMutable<FreeType>(target))
{
free->scope = replacementForNullScope;
}
else if (auto tt = getMutable<TableType>(target))
{
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
tt->scope = replacementForNullScope;
}
(*types)[ty] = target;
queue.emplace_back(target);
return target;
}
TypePackId shallowClone(TypePackId tp) override
{
tp = follow(tp);
if (auto clone = find(tp))
return *clone;
else if (tp->persistent && tp != forceTp)
return tp;
TypePackId target = arena->addTypePack(tp->ty);
if (auto generic = getMutable<GenericTypePack>(target))
generic->scope = nullptr;
else if (auto free = getMutable<FreeTypePack>(target))
free->scope = replacementForNullScope;
(*packs)[tp] = target;
queue.emplace_back(target);
return target;
}
void cloneChildren(LazyType* t) override
{
// Do not clone lazy types
if (!FFlag::LuauIncrementalAutocompleteDemandBasedCloning)
{
if (auto unwrapped = t->unwrapped.load())
t->unwrapped.store(shallowClone(unwrapped));
}
}
};
} // namespace } // namespace
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
{
if (tp->persistent && !ignorePersistent)
return tp;
TypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
ignorePersistent ? tp : nullptr
};
return cloner.shallowClone(tp);
}
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
{
if (typeId->persistent && !ignorePersistent)
return typeId;
TypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
ignorePersistent ? typeId : nullptr,
nullptr
};
return cloner.shallowClone(typeId);
}
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState) TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
{ {
if (tp->persistent) if (tp->persistent)
return tp; return tp;
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}, nullptr, nullptr}; TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
return cloner.clone(tp); return cloner.clone(tp);
} }
@ -608,13 +462,13 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
if (typeId->persistent) if (typeId->persistent)
return typeId; return typeId;
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}, nullptr, nullptr}; TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
return cloner.clone(typeId); return cloner.clone(typeId);
} }
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState) TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState)
{ {
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}, nullptr, nullptr}; TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
TypeFun copy = typeFun; TypeFun copy = typeFun;
@ -639,110 +493,4 @@ TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState)
return copy; return copy;
} }
Binding clone(const Binding& binding, TypeArena& dest, CloneState& cloneState)
{
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}, nullptr, nullptr};
Binding b;
b.deprecated = binding.deprecated;
b.deprecatedSuggestion = binding.deprecatedSuggestion;
b.documentationSymbol = binding.documentationSymbol;
b.location = binding.location;
b.typeId = cloner.clone(binding.typeId);
return b;
}
TypePackId cloneIncremental(TypePackId tp, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
{
if (tp->persistent)
return tp;
FragmentAutocompleteTypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
nullptr,
freshScopeForFreeTypes
};
return cloner.clone(tp);
}
TypeId cloneIncremental(TypeId typeId, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
{
if (typeId->persistent)
return typeId;
FragmentAutocompleteTypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
nullptr,
freshScopeForFreeTypes
};
return cloner.clone(typeId);
}
TypeFun cloneIncremental(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
{
FragmentAutocompleteTypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
nullptr,
freshScopeForFreeTypes
};
TypeFun copy = typeFun;
for (auto& param : copy.typeParams)
{
param.ty = cloner.clone(param.ty);
if (param.defaultValue)
param.defaultValue = cloner.clone(*param.defaultValue);
}
for (auto& param : copy.typePackParams)
{
param.tp = cloner.clone(param.tp);
if (param.defaultValue)
param.defaultValue = cloner.clone(*param.defaultValue);
}
copy.type = cloner.clone(copy.type);
return copy;
}
Binding cloneIncremental(const Binding& binding, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
{
FragmentAutocompleteTypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
nullptr,
freshScopeForFreeTypes
};
Binding b;
b.deprecated = binding.deprecated;
b.deprecatedSuggestion = binding.deprecatedSuggestion;
b.documentationSymbol = binding.documentationSymbol;
b.location = binding.location;
b.typeId = FFlag::LuauDoNotClonePersistentBindings && binding.typeId->persistent ? binding.typeId : cloner.clone(binding.typeId);
return b;
}
} // namespace Luau } // namespace Luau

View file

@ -3,8 +3,6 @@
#include "Luau/Constraint.h" #include "Luau/Constraint.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
namespace Luau namespace Luau
{ {
@ -20,7 +18,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor
DenseHashSet<TypeId>* result; DenseHashSet<TypeId>* result;
explicit ReferenceCountInitializer(DenseHashSet<TypeId>* result) ReferenceCountInitializer(DenseHashSet<TypeId>* result)
: result(result) : result(result)
{ {
} }
@ -43,16 +41,11 @@ struct ReferenceCountInitializer : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId ty, const ExternType&) override bool visit(TypeId ty, const ClassType&) override
{ {
// ExternTypes never contain free types. // ClassTypes never contain free types.
return false; return false;
} }
bool visit(TypeId, const TypeFunctionInstanceType&) override
{
return FFlag::DebugLuauGreedyGeneralization;
}
}; };
bool isReferenceCountedType(const TypeId typ) bool isReferenceCountedType(const TypeId typ)
@ -104,11 +97,6 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
{ {
rci.traverse(fchc->argsPack); rci.traverse(fchc->argsPack);
} }
else if (auto fcc = get<FunctionCallConstraint>(*this); fcc && FFlag::DebugLuauGreedyGeneralization)
{
rci.traverse(fcc->fn);
rci.traverse(fcc->argsPack);
}
else if (auto ptc = get<PrimitiveTypeConstraint>(*this)) else if (auto ptc = get<PrimitiveTypeConstraint>(*this))
{ {
rci.traverse(ptc->freeType); rci.traverse(ptc->freeType);
@ -116,15 +104,12 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
else if (auto hpc = get<HasPropConstraint>(*this)) else if (auto hpc = get<HasPropConstraint>(*this))
{ {
rci.traverse(hpc->resultType); rci.traverse(hpc->resultType);
if (FFlag::DebugLuauGreedyGeneralization) // `HasPropConstraints` should not mutate `subjectType`.
rci.traverse(hpc->subjectType);
} }
else if (auto hic = get<HasIndexerConstraint>(*this)) else if (auto hic = get<HasIndexerConstraint>(*this))
{ {
if (FFlag::DebugLuauGreedyGeneralization)
rci.traverse(hic->subjectType);
rci.traverse(hic->resultType); rci.traverse(hic->resultType);
// `HasIndexerConstraint` should not mutate `indexType`. // `HasIndexerConstraint` should not mutate `subjectType` or `indexType`.
} }
else if (auto apc = get<AssignPropConstraint>(*this)) else if (auto apc = get<AssignPropConstraint>(*this))
{ {
@ -143,18 +128,10 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
rci.traverse(ty); rci.traverse(ty);
// `UnpackConstraint` should not mutate `sourcePack`. // `UnpackConstraint` should not mutate `sourcePack`.
} }
else if (auto rpc = get<ReduceConstraint>(*this); FFlag::DebugLuauGreedyGeneralization && rpc)
{
rci.traverse(rpc->ty);
}
else if (auto rpc = get<ReducePackConstraint>(*this)) else if (auto rpc = get<ReducePackConstraint>(*this))
{ {
rci.traverse(rpc->tp); rci.traverse(rpc->tp);
} }
else if (auto tcc = get<TableCheckConstraint>(*this))
{
rci.traverse(tcc->exprType);
}
return types; return types;
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -36,9 +36,9 @@ void collectOperands(DefId def, std::vector<DefId>* operands)
} }
} }
DefId DefArena::freshCell(Symbol sym, Location location, bool subscripted) DefId DefArena::freshCell(bool subscripted)
{ {
return NotNull{allocator.allocate(Def{Cell{subscripted}, sym, location})}; return NotNull{allocator.allocate(Def{Cell{subscripted}})};
} }
DefId DefArena::phi(DefId a, DefId b) DefId DefArena::phi(DefId a, DefId b)

View file

@ -277,7 +277,7 @@ static DifferResult diffSingleton(DifferEnvironment& env, TypeId left, TypeId ri
static DifferResult diffFunction(DifferEnvironment& env, TypeId left, TypeId right); static DifferResult diffFunction(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId right); static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId right); static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffExternType(DifferEnvironment& env, TypeId left, TypeId right); static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right);
struct FindSeteqCounterexampleResult struct FindSeteqCounterexampleResult
{ {
// nullopt if no counterexample found // nullopt if no counterexample found
@ -481,14 +481,14 @@ static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId rig
return differResult; return differResult;
} }
static DifferResult diffExternType(DifferEnvironment& env, TypeId left, TypeId right) static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right)
{ {
const ExternType* leftExternType = get<ExternType>(left); const ClassType* leftClass = get<ClassType>(left);
const ExternType* rightExternType = get<ExternType>(right); const ClassType* rightClass = get<ClassType>(right);
LUAU_ASSERT(leftExternType); LUAU_ASSERT(leftClass);
LUAU_ASSERT(rightExternType); LUAU_ASSERT(rightClass);
if (leftExternType == rightExternType) if (leftClass == rightClass)
{ {
return DifferResult{}; return DifferResult{};
} }
@ -651,9 +651,9 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig
{ {
return diffNegation(env, left, right); return diffNegation(env, left, right);
} }
else if (auto lc = get<ExternType>(left)) else if (auto lc = get<ClassType>(left))
{ {
return diffExternType(env, left, right); return diffClass(env, left, right);
} }
throw InternalCompilerError{"Unimplemented Simple TypeId variant for diffing"}; throw InternalCompilerError{"Unimplemented Simple TypeId variant for diffing"};
@ -718,7 +718,7 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig
env.popVisiting(); env.popVisiting();
return diffRes; return diffRes;
} }
if (auto le = get<ErrorType>(left)) if (auto le = get<Luau::Unifiable::Error>(left))
{ {
// TODO: return debug-friendly result state // TODO: return debug-friendly result state
env.popVisiting(); env.popVisiting();
@ -960,7 +960,7 @@ bool isSimple(TypeId ty)
{ {
ty = follow(ty); ty = follow(ty);
// TODO: think about GenericType, etc. // TODO: think about GenericType, etc.
return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty) || get<NegationType>(ty) || get<ExternType>(ty) || return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty) || get<NegationType>(ty) || get<ClassType>(ty) ||
get<UnknownType>(ty) || get<NeverType>(ty); get<UnknownType>(ty) || get<NeverType>(ty);
} }

View file

@ -1,13 +1,102 @@
// 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_FASTFLAG(LuauDeclareExternType)
LUAU_FASTFLAG(LuauTypeFunOptional)
namespace Luau namespace Luau
{ {
static const std::string kBuiltinDefinitionBaseSrc = R"BUILTIN_SRC( static const std::string kBuiltinDefinitionLuaSrcChecked = R"BUILTIN_SRC(
declare bit32: {
band: @checked (...number) -> number,
bor: @checked (...number) -> number,
bxor: @checked (...number) -> number,
btest: @checked (number, ...number) -> boolean,
rrotate: @checked (x: number, disp: number) -> number,
lrotate: @checked (x: number, disp: number) -> number,
lshift: @checked (x: number, disp: number) -> number,
arshift: @checked (x: number, disp: number) -> number,
rshift: @checked (x: number, disp: number) -> number,
bnot: @checked (x: number) -> number,
extract: @checked (n: number, field: number, width: number?) -> number,
replace: @checked (n: number, v: number, field: number, width: number?) -> number,
countlz: @checked (n: number) -> number,
countrz: @checked (n: number) -> number,
byteswap: @checked (n: number) -> number,
}
declare math: {
frexp: @checked (n: number) -> (number, number),
ldexp: @checked (s: number, e: number) -> number,
fmod: @checked (x: number, y: number) -> number,
modf: @checked (n: number) -> (number, number),
pow: @checked (x: number, y: number) -> number,
exp: @checked (n: number) -> number,
ceil: @checked (n: number) -> number,
floor: @checked (n: number) -> number,
abs: @checked (n: number) -> number,
sqrt: @checked (n: number) -> number,
log: @checked (n: number, base: number?) -> number,
log10: @checked (n: number) -> number,
rad: @checked (n: number) -> number,
deg: @checked (n: number) -> number,
sin: @checked (n: number) -> number,
cos: @checked (n: number) -> number,
tan: @checked (n: number) -> number,
sinh: @checked (n: number) -> number,
cosh: @checked (n: number) -> number,
tanh: @checked (n: number) -> number,
atan: @checked (n: number) -> number,
acos: @checked (n: number) -> number,
asin: @checked (n: number) -> number,
atan2: @checked (y: number, x: number) -> number,
min: @checked (number, ...number) -> number,
max: @checked (number, ...number) -> number,
pi: number,
huge: number,
randomseed: @checked (seed: number) -> (),
random: @checked (number?, number?) -> number,
sign: @checked (n: number) -> number,
clamp: @checked (n: number, min: number, max: number) -> number,
noise: @checked (x: number, y: number?, z: number?) -> number,
round: @checked (n: number) -> number,
}
type DateTypeArg = {
year: number,
month: number,
day: number,
hour: number?,
min: number?,
sec: number?,
isdst: boolean?,
}
type DateTypeResult = {
year: number,
month: number,
wday: number,
yday: number,
day: number,
hour: number,
min: number,
sec: number,
isdst: boolean,
}
declare os: {
time: (time: DateTypeArg?) -> number,
date: ((formatString: "*t" | "!*t", time: number?) -> DateTypeResult) & ((formatString: string?, time: number?) -> string),
difftime: (t2: DateTypeResult | number, t1: DateTypeResult | number) -> number,
clock: () -> number,
}
@checked declare function require(target: any): any @checked declare function require(target: any): any
@ -55,119 +144,6 @@ declare function loadstring<A...>(src: string, chunkname: string?): (((A...) ->
@checked declare function newproxy(mt: boolean?): any @checked declare function newproxy(mt: boolean?): any
-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype.
declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionBit32Src = R"BUILTIN_SRC(
declare bit32: {
band: @checked (...number) -> number,
bor: @checked (...number) -> number,
bxor: @checked (...number) -> number,
btest: @checked (number, ...number) -> boolean,
rrotate: @checked (x: number, disp: number) -> number,
lrotate: @checked (x: number, disp: number) -> number,
lshift: @checked (x: number, disp: number) -> number,
arshift: @checked (x: number, disp: number) -> number,
rshift: @checked (x: number, disp: number) -> number,
bnot: @checked (x: number) -> number,
extract: @checked (n: number, field: number, width: number?) -> number,
replace: @checked (n: number, v: number, field: number, width: number?) -> number,
countlz: @checked (n: number) -> number,
countrz: @checked (n: number) -> number,
byteswap: @checked (n: number) -> number,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionMathSrc = R"BUILTIN_SRC(
declare math: {
frexp: @checked (n: number) -> (number, number),
ldexp: @checked (s: number, e: number) -> number,
fmod: @checked (x: number, y: number) -> number,
modf: @checked (n: number) -> (number, number),
pow: @checked (x: number, y: number) -> number,
exp: @checked (n: number) -> number,
ceil: @checked (n: number) -> number,
floor: @checked (n: number) -> number,
abs: @checked (n: number) -> number,
sqrt: @checked (n: number) -> number,
log: @checked (n: number, base: number?) -> number,
log10: @checked (n: number) -> number,
rad: @checked (n: number) -> number,
deg: @checked (n: number) -> number,
sin: @checked (n: number) -> number,
cos: @checked (n: number) -> number,
tan: @checked (n: number) -> number,
sinh: @checked (n: number) -> number,
cosh: @checked (n: number) -> number,
tanh: @checked (n: number) -> number,
atan: @checked (n: number) -> number,
acos: @checked (n: number) -> number,
asin: @checked (n: number) -> number,
atan2: @checked (y: number, x: number) -> number,
min: @checked (number, ...number) -> number,
max: @checked (number, ...number) -> number,
pi: number,
huge: number,
randomseed: @checked (seed: number) -> (),
random: @checked (number?, number?) -> number,
sign: @checked (n: number) -> number,
clamp: @checked (n: number, min: number, max: number) -> number,
noise: @checked (x: number, y: number?, z: number?) -> number,
round: @checked (n: number) -> number,
map: @checked (x: number, inmin: number, inmax: number, outmin: number, outmax: number) -> number,
lerp: @checked (a: number, b: number, t: number) -> number,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionOsSrc = R"BUILTIN_SRC(
type DateTypeArg = {
year: number,
month: number,
day: number,
hour: number?,
min: number?,
sec: number?,
isdst: boolean?,
}
type DateTypeResult = {
year: number,
month: number,
wday: number,
yday: number,
day: number,
hour: number,
min: number,
sec: number,
isdst: boolean,
}
declare os: {
time: (time: DateTypeArg?) -> number,
date: ((formatString: "*t" | "!*t", time: number?) -> DateTypeResult) & ((formatString: string?, time: number?) -> string),
difftime: (t2: DateTypeResult | number, t1: DateTypeResult | number) -> number,
clock: () -> number,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionCoroutineSrc = R"BUILTIN_SRC(
declare coroutine: { declare coroutine: {
create: <A..., R...>(f: (A...) -> R...) -> thread, create: <A..., R...>(f: (A...) -> R...) -> thread,
resume: <A..., R...>(co: thread, A...) -> (boolean, R...), resume: <A..., R...>(co: thread, A...) -> (boolean, R...),
@ -179,10 +155,6 @@ declare coroutine: {
close: @checked (co: thread) -> (boolean, any) close: @checked (co: thread) -> (boolean, any)
} }
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTableSrc = R"BUILTIN_SRC(
declare table: { declare table: {
concat: <V>(t: {V}, sep: string?, i: number?, j: number?) -> string, concat: <V>(t: {V}, sep: string?, i: number?, j: number?) -> string,
insert: (<V>(t: {V}, value: V) -> ()) & (<V>(t: {V}, pos: number, value: V) -> ()), insert: (<V>(t: {V}, value: V) -> ()) & (<V>(t: {V}, pos: number, value: V) -> ()),
@ -205,19 +177,11 @@ declare table: {
isfrozen: <K, V>(t: {[K]: V}) -> boolean, isfrozen: <K, V>(t: {[K]: V}) -> boolean,
} }
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionDebugSrc = R"BUILTIN_SRC(
declare debug: { declare debug: {
info: ((thread: thread, level: number, options: string) -> ...any) & ((level: number, options: string) -> ...any) & (<A..., R1...>(func: (A...) -> R1..., options: string) -> ...any), info: (<R...>(thread: thread, level: number, options: string) -> R...) & (<R...>(level: number, options: string) -> R...) & (<A..., R1..., R2...>(func: (A...) -> R1..., options: string) -> R2...),
traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string), traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string),
} }
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC(
declare utf8: { declare utf8: {
char: @checked (...number) -> string, char: @checked (...number) -> string,
charpattern: string, charpattern: string,
@ -227,9 +191,10 @@ declare utf8: {
offset: @checked (s: string, n: number?, i: number?) -> number, offset: @checked (s: string, n: number?, i: number?) -> number,
} }
)BUILTIN_SRC"; -- Cannot use `typeof` here because it will produce a polytype when we expect a monotype.
declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
static const std::string kBuiltinDefinitionBufferSrc = R"BUILTIN_SRC(
--- Buffer API --- Buffer API
declare buffer: { declare buffer: {
create: @checked (size: number) -> buffer, create: @checked (size: number) -> buffer,
@ -256,201 +221,13 @@ declare buffer: {
writef64: @checked (b: buffer, offset: number, value: number) -> (), writef64: @checked (b: buffer, offset: number, value: number) -> (),
readstring: @checked (b: buffer, offset: number, count: number) -> string, readstring: @checked (b: buffer, offset: number, count: number) -> string,
writestring: @checked (b: buffer, offset: number, value: string, count: number?) -> (), writestring: @checked (b: buffer, offset: number, value: string, count: number?) -> (),
readbits: @checked (b: buffer, bitOffset: number, bitCount: number) -> number,
writebits: @checked (b: buffer, bitOffset: number, bitCount: number, value: number) -> (),
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType)
? R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver extern type handling covers most of the properties
declare extern type vector with
x: number
y: number
z: number
end
declare vector: {
create: @checked (x: number, y: number, z: number?) -> vector,
magnitude: @checked (vec: vector) -> number,
normalize: @checked (vec: vector) -> vector,
cross: @checked (vec1: vector, vec2: vector) -> vector,
dot: @checked (vec1: vector, vec2: vector) -> number,
angle: @checked (vec1: vector, vec2: vector, axis: vector?) -> number,
floor: @checked (vec: vector) -> vector,
ceil: @checked (vec: vector) -> vector,
abs: @checked (vec: vector) -> vector,
sign: @checked (vec: vector) -> vector,
clamp: @checked (vec: vector, min: vector, max: vector) -> vector,
max: @checked (vector, ...vector) -> vector,
min: @checked (vector, ...vector) -> vector,
zero: vector,
one: vector,
}
)BUILTIN_SRC"
: R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver class handling covers most of the properties
declare class vector
x: number
y: number
z: number
end
declare vector: {
create: @checked (x: number, y: number, z: number?) -> vector,
magnitude: @checked (vec: vector) -> number,
normalize: @checked (vec: vector) -> vector,
cross: @checked (vec1: vector, vec2: vector) -> vector,
dot: @checked (vec1: vector, vec2: vector) -> number,
angle: @checked (vec1: vector, vec2: vector, axis: vector?) -> number,
floor: @checked (vec: vector) -> vector,
ceil: @checked (vec: vector) -> vector,
abs: @checked (vec: vector) -> vector,
sign: @checked (vec: vector) -> vector,
clamp: @checked (vec: vector, min: vector, max: vector) -> vector,
max: @checked (vector, ...vector) -> vector,
min: @checked (vector, ...vector) -> vector,
zero: vector,
one: vector,
} }
)BUILTIN_SRC"; )BUILTIN_SRC";
std::string getBuiltinDefinitionSource() std::string getBuiltinDefinitionSource()
{ {
std::string result = kBuiltinDefinitionBaseSrc; std::string result = kBuiltinDefinitionLuaSrcChecked;
result += kBuiltinDefinitionBit32Src;
result += kBuiltinDefinitionMathSrc;
result += kBuiltinDefinitionOsSrc;
result += kBuiltinDefinitionCoroutineSrc;
result += kBuiltinDefinitionTableSrc;
result += kBuiltinDefinitionDebugSrc;
result += kBuiltinDefinitionUtf8Src;
result += kBuiltinDefinitionBufferSrc;
result += kBuiltinDefinitionVectorSrc;
return result;
}
// TODO: split into separate tagged unions when the new solver can appropriately handle that.
static const std::string kBuiltinDefinitionTypeMethodSrc = R"BUILTIN_SRC(
export type type = {
tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" |
"singleton" | "negation" | "union" | "intersection" | "table" | "function" | "class" | "generic",
is: (self: type, arg: string) -> boolean,
-- for singleton type
value: (self: type) -> (string | boolean | nil),
-- for negation type
inner: (self: type) -> type,
-- for union and intersection types
components: (self: type) -> {type},
-- for table type
setproperty: (self: type, key: type, value: type?) -> (),
setreadproperty: (self: type, key: type, value: type?) -> (),
setwriteproperty: (self: type, key: type, value: type?) -> (),
readproperty: (self: type, key: type) -> type?,
writeproperty: (self: type, key: type) -> type?,
properties: (self: type) -> { [type]: { read: type?, write: type? } },
setindexer: (self: type, index: type, result: type) -> (),
setreadindexer: (self: type, index: type, result: type) -> (),
setwriteindexer: (self: type, index: type, result: type) -> (),
indexer: (self: type) -> { index: type, readresult: type, writeresult: type }?,
readindexer: (self: type) -> { index: type, result: type }?,
writeindexer: (self: type) -> { index: type, result: type }?,
setmetatable: (self: type, arg: type) -> (),
metatable: (self: type) -> type?,
-- for function type
setparameters: (self: type, head: {type}?, tail: type?) -> (),
parameters: (self: type) -> { head: {type}?, tail: type? },
setreturns: (self: type, head: {type}?, tail: type? ) -> (),
returns: (self: type) -> { head: {type}?, tail: type? },
setgenerics: (self: type, {type}?) -> (),
generics: (self: type) -> {type},
-- for class type
-- 'properties', 'metatable', 'indexer', 'readindexer' and 'writeindexer' are shared with table type
readparent: (self: type) -> type?,
writeparent: (self: type) -> type?,
-- for generic type
name: (self: type) -> string?,
ispack: (self: type) -> boolean,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC(
declare types: {
unknown: type,
never: type,
any: type,
boolean: type,
number: type,
string: type,
thread: type,
buffer: type,
singleton: @checked (arg: string | boolean | nil) -> type,
generic: @checked (name: string, ispack: boolean?) -> type,
negationof: @checked (arg: type) -> type,
unionof: @checked (...type) -> type,
intersectionof: @checked (...type) -> type,
newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type,
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
copy: @checked (arg: type) -> type,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibWithOptionalSrc = R"BUILTIN_SRC(
declare types: {
unknown: type,
never: type,
any: type,
boolean: type,
number: type,
string: type,
thread: type,
buffer: type,
singleton: @checked (arg: string | boolean | nil) -> type,
optional: @checked (arg: type) -> type,
generic: @checked (name: string, ispack: boolean?) -> type,
negationof: @checked (arg: type) -> type,
unionof: @checked (...type) -> type,
intersectionof: @checked (...type) -> type,
newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type,
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
copy: @checked (arg: type) -> type,
}
)BUILTIN_SRC";
std::string getTypeFunctionDefinitionSource()
{
std::string result = kBuiltinDefinitionTypeMethodSrc;
if (FFlag::LuauTypeFunOptional)
result += kBuiltinDefinitionTypesLibWithOptionalSrc;
else
result += kBuiltinDefinitionTypesLibSrc;
return result; return result;
} }

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,6 @@
#include "Luau/StringUtils.h" #include "Luau/StringUtils.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeChecker2.h"
#include "Luau/TypeFunction.h" #include "Luau/TypeFunction.h"
#include <optional> #include <optional>
@ -18,7 +17,8 @@
#include <unordered_set> #include <unordered_set>
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauImproveNonFunctionCallError, false)
static std::string wrongNumberOfArgsString( static std::string wrongNumberOfArgsString(
size_t expectedCount, size_t expectedCount,
@ -70,7 +70,7 @@ namespace Luau
{ {
// this list of binary operator type functions is used for better stringification of type functions errors // this list of binary operator type functions is used for better stringification of type functions errors
static const std::unordered_map<std::string, const char*> DEPRECATED_kBinaryOps{ static const std::unordered_map<std::string, const char*> kBinaryOps{
{"add", "+"}, {"add", "+"},
{"sub", "-"}, {"sub", "-"},
{"mul", "*"}, {"mul", "*"},
@ -86,27 +86,12 @@ static const std::unordered_map<std::string, const char*> DEPRECATED_kBinaryOps{
{"eq", "== or ~="} {"eq", "== or ~="}
}; };
static const std::unordered_map<std::string, const char*> kBinaryOps{
{"add", "+"},
{"sub", "-"},
{"mul", "*"},
{"div", "/"},
{"idiv", "//"},
{"pow", "^"},
{"mod", "%"},
{"concat", ".."},
{"lt", "< or >="},
{"le", "<= or >"},
{"eq", "== or ~="}
};
// this list of unary operator type functions is used for better stringification of type functions errors // this list of unary operator type functions is used for better stringification of type functions errors
static const std::unordered_map<std::string, const char*> kUnaryOps{{"unm", "-"}, {"len", "#"}, {"not", "not"}}; static const std::unordered_map<std::string, const char*> kUnaryOps{{"unm", "-"}, {"len", "#"}, {"not", "not"}};
// this list of type functions will receive a special error indicating that the user should file a bug on the GitHub repository // this list of type functions will receive a special error indicating that the user should file a bug on the GitHub repository
// putting a type function in this list indicates that it is expected to _always_ reduce // putting a type function in this list indicates that it is expected to _always_ reduce
static const std::unordered_set<std::string> DEPRECATED_kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect"}; static const std::unordered_set<std::string> kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect"};
static const std::unordered_set<std::string> kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect", "and", "or"};
struct ErrorConverter struct ErrorConverter
{ {
@ -133,10 +118,7 @@ struct ErrorConverter
size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength); size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength);
if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength) if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength)
return "Type " + given + " could not be converted into " + wanted; return "Type " + given + " could not be converted into " + wanted;
if (FFlag::LuauImproveTypePathsInErrors) return "Type\n " + given + "\ncould not be converted into\n " + wanted;
return "Type\n\t" + given + "\ncould not be converted into\n\t" + wanted;
else
return "Type\n " + given + "\ncould not be converted into\n " + wanted;
}; };
if (givenTypeName == wantedTypeName) if (givenTypeName == wantedTypeName)
@ -203,7 +185,7 @@ struct ErrorConverter
TypeId t = follow(e.table); TypeId t = follow(e.table);
if (get<TableType>(t)) if (get<TableType>(t))
return "Key '" + e.key + "' not found in table '" + Luau::toString(t) + "'"; return "Key '" + e.key + "' not found in table '" + Luau::toString(t) + "'";
else if (get<ExternType>(t)) else if (get<ClassType>(t))
return "Key '" + e.key + "' not found in class '" + Luau::toString(t) + "'"; return "Key '" + e.key + "' not found in class '" + Luau::toString(t) + "'";
else else
return "Type '" + Luau::toString(e.table) + "' does not have key '" + e.key + "'"; return "Type '" + Luau::toString(e.table) + "' does not have key '" + e.key + "'";
@ -371,7 +353,7 @@ struct ErrorConverter
std::string s = "Key '" + e.key + "' not found in "; std::string s = "Key '" + e.key + "' not found in ";
TypeId t = follow(e.table); TypeId t = follow(e.table);
if (get<ExternType>(t)) if (get<ClassType>(t))
s += "class"; s += "class";
else else
s += "table"; s += "table";
@ -402,8 +384,8 @@ struct ErrorConverter
std::optional<TypeId> metatable; std::optional<TypeId> metatable;
if (const MetatableType* mtType = get<MetatableType>(type)) if (const MetatableType* mtType = get<MetatableType>(type))
metatable = mtType->metatable; metatable = mtType->metatable;
else if (const ExternType* externType = get<ExternType>(type)) else if (const ClassType* classType = get<ClassType>(type))
metatable = externType->metatable; metatable = classType->metatable;
if (!metatable) if (!metatable)
return std::nullopt; return std::nullopt;
@ -426,30 +408,35 @@ struct ErrorConverter
std::string operator()(const Luau::CannotCallNonFunction& e) const std::string operator()(const Luau::CannotCallNonFunction& e) const
{ {
if (auto unionTy = get<UnionType>(follow(e.ty))) if (DFFlag::LuauImproveNonFunctionCallError)
{ {
std::string err = "Cannot call a value of the union type:"; if (auto unionTy = get<UnionType>(follow(e.ty)))
for (auto option : unionTy)
{ {
option = follow(option); std::string err = "Cannot call a value of the union type:";
if (get<FunctionType>(option) || findCallMetamethod(option)) for (auto option : unionTy)
{ {
err += "\n | " + toString(option); option = follow(option);
continue;
if (get<FunctionType>(option) || findCallMetamethod(option))
{
err += "\n | " + toString(option);
continue;
}
// early-exit if we find something that isn't callable in the union.
return "Cannot call a value of type " + toString(option) + " in union:\n " + toString(e.ty);
} }
// early-exit if we find something that isn't callable in the union. err += "\nWe are unable to determine the appropriate result type for such a call.";
return "Cannot call a value of type " + toString(option) + " in union:\n " + toString(e.ty);
return err;
} }
err += "\nWe are unable to determine the appropriate result type for such a call."; return "Cannot call a value of type " + toString(e.ty);
return err;
} }
return "Cannot call a value of type " + toString(e.ty); return "Cannot call non-function " + toString(e.ty);
} }
std::string operator()(const Luau::ExtraInformation& e) const std::string operator()(const Luau::ExtraInformation& e) const
{ {
@ -611,7 +598,7 @@ struct ErrorConverter
return ss; return ss;
} }
std::string operator()(const DynamicPropertyLookupOnExternTypesUnsafe& e) const std::string operator()(const DynamicPropertyLookupOnClassesUnsafe& e) const
{ {
return "Attempting a dynamic property access on type '" + Luau::toString(e.ty) + "' is unsafe and may cause exceptions at runtime"; return "Attempting a dynamic property access on type '" + Luau::toString(e.ty) + "' is unsafe and may cause exceptions at runtime";
} }
@ -621,7 +608,7 @@ struct ErrorConverter
auto tfit = get<TypeFunctionInstanceType>(e.ty); auto tfit = get<TypeFunctionInstanceType>(e.ty);
LUAU_ASSERT(tfit); // Luau analysis has actually done something wrong if this type is not a type function. LUAU_ASSERT(tfit); // Luau analysis has actually done something wrong if this type is not a type function.
if (!tfit) if (!tfit)
return "Internal error: Unexpected type " + Luau::toString(e.ty) + " flagged as an uninhabited type function."; return "Unexpected type " + Luau::toString(e.ty) + " flagged as an uninhabited type function.";
// unary operators // unary operators
if (auto unaryString = kUnaryOps.find(tfit->function->name); unaryString != kUnaryOps.end()) if (auto unaryString = kUnaryOps.find(tfit->function->name); unaryString != kUnaryOps.end())
@ -658,8 +645,7 @@ struct ErrorConverter
} }
// binary operators // binary operators
const auto binaryOps = FFlag::DebugLuauGreedyGeneralization ? kBinaryOps : DEPRECATED_kBinaryOps; if (auto binaryString = kBinaryOps.find(tfit->function->name); binaryString != kBinaryOps.end())
if (auto binaryString = binaryOps.find(tfit->function->name); binaryString != binaryOps.end())
{ {
std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types "; std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types ";
@ -713,10 +699,10 @@ struct ErrorConverter
"'"; "'";
} }
if ((FFlag::DebugLuauGreedyGeneralization ? kUnreachableTypeFunctions : DEPRECATED_kUnreachableTypeFunctions).count(tfit->function->name)) if (kUnreachableTypeFunctions.count(tfit->function->name))
{ {
return "Type function instance " + Luau::toString(e.ty) + " is uninhabited\n" + return "Type function instance " + Luau::toString(e.ty) + " is uninhabited\n" +
"This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues"; "This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues";
} }
// Everything should be specialized above to report a more descriptive error that hopefully does not mention "type functions" explicitly. // Everything should be specialized above to report a more descriptive error that hopefully does not mention "type functions" explicitly.
@ -772,15 +758,8 @@ struct ErrorConverter
std::string operator()(const NonStrictFunctionDefinitionError& e) const std::string operator()(const NonStrictFunctionDefinitionError& e) const
{ {
if (e.functionName.empty()) return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
{ "' is used in a way that will run time error";
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' is used in a way that will run time error";
}
else
{
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
"' is used in a way that will run time error";
}
} }
std::string operator()(const PropertyAccessViolation& e) const std::string operator()(const PropertyAccessViolation& e) const
@ -814,16 +793,6 @@ struct ErrorConverter
return "Encountered an unexpected type pack in subtyping: " + toString(e.tp); return "Encountered an unexpected type pack in subtyping: " + toString(e.tp);
} }
std::string operator()(const UserDefinedTypeFunctionError& e) const
{
return e.message;
}
std::string operator()(const ReservedIdentifier& e) const
{
return e.name + " cannot be used as an identifier for a type function or alias";
}
std::string operator()(const CannotAssignToNever& e) const std::string operator()(const CannotAssignToNever& e) const
{ {
std::string result = "Cannot assign a value of type " + toString(e.rhsType) + " to a field of type never"; std::string result = "Cannot assign a value of type " + toString(e.rhsType) + " to a field of type never";
@ -1149,7 +1118,7 @@ bool TypePackMismatch::operator==(const TypePackMismatch& rhs) const
return *wantedTp == *rhs.wantedTp && *givenTp == *rhs.givenTp; return *wantedTp == *rhs.wantedTp && *givenTp == *rhs.givenTp;
} }
bool DynamicPropertyLookupOnExternTypesUnsafe::operator==(const DynamicPropertyLookupOnExternTypesUnsafe& rhs) const bool DynamicPropertyLookupOnClassesUnsafe::operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const
{ {
return ty == rhs.ty; return ty == rhs.ty;
} }
@ -1206,16 +1175,6 @@ bool UnexpectedTypePackInSubtyping::operator==(const UnexpectedTypePackInSubtypi
return tp == rhs.tp; return tp == rhs.tp;
} }
bool UserDefinedTypeFunctionError::operator==(const UserDefinedTypeFunctionError& rhs) const
{
return message == rhs.message;
}
bool ReservedIdentifier::operator==(const ReservedIdentifier& rhs) const
{
return name == rhs.name;
}
bool CannotAssignToNever::operator==(const CannotAssignToNever& rhs) const bool CannotAssignToNever::operator==(const CannotAssignToNever& rhs) const
{ {
if (cause.size() != rhs.cause.size()) if (cause.size() != rhs.cause.size())
@ -1391,7 +1350,7 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
e.wantedTp = clone(e.wantedTp); e.wantedTp = clone(e.wantedTp);
e.givenTp = clone(e.givenTp); e.givenTp = clone(e.givenTp);
} }
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnExternTypesUnsafe>) else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
e.ty = clone(e.ty); e.ty = clone(e.ty);
else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>) else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>)
e.ty = clone(e.ty); e.ty = clone(e.ty);
@ -1425,9 +1384,6 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
e.ty = clone(e.ty); e.ty = clone(e.ty);
else if constexpr (std::is_same_v<T, UnexpectedTypePackInSubtyping>) else if constexpr (std::is_same_v<T, UnexpectedTypePackInSubtyping>)
e.tp = clone(e.tp); e.tp = clone(e.tp);
else if constexpr (std::is_same_v<T, UserDefinedTypeFunctionError>)
{
}
else if constexpr (std::is_same_v<T, CannotAssignToNever>) else if constexpr (std::is_same_v<T, CannotAssignToNever>)
{ {
e.rhsType = clone(e.rhsType); e.rhsType = clone(e.rhsType);
@ -1435,9 +1391,6 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
for (auto& ty : e.cause) for (auto& ty : e.cause)
ty = clone(ty); ty = clone(ty);
} }
else if constexpr (std::is_same_v<T, ReservedIdentifier>)
{
}
else else
static_assert(always_false_v<T>, "Non-exhaustive type switch"); static_assert(always_false_v<T>, "Non-exhaustive type switch");
} }

View file

@ -1,160 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/FileResolver.h"
#include "Luau/Common.h"
#include "Luau/StringUtils.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <string_view>
#include <utility>
namespace Luau
{
static std::optional<RequireSuggestions> processRequireSuggestions(std::optional<RequireSuggestions> suggestions)
{
if (!suggestions)
return suggestions;
for (RequireSuggestion& suggestion : *suggestions)
{
suggestion.fullPath = escape(suggestion.fullPath);
}
return suggestions;
}
static RequireSuggestions makeSuggestionsFromAliases(std::vector<RequireAlias> aliases)
{
RequireSuggestions result;
for (RequireAlias& alias : aliases)
{
RequireSuggestion suggestion;
suggestion.label = "@" + std::move(alias.alias);
suggestion.fullPath = suggestion.label;
suggestion.tags = std::move(alias.tags);
result.push_back(std::move(suggestion));
}
return result;
}
static RequireSuggestions makeSuggestionsForFirstComponent(std::unique_ptr<RequireNode> node)
{
RequireSuggestions result = makeSuggestionsFromAliases(node->getAvailableAliases());
result.push_back(RequireSuggestion{"./", "./", {}});
result.push_back(RequireSuggestion{"../", "../", {}});
return result;
}
static RequireSuggestions makeSuggestionsFromNode(std::unique_ptr<RequireNode> node, const std::string_view path, bool isPartialPath)
{
LUAU_ASSERT(!path.empty());
RequireSuggestions result;
const size_t lastSlashInPath = path.find_last_of('/');
if (lastSlashInPath != std::string_view::npos)
{
// Add a suggestion for the parent directory
RequireSuggestion parentSuggestion;
parentSuggestion.label = "..";
// TODO: after exposing require-by-string's path normalization API, this
// if-else can be replaced. Instead, we can simply normalize the result
// of inserting ".." at the end of the current path.
if (lastSlashInPath >= 2 && path.substr(lastSlashInPath - 2, 3) == "../")
{
parentSuggestion.fullPath = path.substr(0, lastSlashInPath + 1);
parentSuggestion.fullPath += "..";
}
else
{
parentSuggestion.fullPath = path.substr(0, lastSlashInPath);
}
result.push_back(std::move(parentSuggestion));
}
std::string fullPathPrefix;
if (isPartialPath)
{
// ./path/to/chi -> ./path/to/
fullPathPrefix += path.substr(0, lastSlashInPath + 1);
}
else
{
if (path.back() == '/')
{
// ./path/to/ -> ./path/to/
fullPathPrefix += path;
}
else
{
// ./path/to -> ./path/to/
fullPathPrefix += path;
fullPathPrefix += "/";
}
}
for (const std::unique_ptr<RequireNode>& child : node->getChildren())
{
if (!child)
continue;
std::string pathComponent = child->getPathComponent();
// If path component contains a slash, it cannot be required by string.
// There's no point suggesting it.
if (pathComponent.find('/') != std::string::npos)
continue;
RequireSuggestion suggestion;
suggestion.label = isPartialPath || path.back() == '/' ? child->getLabel() : "/" + child->getLabel();
suggestion.fullPath = fullPathPrefix + std::move(pathComponent);
suggestion.tags = child->getTags();
result.push_back(std::move(suggestion));
}
return result;
}
std::optional<RequireSuggestions> RequireSuggester::getRequireSuggestionsImpl(const ModuleName& requirer, const std::optional<std::string>& path)
const
{
if (!path)
return std::nullopt;
std::unique_ptr<RequireNode> requirerNode = getNode(requirer);
if (!requirerNode)
return std::nullopt;
const size_t slashPos = path->find_last_of('/');
if (slashPos == std::string::npos)
return makeSuggestionsForFirstComponent(std::move(requirerNode));
// If path already points at a Node, return the Node's children as paths.
if (std::unique_ptr<RequireNode> node = requirerNode->resolvePathToNode(*path))
return makeSuggestionsFromNode(std::move(node), *path, /* isPartialPath = */ false);
// Otherwise, recover a partial path and use this to generate suggestions.
if (std::unique_ptr<RequireNode> partialNode = requirerNode->resolvePathToNode(path->substr(0, slashPos)))
return makeSuggestionsFromNode(std::move(partialNode), *path, /* isPartialPath = */ true);
return std::nullopt;
}
std::optional<RequireSuggestions> RequireSuggester::getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& path) const
{
return processRequireSuggestions(getRequireSuggestionsImpl(requirer, path));
}
std::optional<RequireSuggestions> FileResolver::getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& path) const
{
return requireSuggester ? requireSuggester->getRequireSuggestions(requirer, path) : std::nullopt;
}
} // namespace Luau

File diff suppressed because it is too large Load diff

View file

@ -1,6 +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/Frontend.h" #include "Luau/Frontend.h"
#include "Luau/AnyTypeSummary.h"
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Clone.h" #include "Luau/Clone.h"
#include "Luau/Common.h" #include "Luau/Common.h"
@ -9,10 +10,8 @@
#include "Luau/ConstraintSolver.h" #include "Luau/ConstraintSolver.h"
#include "Luau/DataFlowGraph.h" #include "Luau/DataFlowGraph.h"
#include "Luau/DcrLogger.h" #include "Luau/DcrLogger.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/FileResolver.h" #include "Luau/FileResolver.h"
#include "Luau/NonStrictTypeChecker.h" #include "Luau/NonStrictTypeChecker.h"
#include "Luau/NotNull.h"
#include "Luau/Parser.h" #include "Luau/Parser.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/StringUtils.h" #include "Luau/StringUtils.h"
@ -37,17 +36,18 @@ LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
LUAU_FASTFLAGVARIABLE(LuauStoreCommentsForDefinitionFiles, false)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile, false)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson) LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes, false)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile) LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode, false)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode, false)
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode) LUAU_FASTFLAGVARIABLE(LuauSourceModuleUpdatedWithSelectedMode, false)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete) LUAU_FASTFLAG(StudioReportLuauAny2)
namespace Luau namespace Luau
{ {
@ -77,20 +77,6 @@ struct BuildQueueItem
Frontend::Stats stats; Frontend::Stats stats;
}; };
struct BuildQueueWorkState
{
std::function<void(std::function<void()> task)> executeTask;
std::vector<BuildQueueItem> buildQueueItems;
std::mutex mtx;
std::condition_variable cv;
std::vector<size_t> readyQueueItems;
size_t processing = 0;
size_t remaining = 0;
};
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments) std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments)
{ {
for (const HotComment& hc : hotcomments) for (const HotComment& hc : hotcomments)
@ -129,9 +115,9 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
prop.documentationSymbol = rootName + "." + name; prop.documentationSymbol = rootName + "." + name;
} }
} }
else if (ExternType* etv = getMutable<ExternType>(ty)) else if (ClassType* ctv = getMutable<ClassType>(ty))
{ {
for (auto& [name, prop] : etv->props) for (auto& [name, prop] : ctv->props)
{ {
prop.documentationSymbol = rootName + "." + name; prop.documentationSymbol = rootName + "." + name;
} }
@ -148,7 +134,7 @@ static ParseResult parseSourceForModule(std::string_view source, Luau::SourceMod
sourceModule.root = parseResult.root; sourceModule.root = parseResult.root;
sourceModule.mode = Mode::Definition; sourceModule.mode = Mode::Definition;
if (options.captureComments) if (FFlag::LuauStoreCommentsForDefinitionFiles && options.captureComments)
{ {
sourceModule.hotcomments = parseResult.hotcomments; sourceModule.hotcomments = parseResult.hotcomments;
sourceModule.commentLocations = parseResult.commentLocations; sourceModule.commentLocations = parseResult.commentLocations;
@ -219,6 +205,72 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(
return LoadDefinitionFileResult{true, parseResult, sourceModule, checkedModule}; return LoadDefinitionFileResult{true, parseResult, sourceModule, checkedModule};
} }
std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr)
{
const AstExprIndexName* indexName = pathExpr.as<AstExprIndexName>();
if (!indexName)
return {};
std::vector<std::string_view> segments{indexName->index.value};
while (true)
{
if (AstExprIndexName* in = indexName->expr->as<AstExprIndexName>())
{
segments.push_back(in->index.value);
indexName = in;
continue;
}
else if (AstExprGlobal* indexNameAsGlobal = indexName->expr->as<AstExprGlobal>())
{
segments.push_back(indexNameAsGlobal->name.value);
break;
}
else if (AstExprLocal* indexNameAsLocal = indexName->expr->as<AstExprLocal>())
{
segments.push_back(indexNameAsLocal->local->name.value);
break;
}
else
return {};
}
std::reverse(segments.begin(), segments.end());
return segments;
}
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& segments)
{
if (segments.empty())
return std::nullopt;
std::vector<std::string_view> result;
auto it = segments.begin();
if (*it == "script" && !currentModuleName.empty())
{
result = split(currentModuleName, '/');
++it;
}
for (; it != segments.end(); ++it)
{
if (result.size() > 1 && *it == "Parent")
result.pop_back();
else
result.push_back(*it);
}
return join(result, "/");
}
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& pathExpr)
{
std::vector<std::string_view> segments = parsePathExpr(pathExpr);
return pathExprToModuleName(currentModuleName, segments);
}
namespace namespace
{ {
@ -299,7 +351,8 @@ static void filterLintOptions(LintOptions& lintOptions, const std::vector<HotCom
std::vector<RequireCycle> getRequireCycles( std::vector<RequireCycle> getRequireCycles(
const FileResolver* resolver, const FileResolver* resolver,
const std::unordered_map<ModuleName, std::shared_ptr<SourceNode>>& sourceNodes, const std::unordered_map<ModuleName, std::shared_ptr<SourceNode>>& sourceNodes,
const SourceNode* start const SourceNode* start,
bool stopAtFirst = false
) )
{ {
std::vector<RequireCycle> result; std::vector<RequireCycle> result;
@ -369,6 +422,9 @@ std::vector<RequireCycle> getRequireCycles(
{ {
result.push_back({depLocation, std::move(cycle)}); result.push_back({depLocation, std::move(cycle)});
if (stopAtFirst)
return result;
// note: if we didn't find a cycle, all nodes that we've seen don't depend [transitively] on start // note: if we didn't find a cycle, all nodes that we've seen don't depend [transitively] on start
// so it's safe to *only* clear seen vector when we find a cycle // so it's safe to *only* clear seen vector when we find a cycle
// if we don't do it, we will not have correct reporting for some cycles // if we don't do it, we will not have correct reporting for some cycles
@ -455,6 +511,20 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
if (item.name == name) if (item.name == name)
checkResult.lintResult = item.module->lintResult; checkResult.lintResult = item.module->lintResult;
if (FFlag::StudioReportLuauAny2 && item.options.retainFullTypeGraphs)
{
if (item.module)
{
const SourceModule& sourceModule = *item.sourceModule;
if (sourceModule.mode == Luau::Mode::Strict)
{
item.module->ats.root = toString(sourceModule.root);
}
item.module->ats.rootSrc = sourceModule.root;
item.module->ats.traverse(item.module.get(), sourceModule.root, NotNull{&builtinTypes_});
}
}
} }
return checkResult; return checkResult;
@ -485,8 +555,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
std::swap(currModuleQueue, moduleQueue); std::swap(currModuleQueue, moduleQueue);
DenseHashSet<Luau::ModuleName> seen{{}}; DenseHashSet<Luau::ModuleName> seen{{}};
std::vector<BuildQueueItem> buildQueueItems;
std::shared_ptr<BuildQueueWorkState> state = std::make_shared<BuildQueueWorkState>();
for (const ModuleName& name : currModuleQueue) for (const ModuleName& name : currModuleQueue)
{ {
@ -510,18 +579,18 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
} }
); );
addBuildQueueItems(state->buildQueueItems, queue, cycleDetected, seen, frontendOptions); addBuildQueueItems(buildQueueItems, queue, cycleDetected, seen, frontendOptions);
} }
if (state->buildQueueItems.empty()) if (buildQueueItems.empty())
return {}; return {};
// We need a mapping from modules to build queue slots // We need a mapping from modules to build queue slots
std::unordered_map<ModuleName, size_t> moduleNameToQueue; std::unordered_map<ModuleName, size_t> moduleNameToQueue;
for (size_t i = 0; i < state->buildQueueItems.size(); i++) for (size_t i = 0; i < buildQueueItems.size(); i++)
{ {
BuildQueueItem& item = state->buildQueueItems[i]; BuildQueueItem& item = buildQueueItems[i];
moduleNameToQueue[item.name] = i; moduleNameToQueue[item.name] = i;
} }
@ -534,13 +603,67 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
}; };
} }
state->executeTask = executeTask; std::mutex mtx;
state->remaining = state->buildQueueItems.size(); std::condition_variable cv;
std::vector<size_t> readyQueueItems;
// Record dependencies between modules size_t processing = 0;
for (size_t i = 0; i < state->buildQueueItems.size(); i++) size_t remaining = buildQueueItems.size();
auto itemTask = [&](size_t i)
{ {
BuildQueueItem& item = state->buildQueueItems[i]; BuildQueueItem& item = buildQueueItems[i];
try
{
checkBuildQueueItem(item);
}
catch (...)
{
item.exception = std::current_exception();
}
{
std::unique_lock guard(mtx);
readyQueueItems.push_back(i);
}
cv.notify_one();
};
auto sendItemTask = [&](size_t i)
{
BuildQueueItem& item = buildQueueItems[i];
item.processing = true;
processing++;
executeTask(
[&itemTask, i]()
{
itemTask(i);
}
);
};
auto sendCycleItemTask = [&]
{
for (size_t i = 0; i < buildQueueItems.size(); i++)
{
BuildQueueItem& item = buildQueueItems[i];
if (!item.processing)
{
sendItemTask(i);
break;
}
}
};
// In a first pass, check modules that have no dependencies and record info of those modules that wait
for (size_t i = 0; i < buildQueueItems.size(); i++)
{
BuildQueueItem& item = buildQueueItems[i];
for (const ModuleName& dep : item.sourceNode->requireSet) for (const ModuleName& dep : item.sourceNode->requireSet)
{ {
@ -550,45 +673,41 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
{ {
item.dirtyDependencies++; item.dirtyDependencies++;
state->buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i); buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i);
} }
} }
} }
if (item.dirtyDependencies == 0)
sendItemTask(i);
} }
// In the first pass, check all modules with no pending dependencies // Not a single item was found, a cycle in the graph was hit
for (size_t i = 0; i < state->buildQueueItems.size(); i++) if (processing == 0)
{ sendCycleItemTask();
if (state->buildQueueItems[i].dirtyDependencies == 0)
sendQueueItemTask(state, i);
}
// If not a single item was found, a cycle in the graph was hit
if (state->processing == 0)
sendQueueCycleItemTask(state);
std::vector<size_t> nextItems; std::vector<size_t> nextItems;
std::optional<size_t> itemWithException; std::optional<size_t> itemWithException;
bool cancelled = false; bool cancelled = false;
while (state->remaining != 0) while (remaining != 0)
{ {
{ {
std::unique_lock guard(state->mtx); std::unique_lock guard(mtx);
// If nothing is ready yet, wait // If nothing is ready yet, wait
state->cv.wait( cv.wait(
guard, guard,
[state] [&readyQueueItems]
{ {
return !state->readyQueueItems.empty(); return !readyQueueItems.empty();
} }
); );
// Handle checked items // Handle checked items
for (size_t i : state->readyQueueItems) for (size_t i : readyQueueItems)
{ {
const BuildQueueItem& item = state->buildQueueItems[i]; const BuildQueueItem& item = buildQueueItems[i];
// If exception was thrown, stop adding new items and wait for processing items to complete // If exception was thrown, stop adding new items and wait for processing items to complete
if (item.exception) if (item.exception)
@ -605,7 +724,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
// Notify items that were waiting for this dependency // Notify items that were waiting for this dependency
for (size_t reverseDep : item.reverseDeps) for (size_t reverseDep : item.reverseDeps)
{ {
BuildQueueItem& reverseDepItem = state->buildQueueItems[reverseDep]; BuildQueueItem& reverseDepItem = buildQueueItems[reverseDep];
LUAU_ASSERT(reverseDepItem.dirtyDependencies != 0); LUAU_ASSERT(reverseDepItem.dirtyDependencies != 0);
reverseDepItem.dirtyDependencies--; reverseDepItem.dirtyDependencies--;
@ -616,26 +735,26 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
} }
} }
LUAU_ASSERT(state->processing >= state->readyQueueItems.size()); LUAU_ASSERT(processing >= readyQueueItems.size());
state->processing -= state->readyQueueItems.size(); processing -= readyQueueItems.size();
LUAU_ASSERT(state->remaining >= state->readyQueueItems.size()); LUAU_ASSERT(remaining >= readyQueueItems.size());
state->remaining -= state->readyQueueItems.size(); remaining -= readyQueueItems.size();
state->readyQueueItems.clear(); readyQueueItems.clear();
} }
if (progress) if (progress)
{ {
if (!progress(state->buildQueueItems.size() - state->remaining, state->buildQueueItems.size())) if (!progress(buildQueueItems.size() - remaining, buildQueueItems.size()))
cancelled = true; cancelled = true;
} }
// Items cannot be submitted while holding the lock // Items cannot be submitted while holding the lock
for (size_t i : nextItems) for (size_t i : nextItems)
sendQueueItemTask(state, i); sendItemTask(i);
nextItems.clear(); nextItems.clear();
if (state->processing == 0) if (processing == 0)
{ {
// Typechecking might have been cancelled by user, don't return partial results // Typechecking might have been cancelled by user, don't return partial results
if (cancelled) if (cancelled)
@ -643,19 +762,19 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
// We might have stopped because of a pending exception // We might have stopped because of a pending exception
if (itemWithException) if (itemWithException)
recordItemResult(state->buildQueueItems[*itemWithException]); recordItemResult(buildQueueItems[*itemWithException]);
} }
// If we aren't done, but don't have anything processing, we hit a cycle // If we aren't done, but don't have anything processing, we hit a cycle
if (state->remaining != 0 && state->processing == 0) if (remaining != 0 && processing == 0)
sendQueueCycleItemTask(state); sendCycleItemTask();
} }
std::vector<ModuleName> checkedModules; std::vector<ModuleName> checkedModules;
checkedModules.reserve(state->buildQueueItems.size()); checkedModules.reserve(buildQueueItems.size());
for (size_t i = 0; i < state->buildQueueItems.size(); i++) for (size_t i = 0; i < buildQueueItems.size(); i++)
checkedModules.push_back(std::move(state->buildQueueItems[i].name)); checkedModules.push_back(std::move(buildQueueItems[i].name));
return checkedModules; return checkedModules;
} }
@ -693,32 +812,6 @@ std::optional<CheckResult> Frontend::getCheckResult(const ModuleName& name, bool
return checkResult; return checkResult;
} }
std::vector<ModuleName> Frontend::getRequiredScripts(const ModuleName& name)
{
RequireTraceResult require = requireTrace[name];
if (isDirty(name))
{
std::optional<SourceCode> source = fileResolver->readSource(name);
if (!source)
{
return {};
}
const Config& config = configResolver->getConfig(name);
ParseOptions opts = config.parseOptions;
opts.captureComments = true;
SourceModule result = parse(name, source->source, opts);
result.type = source->type;
require = traceRequires(fileResolver, result.root, name);
}
std::vector<std::string> requiredModuleNames;
requiredModuleNames.reserve(require.requireList.size());
for (const auto& [moduleName, _] : require.requireList)
{
requiredModuleNames.push_back(moduleName);
}
return requiredModuleNames;
}
bool Frontend::parseGraph( bool Frontend::parseGraph(
std::vector<ModuleName>& buildQueue, std::vector<ModuleName>& buildQueue,
const ModuleName& root, const ModuleName& root,
@ -767,13 +860,6 @@ bool Frontend::parseGraph(
topseen = Permanent; topseen = Permanent;
buildQueue.push_back(top->name); buildQueue.push_back(top->name);
// at this point we know all valid dependencies are processed into SourceNodes
for (const ModuleName& dep : top->requireSet)
{
if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
it->second->dependents.insert(top->name);
}
} }
else else
{ {
@ -862,11 +948,14 @@ void Frontend::addBuildQueueItems(
data.environmentScope = getModuleEnvironment(*sourceModule, data.config, frontendOptions.forAutocomplete); data.environmentScope = getModuleEnvironment(*sourceModule, data.config, frontendOptions.forAutocomplete);
data.recordJsonLog = FFlag::DebugLuauLogSolverToJson; data.recordJsonLog = FFlag::DebugLuauLogSolverToJson;
Mode mode = sourceModule->mode.value_or(data.config.mode);
// in NoCheck mode we only need to compute the value of .cyclic for typeck
// in the future we could replace toposort with an algorithm that can flag cyclic nodes by itself // in the future we could replace toposort with an algorithm that can flag cyclic nodes by itself
// however, for now getRequireCycles isn't expensive in practice on the cases we care about, and long term // however, for now getRequireCycles isn't expensive in practice on the cases we care about, and long term
// all correct programs must be acyclic so this code triggers rarely // all correct programs must be acyclic so this code triggers rarely
if (cycleDetected) if (cycleDetected)
data.requireCycles = getRequireCycles(fileResolver, sourceNodes, sourceNode.get()); data.requireCycles = getRequireCycles(fileResolver, sourceNodes, sourceNode.get(), mode == Mode::NoCheck);
data.options = frontendOptions; data.options = frontendOptions;
@ -898,7 +987,8 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
else else
mode = sourceModule.mode.value_or(config.mode); mode = sourceModule.mode.value_or(config.mode);
item.sourceModule->mode = {mode}; if (FFlag::LuauSourceModuleUpdatedWithSelectedMode)
item.sourceModule->mode = {mode};
ScopePtr environmentScope = item.environmentScope; ScopePtr environmentScope = item.environmentScope;
double timestamp = getTimestamp(); double timestamp = getTimestamp();
const std::vector<RequireCycle>& requireCycles = item.requireCycles; const std::vector<RequireCycle>& requireCycles = item.requireCycles;
@ -951,7 +1041,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
item.stats.timeCheck += duration; item.stats.timeCheck += duration;
item.stats.filesStrict += 1; item.stats.filesStrict += 1;
if (item.options.customModuleCheck) if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck)
item.options.customModuleCheck(sourceModule, *moduleForAutocomplete); item.options.customModuleCheck(sourceModule, *moduleForAutocomplete);
item.module = moduleForAutocomplete; item.module = moduleForAutocomplete;
@ -971,7 +1061,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
item.stats.filesStrict += mode == Mode::Strict; item.stats.filesStrict += mode == Mode::Strict;
item.stats.filesNonstrict += mode == Mode::Nonstrict; item.stats.filesNonstrict += mode == Mode::Nonstrict;
if (item.options.customModuleCheck) if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck)
item.options.customModuleCheck(sourceModule, *module); item.options.customModuleCheck(sourceModule, *module);
if (FFlag::LuauSolverV2 && mode == Mode::NoCheck) if (FFlag::LuauSolverV2 && mode == Mode::NoCheck)
@ -1003,8 +1093,6 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
freeze(module->interfaceTypes); freeze(module->interfaceTypes);
module->internalTypes.clear(); module->internalTypes.clear();
module->defArena.allocator.clear();
module->keyArena.allocator.clear();
module->astTypes.clear(); module->astTypes.clear();
module->astTypePacks.clear(); module->astTypePacks.clear();
@ -1058,35 +1146,17 @@ void Frontend::recordItemResult(const BuildQueueItem& item)
if (item.exception) if (item.exception)
std::rethrow_exception(item.exception); std::rethrow_exception(item.exception);
bool replacedModule = false;
if (item.options.forAutocomplete) if (item.options.forAutocomplete)
{ {
replacedModule = moduleResolverForAutocomplete.setModule(item.name, item.module); moduleResolverForAutocomplete.setModule(item.name, item.module);
item.sourceNode->dirtyModuleForAutocomplete = false; item.sourceNode->dirtyModuleForAutocomplete = false;
} }
else else
{ {
replacedModule = moduleResolver.setModule(item.name, item.module); moduleResolver.setModule(item.name, item.module);
item.sourceNode->dirtyModule = false; item.sourceNode->dirtyModule = false;
} }
if (replacedModule)
{
LUAU_TIMETRACE_SCOPE("Frontend::invalidateDependentModules", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", item.name.c_str());
traverseDependents(
item.name,
[forAutocomplete = item.options.forAutocomplete](SourceNode& sourceNode)
{
bool traverseSubtree = !sourceNode.hasInvalidModuleDependency(forAutocomplete);
sourceNode.setInvalidModuleDependency(true, forAutocomplete);
return traverseSubtree;
}
);
}
item.sourceNode->setInvalidModuleDependency(false, item.options.forAutocomplete);
stats.timeCheck += item.stats.timeCheck; stats.timeCheck += item.stats.timeCheck;
stats.timeLint += item.stats.timeLint; stats.timeLint += item.stats.timeLint;
@ -1094,72 +1164,6 @@ void Frontend::recordItemResult(const BuildQueueItem& item)
stats.filesNonstrict += item.stats.filesNonstrict; stats.filesNonstrict += item.stats.filesNonstrict;
} }
void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos)
{
BuildQueueItem& item = state->buildQueueItems[itemPos];
if (DFFlag::LuauRethrowKnownExceptions)
{
try
{
checkBuildQueueItem(item);
}
catch (const Luau::InternalCompilerError&)
{
item.exception = std::current_exception();
}
}
else
{
try
{
checkBuildQueueItem(item);
}
catch (...)
{
item.exception = std::current_exception();
}
}
{
std::unique_lock guard(state->mtx);
state->readyQueueItems.push_back(itemPos);
}
state->cv.notify_one();
}
void Frontend::sendQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos)
{
BuildQueueItem& item = state->buildQueueItems[itemPos];
LUAU_ASSERT(!item.processing);
item.processing = true;
state->processing++;
state->executeTask(
[this, state, itemPos]()
{
performQueueItemTask(state, itemPos);
}
);
}
void Frontend::sendQueueCycleItemTask(std::shared_ptr<BuildQueueWorkState> state)
{
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
{
BuildQueueItem& item = state->buildQueueItems[i];
if (!item.processing)
{
sendQueueItemTask(state, i);
break;
}
}
}
ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const
{ {
ScopePtr result; ScopePtr result;
@ -1187,12 +1191,6 @@ ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config
return result; return result;
} }
bool Frontend::allModuleDependenciesValid(const ModuleName& name, bool forAutocomplete) const
{
auto it = sourceNodes.find(name);
return it != sourceNodes.end() && !it->second->hasInvalidModuleDependency(forAutocomplete);
}
bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
{ {
auto it = sourceNodes.find(name); auto it = sourceNodes.find(name);
@ -1207,35 +1205,16 @@ bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
*/ */
void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty) void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty)
{ {
LUAU_TIMETRACE_SCOPE("Frontend::markDirty", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
traverseDependents(
name,
[markedDirty](SourceNode& sourceNode)
{
if (markedDirty)
markedDirty->push_back(sourceNode.name);
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
return false;
sourceNode.dirtySourceModule = true;
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
return true;
}
);
}
void Frontend::traverseDependents(const ModuleName& name, std::function<bool(SourceNode&)> processSubtree)
{
LUAU_TIMETRACE_SCOPE("Frontend::traverseDependents", "Frontend");
if (sourceNodes.count(name) == 0) if (sourceNodes.count(name) == 0)
return; return;
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
for (const auto& module : sourceNodes)
{
for (const auto& dep : module.second->requireSet)
reverseDeps[dep].push_back(module.first);
}
std::vector<ModuleName> queue{name}; std::vector<ModuleName> queue{name};
while (!queue.empty()) while (!queue.empty())
@ -1246,10 +1225,22 @@ void Frontend::traverseDependents(const ModuleName& name, std::function<bool(Sou
LUAU_ASSERT(sourceNodes.count(next) > 0); LUAU_ASSERT(sourceNodes.count(next) > 0);
SourceNode& sourceNode = *sourceNodes[next]; SourceNode& sourceNode = *sourceNodes[next];
if (!processSubtree(sourceNode)) if (markedDirty)
markedDirty->push_back(next);
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
continue; continue;
const Set<ModuleName>& dependents = sourceNode.dependents; sourceNode.dirtySourceModule = true;
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
if (0 == reverseDeps.count(next))
continue;
sourceModules.erase(next);
const std::vector<ModuleName>& dependents = reverseDeps[next];
queue.insert(queue.end(), dependents.begin(), dependents.end()); queue.insert(queue.end(), dependents.begin(), dependents.end());
} }
} }
@ -1277,7 +1268,6 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& parentScope, const ScopePtr& parentScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options, FrontendOptions options,
TypeCheckLimits limits, TypeCheckLimits limits,
@ -1294,7 +1284,6 @@ ModulePtr check(
moduleResolver, moduleResolver,
fileResolver, fileResolver,
parentScope, parentScope,
typeFunctionScope,
std::move(prepareModuleScope), std::move(prepareModuleScope),
options, options,
limits, limits,
@ -1305,7 +1294,7 @@ ModulePtr check(
struct InternalTypeFinder : TypeOnceVisitor struct InternalTypeFinder : TypeOnceVisitor
{ {
bool visit(TypeId, const ExternType&) override bool visit(TypeId, const ClassType&) override
{ {
return false; return false;
} }
@ -1356,7 +1345,6 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& parentScope, const ScopePtr& parentScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options, FrontendOptions options,
TypeCheckLimits limits, TypeCheckLimits limits,
@ -1369,15 +1357,11 @@ ModulePtr check(
LUAU_TIMETRACE_ARGUMENT("name", sourceModule.humanReadableName.c_str()); LUAU_TIMETRACE_ARGUMENT("name", sourceModule.humanReadableName.c_str());
ModulePtr result = std::make_shared<Module>(); ModulePtr result = std::make_shared<Module>();
result->checkedInNewSolver = true;
result->name = sourceModule.name; result->name = sourceModule.name;
result->humanReadableName = sourceModule.humanReadableName; result->humanReadableName = sourceModule.humanReadableName;
result->mode = mode; result->mode = mode;
result->internalTypes.owningModule = result.get(); result->internalTypes.owningModule = result.get();
result->interfaceTypes.owningModule = result.get(); result->interfaceTypes.owningModule = result.get();
result->allocator = sourceModule.allocator;
result->names = sourceModule.names;
result->root = sourceModule.root;
iceHandler->moduleName = sourceModule.name; iceHandler->moduleName = sourceModule.name;
@ -1392,87 +1376,47 @@ ModulePtr check(
} }
} }
DataFlowGraph dfg = DataFlowGraphBuilder::build(sourceModule.root, NotNull{&result->defArena}, NotNull{&result->keyArena}, iceHandler); DataFlowGraph dfg = DataFlowGraphBuilder::build(sourceModule.root, iceHandler);
UnifierSharedState unifierState{iceHandler}; UnifierSharedState unifierState{iceHandler};
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit; unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit); unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit);
Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}}; Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}};
SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes);
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
typeFunctionRuntime.allowEvaluation = FFlag::LuauTypeFunResultInAutocomplete || sourceModule.parseErrors.empty();
ConstraintGenerator cg{ ConstraintGenerator cg{
result, result,
NotNull{&normalizer}, NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
moduleResolver, moduleResolver,
builtinTypes, builtinTypes,
iceHandler, iceHandler,
parentScope, parentScope,
typeFunctionScope,
std::move(prepareModuleScope), std::move(prepareModuleScope),
logger.get(), logger.get(),
NotNull{&dfg}, NotNull{&dfg},
requireCycles requireCycles
}; };
// FIXME: Delete this flag when clipping FFlag::DebugLuauGreedyGeneralization. cg.visitModuleRoot(sourceModule.root);
// result->errors = std::move(cg.errors);
// This optional<> only exists so that we can run one constructor when the flag
// is set, and another when it is unset.
std::optional<ConstraintSolver> cs;
if (FFlag::DebugLuauGreedyGeneralization) ConstraintSolver cs{
{ NotNull{&normalizer},
ConstraintSet constraintSet = cg.run(sourceModule.root); NotNull(cg.rootScope),
result->errors = std::move(constraintSet.errors); borrowConstraints(cg.constraints),
result->name,
cs.emplace( moduleResolver,
NotNull{&normalizer}, requireCycles,
NotNull{simplifier.get()}, logger.get(),
NotNull{&typeFunctionRuntime}, limits
result->name, };
moduleResolver,
requireCycles,
logger.get(),
NotNull{&dfg},
limits,
std::move(constraintSet)
);
}
else
{
cg.visitModuleRoot(sourceModule.root);
result->errors = std::move(cg.errors);
cs.emplace(
NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
NotNull(cg.rootScope),
borrowConstraints(cg.constraints),
NotNull{&cg.scopeToFunction},
result->name,
moduleResolver,
requireCycles,
logger.get(),
NotNull{&dfg},
limits
);
}
LUAU_ASSERT(bool(cs));
if (options.randomizeConstraintResolutionSeed) if (options.randomizeConstraintResolutionSeed)
cs->randomize(*options.randomizeConstraintResolutionSeed); cs.randomize(*options.randomizeConstraintResolutionSeed);
try try
{ {
cs->run(); cs.run();
} }
catch (const TimeLimitError&) catch (const TimeLimitError&)
{ {
@ -1492,12 +1436,12 @@ ModulePtr check(
printf("%s\n", output.c_str()); printf("%s\n", output.c_str());
} }
for (TypeError& e : cs->errors) for (TypeError& e : cs.errors)
result->errors.emplace_back(std::move(e)); result->errors.emplace_back(std::move(e));
result->scopes = std::move(cg.scopes); result->scopes = std::move(cg.scopes);
result->type = sourceModule.type; result->type = sourceModule.type;
result->upperBoundContributors = std::move(cs->upperBoundContributors); result->upperBoundContributors = std::move(cs.upperBoundContributors);
if (result->timeout || result->cancelled) if (result->timeout || result->cancelled)
{ {
@ -1517,31 +1461,12 @@ ModulePtr check(
switch (mode) switch (mode)
{ {
case Mode::Nonstrict: case Mode::Nonstrict:
Luau::checkNonStrict( Luau::checkNonStrict(builtinTypes, iceHandler, NotNull{&unifierState}, NotNull{&dfg}, NotNull{&limits}, sourceModule, result.get());
builtinTypes,
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
iceHandler,
NotNull{&unifierState},
NotNull{&dfg},
NotNull{&limits},
sourceModule,
result.get()
);
break; break;
case Mode::Definition: case Mode::Definition:
// fallthrough intentional // fallthrough intentional
case Mode::Strict: case Mode::Strict:
Luau::check( Luau::check(builtinTypes, NotNull{&unifierState}, NotNull{&limits}, logger.get(), sourceModule, result.get());
builtinTypes,
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
NotNull{&unifierState},
NotNull{&limits},
logger.get(),
sourceModule,
result.get()
);
break; break;
case Mode::NoCheck: case Mode::NoCheck:
break; break;
@ -1625,7 +1550,6 @@ ModulePtr Frontend::check(
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver},
NotNull{fileResolver}, NotNull{fileResolver},
environmentScope ? *environmentScope : globals.globalScope, environmentScope ? *environmentScope : globals.globalScope,
globals.globalTypeFunctionScope,
prepareModuleScopeWrap, prepareModuleScopeWrap,
options, options,
typeCheckLimits, typeCheckLimits,
@ -1723,14 +1647,6 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(const ModuleName&
sourceNode->name = sourceModule->name; sourceNode->name = sourceModule->name;
sourceNode->humanReadableName = sourceModule->humanReadableName; sourceNode->humanReadableName = sourceModule->humanReadableName;
// clear all prior dependents. we will re-add them after parsing the rest of the graph
for (const auto& [moduleName, _] : sourceNode->requireLocations)
{
if (auto depIt = sourceNodes.find(moduleName); depIt != sourceNodes.end())
depIt->second->dependents.erase(sourceNode->name);
}
sourceNode->requireSet.clear(); sourceNode->requireSet.clear();
sourceNode->requireLocations.clear(); sourceNode->requireLocations.clear();
sourceNode->dirtySourceModule = false; sourceNode->dirtySourceModule = false;
@ -1852,13 +1768,11 @@ std::string FrontendModuleResolver::getHumanReadableModuleName(const ModuleName&
return frontend->fileResolver->getHumanReadableModuleName(moduleName); return frontend->fileResolver->getHumanReadableModuleName(moduleName);
} }
bool FrontendModuleResolver::setModule(const ModuleName& moduleName, ModulePtr module) void FrontendModuleResolver::setModule(const ModuleName& moduleName, ModulePtr module)
{ {
std::scoped_lock lock(moduleMutex); std::scoped_lock lock(moduleMutex);
bool replaced = modules.count(moduleName) > 0;
modules[moduleName] = std::move(module); modules[moduleName] = std::move(module);
return replaced;
} }
void FrontendModuleResolver::clearModules() void FrontendModuleResolver::clearModules()

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,6 @@ GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
: builtinTypes(builtinTypes) : builtinTypes(builtinTypes)
{ {
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})); globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
globalTypeFunctionScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
globalScope->addBuiltinTypeBinding("any", TypeFun{{}, builtinTypes->anyType}); globalScope->addBuiltinTypeBinding("any", TypeFun{{}, builtinTypes->anyType});
globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, builtinTypes->nilType}); globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, builtinTypes->nilType});

View file

@ -1,169 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/DenseHash.h"
#include "Luau/Polarity.h"
#include "Luau/Scope.h"
#include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
namespace Luau
{
struct InferPolarity : TypeVisitor
{
NotNull<TypeArena> arena;
NotNull<Scope> scope;
DenseHashMap<TypeId, Polarity> types{nullptr};
DenseHashMap<TypePackId, Polarity> packs{nullptr};
Polarity polarity = Polarity::Positive;
explicit InferPolarity(NotNull<TypeArena> arena, NotNull<Scope> scope)
: arena(arena)
, scope(scope)
{
}
void flip()
{
polarity = invert(polarity);
}
bool visit(TypeId ty, const GenericType& gt) override
{
if (ty->owningArena != arena)
return false;
if (subsumes(scope, gt.scope))
types[ty] |= polarity;
return false;
}
bool visit(TypeId ty, const TableType& tt) override
{
if (ty->owningArena != arena)
return false;
const Polarity p = polarity;
for (const auto& [name, prop] : tt.props)
{
if (prop.isShared())
{
polarity = Polarity::Mixed;
traverse(prop.type());
}
else if (prop.isReadOnly())
{
polarity = p;
traverse(*prop.readTy);
}
else if (prop.isWriteOnly())
{
polarity = invert(p);
traverse(*prop.writeTy);
}
else
LUAU_ASSERT(!"Unreachable");
}
if (tt.indexer)
{
polarity = Polarity::Mixed;
traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType);
}
polarity = p;
return false;
}
bool visit(TypeId ty, const FunctionType& ft) override
{
if (ty->owningArena != arena)
return false;
const Polarity p = polarity;
polarity = Polarity::Positive;
// If these types actually occur within the function signature, their
// polarity will be overwritten. If not, we infer that they are phantom
// types.
for (TypeId generic : ft.generics)
{
generic = follow(generic);
const auto gen = get<GenericType>(generic);
if (gen && subsumes(scope, gen->scope))
types[generic] = Polarity::None;
}
for (TypePackId genericPack : ft.genericPacks)
{
genericPack = follow(genericPack);
const auto gen = get<GenericTypePack>(genericPack);
if (gen && subsumes(scope, gen->scope))
packs[genericPack] = Polarity::None;
}
flip();
traverse(ft.argTypes);
flip();
traverse(ft.retTypes);
polarity = p;
return false;
}
bool visit(TypeId, const ExternType&) override
{
return false;
}
bool visit(TypePackId tp, const GenericTypePack& gtp) override
{
packs[tp] |= polarity;
return false;
}
};
template<typename TID>
static void inferGenericPolarities_(NotNull<TypeArena> arena, NotNull<Scope> scope, TID ty)
{
if (!FFlag::LuauNonReentrantGeneralization2)
return;
InferPolarity infer{arena, scope};
infer.traverse(ty);
for (const auto& [ty, polarity] : infer.types)
{
auto gt = getMutable<GenericType>(ty);
LUAU_ASSERT(gt);
gt->polarity = polarity;
}
for (const auto& [tp, polarity] : infer.packs)
{
if (tp->owningArena != arena)
continue;
auto gp = getMutable<GenericTypePack>(tp);
LUAU_ASSERT(gp);
gp->polarity = polarity;
}
}
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypeId ty)
{
inferGenericPolarities_(arena, scope, ty);
}
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypePackId tp)
{
inferGenericPolarities_(arena, scope, tp);
}
} // namespace Luau

View file

@ -49,7 +49,7 @@ bool Instantiation::ignoreChildren(TypeId ty)
{ {
if (log->getMutable<FunctionType>(ty)) if (log->getMutable<FunctionType>(ty))
return true; return true;
else if (get<ExternType>(ty)) else if (get<ClassType>(ty))
return true; return true;
else else
return false; return false;
@ -60,8 +60,10 @@ TypeId Instantiation::clean(TypeId ty)
const FunctionType* ftv = log->getMutable<FunctionType>(ty); const FunctionType* ftv = log->getMutable<FunctionType>(ty);
LUAU_ASSERT(ftv); LUAU_ASSERT(ftv);
FunctionType clone = FunctionType{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf}; FunctionType clone = FunctionType{level, scope, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
clone.magic = ftv->magic; clone.magicFunction = ftv->magicFunction;
clone.dcrMagicFunction = ftv->dcrMagicFunction;
clone.dcrMagicRefinement = ftv->dcrMagicRefinement;
clone.tags = ftv->tags; clone.tags = ftv->tags;
clone.argNames = ftv->argNames; clone.argNames = ftv->argNames;
TypeId result = addType(std::move(clone)); TypeId result = addType(std::move(clone));
@ -119,7 +121,7 @@ bool ReplaceGenerics::ignoreChildren(TypeId ty)
// whenever we quantify, so the vectors overlap if and only if they are equal. // whenever we quantify, so the vectors overlap if and only if they are equal.
return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks); return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks);
} }
else if (get<ExternType>(ty)) else if (get<ClassType>(ty))
return true; return true;
else else
{ {
@ -163,7 +165,7 @@ TypeId ReplaceGenerics::clean(TypeId ty)
} }
else else
{ {
return arena->freshType(builtinTypes, scope, level); return addType(FreeType{scope, level});
} }
} }

View file

@ -6,7 +6,7 @@ namespace Luau
bool Instantiation2::ignoreChildren(TypeId ty) bool Instantiation2::ignoreChildren(TypeId ty)
{ {
if (get<ExternType>(ty)) if (get<ClassType>(ty))
return true; return true;
if (auto ftv = get<FunctionType>(ty)) if (auto ftv = get<FunctionType>(ty))

View file

@ -193,8 +193,8 @@ static void errorToString(std::ostream& stream, const T& err)
stream << "NormalizationTooComplex { }"; stream << "NormalizationTooComplex { }";
else if constexpr (std::is_same_v<T, TypePackMismatch>) else if constexpr (std::is_same_v<T, TypePackMismatch>)
stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }"; stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }";
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnExternTypesUnsafe>) else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
stream << "DynamicPropertyLookupOnExternTypesUnsafe { " << toString(err.ty) << " }"; stream << "DynamicPropertyLookupOnClassesUnsafe { " << toString(err.ty) << " }";
else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>) else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>)
stream << "UninhabitedTypeFunction { " << toString(err.ty) << " }"; stream << "UninhabitedTypeFunction { " << toString(err.ty) << " }";
else if constexpr (std::is_same_v<T, ExplicitFunctionAnnotationRecommended>) else if constexpr (std::is_same_v<T, ExplicitFunctionAnnotationRecommended>)
@ -227,10 +227,6 @@ static void errorToString(std::ostream& stream, const T& err)
stream << "UnexpectedTypeInSubtyping { ty = '" + toString(err.ty) + "' }"; stream << "UnexpectedTypeInSubtyping { ty = '" + toString(err.ty) + "' }";
else if constexpr (std::is_same_v<T, UnexpectedTypePackInSubtyping>) else if constexpr (std::is_same_v<T, UnexpectedTypePackInSubtyping>)
stream << "UnexpectedTypePackInSubtyping { tp = '" + toString(err.tp) + "' }"; stream << "UnexpectedTypePackInSubtyping { tp = '" + toString(err.tp) + "' }";
else if constexpr (std::is_same_v<T, UserDefinedTypeFunctionError>)
stream << "UserDefinedTypeFunctionError { " << err.message << " }";
else if constexpr (std::is_same_v<T, ReservedIdentifier>)
stream << "ReservedIdentifier { " << err.name << " }";
else if constexpr (std::is_same_v<T, CannotAssignToNever>) else if constexpr (std::is_same_v<T, CannotAssignToNever>)
{ {
stream << "CannotAssignToNever { rvalueType = '" << toString(err.rhsType) << "', reason = '" << err.reason << "', cause = { "; stream << "CannotAssignToNever { rvalueType = '" << toString(err.rhsType) << "', reason = '" << err.reason << "', cause = { ";

View file

@ -17,10 +17,8 @@ LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAttribute) LUAU_FASTFLAG(LuauAttribute)
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute) LUAU_FASTFLAG(LuauNativeAttribute)
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute, false)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -909,11 +907,6 @@ private:
return true; return true;
} }
bool visit(AstTypePack* node) override
{
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
bool visit(AstTypeReference* node) override bool visit(AstTypeReference* node) override
{ {
if (!node->prefix) if (!node->prefix)
@ -1976,11 +1969,6 @@ private:
return true; return true;
} }
bool visit(AstTypePack* node) override
{
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
bool visit(AstTypeTable* node) override bool visit(AstTypeTable* node) override
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -2293,57 +2281,6 @@ private:
{ {
} }
bool visit(AstExprLocal* node) override
{
if (FFlag::LuauDeprecatedAttribute)
{
const FunctionType* fty = getFunctionType(node);
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport)
report(node->location, node->local->name.value);
}
return true;
}
bool visit(AstExprGlobal* node) override
{
if (FFlag::LuauDeprecatedAttribute)
{
const FunctionType* fty = getFunctionType(node);
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport)
report(node->location, node->name.value);
}
return true;
}
bool visit(AstStatLocalFunction* node) override
{
if (FFlag::LuauDeprecatedAttribute)
{
check(node->func);
return false;
}
else
return true;
}
bool visit(AstStatFunction* node) override
{
if (FFlag::LuauDeprecatedAttribute)
{
check(node->func);
return false;
}
else
return true;
}
bool visit(AstExprIndexName* node) override bool visit(AstExprIndexName* node) override
{ {
if (std::optional<TypeId> ty = context->getType(node->expr)) if (std::optional<TypeId> ty = context->getType(node->expr))
@ -2383,65 +2320,24 @@ private:
void check(AstExprIndexName* node, TypeId ty) void check(AstExprIndexName* node, TypeId ty)
{ {
if (const ExternType* cty = get<ExternType>(ty)) if (const ClassType* cty = get<ClassType>(ty))
{ {
const Property* prop = lookupExternTypeProp(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); report(node->location, *prop, cty->name.c_str(), node->index.value);
else if (FFlag::LuauDeprecatedAttribute && prop)
{
if (std::optional<TypeId> ty = prop->readTy)
{
const FunctionType* fty = get<FunctionType>(follow(ty));
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport)
{
const char* className = nullptr;
if (AstExprGlobal* global = node->expr->as<AstExprGlobal>())
className = global->name.value;
const char* functionName = node->index.value;
report(node->location, className, functionName);
}
}
}
} }
else if (const TableType* tty = get<TableType>(ty)) else if (const TableType* tty = get<TableType>(ty))
{ {
auto prop = tty->props.find(node->index.value); auto prop = tty->props.find(node->index.value);
if (prop != tty->props.end()) if (prop != tty->props.end() && prop->second.deprecated)
{ {
if (prop->second.deprecated) // strip synthetic typeof() for builtin tables
{ if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
// strip synthetic typeof() for builtin tables report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value);
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')') else
report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value); report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
else
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
}
else if (FFlag::LuauDeprecatedAttribute)
{
if (std::optional<TypeId> ty = prop->second.readTy)
{
const FunctionType* fty = get<FunctionType>(follow(ty));
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport)
{
const char* className = nullptr;
if (AstExprGlobal* global = node->expr->as<AstExprGlobal>())
className = global->name.value;
const char* functionName = node->index.value;
report(node->location, className, functionName);
}
}
}
} }
} }
} }
@ -2460,26 +2356,6 @@ private:
} }
} }
void check(AstExprFunction* func)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(func);
const FunctionType* fty = getFunctionType(func);
bool isDeprecated = fty && fty->isDeprecatedFunction;
// If a function is deprecated, we don't want to flag its recursive uses.
// So we push it on a stack while its body is being analyzed.
// When a deprecated function is used, we check the stack to ensure that we are not inside that function.
if (isDeprecated)
pushScope(fty);
func->visit(this);
if (isDeprecated)
popScope(fty);
}
void report(const Location& location, const Property& prop, const char* container, const char* field) 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()); std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str());
@ -2489,63 +2365,6 @@ private:
else else
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated%s", field, suggestion.c_str()); emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated%s", field, suggestion.c_str());
} }
void report(const Location& location, const char* tableName, const char* functionName)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
if (tableName)
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s.%s' is deprecated", tableName, functionName);
else
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated", functionName);
}
void report(const Location& location, const char* functionName)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Function '%s' is deprecated", functionName);
}
std::vector<const FunctionType*> functionTypeScopeStack;
void pushScope(const FunctionType* fty)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(fty);
functionTypeScopeStack.push_back(fty);
}
void popScope(const FunctionType* fty)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(fty);
LUAU_ASSERT(fty == functionTypeScopeStack.back());
functionTypeScopeStack.pop_back();
}
bool inScope(const FunctionType* fty) const
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(fty);
return std::find(functionTypeScopeStack.begin(), functionTypeScopeStack.end(), fty) != functionTypeScopeStack.end();
}
const FunctionType* getFunctionType(AstExpr* node)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
std::optional<TypeId> ty = context->getType(node);
if (!ty)
return nullptr;
const FunctionType* fty = get<FunctionType>(follow(ty));
return fty;
}
}; };
class LintTableOperations : AstVisitor class LintTableOperations : AstVisitor
@ -3420,6 +3239,7 @@ static void lintComments(LintContext& context, const std::vector<HotComment>& ho
static bool hasNativeCommentDirective(const std::vector<HotComment>& hotcomments) static bool hasNativeCommentDirective(const std::vector<HotComment>& hotcomments)
{ {
LUAU_ASSERT(FFlag::LuauNativeAttribute);
LUAU_ASSERT(FFlag::LintRedundantNativeAttribute); LUAU_ASSERT(FFlag::LintRedundantNativeAttribute);
for (const HotComment& hc : hotcomments) for (const HotComment& hc : hotcomments)
@ -3445,6 +3265,7 @@ struct LintRedundantNativeAttribute : AstVisitor
public: public:
LUAU_NOINLINE static void process(LintContext& context) LUAU_NOINLINE static void process(LintContext& context)
{ {
LUAU_ASSERT(FFlag::LuauNativeAttribute);
LUAU_ASSERT(FFlag::LintRedundantNativeAttribute); LUAU_ASSERT(FFlag::LintRedundantNativeAttribute);
LintRedundantNativeAttribute pass; LintRedundantNativeAttribute pass;
@ -3568,7 +3389,7 @@ std::vector<LintWarning> lint(
if (context.warningEnabled(LintWarning::Code_ComparisonPrecedence)) if (context.warningEnabled(LintWarning::Code_ComparisonPrecedence))
LintComparisonPrecedence::process(context); LintComparisonPrecedence::process(context);
if (FFlag::LintRedundantNativeAttribute && context.warningEnabled(LintWarning::Code_RedundantNativeAttribute)) if (FFlag::LuauNativeAttribute && FFlag::LintRedundantNativeAttribute && context.warningEnabled(LintWarning::Code_RedundantNativeAttribute))
{ {
if (hasNativeCommentDirective(hotcomments)) if (hasNativeCommentDirective(hotcomments))
LintRedundantNativeAttribute::process(context); LintRedundantNativeAttribute::process(context);

View file

@ -15,29 +15,10 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
namespace Luau namespace Luau
{ {
static void defaultLogLuau(std::string_view context, std::string_view input)
{
// The default is to do nothing because we don't want to mess with
// the xml parsing done by the dcr script.
}
Luau::LogLuauProc logLuau = &defaultLogLuau;
void setLogLuau(LogLuauProc ll)
{
logLuau = ll;
}
void resetLogLuauProc()
{
logLuau = &defaultLogLuau;
}
static bool contains(Position pos, Comment comment) static bool contains(Position pos, Comment comment)
{ {
if (comment.location.contains(pos)) if (comment.location.contains(pos))
@ -45,15 +26,13 @@ static bool contains(Position pos, Comment comment)
else if (comment.type == Lexeme::BrokenComment && comment.location.begin <= pos) // Broken comments are broken specifically because they don't else if (comment.type == Lexeme::BrokenComment && comment.location.begin <= pos) // Broken comments are broken specifically because they don't
// have an end // have an end
return true; return true;
// comments actually span the whole line - in incremental mode, we could pass a cursor outside of the current parsed comment range span, but it else if (comment.type == Lexeme::Comment && comment.location.end == pos)
// would still be 'within' the comment So, the cursor must be on the same line and the comment itself must come strictly after the `begin`
else if (comment.type == Lexeme::Comment && comment.location.end.line == pos.line && comment.location.begin <= pos)
return true; return true;
else else
return false; return false;
} }
bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos) static bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
{ {
auto iter = std::lower_bound( auto iter = std::lower_bound(
commentLocations.begin(), commentLocations.begin(),
@ -61,8 +40,6 @@ bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
Comment{Lexeme::Comment, Location{pos, pos}}, Comment{Lexeme::Comment, Location{pos, pos}},
[](const Comment& a, const Comment& b) [](const Comment& a, const Comment& b)
{ {
if (a.type == Lexeme::Comment)
return a.location.end.line < b.location.end.line;
return a.location.end < b.location.end; return a.location.end < b.location.end;
} }
); );
@ -158,26 +135,6 @@ struct ClonePublicInterface : Substitution
else if (TableType* ttv = getMutable<TableType>(result)) else if (TableType* ttv = getMutable<TableType>(result))
{ {
ttv->level = TypeLevel{0, 0}; ttv->level = TypeLevel{0, 0};
if (FFlag::LuauSolverV2)
ttv->scope = nullptr;
}
if (FFlag::LuauSolverV2)
{
if (auto freety = getMutable<FreeType>(result))
{
module->errors.emplace_back(
freety->scope->location,
module->name,
InternalError{"Free type is escaping its module; please report this bug at "
"https://github.com/luau-lang/luau/issues"}
);
result = builtinTypes->errorRecoveryType();
}
else if (auto genericty = getMutable<GenericType>(result))
{
genericty->scope = nullptr;
}
} }
return result; return result;
@ -185,27 +142,7 @@ struct ClonePublicInterface : Substitution
TypePackId clean(TypePackId tp) override TypePackId clean(TypePackId tp) override
{ {
if (FFlag::LuauSolverV2) return clone(tp);
{
auto clonedTp = clone(tp);
if (auto ftp = getMutable<FreeTypePack>(clonedTp))
{
module->errors.emplace_back(
ftp->scope->location,
module->name,
InternalError{"Free type pack is escaping its module; please report this bug at "
"https://github.com/luau-lang/luau/issues"}
);
clonedTp = builtinTypes->errorRecoveryTypePack();
}
else if (auto gtp = getMutable<GenericTypePack>(clonedTp))
gtp->scope = nullptr;
return clonedTp;
}
else
{
return clone(tp);
}
} }
TypeId cloneType(TypeId ty) TypeId cloneType(TypeId ty)
@ -265,10 +202,7 @@ struct ClonePublicInterface : Substitution
TypeId type = cloneType(tf.type); TypeId type = cloneType(tf.type);
if (FFlag::LuauRetainDefinitionAliasLocations) return TypeFun{typeParams, typePackParams, type};
return TypeFun{typeParams, typePackParams, type, tf.definitionLocation};
else
return TypeFun{typeParams, typePackParams, type};
} }
}; };

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -10,17 +10,13 @@
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h" #include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments)
namespace Luau namespace Luau
{ {
OverloadResolver::OverloadResolver( OverloadResolver::OverloadResolver(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<Simplifier> simplifier,
NotNull<Normalizer> normalizer, NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<Scope> scope, NotNull<Scope> scope,
NotNull<InternalErrorReporter> reporter, NotNull<InternalErrorReporter> reporter,
NotNull<TypeCheckLimits> limits, NotNull<TypeCheckLimits> limits,
@ -28,13 +24,11 @@ OverloadResolver::OverloadResolver(
) )
: builtinTypes(builtinTypes) : builtinTypes(builtinTypes)
, arena(arena) , arena(arena)
, simplifier(simplifier)
, normalizer(normalizer) , normalizer(normalizer)
, typeFunctionRuntime(typeFunctionRuntime)
, scope(scope) , scope(scope)
, ice(reporter) , ice(reporter)
, limits(limits) , limits(limits)
, subtyping({builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, ice}) , subtyping({builtinTypes, arena, normalizer, ice})
, callLoc(callLocation) , callLoc(callLocation)
{ {
} }
@ -113,7 +107,6 @@ std::optional<ErrorVec> OverloadResolver::testIsSubtype(const Location& location
case ErrorSuppression::NormalizationFailed: case ErrorSuppression::NormalizationFailed:
errors.emplace_back(location, NormalizationTooComplex{}); errors.emplace_back(location, NormalizationTooComplex{});
// intentionally fallthrough here since we couldn't prove this was error-suppressing // intentionally fallthrough here since we couldn't prove this was error-suppressing
[[fallthrough]];
case ErrorSuppression::DoNotSuppress: case ErrorSuppression::DoNotSuppress:
errors.emplace_back(location, TypeMismatch{superTy, subTy}); errors.emplace_back(location, TypeMismatch{superTy, subTy});
break; break;
@ -143,7 +136,6 @@ std::optional<ErrorVec> OverloadResolver::testIsSubtype(const Location& location
case ErrorSuppression::NormalizationFailed: case ErrorSuppression::NormalizationFailed:
errors.emplace_back(location, NormalizationTooComplex{}); errors.emplace_back(location, NormalizationTooComplex{});
// intentionally fallthrough here since we couldn't prove this was error-suppressing // intentionally fallthrough here since we couldn't prove this was error-suppressing
[[fallthrough]];
case ErrorSuppression::DoNotSuppress: case ErrorSuppression::DoNotSuppress:
errors.emplace_back(location, TypePackMismatch{superTy, subTy}); errors.emplace_back(location, TypePackMismatch{superTy, subTy});
break; break;
@ -205,9 +197,8 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
const std::vector<AstExpr*>* argExprs const std::vector<AstExpr*>* argExprs
) )
{ {
FunctionGraphReductionResult result = reduceTypeFunctions( FunctionGraphReductionResult result =
fnTy, callLoc, TypeFunctionContext{arena, builtinTypes, scope, simplifier, normalizer, typeFunctionRuntime, ice, limits}, /*force=*/true reduceTypeFunctions(fnTy, callLoc, TypeFunctionContext{arena, builtinTypes, scope, normalizer, ice, limits}, /*force=*/true);
);
if (!result.errors.empty()) if (!result.errors.empty())
return {OverloadIsNonviable, result.errors}; return {OverloadIsNonviable, result.errors};
@ -256,32 +247,15 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
} }
// If any of the unsatisfied arguments are not supertypes of // If any of the unsatisfied arguments are not supertypes of
// nil or are `unknown`, then this overload does not match. // nil, then this overload does not match.
for (size_t i = firstUnsatisfiedArgument; i < requiredHead.size(); ++i) for (size_t i = firstUnsatisfiedArgument; i < requiredHead.size(); ++i)
{ {
if (FFlag::LuauArityMismatchOnUndersaturatedUnknownArguments) if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype)
{ {
if (get<UnknownType>(follow(requiredHead[i])) || !subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype) auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
{ TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, isVariadic}};
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
for (auto arg : fn->argTypes)
if (get<UnknownType>(follow(arg)))
minParams += 1;
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, isVariadic}}; return {Analysis::ArityMismatch, {error}};
return {Analysis::ArityMismatch, {error}};
}
}
else
{
if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype)
{
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, isVariadic}};
return {Analysis::ArityMismatch, {error}};
}
} }
} }
@ -327,7 +301,6 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
case ErrorSuppression::NormalizationFailed: case ErrorSuppression::NormalizationFailed:
errors.emplace_back(argLocation, NormalizationTooComplex{}); errors.emplace_back(argLocation, NormalizationTooComplex{});
// intentionally fallthrough here since we couldn't prove this was error-suppressing // intentionally fallthrough here since we couldn't prove this was error-suppressing
[[fallthrough]];
case ErrorSuppression::DoNotSuppress: case ErrorSuppression::DoNotSuppress:
// TODO extract location from the SubtypingResult path and argExprs // TODO extract location from the SubtypingResult path and argExprs
switch (reason.variance) switch (reason.variance)
@ -425,12 +398,10 @@ void OverloadResolver::add(Analysis analysis, TypeId ty, ErrorVec&& errors)
// we wrap calling the overload resolver in a separate function to reduce overall stack pressure in `solveFunctionCall`. // we wrap calling the overload resolver in a separate function to reduce overall stack pressure in `solveFunctionCall`.
// this limits the lifetime of `OverloadResolver`, a large type, to only as long as it is actually needed. // this limits the lifetime of `OverloadResolver`, a large type, to only as long as it is actually needed.
static std::optional<TypeId> selectOverload( std::optional<TypeId> selectOverload(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<Simplifier> simplifier,
NotNull<Normalizer> normalizer, NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<Scope> scope, NotNull<Scope> scope,
NotNull<InternalErrorReporter> iceReporter, NotNull<InternalErrorReporter> iceReporter,
NotNull<TypeCheckLimits> limits, NotNull<TypeCheckLimits> limits,
@ -439,9 +410,8 @@ static std::optional<TypeId> selectOverload(
TypePackId argsPack TypePackId argsPack
) )
{ {
auto resolver = OverloadResolver resolver{builtinTypes, arena, normalizer, scope, iceReporter, limits, location};
std::make_unique<OverloadResolver>(builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, scope, iceReporter, limits, location); auto [status, overload] = resolver.selectOverload(fn, argsPack);
auto [status, overload] = resolver->selectOverload(fn, argsPack);
if (status == OverloadResolver::Analysis::Ok) if (status == OverloadResolver::Analysis::Ok)
return overload; return overload;
@ -455,9 +425,7 @@ static std::optional<TypeId> selectOverload(
SolveResult solveFunctionCall( SolveResult solveFunctionCall(
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
NotNull<Normalizer> normalizer, NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> iceReporter, NotNull<InternalErrorReporter> iceReporter,
NotNull<TypeCheckLimits> limits, NotNull<TypeCheckLimits> limits,
NotNull<Scope> scope, NotNull<Scope> scope,
@ -466,23 +434,22 @@ SolveResult solveFunctionCall(
TypePackId argsPack TypePackId argsPack
) )
{ {
std::optional<TypeId> overloadToUse = std::optional<TypeId> overloadToUse = selectOverload(builtinTypes, arena, normalizer, scope, iceReporter, limits, location, fn, argsPack);
selectOverload(builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, scope, iceReporter, limits, location, fn, argsPack);
if (!overloadToUse) if (!overloadToUse)
return {SolveResult::NoMatchingOverload}; return {SolveResult::NoMatchingOverload};
TypePackId resultPack = arena->freshTypePack(scope); TypePackId resultPack = arena->freshTypePack(scope);
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, argsPack, resultPack}); TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, scope.get(), argsPack, resultPack});
Unifier2 u2{NotNull{arena}, builtinTypes, scope, iceReporter}; Unifier2 u2{NotNull{arena}, builtinTypes, scope, iceReporter};
const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy); const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy);
if (!u2.genericSubstitutions.empty() || !u2.genericPackSubstitutions.empty()) if (!u2.genericSubstitutions.empty() || !u2.genericPackSubstitutions.empty())
{ {
auto instantiation = std::make_unique<Instantiation2>(arena, std::move(u2.genericSubstitutions), std::move(u2.genericPackSubstitutions)); Instantiation2 instantiation{arena, std::move(u2.genericSubstitutions), std::move(u2.genericPackSubstitutions)};
std::optional<TypePackId> subst = instantiation->substitute(resultPack); std::optional<TypePackId> subst = instantiation.substitute(resultPack);
if (!subst) if (!subst)
return {SolveResult::CodeTooComplex}; return {SolveResult::CodeTooComplex};

View file

@ -107,4 +107,134 @@ void quantify(TypeId ty, TypeLevel level)
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end()); ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
} }
struct PureQuantifier : Substitution
{
Scope* scope;
OrderedMap<TypeId, TypeId> insertedGenerics;
OrderedMap<TypePackId, TypePackId> insertedGenericPacks;
bool seenMutableType = false;
bool seenGenericType = false;
PureQuantifier(TypeArena* arena, Scope* scope)
: Substitution(TxnLog::empty(), arena)
, scope(scope)
{
}
bool isDirty(TypeId ty) override
{
LUAU_ASSERT(ty == follow(ty));
if (auto ftv = get<FreeType>(ty))
{
bool result = subsumes(scope, ftv->scope);
seenMutableType |= result;
return result;
}
else if (auto ttv = get<TableType>(ty))
{
if (ttv->state == TableState::Free)
seenMutableType = true;
else if (ttv->state == TableState::Generic)
seenGenericType = true;
return (ttv->state == TableState::Unsealed || ttv->state == TableState::Free) && subsumes(scope, ttv->scope);
}
return false;
}
bool isDirty(TypePackId tp) override
{
if (auto ftp = get<FreeTypePack>(tp))
{
return subsumes(scope, ftp->scope);
}
return false;
}
TypeId clean(TypeId ty) override
{
if (auto ftv = get<FreeType>(ty))
{
TypeId result = arena->addType(GenericType{scope});
insertedGenerics.push(ty, result);
return result;
}
else if (auto ttv = get<TableType>(ty))
{
TypeId result = arena->addType(TableType{});
TableType* resultTable = getMutable<TableType>(result);
LUAU_ASSERT(resultTable);
*resultTable = *ttv;
resultTable->level = TypeLevel{};
resultTable->scope = scope;
if (ttv->state == TableState::Free)
{
resultTable->state = TableState::Generic;
insertedGenerics.push(ty, result);
}
else if (ttv->state == TableState::Unsealed)
resultTable->state = TableState::Sealed;
return result;
}
return ty;
}
TypePackId clean(TypePackId tp) override
{
if (auto ftp = get<FreeTypePack>(tp))
{
TypePackId result = arena->addTypePack(TypePackVar{GenericTypePack{scope}});
insertedGenericPacks.push(tp, result);
return result;
}
return tp;
}
bool ignoreChildren(TypeId ty) override
{
if (get<ClassType>(ty))
return true;
return ty->persistent;
}
bool ignoreChildren(TypePackId ty) override
{
return ty->persistent;
}
};
std::optional<QuantifierResult> quantify(TypeArena* arena, TypeId ty, Scope* scope)
{
PureQuantifier quantifier{arena, scope};
std::optional<TypeId> result = quantifier.substitute(ty);
if (!result)
return std::nullopt;
FunctionType* ftv = getMutable<FunctionType>(*result);
LUAU_ASSERT(ftv);
ftv->scope = scope;
for (auto k : quantifier.insertedGenerics.keys)
{
TypeId g = quantifier.insertedGenerics.pairings[k];
if (get<GenericType>(g))
ftv->generics.push_back(g);
}
for (auto k : quantifier.insertedGenericPacks.keys)
ftv->genericPacks.push_back(quantifier.insertedGenericPacks.pairings[k]);
ftv->hasNoFreeOrGenericTypes = ftv->generics.empty() && ftv->genericPacks.empty() && !quantifier.seenGenericType && !quantifier.seenMutableType;
return std::optional<QuantifierResult>({*result, std::move(quantifier.insertedGenerics), std::move(quantifier.insertedGenericPacks)});
}
} // namespace Luau } // namespace Luau

View file

@ -54,15 +54,7 @@ RefinementId RefinementArena::proposition(const RefinementKey* key, TypeId discr
if (!key) if (!key)
return nullptr; return nullptr;
return NotNull{allocator.allocate(Proposition{key, discriminantTy, false})}; return NotNull{allocator.allocate(Proposition{key, discriminantTy})};
}
RefinementId RefinementArena::implicitProposition(const RefinementKey* key, TypeId discriminantTy)
{
if (!key)
return nullptr;
return NotNull{allocator.allocate(Proposition{key, discriminantTy, true})};
} }
} // namespace Luau } // namespace Luau

View file

@ -4,8 +4,6 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Module.h" #include "Luau/Module.h"
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -67,13 +65,7 @@ struct RequireTracer : AstVisitor
return true; return true;
} }
bool visit(AstTypePack* node) override AstExpr* getDependent(AstExpr* node)
{
// allow resolving require inside `typeof` annotations
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
AstExpr* getDependent_DEPRECATED(AstExpr* node)
{ {
if (AstExprLocal* expr = node->as<AstExprLocal>()) if (AstExprLocal* expr = node->as<AstExprLocal>())
return locals[expr->local]; return locals[expr->local];
@ -86,27 +78,6 @@ struct RequireTracer : AstVisitor
else else
return nullptr; return nullptr;
} }
AstNode* getDependent(AstNode* node)
{
if (AstExprLocal* expr = node->as<AstExprLocal>())
return locals[expr->local];
else if (AstExprIndexName* expr = node->as<AstExprIndexName>())
return expr->expr;
else if (AstExprIndexExpr* expr = node->as<AstExprIndexExpr>())
return expr->expr;
else if (AstExprCall* expr = node->as<AstExprCall>(); expr && expr->self)
return expr->func->as<AstExprIndexName>()->expr;
else if (AstExprGroup* expr = node->as<AstExprGroup>())
return expr->expr;
else if (AstExprTypeAssertion* expr = node->as<AstExprTypeAssertion>())
return expr->annotation;
else if (AstTypeGroup* expr = node->as<AstTypeGroup>())
return expr->type;
else if (AstTypeTypeof* expr = node->as<AstTypeTypeof>())
return expr->expr;
else
return nullptr;
}
void process() void process()
{ {
@ -120,15 +91,13 @@ struct RequireTracer : AstVisitor
// push all dependent expressions to the work stack; note that the vector is modified during traversal // push all dependent expressions to the work stack; note that the vector is modified during traversal
for (size_t i = 0; i < work.size(); ++i) for (size_t i = 0; i < work.size(); ++i)
{ if (AstExpr* dep = getDependent(work[i]))
if (AstNode* dep = getDependent(work[i]))
work.push_back(dep); work.push_back(dep);
}
// resolve all expressions to a module info // resolve all expressions to a module info
for (size_t i = work.size(); i > 0; --i) for (size_t i = work.size(); i > 0; --i)
{ {
AstNode* expr = work[i - 1]; AstExpr* expr = work[i - 1];
// when multiple expressions depend on the same one we push it to work queue multiple times // when multiple expressions depend on the same one we push it to work queue multiple times
if (result.exprs.contains(expr)) if (result.exprs.contains(expr))
@ -136,22 +105,19 @@ struct RequireTracer : AstVisitor
std::optional<ModuleInfo> info; std::optional<ModuleInfo> info;
if (AstNode* dep = getDependent(expr)) if (AstExpr* dep = getDependent(expr))
{ {
const ModuleInfo* context = result.exprs.find(dep); const ModuleInfo* context = result.exprs.find(dep);
if (context && expr->is<AstExprLocal>()) // locals just inherit their dependent context, no resolution required
info = *context; // locals just inherit their dependent context, no resolution required if (expr->is<AstExprLocal>())
else if (context && (expr->is<AstExprGroup>() || expr->is<AstTypeGroup>())) info = context ? std::optional<ModuleInfo>(*context) : std::nullopt;
info = *context; // simple group nodes propagate their value else
else if (context && (expr->is<AstTypeTypeof>() || expr->is<AstExprTypeAssertion>())) info = fileResolver->resolveModule(context, expr);
info = *context; // typeof type annotations will resolve to the typeof content
else if (AstExpr* asExpr = expr->asExpr())
info = fileResolver->resolveModule(context, asExpr);
} }
else if (AstExpr* asExpr = expr->asExpr()) else
{ {
info = fileResolver->resolveModule(&moduleContext, asExpr); info = fileResolver->resolveModule(&moduleContext, expr);
} }
if (info) if (info)
@ -184,7 +150,7 @@ struct RequireTracer : AstVisitor
ModuleName currentModuleName; ModuleName currentModuleName;
DenseHashMap<AstLocal*, AstExpr*> locals; DenseHashMap<AstLocal*, AstExpr*> locals;
std::vector<AstNode*> work; std::vector<AstExpr*> work;
std::vector<AstExprCall*> requireCalls; std::vector<AstExprCall*> requireCalls;
}; };

View file

@ -84,17 +84,6 @@ std::optional<TypeId> Scope::lookupUnrefinedType(DefId def) const
return std::nullopt; return std::nullopt;
} }
std::optional<TypeId> Scope::lookupRValueRefinementType(DefId def) const
{
for (const Scope* current = this; current; current = current->parent.get())
{
if (auto ty = current->rvalueRefinements.find(def))
return *ty;
}
return std::nullopt;
}
std::optional<TypeId> Scope::lookup(DefId def) const std::optional<TypeId> Scope::lookup(DefId def) const
{ {
for (const Scope* current = this; current; current = current->parent.get()) for (const Scope* current = this; current; current = current->parent.get())
@ -192,29 +181,6 @@ std::optional<Binding> Scope::linearSearchForBinding(const std::string& name, bo
return std::nullopt; return std::nullopt;
} }
std::optional<std::pair<Symbol, Binding>> Scope::linearSearchForBindingPair(const std::string& name, bool traverseScopeChain) const
{
const Scope* scope = this;
while (scope)
{
for (auto& [n, binding] : scope->bindings)
{
if (n.local && n.local->name == name.c_str())
return {{n, binding}};
else if (n.global.value && n.global == name.c_str())
return {{n, binding}};
}
scope = scope->parent.get();
if (!traverseScopeChain)
break;
}
return std::nullopt;
}
// Updates the `this` scope with the assignments from the `childScope` including ones that doesn't exist in `this`. // Updates the `this` scope with the assignments from the `childScope` including ones that doesn't exist in `this`.
void Scope::inheritAssignments(const ScopePtr& childScope) void Scope::inheritAssignments(const ScopePtr& childScope)
{ {
@ -245,16 +211,6 @@ void Scope::inheritRefinements(const ScopePtr& childScope)
} }
} }
bool Scope::shouldWarnGlobal(std::string name) const
{
for (const Scope* current = this; current; current = current->parent.get())
{
if (current->globalsToWarn.contains(name))
return true;
}
return false;
}
bool subsumesStrict(Scope* left, Scope* right) bool subsumesStrict(Scope* left, Scope* right)
{ {
while (right) while (right)

View file

@ -2,11 +2,9 @@
#include "Luau/Simplify.h" #include "Luau/Simplify.h"
#include "Luau/Common.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Set.h" #include "Luau/Set.h"
#include "Luau/Type.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypePairHash.h" #include "Luau/TypePairHash.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
@ -15,10 +13,7 @@
LUAU_FASTINT(LuauTypeReductionRecursionLimit) LUAU_FASTINT(LuauTypeReductionRecursionLimit)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8) LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8);
LUAU_FASTFLAGVARIABLE(LuauSimplificationRecheckAssumption)
LUAU_FASTFLAGVARIABLE(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType)
namespace Luau namespace Luau
{ {
@ -34,27 +29,25 @@ struct TypeSimplifier
int recursionDepth = 0; int recursionDepth = 0;
TypeId mkNegation(TypeId ty) const; TypeId mkNegation(TypeId ty);
TypeId intersectFromParts(std::set<TypeId> parts); TypeId intersectFromParts(std::set<TypeId> parts);
TypeId intersectUnionWithType(TypeId left, TypeId right); TypeId intersectUnionWithType(TypeId unionTy, TypeId right);
TypeId intersectUnions(TypeId left, TypeId right); TypeId intersectUnions(TypeId left, TypeId right);
TypeId intersectNegatedUnion(TypeId left, TypeId right); TypeId intersectNegatedUnion(TypeId unionTy, TypeId right);
TypeId intersectTypeWithNegation(TypeId left, TypeId right); TypeId intersectTypeWithNegation(TypeId a, TypeId b);
TypeId intersectNegations(TypeId left, TypeId right); TypeId intersectNegations(TypeId a, TypeId b);
TypeId intersectIntersectionWithType(TypeId left, TypeId right); TypeId intersectIntersectionWithType(TypeId left, TypeId right);
// Attempt to intersect the two types. Does not recurse. Does not handle // Attempt to intersect the two types. Does not recurse. Does not handle
// unions, intersections, or negations. // unions, intersections, or negations.
std::optional<TypeId> basicIntersect(TypeId left, TypeId right); std::optional<TypeId> basicIntersect(TypeId left, TypeId right);
std::optional<TypeId> basicIntersectWithTruthy(TypeId target) const;
std::optional<TypeId> basicIntersectWithFalsy(TypeId target) const;
TypeId intersect(TypeId left, TypeId right); TypeId intersect(TypeId ty, TypeId discriminant);
TypeId union_(TypeId left, TypeId right); TypeId union_(TypeId ty, TypeId discriminant);
TypeId simplify(TypeId ty); TypeId simplify(TypeId ty);
TypeId simplify(TypeId ty, DenseHashSet<TypeId>& seen); TypeId simplify(TypeId ty, DenseHashSet<TypeId>& seen);
@ -148,7 +141,6 @@ Relation combine(Relation a, Relation b)
case Relation::Superset: case Relation::Superset:
return Relation::Intersects; return Relation::Intersects;
} }
break;
case Relation::Coincident: case Relation::Coincident:
switch (b) switch (b)
{ {
@ -163,7 +155,6 @@ Relation combine(Relation a, Relation b)
case Relation::Superset: case Relation::Superset:
return Relation::Intersects; return Relation::Intersects;
} }
break;
case Relation::Superset: case Relation::Superset:
switch (b) switch (b)
{ {
@ -178,7 +169,6 @@ Relation combine(Relation a, Relation b)
case Relation::Superset: case Relation::Superset:
return Relation::Superset; return Relation::Superset;
} }
break;
case Relation::Subset: case Relation::Subset:
switch (b) switch (b)
{ {
@ -193,7 +183,6 @@ Relation combine(Relation a, Relation b)
case Relation::Superset: case Relation::Superset:
return Relation::Intersects; return Relation::Intersects;
} }
break;
case Relation::Intersects: case Relation::Intersects:
switch (b) switch (b)
{ {
@ -208,7 +197,6 @@ Relation combine(Relation a, Relation b)
case Relation::Superset: case Relation::Superset:
return Relation::Intersects; return Relation::Intersects;
} }
break;
} }
LUAU_UNREACHABLE(); LUAU_UNREACHABLE();
@ -318,14 +306,12 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{ {
if (get<AnyType>(right)) if (get<AnyType>(right))
return Relation::Subset; return Relation::Subset;
else if (get<UnknownType>(right))
if (get<UnknownType>(right))
return Relation::Coincident; return Relation::Coincident;
else if (get<ErrorType>(right))
if (get<ErrorType>(right))
return Relation::Disjoint; return Relation::Disjoint;
else
return Relation::Superset; return Relation::Superset;
} }
if (get<UnknownType>(right)) if (get<UnknownType>(right))
@ -335,8 +321,8 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{ {
if (get<AnyType>(right)) if (get<AnyType>(right))
return Relation::Coincident; return Relation::Coincident;
else
return Relation::Superset; return Relation::Superset;
} }
if (get<AnyType>(right)) if (get<AnyType>(right))
@ -360,7 +346,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
// * FunctionType // * FunctionType
// * TableType // * TableType
// * MetatableType // * MetatableType
// * ExternType // * ClassType
// * UnionType // * UnionType
// * IntersectionType // * IntersectionType
// * NegationType // * NegationType
@ -368,33 +354,26 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
if (isTypeVariable(left) || isTypeVariable(right)) if (isTypeVariable(left) || isTypeVariable(right))
return Relation::Intersects; return Relation::Intersects;
if (FFlag::LuauSimplificationTableExternType)
{
// if either type is a type function, we cannot know if they'll be related.
if (get<TypeFunctionInstanceType>(left) || get<TypeFunctionInstanceType>(right))
return Relation::Intersects;
}
if (get<ErrorType>(left)) if (get<ErrorType>(left))
{ {
if (get<ErrorType>(right)) if (get<ErrorType>(right))
return Relation::Coincident; return Relation::Coincident;
else if (get<AnyType>(right)) else if (get<AnyType>(right))
return Relation::Subset; return Relation::Subset;
else
return Relation::Disjoint; return Relation::Disjoint;
} }
else if (get<ErrorType>(right)) if (get<ErrorType>(right))
return flip(relate(right, left, seen)); return flip(relate(right, left, seen));
if (get<NeverType>(left)) if (get<NeverType>(left))
{ {
if (get<NeverType>(right)) if (get<NeverType>(right))
return Relation::Coincident; return Relation::Coincident;
else
return Relation::Subset; return Relation::Subset;
} }
else if (get<NeverType>(right)) if (get<NeverType>(right))
return flip(relate(right, left, seen)); return flip(relate(right, left, seen));
if (auto ut = get<IntersectionType>(left)) if (auto ut = get<IntersectionType>(left))
@ -458,54 +437,52 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{ {
if (lp->type == rp->type) if (lp->type == rp->type)
return Relation::Coincident; return Relation::Coincident;
else
return Relation::Disjoint; return Relation::Disjoint;
} }
if (auto rs = get<SingletonType>(right)) if (auto rs = get<SingletonType>(right))
{ {
if (lp->type == PrimitiveType::String && rs->variant.get_if<StringSingleton>()) if (lp->type == PrimitiveType::String && rs->variant.get_if<StringSingleton>())
return Relation::Superset; return Relation::Superset;
else if (lp->type == PrimitiveType::Boolean && rs->variant.get_if<BooleanSingleton>())
if (lp->type == PrimitiveType::Boolean && rs->variant.get_if<BooleanSingleton>())
return Relation::Superset; return Relation::Superset;
else
return Relation::Disjoint; return Relation::Disjoint;
} }
if (lp->type == PrimitiveType::Function) if (lp->type == PrimitiveType::Function)
{ {
if (get<FunctionType>(right)) if (get<FunctionType>(right))
return Relation::Superset; return Relation::Superset;
else
return Relation::Disjoint; return Relation::Disjoint;
} }
if (lp->type == PrimitiveType::Table) if (lp->type == PrimitiveType::Table)
{ {
if (get<TableType>(right)) if (get<TableType>(right))
return Relation::Superset; return Relation::Superset;
else
return Relation::Disjoint; return Relation::Disjoint;
} }
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ExternType>(right)) if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ClassType>(right))
return Relation::Disjoint; return Relation::Disjoint;
} }
if (auto ls = get<SingletonType>(left)) if (auto ls = get<SingletonType>(left))
{ {
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ExternType>(right)) if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ClassType>(right))
return Relation::Disjoint; return Relation::Disjoint;
if (get<PrimitiveType>(right)) if (get<PrimitiveType>(right))
return flip(relate(right, left, seen)); return flip(relate(right, left, seen));
if (auto rs = get<SingletonType>(right)) if (auto rs = get<SingletonType>(right))
{ {
if (ls->variant == rs->variant) if (ls->variant == rs->variant)
return Relation::Coincident; return Relation::Coincident;
else
return Relation::Disjoint; return Relation::Disjoint;
} }
} }
@ -515,11 +492,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{ {
if (rp->type == PrimitiveType::Function) if (rp->type == PrimitiveType::Function)
return Relation::Subset; return Relation::Subset;
else
return Relation::Disjoint; return Relation::Disjoint;
} }
else
return Relation::Intersects; return Relation::Intersects;
} }
if (auto lt = get<TableType>(left)) if (auto lt = get<TableType>(left))
@ -528,11 +505,10 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{ {
if (rp->type == PrimitiveType::Table) if (rp->type == PrimitiveType::Table)
return Relation::Subset; return Relation::Subset;
else
return Relation::Disjoint; return Relation::Disjoint;
} }
else if (auto rt = get<TableType>(right))
if (auto rt = get<TableType>(right))
{ {
// TODO PROBABLY indexers and metatables. // TODO PROBABLY indexers and metatables.
if (1 == rt->props.size()) if (1 == rt->props.size())
@ -552,58 +528,29 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
*/ */
if (lt->props.size() > 1 && r == Relation::Superset) if (lt->props.size() > 1 && r == Relation::Superset)
return Relation::Intersects; return Relation::Intersects;
else
return r; return r;
} }
else if (1 == lt->props.size())
if (1 == lt->props.size())
return flip(relate(right, left, seen)); return flip(relate(right, left, seen));
else
return Relation::Intersects; return Relation::Intersects;
} }
if (FFlag::LuauSimplificationTableExternType)
{
if (auto re = get<ExternType>(right))
{
Relation overall = Relation::Coincident;
for (auto& [name, prop] : lt->props)
{
if (auto propInExternType = re->props.find(name); propInExternType != re->props.end())
{
Relation propRel = relate(prop.type(), propInExternType->second.type());
if (propRel == Relation::Disjoint)
return Relation::Disjoint;
if (propRel == Relation::Coincident)
continue;
overall = Relation::Intersects;
}
}
return overall;
}
}
// TODO metatables // TODO metatables
return Relation::Disjoint; return Relation::Disjoint;
} }
if (auto ct = get<ExternType>(left)) if (auto ct = get<ClassType>(left))
{ {
if (auto rct = get<ExternType>(right)) if (auto rct = get<ClassType>(right))
{ {
if (isSubclass(ct, rct)) if (isSubclass(ct, rct))
return Relation::Subset; return Relation::Subset;
else if (isSubclass(rct, ct))
if (isSubclass(rct, ct))
return Relation::Superset; return Relation::Superset;
else
return Relation::Disjoint; return Relation::Disjoint;
} }
return Relation::Disjoint; return Relation::Disjoint;
@ -619,7 +566,7 @@ Relation relate(TypeId left, TypeId right)
return relate(left, right, seen); return relate(left, right, seen);
} }
TypeId TypeSimplifier::mkNegation(TypeId ty) const TypeId TypeSimplifier::mkNegation(TypeId ty)
{ {
TypeId result = nullptr; TypeId result = nullptr;
@ -753,9 +700,7 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right)
bool changed = false; bool changed = false;
std::set<TypeId> newParts; std::set<TypeId> newParts;
size_t maxSize = DFInt::LuauSimplificationComplexityLimit; if (leftUnion->options.size() > (size_t)DFInt::LuauSimplificationComplexityLimit)
if (leftUnion->options.size() > maxSize)
return arena->addType(IntersectionType{{left, right}}); return arena->addType(IntersectionType{{left, right}});
for (TypeId part : leftUnion) for (TypeId part : leftUnion)
@ -770,13 +715,6 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right)
} }
newParts.insert(simplified); newParts.insert(simplified);
if (FFlag::LuauSimplificationRecheckAssumption)
{
// Initial combination size check could not predict nested union iteration
if (newParts.size() > maxSize)
return arena->addType(IntersectionType{{left, right}});
}
} }
if (!changed) if (!changed)
@ -817,13 +755,6 @@ TypeId TypeSimplifier::intersectUnions(TypeId left, TypeId right)
continue; continue;
newParts.insert(simplified); newParts.insert(simplified);
if (FFlag::LuauSimplificationRecheckAssumption)
{
// Initial combination size check could not predict nested union iteration
if (newParts.size() > maxSize)
return arena->addType(IntersectionType{{left, right}});
}
} }
} }
@ -902,78 +833,6 @@ TypeId TypeSimplifier::intersectNegatedUnion(TypeId left, TypeId right)
return intersectFromParts(std::move(newParts)); return intersectFromParts(std::move(newParts));
} }
std::optional<TypeId> TypeSimplifier::basicIntersectWithTruthy(TypeId target) const
{
target = follow(target);
if (is<UnknownType>(target))
return builtinTypes->truthyType;
if (is<AnyType>(target))
// any = *error-type* | unknown, so truthy & any = *error-type* | truthy
return arena->addType(UnionType{{builtinTypes->truthyType, builtinTypes->errorType}});
if (is<NeverType, ErrorType>(target))
return target;
if (is<FunctionType, TableType, MetatableType, ExternType>(target))
return target;
if (auto pt = get<PrimitiveType>(target))
{
switch (pt->type)
{
case PrimitiveType::NilType:
return builtinTypes->neverType;
case PrimitiveType::Boolean:
return builtinTypes->trueType;
default:
return target;
}
}
if (auto st = get<SingletonType>(target))
return st->variant == BooleanSingleton{false} ? builtinTypes->neverType : target;
return std::nullopt;
}
std::optional<TypeId> TypeSimplifier::basicIntersectWithFalsy(TypeId target) const
{
target = follow(target);
if (is<NeverType, ErrorType>(target))
return target;
if (is<AnyType>(target))
// any = *error-type* | unknown, so falsy & any = *error-type* | falsy
return arena->addType(UnionType{{builtinTypes->falsyType, builtinTypes->errorType}});
if (is<UnknownType>(target))
return builtinTypes->falsyType;
if (is<FunctionType, TableType, MetatableType, ExternType>(target))
return builtinTypes->neverType;
if (auto pt = get<PrimitiveType>(target))
{
switch (pt->type)
{
case PrimitiveType::NilType:
return builtinTypes->nilType;
case PrimitiveType::Boolean:
return builtinTypes->falseType;
default:
return builtinTypes->neverType;
}
}
if (auto st = get<SingletonType>(target))
return st->variant == BooleanSingleton{false} ? builtinTypes->falseType : builtinTypes->neverType;
return std::nullopt;
}
TypeId TypeSimplifier::intersectTypeWithNegation(TypeId left, TypeId right) TypeId TypeSimplifier::intersectTypeWithNegation(TypeId left, TypeId right)
{ {
const NegationType* leftNegation = get<NegationType>(left); const NegationType* leftNegation = get<NegationType>(left);
@ -1200,9 +1059,6 @@ TypeId TypeSimplifier::intersectIntersectionWithType(TypeId left, TypeId right)
std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right) std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
{ {
left = follow(left);
right = follow(right);
if (get<AnyType>(left) && get<ErrorType>(right)) if (get<AnyType>(left) && get<ErrorType>(right))
return right; return right;
if (get<AnyType>(right) && get<ErrorType>(left)) if (get<AnyType>(right) && get<ErrorType>(left))
@ -1310,25 +1166,6 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
return std::nullopt; return std::nullopt;
} }
if (FFlag::LuauOptimizeFalsyAndTruthyIntersect)
{
if (isTruthyType(left))
if (auto res = basicIntersectWithTruthy(right))
return res;
if (isTruthyType(right))
if (auto res = basicIntersectWithTruthy(left))
return res;
if (isFalsyType(left))
if (auto res = basicIntersectWithFalsy(right))
return res;
if (isFalsyType(right))
if (auto res = basicIntersectWithFalsy(left))
return res;
}
Relation relation = relate(left, right); Relation relation = relate(left, right);
if (left == right || Relation::Coincident == relation) if (left == right || Relation::Coincident == relation)
return left; return left;
@ -1561,6 +1398,8 @@ TypeId TypeSimplifier::simplify(TypeId ty, DenseHashSet<TypeId>& seen)
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right) SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right)
{ {
LUAU_ASSERT(FFlag::LuauSolverV2);
TypeSimplifier s{builtinTypes, arena}; TypeSimplifier s{builtinTypes, arena};
// fprintf(stderr, "Intersect %s and %s ...\n", toString(left).c_str(), toString(right).c_str()); // fprintf(stderr, "Intersect %s and %s ...\n", toString(left).c_str(), toString(right).c_str());
@ -1574,6 +1413,8 @@ SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, std::set<TypeId> parts) SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, std::set<TypeId> parts)
{ {
LUAU_ASSERT(FFlag::LuauSolverV2);
TypeSimplifier s{builtinTypes, arena}; TypeSimplifier s{builtinTypes, arena};
TypeId res = s.intersectFromParts(std::move(parts)); TypeId res = s.intersectFromParts(std::move(parts));
@ -1583,6 +1424,8 @@ SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<
SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right) SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right)
{ {
LUAU_ASSERT(FFlag::LuauSolverV2);
TypeSimplifier s{builtinTypes, arena}; TypeSimplifier s{builtinTypes, arena};
TypeId res = s.union_(left, right); TypeId res = s.union_(left, right);

View file

@ -2,23 +2,22 @@
#include "Luau/Substitution.h" #include "Luau/Substitution.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Clone.h"
#include "Luau/TxnLog.h" #include "Luau/TxnLog.h"
#include "Luau/Type.h"
#include <algorithm> #include <algorithm>
#include <stdexcept>
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256) LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256);
LUAU_FASTFLAG(LuauSyntheticErrors)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
namespace Luau namespace Luau
{ {
static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log) static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysClone)
{ {
auto go = [ty, &dest](auto&& a) auto go = [ty, &dest, alwaysClone](auto&& a)
{ {
using T = std::decay_t<decltype(a)>; using T = std::decay_t<decltype(a)>;
@ -51,33 +50,11 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
LUAU_ASSERT(ty->persistent); LUAU_ASSERT(ty->persistent);
return ty; return ty;
} }
else if constexpr (std::is_same_v<T, NoRefineType>) else if constexpr (std::is_same_v<T, ErrorType>)
{ {
LUAU_ASSERT(ty->persistent); LUAU_ASSERT(ty->persistent);
return ty; return ty;
} }
else if constexpr (std::is_same_v<T, ErrorType>)
{
if (FFlag::LuauSyntheticErrors)
{
LUAU_ASSERT(ty->persistent || a.synthetic);
if (ty->persistent)
return ty;
// While this code intentionally works (and clones) even if `a.synthetic` is `std::nullopt`,
// we still assert above because we consider it a bug to have a non-persistent error type
// without any associated metadata. We should always use the persistent version in such cases.
ErrorType clone = ErrorType{};
clone.synthetic = a.synthetic;
return dest.addType(clone);
}
else
{
LUAU_ASSERT(ty->persistent);
return ty;
}
}
else if constexpr (std::is_same_v<T, UnknownType>) else if constexpr (std::is_same_v<T, UnknownType>)
{ {
LUAU_ASSERT(ty->persistent); LUAU_ASSERT(ty->persistent);
@ -94,15 +71,15 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
return dest.addType(a); return dest.addType(a);
else if constexpr (std::is_same_v<T, FunctionType>) else if constexpr (std::is_same_v<T, FunctionType>)
{ {
FunctionType clone = FunctionType{a.level, a.argTypes, a.retTypes, a.definition, a.hasSelf}; FunctionType clone = FunctionType{a.level, a.scope, a.argTypes, a.retTypes, a.definition, a.hasSelf};
clone.generics = a.generics; clone.generics = a.generics;
clone.genericPacks = a.genericPacks; clone.genericPacks = a.genericPacks;
clone.magic = a.magic; clone.magicFunction = a.magicFunction;
clone.dcrMagicFunction = a.dcrMagicFunction;
clone.dcrMagicRefinement = a.dcrMagicRefinement;
clone.tags = a.tags; clone.tags = a.tags;
clone.argNames = a.argNames; clone.argNames = a.argNames;
clone.isCheckedFunction = a.isCheckedFunction; clone.isCheckedFunction = a.isCheckedFunction;
if (FFlag::LuauDeprecatedAttribute)
clone.isDeprecatedFunction = a.isDeprecatedFunction;
return dest.addType(std::move(clone)); return dest.addType(std::move(clone));
} }
else if constexpr (std::is_same_v<T, TableType>) else if constexpr (std::is_same_v<T, TableType>)
@ -136,16 +113,21 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
clone.parts = a.parts; clone.parts = a.parts;
return dest.addType(std::move(clone)); return dest.addType(std::move(clone));
} }
else if constexpr (std::is_same_v<T, ExternType>) else if constexpr (std::is_same_v<T, ClassType>)
{ {
ExternType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer}; if (alwaysClone)
return dest.addType(std::move(clone)); {
ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer};
return dest.addType(std::move(clone));
}
else
return ty;
} }
else if constexpr (std::is_same_v<T, NegationType>) else if constexpr (std::is_same_v<T, NegationType>)
return dest.addType(NegationType{a.ty}); return dest.addType(NegationType{a.ty});
else if constexpr (std::is_same_v<T, TypeFunctionInstanceType>) else if constexpr (std::is_same_v<T, TypeFunctionInstanceType>)
{ {
TypeFunctionInstanceType clone{a.function, a.typeArguments, a.packArguments, a.userFuncName, a.userFuncData}; TypeFunctionInstanceType clone{a.function, a.typeArguments, a.packArguments, a.userFuncName, a.userFuncBody};
return dest.addType(std::move(clone)); return dest.addType(std::move(clone));
} }
else else
@ -252,21 +234,21 @@ void Tarjan::visitChildren(TypeId ty, int index)
for (TypePackId a : tfit->packArguments) for (TypePackId a : tfit->packArguments)
visitChild(a); visitChild(a);
} }
else if (const ExternType* etv = get<ExternType>(ty)) else if (const ClassType* ctv = get<ClassType>(ty))
{ {
for (const auto& [name, prop] : etv->props) for (const auto& [name, prop] : ctv->props)
visitChild(prop.type()); visitChild(prop.type());
if (etv->parent) if (ctv->parent)
visitChild(*etv->parent); visitChild(*ctv->parent);
if (etv->metatable) if (ctv->metatable)
visitChild(*etv->metatable); visitChild(*ctv->metatable);
if (etv->indexer) if (ctv->indexer)
{ {
visitChild(etv->indexer->indexType); visitChild(ctv->indexer->indexType);
visitChild(etv->indexer->indexResultType); visitChild(ctv->indexer->indexResultType);
} }
} }
else if (const NegationType* ntv = get<NegationType>(ty)) else if (const NegationType* ntv = get<NegationType>(ty))
@ -540,27 +522,6 @@ void Tarjan::visitSCC(int index)
} }
} }
bool Tarjan::ignoreChildren(TypeId ty)
{
return false;
}
bool Tarjan::ignoreChildren(TypePackId ty)
{
return false;
}
// Some subclasses might ignore children visit, but not other actions like replacing the children
bool Tarjan::ignoreChildrenVisit(TypeId ty)
{
return ignoreChildren(ty);
}
bool Tarjan::ignoreChildrenVisit(TypePackId ty)
{
return ignoreChildren(ty);
}
TarjanResult Tarjan::findDirty(TypeId ty) TarjanResult Tarjan::findDirty(TypeId ty)
{ {
return visitRoot(ty); return visitRoot(ty);
@ -571,11 +532,6 @@ TarjanResult Tarjan::findDirty(TypePackId tp)
return visitRoot(tp); return visitRoot(tp);
} }
Substitution::Substitution(TypeArena* arena)
: Substitution(TxnLog::empty(), arena)
{
}
Substitution::Substitution(const TxnLog* log_, TypeArena* arena) Substitution::Substitution(const TxnLog* log_, TypeArena* arena)
: arena(arena) : arena(arena)
{ {
@ -676,7 +632,7 @@ void Substitution::resetState(const TxnLog* log, TypeArena* arena)
TypeId Substitution::clone(TypeId ty) TypeId Substitution::clone(TypeId ty)
{ {
return shallowClone(ty, *arena, log); return shallowClone(ty, *arena, log, /* alwaysClone */ true);
} }
TypePackId Substitution::clone(TypePackId tp) TypePackId Substitution::clone(TypePackId tp)
@ -838,21 +794,21 @@ void Substitution::replaceChildren(TypeId ty)
for (TypePackId& a : tfit->packArguments) for (TypePackId& a : tfit->packArguments)
a = replace(a); a = replace(a);
} }
else if (ExternType* etv = getMutable<ExternType>(ty)) else if (ClassType* ctv = getMutable<ClassType>(ty))
{ {
for (auto& [name, prop] : etv->props) for (auto& [name, prop] : ctv->props)
prop.setType(replace(prop.type())); prop.setType(replace(prop.type()));
if (etv->parent) if (ctv->parent)
etv->parent = replace(*etv->parent); ctv->parent = replace(*ctv->parent);
if (etv->metatable) if (ctv->metatable)
etv->metatable = replace(*etv->metatable); ctv->metatable = replace(*ctv->metatable);
if (etv->indexer) if (ctv->indexer)
{ {
etv->indexer->indexType = replace(etv->indexer->indexType); ctv->indexer->indexType = replace(ctv->indexer->indexType);
etv->indexer->indexResultType = replace(etv->indexer->indexResultType); ctv->indexer->indexResultType = replace(ctv->indexer->indexResultType);
} }
} }
else if (NegationType* ntv = getMutable<NegationType>(ty)) else if (NegationType* ntv = getMutable<NegationType>(ty))
@ -892,13 +848,4 @@ void Substitution::replaceChildren(TypePackId tp)
} }
} }
template<typename Ty>
std::optional<Ty> Substitution::replace(std::optional<Ty> ty)
{
if (ty)
return replace(*ty);
else
return std::nullopt;
}
} // namespace Luau } // namespace Luau

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