Compare commits

..

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

527 changed files with 13314 additions and 46298 deletions

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:

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:

1
.gitignore vendored
View file

@ -13,7 +13,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,90 @@ 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,
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 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

@ -65,12 +65,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);

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>
@ -27,22 +26,13 @@ struct CloneState
* while `clone` will make a deep copy of the entire type and its every component. * while `clone` will make a deep copy of the entire type and its every component.
* *
* Be mindful about which behavior you actually _want_. * 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); TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent = false); TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState);
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
{ {
@ -92,11 +92,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 +109,36 @@ 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. // Needed to register all available type functions for execution at later stages.
NotNull<TypeFunctionRuntime> typeFunctionRuntime; 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<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.
@ -164,26 +149,19 @@ struct ConstraintGenerator
void visitFragmentRoot(const ScopePtr& resumeScope, 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.
@ -272,7 +250,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 +282,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 +291,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);
@ -380,11 +344,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 +353,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 +374,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 +391,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 +405,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 +435,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,8 @@
#pragma once #pragma once
#include "Luau/Constraint.h" #include "Luau/Constraint.h"
#include "Luau/ConstraintSet.h"
#include "Luau/DataFlowGraph.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"
@ -60,37 +58,15 @@ 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; 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;
@ -121,9 +97,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,24 +117,9 @@ 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<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,
@ -186,14 +144,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.
* *
@ -226,7 +179,6 @@ public:
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);
@ -256,7 +208,7 @@ public:
// 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);
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 +216,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 +275,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);
@ -362,7 +313,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 +334,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,10 +384,6 @@ 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;
@ -446,14 +393,8 @@ public:
void throwUserCancelError() const; void throwUserCancelError() const;
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"
@ -38,6 +37,8 @@ struct DataFlowGraph
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 the definition optionally, knowing it may not be present.
std::optional<DefId> getDefOptional(const AstExpr* expr) const; std::optional<DefId> getDefOptional(const AstExpr* expr) const;
// Look up for the rvalue def for a compound assignment.
std::optional<DefId> getRValueDefForCompoundAssign(const AstExpr* expr) const;
DefId getDef(const AstLocal* local) const; DefId getDef(const AstLocal* local) const;
@ -47,13 +48,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,6 +65,10 @@ 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;
}; };
@ -79,6 +84,7 @@ struct DfgScope
DfgScope* parent; DfgScope* parent;
ScopeType scopeType; ScopeType scopeType;
Location location;
using Bindings = DenseHashMap<Symbol, const Def*>; using Bindings = DenseHashMap<Symbol, const Def*>;
using Props = DenseHashMap<const Def*, std::unordered_map<std::string, const Def*>>; using Props = DenseHashMap<const Def*, std::unordered_map<std::string, const Def*>>;
@ -105,22 +111,49 @@ using ScopeStack = std::vector<DfgScope*>;
struct DataFlowGraphBuilder struct DataFlowGraphBuilder
{ {
static DataFlowGraph build( static DataFlowGraph build(AstStatBlock* root, NotNull<struct InternalErrorReporter> handle);
/**
* This method is identical to the build method above, but returns a pair of dfg, scopes as the data flow graph
* here is intended to live on the module between runs of typechecking. Before, the DFG only needed to live as
* long as the typecheck, but in a world with incremental typechecking, we need the information on the dfg to incrementally
* typecheck small fragments of code.
* @param block - pointer to the ast to build the dfg for
* @param handle - for raising internal errors while building the dfg
*/
static std::pair<std::shared_ptr<DataFlowGraph>, std::vector<std::unique_ptr<DfgScope>>> buildShared(
AstStatBlock* block, AstStatBlock* block,
NotNull<DefArena> defArena, NotNull<InternalErrorReporter> handle
NotNull<RefinementKeyArena> keyArena, );
NotNull<struct InternalErrorReporter> handle
/**
* Takes a stale graph along with a list of scopes, a small fragment of the ast, and a cursor position
* and constructs the DataFlowGraph for just that fragment. This method will fabricate defs in the final
* DFG for things that have been referenced and exist in the stale dfg.
* For example, the fragment local z = x + y will populate defs for x and y from the stale graph.
* @param staleGraph - the old DFG
* @param scopes - the old DfgScopes in the graph
* @param fragment - the Ast Fragment to re-build the root for
* @param cursorPos - the current location of the cursor - used to determine which scope we are currently in
* @param handle - for internal compiler errors
*/
static DataFlowGraph updateGraph(
const DataFlowGraph& staleGraph,
const std::vector<std::unique_ptr<DfgScope>>& scopes,
AstStatBlock* fragment,
const Position& cursorPos,
NotNull<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;
@ -129,8 +162,8 @@ private:
/// A stack of scopes used by the visitor to see where we are. /// A stack of scopes used by the visitor to see where we are.
ScopeStack scopeStack; ScopeStack scopeStack;
NotNull<DfgScope> currentScope();
DfgScope* currentScope_DEPRECATED(); DfgScope* currentScope();
struct FunctionCapture struct FunctionCapture
{ {
@ -142,14 +175,14 @@ private:
DenseHashMap<Symbol, FunctionCapture> captures{Symbol{}}; DenseHashMap<Symbol, FunctionCapture> captures{Symbol{}};
void resolveCaptures(); void resolveCaptures();
DfgScope* makeChildScope(DfgScope::ScopeType scopeType = DfgScope::Linear); DfgScope* makeChildScope(Location loc, 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(Symbol symbol);
DefId lookup(DefId def, const std::string& key, Location location); DefId lookup(DefId def, const std::string& key);
ControlFlow visit(AstStatBlock* b); ControlFlow visit(AstStatBlock* b);
ControlFlow visitBlockWithoutChildScope(AstStatBlock* b); ControlFlow visitBlockWithoutChildScope(AstStatBlock* b);
@ -173,7 +206,7 @@ private:
ControlFlow visit(AstStatTypeFunction* f); ControlFlow visit(AstStatTypeFunction* f);
ControlFlow visit(AstStatDeclareGlobal* d); ControlFlow visit(AstStatDeclareGlobal* d);
ControlFlow visit(AstStatDeclareFunction* d); ControlFlow visit(AstStatDeclareFunction* d);
ControlFlow visit(AstStatDeclareExternType* d); ControlFlow visit(AstStatDeclareClass* d);
ControlFlow visit(AstStatError* error); ControlFlow visit(AstStatError* error);
DataFlowResult visitExpr(AstExpr* e); DataFlowResult visitExpr(AstExpr* e);
@ -215,8 +248,8 @@ private:
void visitTypeList(AstTypeList l); void visitTypeList(AstTypeList l);
void visitGenerics(AstArray<AstGenericType*> g); void visitGenerics(AstArray<AstGenericType> g);
void visitGenericPacks(AstArray<AstGenericTypePack*> g); void visitGenericPacks(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
@ -455,13 +455,6 @@ struct UserDefinedTypeFunctionError
bool operator==(const UserDefinedTypeFunctionError& rhs) const; 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 +492,7 @@ using TypeErrorData = Variant<
TypesAreUnrelated, TypesAreUnrelated,
NormalizationTooComplex, NormalizationTooComplex,
TypePackMismatch, TypePackMismatch,
DynamicPropertyLookupOnExternTypesUnsafe, DynamicPropertyLookupOnClassesUnsafe,
UninhabitedTypeFunction, UninhabitedTypeFunction,
UninhabitedTypePackFunction, UninhabitedTypePackFunction,
WhereClauseNeeded, WhereClauseNeeded,
@ -511,8 +504,7 @@ using TypeErrorData = Variant<
UnexpectedTypeInSubtyping, UnexpectedTypeInSubtyping,
UnexpectedTypePackInSubtyping, UnexpectedTypePackInSubtyping,
ExplicitFunctionAnnotationRecommended, ExplicitFunctionAnnotationRecommended,
UserDefinedTypeFunctionError, UserDefinedTypeFunctionError>;
ReservedIdentifier>;
struct TypeErrorSummary struct TypeErrorSummary
{ {

View file

@ -1,9 +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 <optional>
#include <vector> #include <vector>
namespace Luau namespace Luau
@ -20,7 +19,7 @@ struct SourceCode
None, None,
Module, Module,
Script, Script,
Local_DEPRECATED Local
}; };
std::string source; std::string source;
@ -33,71 +32,11 @@ struct ModuleInfo
bool optional = false; bool optional = false;
}; };
struct RequireAlias using RequireSuggestion = std::string;
{
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>; 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;
@ -117,9 +56,10 @@ struct FileResolver
return std::nullopt; return std::nullopt;
} }
std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const; virtual std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const
{
std::shared_ptr<RequireSuggester> requireSuggester; return std::nullopt;
}
}; };
struct NullFileResolver : FileResolver struct NullFileResolver : FileResolver

View file

@ -3,10 +3,9 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Parser.h" #include "Luau/Parser.h"
#include "Luau/AutocompleteTypes.h" #include "Luau/Autocomplete.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/Frontend.h"
#include <memory> #include <memory>
#include <vector> #include <vector>
@ -15,42 +14,12 @@ namespace Luau
{ {
struct FrontendOptions; 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 struct FragmentAutocompleteAncestryResult
{ {
DenseHashMap<AstName, AstLocal*> localMap{AstName()}; DenseHashMap<AstName, AstLocal*> localMap{AstName()};
std::vector<AstLocal*> localStack; std::vector<AstLocal*> localStack;
std::vector<AstNode*> ancestry; std::vector<AstNode*> ancestry;
AstStat* nearestStatement = nullptr; AstStat* nearestStatement = nullptr;
AstStatBlock* parentBlock = nullptr;
Location fragmentSelectionRegion;
}; };
struct FragmentParseResult struct FragmentParseResult
@ -58,139 +27,35 @@ struct FragmentParseResult
std::string fragmentToParse; std::string fragmentToParse;
AstStatBlock* root = nullptr; AstStatBlock* root = nullptr;
std::vector<AstNode*> ancestry; std::vector<AstNode*> ancestry;
AstStat* nearestStatement = nullptr;
std::vector<Comment> commentLocations;
std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>(); std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>();
Position scopePos{0, 0};
}; };
struct FragmentTypeCheckResult struct FragmentTypeCheckResult
{ {
ModulePtr incrementalModule = nullptr; ModulePtr incrementalModule = nullptr;
ScopePtr freshScope; Scope* freshScope = nullptr;
std::vector<AstNode*> ancestry;
}; };
struct FragmentAutocompleteResult FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* root, const Position& cursorPos);
{
ModulePtr incrementalModule;
Scope* freshScope;
TypeArena arenaForAutocomplete_DEPRECATED;
AutocompleteResult acResults;
};
struct FragmentRegion FragmentParseResult parseFragment(const SourceModule& srcModule, std::string_view src, const Position& cursorPos);
{
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); FragmentTypeCheckResult typecheckFragment(
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, Frontend& frontend,
const ModuleName& moduleName, const ModuleName& moduleName,
const Position& cursorPos, const Position& cursorPos,
std::optional<FrontendOptions> opts, std::optional<FrontendOptions> opts,
std::string_view src, std::string_view src
std::optional<Position> fragmentEndPosition,
AstStatBlock* recentParse = nullptr,
IFragmentAutocompleteReporter* reporter = nullptr
); );
FragmentAutocompleteResult fragmentAutocomplete( AutocompleteResult fragmentAutocomplete(
Frontend& frontend, Frontend& frontend,
std::string_view src, std::string_view src,
const ModuleName& moduleName, const ModuleName& moduleName,
Position cursorPosition, Position& cursorPosition,
std::optional<FrontendOptions> opts, std::optional<FrontendOptions> opts,
StringCompletionCallback callback, 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 } // 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
{ {
@ -56,32 +56,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 +117,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 +151,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 +194,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 +226,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 +271,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 +285,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

@ -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,6 +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/AnyTypeSummary.h"
#include "Luau/DataFlowGraph.h" #include "Luau/DataFlowGraph.h"
#include <memory> #include <memory>
@ -18,13 +19,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 +55,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 +68,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
@ -137,11 +132,9 @@ struct Module
TypePackId returnType = nullptr; TypePackId returnType = nullptr;
std::unordered_map<Name, TypeFun> exportedTypeBindings; std::unordered_map<Name, TypeFun> exportedTypeBindings;
// We also need to keep DFG data alive between runs
// Arenas related to the DFG must persist after the DFG no longer exists, as std::shared_ptr<DataFlowGraph> dataFlowGraph = nullptr;
// Module objects maintain raw pointers to objects in these arenas. std::vector<std::unique_ptr<DfgScope>> dfgScopes;
DefArena defArena;
RefinementKeyArena keyArena;
bool hasModuleScope() const; bool hasModuleScope() const;
ScopePtr getModuleScope() const; ScopePtr getModuleScope() const;

View file

@ -1,10 +1,9 @@
// 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
{ {
@ -16,7 +15,6 @@ struct TypeCheckLimits;
void checkNonStrict( void checkNonStrict(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime, NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> ice, NotNull<InternalErrorReporter> ice,
NotNull<UnifierSharedState> unifierState, NotNull<UnifierSharedState> unifierState,

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,8 @@ 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,
NotNull<Scope> scope,
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 +166,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 +177,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 +230,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 +245,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 +318,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;
@ -391,10 +376,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);
@ -423,8 +408,8 @@ 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, SeenTablePropPairs& seenTablePropPairs, Set<TypeId>& seenSet);

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,7 +34,6 @@ 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<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<Scope> scope, NotNull<Scope> scope,
@ -46,7 +44,6 @@ 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<TypeFunctionRuntime> typeFunctionRuntime;
NotNull<Scope> scope; NotNull<Scope> scope;
@ -113,7 +110,6 @@ 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<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> iceReporter, NotNull<InternalErrorReporter> iceReporter,

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;
@ -121,7 +120,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.
@ -135,7 +134,6 @@ 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<TypeFunctionRuntime> typeFunctionRuntime;
NotNull<InternalErrorReporter> iceReporter; NotNull<InternalErrorReporter> iceReporter;
@ -157,7 +155,6 @@ 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<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> iceReporter NotNull<InternalErrorReporter> iceReporter
@ -229,8 +226,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 +256,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,18 @@ 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;
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
) )
: function(function) : function(function)
, typeArguments(typeArguments) , typeArguments(typeArguments)
, packArguments(packArguments) , packArguments(packArguments)
, userFuncName(userFuncName) , userFuncName(userFuncName)
, userFuncData(userFuncData)
{ {
} }
@ -656,13 +640,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.
@ -762,7 +739,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 +752,7 @@ using TypeVariant = Unifiable::Variant<
FunctionType, FunctionType,
TableType, TableType,
MetatableType, MetatableType,
ExternType, ClassType,
AnyType, AnyType,
UnionType, UnionType,
IntersectionType, IntersectionType,
@ -868,9 +845,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 +852,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 +953,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;
@ -998,7 +965,6 @@ public:
const TypeId noRefineType; 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 +985,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,7 +1165,7 @@ 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);

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,8 @@ struct Reasonings
void check( void check(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime, NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<UnifierSharedState> unifierState, NotNull<UnifierSharedState> sharedState,
NotNull<TypeCheckLimits> limits, NotNull<TypeCheckLimits> limits,
DcrLogger* logger, DcrLogger* logger,
const SourceModule& sourceModule, const SourceModule& sourceModule,
@ -86,7 +71,6 @@ void check(
struct TypeChecker2 struct TypeChecker2
{ {
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
NotNull<Simplifier> simplifier;
NotNull<TypeFunctionRuntime> typeFunctionRuntime; NotNull<TypeFunctionRuntime> typeFunctionRuntime;
DcrLogger* logger; DcrLogger* logger;
const NotNull<TypeCheckLimits> limits; const NotNull<TypeCheckLimits> limits;
@ -106,7 +90,6 @@ struct TypeChecker2
TypeChecker2( TypeChecker2(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime, NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<UnifierSharedState> unifierState, NotNull<UnifierSharedState> unifierState,
NotNull<TypeCheckLimits> limits, NotNull<TypeCheckLimits> limits,
@ -129,14 +112,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 +143,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 +156,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 +171,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 +213,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

@ -2,7 +2,6 @@
#pragma once #pragma once
#include "Luau/Constraint.h" #include "Luau/Constraint.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"
@ -42,18 +41,9 @@ struct TypeFunctionRuntime
StateRef state; 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 // Evaluation of type functions should only be performed in the absence of parse errors in the source module
bool allowEvaluation = true; 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: private:
void prepareState(); void prepareState();
}; };
@ -63,7 +53,6 @@ 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<TypeFunctionRuntime> typeFunctionRuntime;
NotNull<InternalErrorReporter> ice; NotNull<InternalErrorReporter> ice;
@ -74,7 +63,7 @@ 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
TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint); TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint);
@ -82,7 +71,6 @@ 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<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> ice, NotNull<InternalErrorReporter> ice,
@ -91,7 +79,6 @@ struct TypeFunctionContext
: arena(arena) : arena(arena)
, builtins(builtins) , builtins(builtins)
, scope(scope) , scope(scope)
, simplifier(simplifier)
, normalizer(normalizer) , normalizer(normalizer)
, typeFunctionRuntime(typeFunctionRuntime) , typeFunctionRuntime(typeFunctionRuntime)
, ice(ice) , ice(ice)
@ -104,31 +91,19 @@ struct TypeFunctionContext
NotNull<Constraint> pushConstraint(ConstraintV&& c) const; NotNull<Constraint> pushConstraint(ConstraintV&& c) const;
}; };
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,
/// may have concretely failed to reduce the type, or may simply be stuck /// may have concretely failed to reduce the type, or may simply be stuck
/// without more information. /// without more information.
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;
@ -137,8 +112,6 @@ struct TypeFunctionReductionResult
std::vector<TypePackId> blockedPacks; std::vector<TypePackId> blockedPacks;
/// A runtime error message from user-defined type functions /// A runtime error message from user-defined type functions
std::optional<std::string> error; 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 +128,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 +140,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 +216,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

@ -3,7 +3,6 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/TypeFwd.h"
#include <optional> #include <optional>
#include <string> #include <string>
@ -32,8 +31,6 @@ struct TypeFunctionPrimitiveType
Boolean, Boolean,
Number, Number,
String, String,
Thread,
Buffer,
}; };
Type type; Type type;
@ -120,14 +117,7 @@ struct TypeFunctionVariadicTypePack
TypeFunctionTypeId type; TypeFunctionTypeId type;
}; };
struct TypeFunctionGenericTypePack using TypeFunctionTypePackVariant = Variant<TypeFunctionTypePack, TypeFunctionVariadicTypePack>;
{
bool isNamed = false;
std::string name;
};
using TypeFunctionTypePackVariant = Variant<TypeFunctionTypePack, TypeFunctionVariadicTypePack, TypeFunctionGenericTypePack>;
struct TypeFunctionTypePackVar struct TypeFunctionTypePackVar
{ {
@ -143,9 +133,6 @@ struct TypeFunctionTypePackVar
struct TypeFunctionFunctionType struct TypeFunctionFunctionType
{ {
std::vector<TypeFunctionTypeId> generics;
std::vector<TypeFunctionTypePackId> genericPacks;
TypeFunctionTypePackId argTypes; TypeFunctionTypePackId argTypes;
TypeFunctionTypePackId retTypes; TypeFunctionTypePackId retTypes;
}; };
@ -205,7 +192,7 @@ struct TypeFunctionTableType
std::optional<TypeFunctionTypeId> metatable; std::optional<TypeFunctionTypeId> metatable;
}; };
struct TypeFunctionExternType struct TypeFunctionClassType
{ {
using Name = std::string; using Name = std::string;
using Props = std::map<Name, TypeFunctionProperty>; using Props = std::map<Name, TypeFunctionProperty>;
@ -216,19 +203,7 @@ struct TypeFunctionExternType
std::optional<TypeFunctionTypeId> metatable; // metaclass? std::optional<TypeFunctionTypeId> metatable; // metaclass?
// this was mistaken, and we should actually be keeping separate read/write types here. std::optional<TypeFunctionTypeId> parent;
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; std::string name;
}; };
@ -244,8 +219,7 @@ using TypeFunctionTypeVariant = Luau::Variant<
TypeFunctionNegationType, TypeFunctionNegationType,
TypeFunctionFunctionType, TypeFunctionFunctionType,
TypeFunctionTableType, TypeFunctionTableType,
TypeFunctionExternType, TypeFunctionClassType>;
TypeFunctionGenericType>;
struct TypeFunctionType struct TypeFunctionType
{ {
@ -291,6 +265,4 @@ void registerTypeUserData(lua_State* L);
void setTypeFunctionEnvironment(lua_State* L); void setTypeFunctionEnvironment(lua_State* L);
void resetTypeFunctionState(lua_State* L);
} // namespace Luau } // namespace Luau

View file

@ -28,12 +28,20 @@ struct TypeFunctionRuntimeBuilderState
{ {
NotNull<TypeFunctionContext> ctx; NotNull<TypeFunctionContext> ctx;
// Mapping of class name to ClassType
// Invariant: users can not create a new class types -> any class types that get deserialized must have been an argument to the type function
// Using this invariant, whenever a ClassType is serialized, we can put it into this map
// whenever a ClassType is deserialized, we can use this map to return the corresponding value
DenseHashMap<std::string, TypeId> classesSerialized{{}};
// List of errors that occur during serialization/deserialization // List of errors that occur during serialization/deserialization
// At every iteration of serialization/deserialization, if this list.size() != 0, we halt the process // At every iteration of serialization/deserialzation, if this list.size() != 0, we halt the process
std::vector<std::string> errors{}; std::vector<std::string> errors{};
TypeFunctionRuntimeBuilderState(NotNull<TypeFunctionContext> ctx) TypeFunctionRuntimeBuilderState(NotNull<TypeFunctionContext> ctx)
: ctx(ctx) : ctx(ctx)
, classesSerialized({})
, errors({})
{ {
} }
}; };

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,19 +42,9 @@ 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;
}; };
@ -215,9 +205,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)
{ {
@ -280,15 +280,4 @@ std::vector<TypeId> findBlockedTypesIn(AstExprTable* expr, NotNull<DenseHashMap<
*/ */
std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes); 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:
@ -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);
} }
@ -191,7 +190,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 +312,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,16 +334,16 @@ 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);
} }
} }
} }
@ -396,7 +395,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 +461,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
{ {
@ -433,16 +431,8 @@ struct AstJsonEncoder : public AstVisitor
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 +465,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("}");
} }
@ -912,10 +902,7 @@ struct AstJsonEncoder : public AstVisitor
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 +923,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 +936,7 @@ struct AstJsonEncoder : public AstVisitor
writeRaw("}"); writeRaw("}");
} }
void write(class AstStatDeclareExternType* node) void write(class AstStatDeclareClass* node)
{ {
writeNode( writeNode(
node, node,
@ -1061,10 +1048,7 @@ struct AstJsonEncoder : public AstVisitor
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 +1065,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(
@ -1167,8 +1146,6 @@ struct AstJsonEncoder : public AstVisitor
return writeString("checked"); return writeString("checked");
case AstAttr::Type::Native: case AstAttr::Type::Native:
return writeString("native"); return writeString("native");
case AstAttr::Type::Deprecated:
return writeString("deprecated");
} }
} }
@ -1184,19 +1161,6 @@ struct AstJsonEncoder : public AstVisitor
); );
} }
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 +1409,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

@ -13,6 +13,8 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauDocumentationAtPosition, false)
namespace Luau namespace Luau
{ {
@ -41,15 +43,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;
} }
@ -520,6 +518,7 @@ static std::optional<DocumentationSymbol> getMetatableDocumentation(
const AstName& index const AstName& index
) )
{ {
LUAU_ASSERT(FFlag::LuauDocumentationAtPosition);
auto indexIt = mtable->props.find("__index"); auto indexIt = mtable->props.find("__index");
if (indexIt == mtable->props.end()) if (indexIt == mtable->props.end())
return std::nullopt; return std::nullopt;
@ -574,11 +573,30 @@ 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 (FFlag::LuauDocumentationAtPosition)
{ {
if (auto propIt = etv->props.find(indexName->index.value); propIt != etv->props.end()) while (ctv)
{
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
{
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
);
}
ctv = ctv->parent ? Luau::get<Luau::ClassType>(*ctv->parent) : nullptr;
}
}
else
{
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
@ -590,15 +608,17 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol
); );
} }
etv = etv->parent ? Luau::get<Luau::ExternType>(*etv->parent) : nullptr;
} }
} }
else if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable) else if (FFlag::LuauDocumentationAtPosition)
{ {
if (auto mtable = get<TableType>(*ptv->metatable)) if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable)
{ {
if (std::optional<std::string> docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index)) if (auto mtable = get<TableType>(*ptv->metatable))
return docSymbol; {
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

@ -3,23 +3,21 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Clone.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/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>
@ -30,92 +28,51 @@
*/ */
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAGVARIABLE(LuauTypestateBuiltins, false)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauStringFormatArityFix, false)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked)
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition) LUAU_FASTFLAG(AutocompleteRequirePathSuggestions)
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; static bool dcrMagicFunctionFreeze(MagicFunctionCallContext context);
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 +167,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 +229,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 +270,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 +277,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 +286,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 +300,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 +313,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 +325,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 +359,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 +386,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 +399,12 @@ 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}); TypeId idTyWithMagic = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
ttv->props["freeze"] = makeProperty(idTyWithMagic, "@luau/global/table.freeze"); 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)
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,67 +419,23 @@ 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>()); if (FFlag::LuauTypestateBuiltins)
attachMagicFunction(ttv->props["freeze"].type(), std::make_shared<MagicFreeze>()); attachDcrMagicFunction(ttv->props["freeze"].type(), dcrMagicFunctionFreeze);
} }
TypeId requireTy = getGlobalBinding(globals, "require"); if (FFlag::AutocompleteRequirePathSuggestions)
attachTag(requireTy, kRequireTagName);
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 TypeId requireTy = getGlobalBinding(globals, "require");
globals.globalTypeFunctionScope->exportedTypeBindings = globals.globalScope->exportedTypeBindings; attachTag(requireTy, kRequireTagName);
globals.globalTypeFunctionScope->builtinTypeNames = globals.globalScope->builtinTypeNames; attachMagicFunction(requireTy, magicFunctionRequire);
attachDcrMagicFunction(requireTy, dcrMagicFunctionRequire);
// 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[] = { else
// Libraries {
"math", attachMagicFunction(getGlobalBinding(globals, "require"), magicFunctionRequire);
"table", attachDcrMagicFunction(getGlobalBinding(globals, "require"), dcrMagicFunctionRequire);
"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);
} }
} }
@ -590,7 +475,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 +525,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 +569,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)
@ -700,18 +585,11 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
if (!fmt) if (!fmt)
{ {
context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location); if (FFlag::LuauStringFormatArityFix)
return true; context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location);
return;
} }
// 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 +601,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 +672,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 +708,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 +741,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 +781,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 +817,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 +875,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 +952,11 @@ 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);
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 +970,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 +987,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 +1049,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 +1094,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 +1139,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 +1221,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 +1255,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 +1298,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,167 +1338,54 @@ bool MagicPack::infer(const MagicFunctionCallContext& context)
return true; return true;
} }
std::optional<WithPredicate<TypePackId>> MagicClone::handleOldSolver( static bool dcrMagicFunctionFreeze(MagicFunctionCallContext context)
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
WithPredicate<TypePackId> withPredicate
)
{ {
LUAU_ASSERT(FFlag::LuauTableCloneClonesType3); LUAU_ASSERT(FFlag::LuauTypestateBuiltins);
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; TypeArena* arena = context.solver->arena;
const DataFlowGraph* dfg = context.solver->dfg.get(); const DataFlowGraph* dfg = context.solver->dfg.get();
Scope* scope = context.constraint->scope.get(); Scope* scope = context.constraint->scope.get();
const auto& [paramTypes, paramTail] = extendTypePack(*arena, context.solver->builtinTypes, context.arguments, 1); const auto& [paramTypes, paramTail] = extendTypePack(*arena, context.solver->builtinTypes, context.arguments, 1);
if (paramTypes.empty() || context.callSite->args.size == 0) LUAU_ASSERT(paramTypes.size() >= 1);
TypeId inputType = follow(paramTypes.at(0));
// we'll check if it's a table first since this magic function also produces the error if it's not until we have bounded generics
if (!get<TableType>(inputType))
{ {
context.solver->reportError(CountMismatch{1, std::nullopt, 0}, context.callSite->argLocation); context.solver->reportError(TypeMismatch{context.solver->builtinTypes->tableType, inputType}, context.callSite->argLocation);
return false; return false;
} }
TypeId inputType = follow(paramTypes[0]);
AstExpr* targetExpr = context.callSite->args.data[0]; AstExpr* targetExpr = context.callSite->args.data[0];
std::optional<DefId> resultDef = dfg->getDefOptional(targetExpr); std::optional<DefId> resultDef = dfg->getDefOptional(targetExpr);
std::optional<TypeId> resultTy = resultDef ? scope->lookup(*resultDef) : std::nullopt; std::optional<TypeId> resultTy = resultDef ? scope->lookup(*resultDef) : std::nullopt;
if (FFlag::LuauMagicFreezeCheckBlocked) // Clone the input type, this will become our final result type after we mutate it.
CloneState cloneState{context.solver->builtinTypes};
TypeId clonedType = shallowClone(inputType, *arena, cloneState);
auto tableTy = getMutable<TableType>(clonedType);
// `clone` should not break this.
LUAU_ASSERT(tableTy);
tableTy->state = TableState::Sealed;
tableTy->syntheticName = std::nullopt;
// We'll mutate the table to make every property type read-only.
for (auto iter = tableTy->props.begin(); iter != tableTy->props.end();)
{ {
if (resultTy && !get<BlockedType>(resultTy)) if (iter->second.isWriteOnly())
iter = tableTy->props.erase(iter);
else
{ {
// If there's an existing result type but it's _not_ blocked, then iter->second.writeTy = std::nullopt;
// we aren't type stating this builtin and should fall back to iter++;
// 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) if (resultTy)
asMutable(*resultTy)->ty.emplace<BoundType>(*frozenType); asMutable(*resultTy)->ty.emplace<BoundType>(clonedType);
asMutable(context.result)->ty.emplace<BoundTypePack>(arena->addTypePack({*frozenType})); asMutable(context.result)->ty.emplace<BoundTypePack>(arena->addTypePack({clonedType}));
return true; return true;
} }
@ -1667,7 +1412,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 +1458,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)
{ {

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;
} }
@ -162,14 +141,14 @@ protected:
} }
public: public:
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);
@ -276,7 +257,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 +337,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);
@ -395,7 +377,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 +428,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,122 +452,23 @@ 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) TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
{ {
if (tp->persistent && !ignorePersistent) if (tp->persistent)
return tp; return tp;
TypeCloner cloner{ TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
ignorePersistent ? tp : nullptr
};
return cloner.shallowClone(tp); return cloner.shallowClone(tp);
} }
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent) TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
{ {
if (typeId->persistent && !ignorePersistent) if (typeId->persistent)
return typeId; return typeId;
TypeCloner cloner{ TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
ignorePersistent ? typeId : nullptr,
nullptr
};
return cloner.shallowClone(typeId); return cloner.shallowClone(typeId);
} }
@ -599,7 +477,7 @@ 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 +486,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 +517,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

View file

@ -13,25 +13,33 @@
LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument) LUAU_FASTFLAG(LuauTypestateBuiltins)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow)
namespace Luau namespace Luau
{ {
bool doesCallError(const AstExprCall* call); // TypeInfer.cpp bool doesCallError(const AstExprCall* call); // TypeInfer.cpp
struct ReferencedDefFinder : public AstVisitor
{
bool visit(AstExprLocal* local) override
{
referencedLocalDefs.push_back(local->local);
return true;
}
// ast defs is just a mapping from expr -> def in general
// will get built up by the dfg builder
// localDefs, we need to copy over
std::vector<AstLocal*> referencedLocalDefs;
};
struct PushScope struct PushScope
{ {
ScopeStack& stack; ScopeStack& stack;
size_t previousSize;
PushScope(ScopeStack& stack, DfgScope* scope) PushScope(ScopeStack& stack, DfgScope* scope)
: stack(stack) : stack(stack)
, previousSize(stack.size())
{ {
// `scope` should never be `nullptr` here. // `scope` should never be `nullptr` here.
LUAU_ASSERT(scope); LUAU_ASSERT(scope);
@ -40,18 +48,7 @@ struct PushScope
~PushScope() ~PushScope()
{ {
if (FFlag::LuauDfgScopeStackTrueReset) stack.pop_back();
{
// If somehow this stack has _shrunk_ to be smaller than we expect,
// something very strange has happened.
LUAU_ASSERT(stack.size() > previousSize);
while (stack.size() > previousSize)
stack.pop_back();
}
else
{
stack.pop_back();
}
} }
}; };
@ -65,12 +62,6 @@ const RefinementKey* RefinementKeyArena::node(const RefinementKey* parent, DefId
return allocator.allocate(RefinementKey{parent, def, propName}); return allocator.allocate(RefinementKey{parent, def, propName});
} }
DataFlowGraph::DataFlowGraph(NotNull<DefArena> defArena, NotNull<RefinementKeyArena> keyArena)
: defArena{defArena}
, keyArena{keyArena}
{
}
DefId DataFlowGraph::getDef(const AstExpr* expr) const DefId DataFlowGraph::getDef(const AstExpr* expr) const
{ {
auto def = astDefs.find(expr); auto def = astDefs.find(expr);
@ -86,6 +77,12 @@ std::optional<DefId> DataFlowGraph::getDefOptional(const AstExpr* expr) const
return NotNull{*def}; return NotNull{*def};
} }
std::optional<DefId> DataFlowGraph::getRValueDefForCompoundAssign(const AstExpr* expr) const
{
auto def = compoundAssignDefs.find(expr);
return def ? std::optional<DefId>(*def) : std::nullopt;
}
DefId DataFlowGraph::getDef(const AstLocal* local) const DefId DataFlowGraph::getDef(const AstLocal* local) const
{ {
auto def = localDefs.find(local); auto def = localDefs.find(local);
@ -181,33 +178,15 @@ bool DfgScope::canUpdateDefinition(DefId def, const std::string& key) const
return true; return true;
} }
DataFlowGraphBuilder::DataFlowGraphBuilder(NotNull<DefArena> defArena, NotNull<RefinementKeyArena> keyArena) DataFlowGraph DataFlowGraphBuilder::build(AstStatBlock* block, NotNull<InternalErrorReporter> handle)
: graph{defArena, keyArena}
, defArena{defArena}
, keyArena{keyArena}
{
}
DataFlowGraph DataFlowGraphBuilder::build(
AstStatBlock* block,
NotNull<DefArena> defArena,
NotNull<RefinementKeyArena> keyArena,
NotNull<struct InternalErrorReporter> handle
)
{ {
LUAU_TIMETRACE_SCOPE("DataFlowGraphBuilder::build", "Typechecking"); LUAU_TIMETRACE_SCOPE("DataFlowGraphBuilder::build", "Typechecking");
DataFlowGraphBuilder builder(defArena, keyArena); LUAU_ASSERT(FFlag::LuauSolverV2);
builder.handle = handle;
DfgScope* moduleScope; DataFlowGraphBuilder builder;
// We're not explicitly calling makeChildScope here because that function relies on currentScope builder.handle = handle;
// which guarantees that the scope being returned is NotNull DfgScope* moduleScope = builder.makeChildScope(block->location);
// This means that while the scope stack is empty, we'll have to manually initialize the global scope
if (FFlag::LuauDfgScopeStackNotNull)
moduleScope = builder.scopes.emplace_back(new DfgScope{nullptr, DfgScope::ScopeType::Linear}).get();
else
moduleScope = builder.makeChildScope();
PushScope ps{builder.scopeStack, moduleScope}; PushScope ps{builder.scopeStack, moduleScope};
builder.visitBlockWithoutChildScope(block); builder.visitBlockWithoutChildScope(block);
builder.resolveCaptures(); builder.resolveCaptures();
@ -221,6 +200,82 @@ DataFlowGraph DataFlowGraphBuilder::build(
return std::move(builder.graph); return std::move(builder.graph);
} }
std::pair<std::shared_ptr<DataFlowGraph>, std::vector<std::unique_ptr<DfgScope>>> DataFlowGraphBuilder::buildShared(
AstStatBlock* block,
NotNull<InternalErrorReporter> handle
)
{
LUAU_TIMETRACE_SCOPE("DataFlowGraphBuilder::build", "Typechecking");
LUAU_ASSERT(FFlag::LuauSolverV2);
DataFlowGraphBuilder builder;
builder.handle = handle;
DfgScope* moduleScope = builder.makeChildScope(block->location);
PushScope ps{builder.scopeStack, moduleScope};
builder.visitBlockWithoutChildScope(block);
builder.resolveCaptures();
if (FFlag::DebugLuauFreezeArena)
{
builder.defArena->allocator.freeze();
builder.keyArena->allocator.freeze();
}
return {std::make_shared<DataFlowGraph>(std::move(builder.graph)), std::move(builder.scopes)};
}
DataFlowGraph DataFlowGraphBuilder::updateGraph(
const DataFlowGraph& staleGraph,
const std::vector<std::unique_ptr<DfgScope>>& scopes,
AstStatBlock* fragment,
const Position& cursorPos,
NotNull<InternalErrorReporter> handle
)
{
LUAU_TIMETRACE_SCOPE("DataFlowGraphBuilder::build", "Typechecking");
LUAU_ASSERT(FFlag::LuauSolverV2);
DataFlowGraphBuilder builder;
builder.handle = handle;
// Generate a list of prepopulated locals
ReferencedDefFinder finder;
fragment->visit(&finder);
for (AstLocal* loc : finder.referencedLocalDefs)
{
if (staleGraph.localDefs.contains(loc))
{
builder.graph.localDefs[loc] = *staleGraph.localDefs.find(loc);
}
}
// Figure out which scope we should start re-accumulating DFG information from again
DfgScope* nearest = nullptr;
for (auto& sc : scopes)
{
if (nearest == nullptr || (sc->location.begin <= cursorPos && nearest->location.begin < sc->location.begin))
nearest = sc.get();
}
// The scope stack should start with the nearest enclosing scope so we can resume DFG'ing correctly
PushScope ps{builder.scopeStack, nearest};
// Conspire for the current scope in the scope stack to be a fresh dfg scope, parented to the above nearest enclosing scope, so any insertions are
// isolated there
DfgScope* scope = builder.makeChildScope(fragment->location);
PushScope psAgain{builder.scopeStack, scope};
builder.visitBlockWithoutChildScope(fragment);
if (FFlag::DebugLuauFreezeArena)
{
builder.defArena->allocator.freeze();
builder.keyArena->allocator.freeze();
}
return std::move(builder.graph);
}
void DataFlowGraphBuilder::resolveCaptures() void DataFlowGraphBuilder::resolveCaptures()
{ {
for (const auto& [_, capture] : captures) for (const auto& [_, capture] : captures)
@ -239,25 +294,16 @@ void DataFlowGraphBuilder::resolveCaptures()
} }
} }
NotNull<DfgScope> DataFlowGraphBuilder::currentScope() DfgScope* DataFlowGraphBuilder::currentScope()
{
LUAU_ASSERT(!scopeStack.empty());
return NotNull{scopeStack.back()};
}
DfgScope* DataFlowGraphBuilder::currentScope_DEPRECATED()
{ {
if (scopeStack.empty()) if (scopeStack.empty())
return nullptr; // nullptr is the root DFG scope. return nullptr; // nullptr is the root DFG scope.
return scopeStack.back(); return scopeStack.back();
} }
DfgScope* DataFlowGraphBuilder::makeChildScope(DfgScope::ScopeType scopeType) DfgScope* DataFlowGraphBuilder::makeChildScope(Location loc, DfgScope::ScopeType scopeType)
{ {
if (FFlag::LuauDfgScopeStackNotNull) return scopes.emplace_back(new DfgScope{currentScope(), scopeType, loc}).get();
return scopes.emplace_back(new DfgScope{currentScope(), scopeType}).get();
else
return scopes.emplace_back(new DfgScope{currentScope_DEPRECATED(), scopeType}).get();
} }
void DataFlowGraphBuilder::join(DfgScope* p, DfgScope* a, DfgScope* b) void DataFlowGraphBuilder::join(DfgScope* p, DfgScope* a, DfgScope* b)
@ -332,9 +378,9 @@ void DataFlowGraphBuilder::joinProps(DfgScope* result, const DfgScope& a, const
} }
} }
DefId DataFlowGraphBuilder::lookup(Symbol symbol, Location location) DefId DataFlowGraphBuilder::lookup(Symbol symbol)
{ {
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = currentScope();
// true if any of the considered scopes are a loop. // true if any of the considered scopes are a loop.
bool outsideLoopScope = false; bool outsideLoopScope = false;
@ -359,15 +405,15 @@ DefId DataFlowGraphBuilder::lookup(Symbol symbol, Location location)
} }
} }
DefId result = defArena->freshCell(symbol, location); DefId result = defArena->freshCell();
scope->bindings[symbol] = result; scope->bindings[symbol] = result;
captures[symbol].allVersions.push_back(result); captures[symbol].allVersions.push_back(result);
return result; return result;
} }
DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key, Location location) DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key)
{ {
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = currentScope();
for (DfgScope* current = scope; current; current = current->parent) for (DfgScope* current = scope; current; current = current->parent)
{ {
if (auto props = current->props.find(def)) if (auto props = current->props.find(def))
@ -377,7 +423,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key, Location l
} }
else if (auto phi = get<Phi>(def); phi && phi->operands.empty()) // Unresolved phi nodes else if (auto phi = get<Phi>(def); phi && phi->operands.empty()) // Unresolved phi nodes
{ {
DefId result = defArena->freshCell(def->name, location); DefId result = defArena->freshCell();
scope->props[def][key] = result; scope->props[def][key] = result;
return result; return result;
} }
@ -387,7 +433,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key, Location l
{ {
std::vector<DefId> defs; std::vector<DefId> defs;
for (DefId operand : phi->operands) for (DefId operand : phi->operands)
defs.push_back(lookup(operand, key, location)); defs.push_back(lookup(operand, key));
DefId result = defArena->phi(defs); DefId result = defArena->phi(defs);
scope->props[def][key] = result; scope->props[def][key] = result;
@ -395,7 +441,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key, Location l
} }
else if (get<Cell>(def)) else if (get<Cell>(def))
{ {
DefId result = defArena->freshCell(def->name, location); DefId result = defArena->freshCell();
scope->props[def][key] = result; scope->props[def][key] = result;
return result; return result;
} }
@ -405,7 +451,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key, Location l
ControlFlow DataFlowGraphBuilder::visit(AstStatBlock* b) ControlFlow DataFlowGraphBuilder::visit(AstStatBlock* b)
{ {
DfgScope* child = makeChildScope(); DfgScope* child = makeChildScope(b->location);
ControlFlow cf; ControlFlow cf;
{ {
@ -413,10 +459,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatBlock* b)
cf = visitBlockWithoutChildScope(b); cf = visitBlockWithoutChildScope(b);
} }
if (FFlag::LuauDfgScopeStackNotNull) currentScope()->inherit(child);
currentScope()->inherit(child);
else
currentScope_DEPRECATED()->inherit(child);
return cf; return cf;
} }
@ -473,7 +516,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStat* s)
return visit(d); return visit(d);
else if (auto d = s->as<AstStatDeclareFunction>()) else if (auto d = s->as<AstStatDeclareFunction>())
return visit(d); return visit(d);
else if (auto d = s->as<AstStatDeclareExternType>()) else if (auto d = s->as<AstStatDeclareClass>())
return visit(d); return visit(d);
else if (auto error = s->as<AstStatError>()) else if (auto error = s->as<AstStatError>())
return visit(error); return visit(error);
@ -485,8 +528,8 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
{ {
visitExpr(i->condition); visitExpr(i->condition);
DfgScope* thenScope = makeChildScope(); DfgScope* thenScope = makeChildScope(i->thenbody->location);
DfgScope* elseScope = makeChildScope(); DfgScope* elseScope = makeChildScope(i->elsebody ? i->elsebody->location : i->location);
ControlFlow thencf; ControlFlow thencf;
{ {
@ -501,27 +544,13 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
elsecf = visit(i->elsebody); elsecf = visit(i->elsebody);
} }
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = currentScope();
if (FFlag::LuauDfgIfBlocksShouldRespectControlFlow) if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
{ join(scope, scope, elseScope);
// If the control flow from the `if` or `else` block is non-linear, else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
// then we should assume that the _other_ branch is the one taken. join(scope, thenScope, scope);
if (thencf != ControlFlow::None && elsecf == ControlFlow::None) else if ((thencf | elsecf) == ControlFlow::None)
scope->inherit(elseScope); join(scope, thenScope, elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
scope->inherit(thenScope);
else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope);
}
else
{
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
join(scope, scope, elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
join(scope, thenScope, scope);
else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope);
}
if (thencf == elsecf) if (thencf == elsecf)
return thencf; return thencf;
@ -534,7 +563,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w) ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
{ {
// TODO(controlflow): entry point has a back edge from exit point // TODO(controlflow): entry point has a back edge from exit point
DfgScope* whileScope = makeChildScope(DfgScope::Loop); DfgScope* whileScope = makeChildScope(w->location, DfgScope::Loop);
{ {
PushScope ps{scopeStack, whileScope}; PushScope ps{scopeStack, whileScope};
@ -542,10 +571,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
visit(w->body); visit(w->body);
} }
if (FFlag::LuauDfgScopeStackNotNull) currentScope()->inherit(whileScope);
currentScope()->inherit(whileScope);
else
currentScope_DEPRECATED()->inherit(whileScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -553,7 +579,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r) ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r)
{ {
// TODO(controlflow): entry point has a back edge from exit point // TODO(controlflow): entry point has a back edge from exit point
DfgScope* repeatScope = makeChildScope(DfgScope::Loop); DfgScope* repeatScope = makeChildScope(r->location, DfgScope::Loop);
{ {
PushScope ps{scopeStack, repeatScope}; PushScope ps{scopeStack, repeatScope};
@ -561,10 +587,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r)
visitExpr(r->condition); visitExpr(r->condition);
} }
if (FFlag::LuauDfgScopeStackNotNull) currentScope()->inherit(repeatScope);
currentScope()->inherit(repeatScope);
else
currentScope_DEPRECATED()->inherit(repeatScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -613,7 +636,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l)
// We need to create a new def to intentionally avoid alias tracking, but we'd like to // We need to create a new def to intentionally avoid alias tracking, but we'd like to
// make sure that the non-aliased defs are also marked as a subscript for refinements. // make sure that the non-aliased defs are also marked as a subscript for refinements.
bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]); bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]);
DefId def = defArena->freshCell(local, local->location, subscripted); DefId def = defArena->freshCell(subscripted);
if (i < l->values.size) if (i < l->values.size)
{ {
AstExpr* e = l->values.data[i]; AstExpr* e = l->values.data[i];
@ -623,10 +646,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l)
} }
} }
graph.localDefs[local] = def; graph.localDefs[local] = def;
if (FFlag::LuauDfgScopeStackNotNull) currentScope()->bindings[local] = def;
currentScope()->bindings[local] = def;
else
currentScope_DEPRECATED()->bindings[local] = def;
captures[local].allVersions.push_back(def); captures[local].allVersions.push_back(def);
} }
@ -635,7 +655,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l)
ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f) ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f)
{ {
DfgScope* forScope = makeChildScope(DfgScope::Loop); DfgScope* forScope = makeChildScope(f->location, DfgScope::Loop);
visitExpr(f->from); visitExpr(f->from);
visitExpr(f->to); visitExpr(f->to);
@ -648,29 +668,23 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f)
if (f->var->annotation) if (f->var->annotation)
visitType(f->var->annotation); visitType(f->var->annotation);
DefId def = defArena->freshCell(f->var, f->var->location); DefId def = defArena->freshCell();
graph.localDefs[f->var] = def; graph.localDefs[f->var] = def;
if (FFlag::LuauDfgScopeStackNotNull) currentScope()->bindings[f->var] = def;
currentScope()->bindings[f->var] = def;
else
currentScope_DEPRECATED()->bindings[f->var] = def;
captures[f->var].allVersions.push_back(def); captures[f->var].allVersions.push_back(def);
// TODO(controlflow): entry point has a back edge from exit point // TODO(controlflow): entry point has a back edge from exit point
visit(f->body); visit(f->body);
} }
if (FFlag::LuauDfgScopeStackNotNull) currentScope()->inherit(forScope);
currentScope()->inherit(forScope);
else
currentScope_DEPRECATED()->inherit(forScope);
return ControlFlow::None; return ControlFlow::None;
} }
ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f) ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
{ {
DfgScope* forScope = makeChildScope(DfgScope::Loop); DfgScope* forScope = makeChildScope(f->location, DfgScope::Loop);
{ {
PushScope ps{scopeStack, forScope}; PushScope ps{scopeStack, forScope};
@ -680,12 +694,9 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
if (local->annotation) if (local->annotation)
visitType(local->annotation); visitType(local->annotation);
DefId def = defArena->freshCell(local, local->location); DefId def = defArena->freshCell();
graph.localDefs[local] = def; graph.localDefs[local] = def;
if (FFlag::LuauDfgScopeStackNotNull) currentScope()->bindings[local] = def;
currentScope()->bindings[local] = def;
else
currentScope_DEPRECATED()->bindings[local] = def;
captures[local].allVersions.push_back(def); captures[local].allVersions.push_back(def);
} }
@ -696,10 +707,8 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
visit(f->body); visit(f->body);
} }
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(forScope); currentScope()->inherit(forScope);
else
currentScope_DEPRECATED()->inherit(forScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -714,7 +723,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatAssign* a)
for (size_t i = 0; i < a->vars.size; ++i) for (size_t i = 0; i < a->vars.size; ++i)
{ {
AstExpr* v = a->vars.data[i]; AstExpr* v = a->vars.data[i];
visitLValue(v, i < defs.size() ? defs[i] : defArena->freshCell(Symbol{}, v->location)); visitLValue(v, i < defs.size() ? defs[i] : defArena->freshCell());
} }
return ControlFlow::None; return ControlFlow::None;
@ -740,7 +749,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFunction* f)
// //
// which is evidence that references to variables must be a phi node of all possible definitions, // which is evidence that references to variables must be a phi node of all possible definitions,
// but for bug compatibility, we'll assume the same thing here. // but for bug compatibility, we'll assume the same thing here.
visitLValue(f->name, defArena->freshCell(Symbol{}, f->name->location)); visitLValue(f->name, defArena->freshCell());
visitExpr(f->func); visitExpr(f->func);
if (auto local = f->name->as<AstExprLocal>()) if (auto local = f->name->as<AstExprLocal>())
@ -760,12 +769,9 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFunction* f)
ControlFlow DataFlowGraphBuilder::visit(AstStatLocalFunction* l) ControlFlow DataFlowGraphBuilder::visit(AstStatLocalFunction* l)
{ {
DefId def = defArena->freshCell(l->name, l->location); DefId def = defArena->freshCell();
graph.localDefs[l->name] = def; graph.localDefs[l->name] = def;
if (FFlag::LuauDfgScopeStackNotNull) currentScope()->bindings[l->name] = def;
currentScope()->bindings[l->name] = def;
else
currentScope_DEPRECATED()->bindings[l->name] = def;
captures[l->name].allVersions.push_back(def); captures[l->name].allVersions.push_back(def);
visitExpr(l->func); visitExpr(l->func);
@ -774,7 +780,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocalFunction* l)
ControlFlow DataFlowGraphBuilder::visit(AstStatTypeAlias* t) ControlFlow DataFlowGraphBuilder::visit(AstStatTypeAlias* t)
{ {
DfgScope* unreachable = makeChildScope(); DfgScope* unreachable = makeChildScope(t->location);
PushScope ps{scopeStack, unreachable}; PushScope ps{scopeStack, unreachable};
visitGenerics(t->generics); visitGenerics(t->generics);
@ -786,7 +792,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatTypeAlias* t)
ControlFlow DataFlowGraphBuilder::visit(AstStatTypeFunction* f) ControlFlow DataFlowGraphBuilder::visit(AstStatTypeFunction* f)
{ {
DfgScope* unreachable = makeChildScope(); DfgScope* unreachable = makeChildScope(f->location);
PushScope ps{scopeStack, unreachable}; PushScope ps{scopeStack, unreachable};
visitExpr(f->body); visitExpr(f->body);
@ -796,12 +802,9 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatTypeFunction* f)
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d) ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d)
{ {
DefId def = defArena->freshCell(d->name, d->nameLocation); DefId def = defArena->freshCell();
graph.declaredDefs[d] = def; graph.declaredDefs[d] = def;
if (FFlag::LuauDfgScopeStackNotNull) currentScope()->bindings[d->name] = def;
currentScope()->bindings[d->name] = def;
else
currentScope_DEPRECATED()->bindings[d->name] = def;
captures[d->name].allVersions.push_back(def); captures[d->name].allVersions.push_back(def);
visitType(d->type); visitType(d->type);
@ -811,37 +814,31 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d)
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d) ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d)
{ {
DefId def = defArena->freshCell(d->name, d->nameLocation); DefId def = defArena->freshCell();
graph.declaredDefs[d] = def; graph.declaredDefs[d] = def;
if (FFlag::LuauDfgScopeStackNotNull) currentScope()->bindings[d->name] = def;
currentScope()->bindings[d->name] = def;
else
currentScope_DEPRECATED()->bindings[d->name] = def;
captures[d->name].allVersions.push_back(def); captures[d->name].allVersions.push_back(def);
DfgScope* unreachable = makeChildScope(); DfgScope* unreachable = makeChildScope(d->location);
PushScope ps{scopeStack, unreachable}; PushScope ps{scopeStack, unreachable};
visitGenerics(d->generics); visitGenerics(d->generics);
visitGenericPacks(d->genericPacks); visitGenericPacks(d->genericPacks);
visitTypeList(d->params); visitTypeList(d->params);
if (FFlag::LuauStoreReturnTypesAsPackOnAst) visitTypeList(d->retTypes);
visitTypePack(d->retTypes);
else
visitTypeList(d->retTypes_DEPRECATED);
return ControlFlow::None; return ControlFlow::None;
} }
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareExternType* d) ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* d)
{ {
// This declaration does not "introduce" any bindings in value namespace, // This declaration does not "introduce" any bindings in value namespace,
// so there's no symbolic value to begin with. We'll traverse the properties // so there's no symbolic value to begin with. We'll traverse the properties
// because their type annotations may depend on something in the value namespace. // because their type annotations may depend on something in the value namespace.
DfgScope* unreachable = makeChildScope(); DfgScope* unreachable = makeChildScope(d->location);
PushScope ps{scopeStack, unreachable}; PushScope ps{scopeStack, unreachable};
for (AstDeclaredExternTypeProperty prop : d->props) for (AstDeclaredClassProp prop : d->props)
visitType(prop.ty); visitType(prop.ty);
return ControlFlow::None; return ControlFlow::None;
@ -849,7 +846,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareExternType* d)
ControlFlow DataFlowGraphBuilder::visit(AstStatError* error) ControlFlow DataFlowGraphBuilder::visit(AstStatError* error)
{ {
DfgScope* unreachable = makeChildScope(); DfgScope* unreachable = makeChildScope(error->location);
PushScope ps{scopeStack, unreachable}; PushScope ps{scopeStack, unreachable};
for (AstStat* s : error->statements) for (AstStat* s : error->statements)
@ -874,19 +871,19 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExpr* e)
if (auto g = e->as<AstExprGroup>()) if (auto g = e->as<AstExprGroup>())
return visitExpr(g); return visitExpr(g);
else if (auto c = e->as<AstExprConstantNil>()) else if (auto c = e->as<AstExprConstantNil>())
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok return {defArena->freshCell(), nullptr}; // ok
else if (auto c = e->as<AstExprConstantBool>()) else if (auto c = e->as<AstExprConstantBool>())
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok return {defArena->freshCell(), nullptr}; // ok
else if (auto c = e->as<AstExprConstantNumber>()) else if (auto c = e->as<AstExprConstantNumber>())
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok return {defArena->freshCell(), nullptr}; // ok
else if (auto c = e->as<AstExprConstantString>()) else if (auto c = e->as<AstExprConstantString>())
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok return {defArena->freshCell(), nullptr}; // ok
else if (auto l = e->as<AstExprLocal>()) else if (auto l = e->as<AstExprLocal>())
return visitExpr(l); return visitExpr(l);
else if (auto g = e->as<AstExprGlobal>()) else if (auto g = e->as<AstExprGlobal>())
return visitExpr(g); return visitExpr(g);
else if (auto v = e->as<AstExprVarargs>()) else if (auto v = e->as<AstExprVarargs>())
return {defArena->freshCell(Symbol{}, v->location), nullptr}; // ok return {defArena->freshCell(), nullptr}; // ok
else if (auto c = e->as<AstExprCall>()) else if (auto c = e->as<AstExprCall>())
return visitExpr(c); return visitExpr(c);
else if (auto i = e->as<AstExprIndexName>()) else if (auto i = e->as<AstExprIndexName>())
@ -927,14 +924,14 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGroup* group)
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprLocal* l) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprLocal* l)
{ {
DefId def = lookup(l->local, l->local->location); DefId def = lookup(l->local);
const RefinementKey* key = keyArena->leaf(def); const RefinementKey* key = keyArena->leaf(def);
return {def, key}; return {def, key};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGlobal* g) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGlobal* g)
{ {
DefId def = lookup(g->name, g->location); DefId def = lookup(g->name);
return {def, keyArena->leaf(def)}; return {def, keyArena->leaf(def)};
} }
@ -942,13 +939,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
{ {
visitExpr(c->func); visitExpr(c->func);
if (FFlag::LuauPreprocessTypestatedArgument) if (FFlag::LuauTypestateBuiltins && shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin()))
{
for (AstExpr* arg : c->args)
visitExpr(arg);
}
if (shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin()))
{ {
AstExpr* firstArg = *c->args.begin(); AstExpr* firstArg = *c->args.begin();
@ -967,7 +958,10 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
LUAU_ASSERT(result); LUAU_ASSERT(result);
DfgScope* child = makeChildScope(); Location location = currentScope()->location;
// This scope starts at the end of the call site and continues to the end of the original scope.
location.begin = c->location.end;
DfgScope* child = makeChildScope(location);
scopeStack.push_back(child); scopeStack.push_back(child);
auto [def, key] = *result; auto [def, key] = *result;
@ -978,31 +972,20 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
visitLValue(firstArg, def); visitLValue(firstArg, def);
} }
if (!FFlag::LuauPreprocessTypestatedArgument) for (AstExpr* arg : c->args)
{ visitExpr(arg);
for (AstExpr* arg : c->args)
visitExpr(arg);
}
// We treat function calls as "subscripted" as they could potentially // calls should be treated as subscripted.
// return a subscripted value, consider: return {defArena->freshCell(/* subscripted */ true), nullptr};
//
// local function foo(tbl: {[string]: woof)
// return tbl["foobarbaz"]
// end
//
// local v = foo({})
//
// We want to consider `v` to be subscripted here.
return {defArena->freshCell(Symbol{}, c->location, /*subscripted=*/true)};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i)
{ {
auto [parentDef, parentKey] = visitExpr(i->expr); auto [parentDef, parentKey] = visitExpr(i->expr);
std::string index = i->index.value; std::string index = i->index.value;
DefId def = lookup(parentDef, index, i->location); DefId def = lookup(parentDef, index);
return {def, keyArena->node(parentKey, def, index)}; return {def, keyArena->node(parentKey, def, index)};
} }
@ -1015,16 +998,16 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexExpr* i)
{ {
std::string index{string->value.data, string->value.size}; std::string index{string->value.data, string->value.size};
DefId def = lookup(parentDef, index, i->location); DefId def = lookup(parentDef, index);
return {def, keyArena->node(parentKey, def, index)}; return {def, keyArena->node(parentKey, def, index)};
} }
return {defArena->freshCell(Symbol{}, i->location, /* subscripted= */ true), nullptr}; return {defArena->freshCell(/* subscripted= */ true), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
{ {
DfgScope* signatureScope = makeChildScope(DfgScope::Function); DfgScope* signatureScope = makeChildScope(f->location, DfgScope::Function);
PushScope ps{scopeStack, signatureScope}; PushScope ps{scopeStack, signatureScope};
if (AstLocal* self = f->self) if (AstLocal* self = f->self)
@ -1032,7 +1015,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
// There's no syntax for `self` to have an annotation if using `function t:m()` // There's no syntax for `self` to have an annotation if using `function t:m()`
LUAU_ASSERT(!self->annotation); LUAU_ASSERT(!self->annotation);
DefId def = defArena->freshCell(f->debugname, f->location); DefId def = defArena->freshCell();
graph.localDefs[self] = def; graph.localDefs[self] = def;
signatureScope->bindings[self] = def; signatureScope->bindings[self] = def;
captures[self].allVersions.push_back(def); captures[self].allVersions.push_back(def);
@ -1043,7 +1026,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
if (param->annotation) if (param->annotation)
visitType(param->annotation); visitType(param->annotation);
DefId def = defArena->freshCell(param, param->location); DefId def = defArena->freshCell();
graph.localDefs[param] = def; graph.localDefs[param] = def;
signatureScope->bindings[param] = def; signatureScope->bindings[param] = def;
captures[param].allVersions.push_back(def); captures[param].allVersions.push_back(def);
@ -1052,16 +1035,8 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
if (f->varargAnnotation) if (f->varargAnnotation)
visitTypePack(f->varargAnnotation); visitTypePack(f->varargAnnotation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst) if (f->returnAnnotation)
{ visitTypeList(*f->returnAnnotation);
if (f->returnAnnotation)
visitTypePack(f->returnAnnotation);
}
else
{
if (f->returnAnnotation_DEPRECATED)
visitTypeList(*f->returnAnnotation_DEPRECATED);
}
// TODO: function body can be re-entrant, as in mutations that occurs at the end of the function can also be // TODO: function body can be re-entrant, as in mutations that occurs at the end of the function can also be
// visible to the beginning of the function, so statically speaking, the body of the function has an exit point // visible to the beginning of the function, so statically speaking, the body of the function has an exit point
@ -1073,16 +1048,13 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
// g() --> 5 // g() --> 5
visit(f->body); visit(f->body);
return {defArena->freshCell(f->debugname, f->location), nullptr}; return {defArena->freshCell(), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t)
{ {
DefId tableCell = defArena->freshCell(Symbol{}, t->location); DefId tableCell = defArena->freshCell();
if (FFlag::LuauDfgScopeStackNotNull) currentScope()->props[tableCell] = {};
currentScope()->props[tableCell] = {};
else
currentScope_DEPRECATED()->props[tableCell] = {};
for (AstExprTable::Item item : t->items) for (AstExprTable::Item item : t->items)
{ {
DataFlowResult result = visitExpr(item.value); DataFlowResult result = visitExpr(item.value);
@ -1090,12 +1062,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t)
{ {
visitExpr(item.key); visitExpr(item.key);
if (auto string = item.key->as<AstExprConstantString>()) if (auto string = item.key->as<AstExprConstantString>())
{ currentScope()->props[tableCell][string->value.data] = result.def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->props[tableCell][string->value.data] = result.def;
else
currentScope_DEPRECATED()->props[tableCell][string->value.data] = result.def;
}
} }
} }
@ -1106,7 +1073,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprUnary* u)
{ {
visitExpr(u->expr); visitExpr(u->expr);
return {defArena->freshCell(Symbol{}, u->location), nullptr}; return {defArena->freshCell(), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
@ -1114,7 +1081,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
visitExpr(b->left); visitExpr(b->left);
visitExpr(b->right); visitExpr(b->right);
return {defArena->freshCell(Symbol{}, b->location), nullptr}; return {defArena->freshCell(), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t)
@ -1131,7 +1098,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIfElse* i)
visitExpr(i->trueExpr); visitExpr(i->trueExpr);
visitExpr(i->falseExpr); visitExpr(i->falseExpr);
return {defArena->freshCell(Symbol{}, i->location), nullptr}; return {defArena->freshCell(), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i)
@ -1139,18 +1106,18 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i)
for (AstExpr* e : i->expressions) for (AstExpr* e : i->expressions)
visitExpr(e); visitExpr(e);
return {defArena->freshCell(Symbol{}, i->location), nullptr}; return {defArena->freshCell(), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprError* error) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprError* error)
{ {
DfgScope* unreachable = makeChildScope(); DfgScope* unreachable = makeChildScope(error->location);
PushScope ps{scopeStack, unreachable}; PushScope ps{scopeStack, unreachable};
for (AstExpr* e : error->expressions) for (AstExpr* e : error->expressions)
visitExpr(e); visitExpr(e);
return {defArena->freshCell(Symbol{}, error->location), nullptr}; return {defArena->freshCell(), nullptr};
} }
void DataFlowGraphBuilder::visitLValue(AstExpr* e, DefId incomingDef) void DataFlowGraphBuilder::visitLValue(AstExpr* e, DefId incomingDef)
@ -1176,12 +1143,12 @@ void DataFlowGraphBuilder::visitLValue(AstExpr* e, DefId incomingDef)
DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef) DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
{ {
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = currentScope();
// In order to avoid alias tracking, we need to clip the reference to the parent def. // In order to avoid alias tracking, we need to clip the reference to the parent def.
if (scope->canUpdateDefinition(l->local) && !(FFlag::LuauDoNotAddUpvalueTypesToLocalType && l->upvalue)) if (scope->canUpdateDefinition(l->local))
{ {
DefId updated = defArena->freshCell(l->local, l->location, containsSubscriptedDefinition(incomingDef)); DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
scope->bindings[l->local] = updated; scope->bindings[l->local] = updated;
captures[l->local].allVersions.push_back(updated); captures[l->local].allVersions.push_back(updated);
return updated; return updated;
@ -1192,12 +1159,12 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
DefId DataFlowGraphBuilder::visitLValue(AstExprGlobal* g, DefId incomingDef) DefId DataFlowGraphBuilder::visitLValue(AstExprGlobal* g, DefId incomingDef)
{ {
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = currentScope();
// In order to avoid alias tracking, we need to clip the reference to the parent def. // In order to avoid alias tracking, we need to clip the reference to the parent def.
if (scope->canUpdateDefinition(g->name)) if (scope->canUpdateDefinition(g->name))
{ {
DefId updated = defArena->freshCell(g->name, g->location, containsSubscriptedDefinition(incomingDef)); DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
scope->bindings[g->name] = updated; scope->bindings[g->name] = updated;
captures[g->name].allVersions.push_back(updated); captures[g->name].allVersions.push_back(updated);
return updated; return updated;
@ -1210,10 +1177,10 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexName* i, DefId incomingDef)
{ {
DefId parentDef = visitExpr(i->expr).def; DefId parentDef = visitExpr(i->expr).def;
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = currentScope();
if (scope->canUpdateDefinition(parentDef, i->index.value)) if (scope->canUpdateDefinition(parentDef, i->index.value))
{ {
DefId updated = defArena->freshCell(i->index, i->location, containsSubscriptedDefinition(incomingDef)); DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
scope->props[parentDef][i->index.value] = updated; scope->props[parentDef][i->index.value] = updated;
return updated; return updated;
} }
@ -1226,12 +1193,12 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexExpr* i, DefId incomingDef)
DefId parentDef = visitExpr(i->expr).def; DefId parentDef = visitExpr(i->expr).def;
visitExpr(i->index); visitExpr(i->index);
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = currentScope();
if (auto string = i->index->as<AstExprConstantString>()) if (auto string = i->index->as<AstExprConstantString>())
{ {
if (scope->canUpdateDefinition(parentDef, string->value.data)) if (scope->canUpdateDefinition(parentDef, string->value.data))
{ {
DefId updated = defArena->freshCell(Symbol{}, i->location, containsSubscriptedDefinition(incomingDef)); DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
scope->props[parentDef][string->value.data] = updated; scope->props[parentDef][string->value.data] = updated;
return updated; return updated;
} }
@ -1239,7 +1206,7 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexExpr* i, DefId incomingDef)
return visitExpr(static_cast<AstExpr*>(i)).def; return visitExpr(static_cast<AstExpr*>(i)).def;
} }
else else
return defArena->freshCell(Symbol{}, i->location, /*subscripted=*/true); return defArena->freshCell(/*subscripted=*/true);
} }
DefId DataFlowGraphBuilder::visitLValue(AstExprError* error, DefId incomingDef) DefId DataFlowGraphBuilder::visitLValue(AstExprError* error, DefId incomingDef)
@ -1257,8 +1224,6 @@ void DataFlowGraphBuilder::visitType(AstType* t)
return visitType(f); return visitType(f);
else if (auto tyof = t->as<AstTypeTypeof>()) else if (auto tyof = t->as<AstTypeTypeof>())
return visitType(tyof); return visitType(tyof);
else if (auto o = t->as<AstTypeOptional>())
return;
else if (auto u = t->as<AstTypeUnion>()) else if (auto u = t->as<AstTypeUnion>())
return visitType(u); return visitType(u);
else if (auto i = t->as<AstTypeIntersection>()) else if (auto i = t->as<AstTypeIntersection>())
@ -1269,8 +1234,6 @@ void DataFlowGraphBuilder::visitType(AstType* t)
return; // ok return; // ok
else if (auto s = t->as<AstTypeSingletonString>()) else if (auto s = t->as<AstTypeSingletonString>())
return; // ok return; // ok
else if (auto g = t->as<AstTypeGroup>())
return visitType(g->type);
else else
handle->ice("Unknown AstType in DataFlowGraphBuilder::visitType"); handle->ice("Unknown AstType in DataFlowGraphBuilder::visitType");
} }
@ -1303,10 +1266,7 @@ void DataFlowGraphBuilder::visitType(AstTypeFunction* f)
visitGenerics(f->generics); visitGenerics(f->generics);
visitGenericPacks(f->genericPacks); visitGenericPacks(f->genericPacks);
visitTypeList(f->argTypes); visitTypeList(f->argTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst) visitTypeList(f->returnTypes);
visitTypePack(f->returnTypes);
else
visitTypeList(f->returnTypes_DEPRECATED);
} }
void DataFlowGraphBuilder::visitType(AstTypeTypeof* t) void DataFlowGraphBuilder::visitType(AstTypeTypeof* t)
@ -1363,21 +1323,21 @@ void DataFlowGraphBuilder::visitTypeList(AstTypeList l)
visitTypePack(l.tailType); visitTypePack(l.tailType);
} }
void DataFlowGraphBuilder::visitGenerics(AstArray<AstGenericType*> g) void DataFlowGraphBuilder::visitGenerics(AstArray<AstGenericType> g)
{ {
for (AstGenericType* generic : g) for (AstGenericType generic : g)
{ {
if (generic->defaultValue) if (generic.defaultValue)
visitType(generic->defaultValue); visitType(generic.defaultValue);
} }
} }
void DataFlowGraphBuilder::visitGenericPacks(AstArray<AstGenericTypePack*> g) void DataFlowGraphBuilder::visitGenericPacks(AstArray<AstGenericTypePack> g)
{ {
for (AstGenericTypePack* generic : g) for (AstGenericTypePack generic : g)
{ {
if (generic->defaultValue) if (generic.defaultValue)
visitTypePack(generic->defaultValue); visitTypePack(generic.defaultValue);
} }
} }

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

@ -13,6 +13,7 @@
namespace Luau namespace Luau
{ {
std::string DiffPathNode::toString() const std::string DiffPathNode::toString() const
{ {
switch (kind) switch (kind)
@ -277,7 +278,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 +482,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 +652,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 +719,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();
@ -944,12 +945,14 @@ std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator DifferEnvironment
return visitingStack.crend(); return visitingStack.crend();
} }
DifferResult diff(TypeId ty1, TypeId ty2) DifferResult diff(TypeId ty1, TypeId ty2)
{ {
DifferEnvironment differEnv{ty1, ty2, std::nullopt, std::nullopt}; DifferEnvironment differEnv{ty1, ty2, std::nullopt, std::nullopt};
return diffUsingEnv(differEnv, ty1, ty2); return diffUsingEnv(differEnv, ty1, ty2);
} }
DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional<std::string> symbol1, std::optional<std::string> symbol2) DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional<std::string> symbol1, std::optional<std::string> symbol2)
{ {
DifferEnvironment differEnv{ty1, ty2, symbol1, symbol2}; DifferEnvironment differEnv{ty1, ty2, symbol1, symbol2};
@ -960,7 +963,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,105 @@
// 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(LuauMathMap)
LUAU_FASTFLAG(LuauTypeFunOptional)
namespace Luau namespace Luau
{ {
static const std::string kBuiltinDefinitionBaseSrc = R"BUILTIN_SRC( // TODO: there has to be a better way, like splitting up per library
static const std::string kBuiltinDefinitionLuaSrcChecked_DEPRECATED = 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,12 +147,88 @@ declare function loadstring<A...>(src: string, chunkname: string?): (((A...) ->
@checked declare function newproxy(mt: boolean?): any @checked declare function newproxy(mt: boolean?): any
declare coroutine: {
create: <A..., R...>(f: (A...) -> R...) -> thread,
resume: <A..., R...>(co: thread, A...) -> (boolean, R...),
running: () -> thread,
status: @checked (co: thread) -> "dead" | "running" | "normal" | "suspended",
wrap: <A..., R...>(f: (A...) -> R...) -> ((A...) -> R...),
yield: <A..., R...>(A...) -> R...,
isyieldable: () -> boolean,
close: @checked (co: thread) -> (boolean, any)
}
declare table: {
concat: <V>(t: {V}, sep: string?, i: number?, j: number?) -> string,
insert: (<V>(t: {V}, value: V) -> ()) & (<V>(t: {V}, pos: number, value: V) -> ()),
maxn: <V>(t: {V}) -> number,
remove: <V>(t: {V}, number?) -> V?,
sort: <V>(t: {V}, comp: ((V, V) -> boolean)?) -> (),
create: <V>(count: number, value: V?) -> {V},
find: <V>(haystack: {V}, needle: V, init: number?) -> number?,
unpack: <V>(list: {V}, i: number?, j: number?) -> ...V,
pack: <V>(...V) -> { n: number, [number]: V },
getn: <V>(t: {V}) -> number,
foreach: <K, V>(t: {[K]: V}, f: (K, V) -> ()) -> (),
foreachi: <V>({V}, (number, V) -> ()) -> (),
move: <V>(src: {V}, a: number, b: number, t: number, dst: {V}?) -> {V},
clear: <K, V>(table: {[K]: V}) -> (),
isfrozen: <K, V>(t: {[K]: V}) -> boolean,
}
declare debug: {
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),
}
declare utf8: {
char: @checked (...number) -> string,
charpattern: string,
codes: @checked (str: string) -> ((string, number) -> (number, number), string, number),
codepoint: @checked (str: string, i: number?, j: number?) -> ...number,
len: @checked (s: string, i: number?, j: number?) -> (number?, number?),
offset: @checked (s: string, n: number?, i: number?) -> number,
}
-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype. -- 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 declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
--- Buffer API
declare buffer: {
create: @checked (size: number) -> buffer,
fromstring: @checked (str: string) -> buffer,
tostring: @checked (b: buffer) -> string,
len: @checked (b: buffer) -> number,
copy: @checked (target: buffer, targetOffset: number, source: buffer, sourceOffset: number?, count: number?) -> (),
fill: @checked (b: buffer, offset: number, value: number, count: number?) -> (),
readi8: @checked (b: buffer, offset: number) -> number,
readu8: @checked (b: buffer, offset: number) -> number,
readi16: @checked (b: buffer, offset: number) -> number,
readu16: @checked (b: buffer, offset: number) -> number,
readi32: @checked (b: buffer, offset: number) -> number,
readu32: @checked (b: buffer, offset: number) -> number,
readf32: @checked (b: buffer, offset: number) -> number,
readf64: @checked (b: buffer, offset: number) -> number,
writei8: @checked (b: buffer, offset: number, value: number) -> (),
writeu8: @checked (b: buffer, offset: number, value: number) -> (),
writei16: @checked (b: buffer, offset: number, value: number) -> (),
writeu16: @checked (b: buffer, offset: number, value: number) -> (),
writei32: @checked (b: buffer, offset: number, value: number) -> (),
writeu32: @checked (b: buffer, offset: number, value: number) -> (),
writef32: @checked (b: buffer, offset: number, value: number) -> (),
writef64: @checked (b: buffer, offset: number, value: number) -> (),
readstring: @checked (b: buffer, offset: number, count: number) -> string,
writestring: @checked (b: buffer, offset: number, value: string, count: number?) -> (),
}
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionBit32Src = R"BUILTIN_SRC( static const std::string kBuiltinDefinitionLuaSrcChecked = R"BUILTIN_SRC(
declare bit32: { declare bit32: {
band: @checked (...number) -> number, band: @checked (...number) -> number,
@ -80,10 +248,6 @@ declare bit32: {
byteswap: @checked (n: number) -> number, byteswap: @checked (n: number) -> number,
} }
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionMathSrc = R"BUILTIN_SRC(
declare math: { declare math: {
frexp: @checked (n: number) -> (number, number), frexp: @checked (n: number) -> (number, number),
ldexp: @checked (s: number, e: number) -> number, ldexp: @checked (s: number, e: number) -> number,
@ -128,13 +292,8 @@ declare math: {
noise: @checked (x: number, y: number?, z: number?) -> number, noise: @checked (x: number, y: number?, z: number?) -> number,
round: @checked (n: number) -> number, round: @checked (n: number) -> number,
map: @checked (x: number, inmin: number, inmax: number, outmin: number, outmax: 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 = { type DateTypeArg = {
year: number, year: number,
month: number, month: number,
@ -164,9 +323,51 @@ declare os: {
clock: () -> number, clock: () -> number,
} }
)BUILTIN_SRC"; @checked declare function require(target: any): any
static const std::string kBuiltinDefinitionCoroutineSrc = R"BUILTIN_SRC( @checked declare function getfenv(target: any): { [string]: any }
declare _G: any
declare _VERSION: string
declare function gcinfo(): number
declare function print<T...>(...: T...)
declare function type<T>(value: T): string
declare function typeof<T>(value: T): string
-- `assert` has a magic function attached that will give more detailed type information
declare function assert<T>(value: T, errorMessage: string?): T
declare function error<T>(message: T, level: number?): never
declare function tostring<T>(value: T): string
declare function tonumber<T>(value: T, radix: number?): number?
declare function rawequal<T1, T2>(a: T1, b: T2): boolean
declare function rawget<K, V>(tab: {[K]: V}, k: K): V
declare function rawset<K, V>(tab: {[K]: V}, k: K, v: V): {[K]: V}
declare function rawlen<K, V>(obj: {[K]: V} | string): number
declare function setfenv<T..., R...>(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)?
declare function ipairs<V>(tab: {V}): (({V}, number) -> (number?, V), {V}, number)
declare function pcall<A..., R...>(f: (A...) -> R..., ...: A...): (boolean, R...)
-- FIXME: The actual type of `xpcall` is:
-- <E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., A...) -> (true, R1...) | (false, R2...)
-- Since we can't represent the return value, we use (boolean, R1...).
declare function xpcall<E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., ...: A...): (boolean, R1...)
-- `select` has a magic function attached to provide more detailed type information
declare function select<A...>(i: string | number, ...: A...): ...any
-- FIXME: This type is not entirely correct - `loadstring` returns a function or
-- (nil, string).
declare function loadstring<A...>(src: string, chunkname: string?): (((A...) -> any)?, string?)
@checked declare function newproxy(mt: boolean?): any
declare coroutine: { declare coroutine: {
create: <A..., R...>(f: (A...) -> R...) -> thread, create: <A..., R...>(f: (A...) -> R...) -> thread,
@ -179,10 +380,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 +402,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 +416,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 +446,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 = FFlag::LuauMathMap ? kBuiltinDefinitionLuaSrcChecked : kBuiltinDefinitionLuaSrcChecked_DEPRECATED;
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,6 @@
#include <unordered_set> #include <unordered_set>
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
static std::string wrongNumberOfArgsString( static std::string wrongNumberOfArgsString(
size_t expectedCount, size_t expectedCount,
@ -70,7 +68,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 +84,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 +116,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 +183,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 +351,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 +382,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;
@ -611,7 +591,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 +601,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 +638,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 +692,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 +751,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
@ -819,11 +791,6 @@ struct ErrorConverter
return e.message; 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 +1116,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;
} }
@ -1211,11 +1178,6 @@ bool UserDefinedTypeFunctionError::operator==(const UserDefinedTypeFunctionError
return message == rhs.message; 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 +1353,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);
@ -1435,9 +1397,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,20 @@ 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(LuauUserDefinedTypeFunctionNoEvaluation, false)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
LUAU_FASTFLAGVARIABLE(LuauMoreThoroughCycleDetection, false)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete) LUAU_FASTFLAG(StudioReportLuauAny2)
LUAU_FASTFLAGVARIABLE(LuauStoreDFGOnModule2, false)
namespace Luau namespace Luau
{ {
@ -77,20 +79,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 +117,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 +136,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;
@ -299,7 +287,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 +358,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 +447,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 +491,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 +515,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 +539,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 +609,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 +660,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 +671,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 +698,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 +748,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 +796,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 +884,18 @@ 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;
const Mode mode = sourceModule->mode.value_or(data.config.mode);
// 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()); {
if (FFlag::LuauMoreThoroughCycleDetection)
data.requireCycles = getRequireCycles(fileResolver, sourceNodes, sourceNode.get(), false);
else
data.requireCycles = getRequireCycles(fileResolver, sourceNodes, sourceNode.get(), mode == Mode::NoCheck);
}
data.options = frontendOptions; data.options = frontendOptions;
@ -951,7 +980,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 +1000,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 +1032,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 +1085,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 +1103,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 +1130,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 +1144,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 +1164,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 +1207,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 +1223,6 @@ ModulePtr check(
moduleResolver, moduleResolver,
fileResolver, fileResolver,
parentScope, parentScope,
typeFunctionScope,
std::move(prepareModuleScope), std::move(prepareModuleScope),
options, options,
limits, limits,
@ -1305,7 +1233,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 +1284,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 +1296,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 +1315,66 @@ ModulePtr check(
} }
} }
DataFlowGraph dfg = DataFlowGraphBuilder::build(sourceModule.root, NotNull{&result->defArena}, NotNull{&result->keyArena}, iceHandler); DataFlowGraph oldDfg = DataFlowGraphBuilder::build(sourceModule.root, iceHandler);
DataFlowGraph* dfgForConstraintGeneration = nullptr;
if (FFlag::LuauStoreDFGOnModule2)
{
auto [dfg, scopes] = DataFlowGraphBuilder::buildShared(sourceModule.root, iceHandler);
result->dataFlowGraph = std::move(dfg);
result->dfgScopes = std::move(scopes);
dfgForConstraintGeneration = result->dataFlowGraph.get();
}
else
{
dfgForConstraintGeneration = &oldDfg;
}
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 typeFunctionRuntime{iceHandler, NotNull{&limits}};
typeFunctionRuntime.allowEvaluation = FFlag::LuauTypeFunResultInAutocomplete || sourceModule.parseErrors.empty(); if (FFlag::LuauUserDefinedTypeFunctionNoEvaluation)
typeFunctionRuntime.allowEvaluation = sourceModule.parseErrors.empty();
ConstraintGenerator cg{ ConstraintGenerator cg{
result, result,
NotNull{&normalizer}, NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime}, NotNull{&typeFunctionRuntime},
moduleResolver, moduleResolver,
builtinTypes, builtinTypes,
iceHandler, iceHandler,
parentScope, parentScope,
typeFunctionScope,
std::move(prepareModuleScope), std::move(prepareModuleScope),
logger.get(), logger.get(),
NotNull{&dfg}, NotNull{dfgForConstraintGeneration},
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{&typeFunctionRuntime},
result->errors = std::move(constraintSet.errors); NotNull(cg.rootScope),
borrowConstraints(cg.constraints),
cs.emplace( result->name,
NotNull{&normalizer}, moduleResolver,
NotNull{simplifier.get()}, requireCycles,
NotNull{&typeFunctionRuntime}, logger.get(),
result->name, NotNull{dfgForConstraintGeneration},
moduleResolver, limits
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 +1394,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,30 +1419,38 @@ ModulePtr check(
switch (mode) switch (mode)
{ {
case Mode::Nonstrict: case Mode::Nonstrict:
Luau::checkNonStrict( if (FFlag::LuauStoreDFGOnModule2)
builtinTypes, {
NotNull{simplifier.get()}, Luau::checkNonStrict(
NotNull{&typeFunctionRuntime}, builtinTypes,
iceHandler, NotNull{&typeFunctionRuntime},
NotNull{&unifierState}, iceHandler,
NotNull{&dfg}, NotNull{&unifierState},
NotNull{&limits}, NotNull{dfgForConstraintGeneration},
sourceModule, NotNull{&limits},
result.get() sourceModule,
); result.get()
);
}
else
{
Luau::checkNonStrict(
builtinTypes,
NotNull{&typeFunctionRuntime},
iceHandler,
NotNull{&unifierState},
NotNull{&oldDfg},
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, builtinTypes, NotNull{&typeFunctionRuntime}, NotNull{&unifierState}, NotNull{&limits}, logger.get(), sourceModule, result.get()
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
NotNull{&unifierState},
NotNull{&limits},
logger.get(),
sourceModule,
result.get()
); );
break; break;
case Mode::NoCheck: case Mode::NoCheck:
@ -1625,7 +1535,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 +1632,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 +1753,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>)
@ -229,8 +229,6 @@ static void errorToString(std::ostream& stream, const T& err)
stream << "UnexpectedTypePackInSubtyping { tp = '" + toString(err.tp) + "' }"; stream << "UnexpectedTypePackInSubtyping { tp = '" + toString(err.tp) + "' }";
else if constexpr (std::is_same_v<T, UserDefinedTypeFunctionError>) else if constexpr (std::is_same_v<T, UserDefinedTypeFunctionError>)
stream << "UserDefinedTypeFunctionError { " << err.message << " }"; 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,11 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
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 +27,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 +41,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;
} }
); );
@ -154,25 +132,34 @@ struct ClonePublicInterface : Substitution
} }
ftv->level = TypeLevel{0, 0}; ftv->level = TypeLevel{0, 0};
if (FFlag::LuauSolverV2 && DFInt::LuauTypeSolverRelease >= 645)
ftv->scope = nullptr;
} }
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) if (FFlag::LuauSolverV2 && DFInt::LuauTypeSolverRelease >= 645)
ttv->scope = nullptr; ttv->scope = nullptr;
} }
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2 && DFInt::LuauTypeSolverRelease >= 645)
{ {
if (auto freety = getMutable<FreeType>(result)) if (auto freety = getMutable<FreeType>(result))
{ {
module->errors.emplace_back( if (DFInt::LuauTypeSolverRelease >= 646)
freety->scope->location, {
module->name, module->errors.emplace_back(
InternalError{"Free type is escaping its module; please report this bug at " freety->scope->location,
"https://github.com/luau-lang/luau/issues"} module->name,
); InternalError{"Free type is escaping its module; please report this bug at "
result = builtinTypes->errorRecoveryType(); "https://github.com/luau-lang/luau/issues"}
);
result = builtinTypes->errorRecoveryType();
}
else
{
freety->scope = nullptr;
}
} }
else if (auto genericty = getMutable<GenericType>(result)) else if (auto genericty = getMutable<GenericType>(result))
{ {
@ -185,18 +172,26 @@ struct ClonePublicInterface : Substitution
TypePackId clean(TypePackId tp) override TypePackId clean(TypePackId tp) override
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2 && DFInt::LuauTypeSolverRelease >= 645)
{ {
auto clonedTp = clone(tp); auto clonedTp = clone(tp);
if (auto ftp = getMutable<FreeTypePack>(clonedTp)) if (auto ftp = getMutable<FreeTypePack>(clonedTp))
{ {
module->errors.emplace_back(
ftp->scope->location, if (DFInt::LuauTypeSolverRelease >= 646)
module->name, {
InternalError{"Free type pack is escaping its module; please report this bug at " module->errors.emplace_back(
"https://github.com/luau-lang/luau/issues"} ftp->scope->location,
); module->name,
clonedTp = builtinTypes->errorRecoveryTypePack(); InternalError{"Free type pack is escaping its module; please report this bug at "
"https://github.com/luau-lang/luau/issues"}
);
clonedTp = builtinTypes->errorRecoveryTypePack();
}
else
{
ftp->scope = nullptr;
}
} }
else if (auto gtp = getMutable<GenericTypePack>(clonedTp)) else if (auto gtp = getMutable<GenericTypePack>(clonedTp))
gtp->scope = nullptr; gtp->scope = nullptr;
@ -265,10 +260,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

View file

@ -15,14 +15,14 @@
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include "Luau/Unifier.h" #include "Luau/Unifier.h"
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant) LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200) LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100) LUAU_FASTFLAGVARIABLE(LuauNormalizationTracksCyclicPairsThroughInhabitance, false);
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauIntersectNormalsNeedsToTrackResourceLimits, false);
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
LUAU_FASTFLAGVARIABLE(LuauNormalizationCatchMetatableCycles)
namespace Luau namespace Luau
{ {
@ -249,23 +249,23 @@ bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& s
return true; return true;
} }
void NormalizedExternType::pushPair(TypeId ty, TypeIds negations) void NormalizedClassType::pushPair(TypeId ty, TypeIds negations)
{ {
auto result = externTypes.insert(std::make_pair(ty, std::move(negations))); auto result = classes.insert(std::make_pair(ty, std::move(negations)));
if (result.second) if (result.second)
ordering.push_back(ty); ordering.push_back(ty);
LUAU_ASSERT(ordering.size() == externTypes.size()); LUAU_ASSERT(ordering.size() == classes.size());
} }
void NormalizedExternType::resetToNever() void NormalizedClassType::resetToNever()
{ {
ordering.clear(); ordering.clear();
externTypes.clear(); classes.clear();
} }
bool NormalizedExternType::isNever() const bool NormalizedClassType::isNever() const
{ {
return externTypes.empty(); return classes.empty();
} }
void NormalizedFunctionType::resetToTop() void NormalizedFunctionType::resetToTop()
@ -304,17 +304,17 @@ bool NormalizedType::isUnknown() const
// Otherwise, we can still be unknown! // Otherwise, we can still be unknown!
bool hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) && bool hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) &&
strings.isString() && isThread(threads) && isBuffer(buffers); strings.isString() && isPrim(threads, PrimitiveType::Thread) && isThread(threads);
// Check is class // Check is class
bool isTopExternType = false; bool isTopClass = false;
for (const auto& [t, disj] : externTypes.externTypes) for (auto [t, disj] : classes.classes)
{ {
if (auto ct = get<ExternType>(t)) if (auto ct = get<ClassType>(t))
{ {
if (ct->name == "class" && disj.empty()) if (ct->name == "class" && disj.empty())
{ {
isTopExternType = true; isTopClass = true;
break; break;
} }
} }
@ -330,24 +330,24 @@ bool NormalizedType::isUnknown() const
} }
} }
// any = unknown or error ==> we need to make sure we have all the unknown components, but not errors // any = unknown or error ==> we need to make sure we have all the unknown components, but not errors
return get<NeverType>(errors) && hasAllPrimitives && isTopExternType && isTopTable && functions.isTop; return get<NeverType>(errors) && hasAllPrimitives && isTopClass && isTopTable && functions.isTop;
} }
bool NormalizedType::isExactlyNumber() const bool NormalizedType::isExactlyNumber() const
{ {
return hasNumbers() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() && return hasNumbers() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars(); !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
} }
bool NormalizedType::isSubtypeOfString() const bool NormalizedType::isSubtypeOfString() const
{ {
return hasStrings() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() && return hasStrings() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars(); !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
} }
bool NormalizedType::isSubtypeOfBooleans() const bool NormalizedType::isSubtypeOfBooleans() const
{ {
return hasBooleans() && !hasTops() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() && return hasBooleans() && !hasTops() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars(); !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
} }
@ -380,9 +380,9 @@ bool NormalizedType::hasBooleans() const
return !get<NeverType>(booleans); return !get<NeverType>(booleans);
} }
bool NormalizedType::hasExternTypes() const bool NormalizedType::hasClasses() const
{ {
return !externTypes.isNever(); return !classes.isNever();
} }
bool NormalizedType::hasErrors() const bool NormalizedType::hasErrors() const
@ -440,7 +440,7 @@ bool NormalizedType::isFalsy() const
hasAFalse = !bs->value; hasAFalse = !bs->value;
} }
return (hasAFalse || hasNils()) && (!hasTops() && !hasExternTypes() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() && return (hasAFalse || hasNils()) && (!hasTops() && !hasClasses() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars()); !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars());
} }
@ -452,7 +452,7 @@ bool NormalizedType::isTruthy() const
static bool isShallowInhabited(const NormalizedType& norm) static bool isShallowInhabited(const NormalizedType& norm)
{ {
// This test is just a shallow check, for example it returns `true` for `{ p : never }` // This test is just a shallow check, for example it returns `true` for `{ p : never }`
return !get<NeverType>(norm.tops) || !get<NeverType>(norm.booleans) || !norm.externTypes.isNever() || !get<NeverType>(norm.errors) || return !get<NeverType>(norm.tops) || !get<NeverType>(norm.booleans) || !norm.classes.isNever() || !get<NeverType>(norm.errors) ||
!get<NeverType>(norm.nils) || !get<NeverType>(norm.numbers) || !norm.strings.isNever() || !get<NeverType>(norm.threads) || !get<NeverType>(norm.nils) || !get<NeverType>(norm.numbers) || !norm.strings.isNever() || !get<NeverType>(norm.threads) ||
!get<NeverType>(norm.buffers) || !norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty(); !get<NeverType>(norm.buffers) || !norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty();
} }
@ -471,7 +471,7 @@ NormalizationResult Normalizer::isInhabited(const NormalizedType* norm, Set<Type
return NormalizationResult::HitLimits; return NormalizationResult::HitLimits;
if (!get<NeverType>(norm->tops) || !get<NeverType>(norm->booleans) || !get<NeverType>(norm->errors) || !get<NeverType>(norm->nils) || if (!get<NeverType>(norm->tops) || !get<NeverType>(norm->booleans) || !get<NeverType>(norm->errors) || !get<NeverType>(norm->nils) ||
!get<NeverType>(norm->numbers) || !get<NeverType>(norm->threads) || !get<NeverType>(norm->buffers) || !norm->externTypes.isNever() || !get<NeverType>(norm->numbers) || !get<NeverType>(norm->threads) || !get<NeverType>(norm->buffers) || !norm->classes.isNever() ||
!norm->strings.isNever() || !norm->functions.isNever()) !norm->strings.isNever() || !norm->functions.isNever())
return NormalizationResult::True; return NormalizationResult::True;
@ -579,7 +579,7 @@ NormalizationResult Normalizer::isIntersectionInhabited(TypeId left, TypeId righ
{ {
left = follow(left); left = follow(left);
right = follow(right); right = follow(right);
// We're asking if intersection is inhabited between left and right but we've already seen them .... // We're asking if intersection is inahbited between left and right but we've already seen them ....
if (cacheInhabitance) if (cacheInhabitance)
{ {
@ -619,13 +619,13 @@ static int tyvarIndex(TypeId ty)
return 0; return 0;
} }
static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedExternType& externTypes) static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedClassType& classes)
{ {
if (externTypes.externTypes.size() != 1) if (classes.classes.size() != 1)
return false; return false;
auto first = externTypes.externTypes.begin(); auto first = classes.classes.begin();
if (first->first != builtinTypes->externType) if (first->first != builtinTypes->classType)
return false; return false;
if (!first->second.empty()) if (!first->second.empty())
@ -634,11 +634,11 @@ static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedExternType
return true; return true;
} }
static void resetToTop(NotNull<BuiltinTypes> builtinTypes, NormalizedExternType& externTypes) static void resetToTop(NotNull<BuiltinTypes> builtinTypes, NormalizedClassType& classes)
{ {
externTypes.ordering.clear(); classes.ordering.clear();
externTypes.externTypes.clear(); classes.classes.clear();
externTypes.pushPair(builtinTypes->externType, TypeIds{}); classes.pushPair(builtinTypes->classType, TypeIds{});
} }
#ifdef LUAU_ASSERTENABLED #ifdef LUAU_ASSERTENABLED
@ -762,50 +762,50 @@ static bool areNormalizedTables(const TypeIds& tys)
return true; return true;
} }
static bool areNormalizedExternTypes(const NormalizedExternType& tys) static bool areNormalizedClasses(const NormalizedClassType& tys)
{ {
for (const auto& [ty, negations] : tys.externTypes) for (const auto& [ty, negations] : tys.classes)
{ {
const ExternType* etv = get<ExternType>(ty); const ClassType* ctv = get<ClassType>(ty);
if (!etv) if (!ctv)
{ {
return false; return false;
} }
for (TypeId negation : negations) for (TypeId negation : negations)
{ {
const ExternType* nctv = get<ExternType>(negation); const ClassType* nctv = get<ClassType>(negation);
if (!nctv) if (!nctv)
{ {
return false; return false;
} }
if (!isSubclass(nctv, etv)) if (!isSubclass(nctv, ctv))
{ {
return false; return false;
} }
} }
for (const auto& [otherTy, otherNegations] : tys.externTypes) for (const auto& [otherTy, otherNegations] : tys.classes)
{ {
if (otherTy == ty) if (otherTy == ty)
continue; continue;
const ExternType* octv = get<ExternType>(otherTy); const ClassType* octv = get<ClassType>(otherTy);
if (!octv) if (!octv)
{ {
return false; return false;
} }
if (isSubclass(etv, octv)) if (isSubclass(ctv, octv))
{ {
auto iss = [etv](TypeId t) auto iss = [ctv](TypeId t)
{ {
const ExternType* c = get<ExternType>(t); const ClassType* c = get<ClassType>(t);
if (!c) if (!c)
return false; return false;
return isSubclass(etv, c); return isSubclass(ctv, c);
}; };
if (!std::any_of(otherNegations.begin(), otherNegations.end(), iss)) if (!std::any_of(otherNegations.begin(), otherNegations.end(), iss))
@ -847,7 +847,7 @@ static void assertInvariant(const NormalizedType& norm)
LUAU_ASSERT(isNormalizedTop(norm.tops)); LUAU_ASSERT(isNormalizedTop(norm.tops));
LUAU_ASSERT(isNormalizedBoolean(norm.booleans)); LUAU_ASSERT(isNormalizedBoolean(norm.booleans));
LUAU_ASSERT(areNormalizedExternTypes(norm.externTypes)); LUAU_ASSERT(areNormalizedClasses(norm.classes));
LUAU_ASSERT(isNormalizedError(norm.errors)); LUAU_ASSERT(isNormalizedError(norm.errors));
LUAU_ASSERT(isNormalizedNil(norm.nils)); LUAU_ASSERT(isNormalizedNil(norm.nils));
LUAU_ASSERT(isNormalizedNumber(norm.numbers)); LUAU_ASSERT(isNormalizedNumber(norm.numbers));
@ -988,7 +988,7 @@ void Normalizer::clearNormal(NormalizedType& norm)
{ {
norm.tops = builtinTypes->neverType; norm.tops = builtinTypes->neverType;
norm.booleans = builtinTypes->neverType; norm.booleans = builtinTypes->neverType;
norm.externTypes.resetToNever(); norm.classes.resetToNever();
norm.errors = builtinTypes->neverType; norm.errors = builtinTypes->neverType;
norm.nils = builtinTypes->neverType; norm.nils = builtinTypes->neverType;
norm.numbers = builtinTypes->neverType; norm.numbers = builtinTypes->neverType;
@ -1138,17 +1138,17 @@ TypeId Normalizer::unionOfBools(TypeId here, TypeId there)
return builtinTypes->booleanType; return builtinTypes->booleanType;
} }
void Normalizer::unionExternTypesWithExternType(TypeIds& heres, TypeId there) void Normalizer::unionClassesWithClass(TypeIds& heres, TypeId there)
{ {
if (heres.count(there)) if (heres.count(there))
return; return;
const ExternType* tctv = get<ExternType>(there); const ClassType* tctv = get<ClassType>(there);
for (auto it = heres.begin(); it != heres.end();) for (auto it = heres.begin(); it != heres.end();)
{ {
TypeId here = *it; TypeId here = *it;
const ExternType* hctv = get<ExternType>(here); const ClassType* hctv = get<ClassType>(here);
if (isSubclass(tctv, hctv)) if (isSubclass(tctv, hctv))
return; return;
else if (isSubclass(hctv, tctv)) else if (isSubclass(hctv, tctv))
@ -1160,16 +1160,16 @@ void Normalizer::unionExternTypesWithExternType(TypeIds& heres, TypeId there)
heres.insert(there); heres.insert(there);
} }
void Normalizer::unionExternTypes(TypeIds& heres, const TypeIds& theres) void Normalizer::unionClasses(TypeIds& heres, const TypeIds& theres)
{ {
for (TypeId there : theres) for (TypeId there : theres)
unionExternTypesWithExternType(heres, there); unionClassesWithClass(heres, there);
} }
static bool isSubclass(TypeId test, TypeId parent) static bool isSubclass(TypeId test, TypeId parent)
{ {
const ExternType* testCtv = get<ExternType>(test); const ClassType* testCtv = get<ClassType>(test);
const ExternType* parentCtv = get<ExternType>(parent); const ClassType* parentCtv = get<ClassType>(parent);
LUAU_ASSERT(testCtv); LUAU_ASSERT(testCtv);
LUAU_ASSERT(parentCtv); LUAU_ASSERT(parentCtv);
@ -1177,12 +1177,12 @@ static bool isSubclass(TypeId test, TypeId parent)
return isSubclass(testCtv, parentCtv); return isSubclass(testCtv, parentCtv);
} }
void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there) void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
{ {
for (auto it = heres.ordering.begin(); it != heres.ordering.end();) for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{ {
TypeId hereTy = *it; TypeId hereTy = *it;
TypeIds& hereNegations = heres.externTypes.at(hereTy); TypeIds& hereNegations = heres.classes.at(hereTy);
// If the incoming class is a subclass of another class in the map, we // If the incoming class is a subclass of another class in the map, we
// must ensure that it is negated by one of the negations in the same // must ensure that it is negated by one of the negations in the same
@ -1204,7 +1204,7 @@ void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, Typ
} }
// If the incoming class is a superclass of one of the // If the incoming class is a superclass of one of the
// negations, then the negation no longer applies and must be // negations, then the negation no longer applies and must be
// removed. This is also true if they are equal. Since extern types // removed. This is also true if they are equal. Since classes
// are, at this time, entirely persistent (we do not clone // are, at this time, entirely persistent (we do not clone
// them), a pointer identity check is sufficient. // them), a pointer identity check is sufficient.
else if (isSubclass(hereNegation, there)) else if (isSubclass(hereNegation, there))
@ -1231,7 +1231,7 @@ void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, Typ
{ {
TypeIds negations = std::move(hereNegations); TypeIds negations = std::move(hereNegations);
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy); heres.classes.erase(hereTy);
heres.pushPair(there, std::move(negations)); heres.pushPair(there, std::move(negations));
return; return;
@ -1248,10 +1248,10 @@ void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, Typ
heres.pushPair(there, TypeIds{}); heres.pushPair(there, TypeIds{});
} }
void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres) void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassType& theres)
{ {
// This method bears much similarity with unionExternTypesWithExternType, but is // This method bears much similarity with unionClassesWithClass, but is
// solving a more general problem. In unionExternTypesWithExternType, we are dealing // solving a more general problem. In unionClassesWithClass, we are dealing
// with a singular positive type. Since it's one type, we can use early // with a singular positive type. Since it's one type, we can use early
// returns as control flow. Since it's guaranteed to be positive, we do not // returns as control flow. Since it's guaranteed to be positive, we do not
// have negations to worry about combining. The two aspects combine to make // have negations to worry about combining. The two aspects combine to make
@ -1260,9 +1260,9 @@ void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedE
for (const TypeId thereTy : theres.ordering) for (const TypeId thereTy : theres.ordering)
{ {
const TypeIds& thereNegations = theres.externTypes.at(thereTy); const TypeIds& thereNegations = theres.classes.at(thereTy);
// If it happens that there are _no_ extern types in the current map, or the // If it happens that there are _no_ classes in the current map, or the
// incoming class is completely unrelated to any class in the current // incoming class is completely unrelated to any class in the current
// map, we must insert the incoming pair as-is. // map, we must insert the incoming pair as-is.
bool insert = true; bool insert = true;
@ -1270,7 +1270,7 @@ void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedE
for (auto it = heres.ordering.begin(); it != heres.ordering.end();) for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{ {
TypeId hereTy = *it; TypeId hereTy = *it;
TypeIds& hereNegations = heres.externTypes.at(hereTy); TypeIds& hereNegations = heres.classes.at(hereTy);
if (isSubclass(thereTy, hereTy)) if (isSubclass(thereTy, hereTy))
{ {
@ -1294,7 +1294,7 @@ void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedE
// If the incoming class is a superclass of one of the // If the incoming class is a superclass of one of the
// negations, then the negation no longer applies and must // negations, then the negation no longer applies and must
// be removed. This is also true if they are equal. Since // be removed. This is also true if they are equal. Since
// extern types are, at this time, entirely persistent (we do not // classes are, at this time, entirely persistent (we do not
// clone them), a pointer identity check is sufficient. // clone them), a pointer identity check is sufficient.
else if (isSubclass(hereNegateTy, thereTy)) else if (isSubclass(hereNegateTy, thereTy))
{ {
@ -1319,17 +1319,17 @@ void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedE
else if (isSubclass(hereTy, thereTy)) else if (isSubclass(hereTy, thereTy))
{ {
TypeIds negations = std::move(hereNegations); TypeIds negations = std::move(hereNegations);
unionExternTypes(negations, thereNegations); unionClasses(negations, thereNegations);
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy); heres.classes.erase(hereTy);
heres.pushPair(thereTy, std::move(negations)); heres.pushPair(thereTy, std::move(negations));
insert = false; insert = false;
break; break;
} }
else if (hereTy == thereTy) else if (hereTy == thereTy)
{ {
unionExternTypes(hereNegations, thereNegations); unionClasses(hereNegations, thereNegations);
insert = false; insert = false;
break; break;
} }
@ -1685,12 +1685,8 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
return res; return res;
} }
// Limit based on worst-case expansion of the function unions
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeUnionLimit))
return NormalizationResult::HitLimits;
here.booleans = unionOfBools(here.booleans, there.booleans); here.booleans = unionOfBools(here.booleans, there.booleans);
unionExternTypes(here.externTypes, there.externTypes); unionClasses(here.classes, there.classes);
here.errors = (get<NeverType>(there.errors) ? here.errors : there.errors); here.errors = (get<NeverType>(there.errors) ? here.errors : there.errors);
here.nils = (get<NeverType>(there.nils) ? here.nils : there.nils); here.nils = (get<NeverType>(there.nils) ? here.nils : there.nils);
@ -1700,7 +1696,6 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers); here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers);
unionFunctions(here.functions, there.functions); unionFunctions(here.functions, there.functions);
unionTables(here.tables, there.tables); unionTables(here.tables, there.tables);
return NormalizationResult::True; return NormalizationResult::True;
} }
@ -1740,7 +1735,7 @@ NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, N
return NormalizationResult::True; return NormalizationResult::True;
} }
// See above for an explanation of `ignoreSmallerTyvars`. // See above for an explaination of `ignoreSmallerTyvars`.
NormalizationResult Normalizer::unionNormalWithTy( NormalizationResult Normalizer::unionNormalWithTy(
NormalizedType& here, NormalizedType& here,
TypeId there, TypeId there,
@ -1814,8 +1809,7 @@ NormalizationResult Normalizer::unionNormalWithTy(
} }
else if (get<UnknownType>(here.tops)) else if (get<UnknownType>(here.tops))
return NormalizationResult::True; return NormalizationResult::True;
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) || else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) || get<TypeFunctionInstanceType>(there))
get<TypeFunctionInstanceType>(there))
{ {
if (tyvarIndex(there) <= ignoreSmallerTyvars) if (tyvarIndex(there) <= ignoreSmallerTyvars)
return NormalizationResult::True; return NormalizationResult::True;
@ -1830,8 +1824,8 @@ NormalizationResult Normalizer::unionNormalWithTy(
unionFunctionsWithFunction(here.functions, there); unionFunctionsWithFunction(here.functions, there);
else if (get<TableType>(there) || get<MetatableType>(there)) else if (get<TableType>(there) || get<MetatableType>(there))
unionTablesWithTable(here.tables, there); unionTablesWithTable(here.tables, there);
else if (get<ExternType>(there)) else if (get<ClassType>(there))
unionExternTypesWithExternType(here.externTypes, there); unionClassesWithClass(here.classes, there);
else if (get<ErrorType>(there)) else if (get<ErrorType>(there))
here.errors = there; here.errors = there;
else if (const PrimitiveType* ptv = get<PrimitiveType>(there)) else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
@ -1944,29 +1938,29 @@ std::optional<NormalizedType> Normalizer::negateNormal(const NormalizedType& her
result.booleans = builtinTypes->trueType; result.booleans = builtinTypes->trueType;
} }
if (here.externTypes.isNever()) if (here.classes.isNever())
{ {
resetToTop(builtinTypes, result.externTypes); resetToTop(builtinTypes, result.classes);
} }
else if (isTop(builtinTypes, result.externTypes)) else if (isTop(builtinTypes, result.classes))
{ {
result.externTypes.resetToNever(); result.classes.resetToNever();
} }
else else
{ {
TypeIds rootNegations{}; TypeIds rootNegations{};
for (const auto& [hereParent, hereNegations] : here.externTypes.externTypes) for (const auto& [hereParent, hereNegations] : here.classes.classes)
{ {
if (hereParent != builtinTypes->externType) if (hereParent != builtinTypes->classType)
rootNegations.insert(hereParent); rootNegations.insert(hereParent);
for (TypeId hereNegation : hereNegations) for (TypeId hereNegation : hereNegations)
unionExternTypesWithExternType(result.externTypes, hereNegation); unionClassesWithClass(result.classes, hereNegation);
} }
if (!rootNegations.empty()) if (!rootNegations.empty())
result.externTypes.pushPair(builtinTypes->externType, rootNegations); result.classes.pushPair(builtinTypes->classType, rootNegations);
} }
result.nils = get<NeverType>(here.nils) ? builtinTypes->nilType : builtinTypes->neverType; result.nils = get<NeverType>(here.nils) ? builtinTypes->nilType : builtinTypes->neverType;
@ -2144,7 +2138,7 @@ TypeId Normalizer::intersectionOfBools(TypeId here, TypeId there)
return there; return there;
} }
void Normalizer::intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres) void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres)
{ {
if (theres.isNever()) if (theres.isNever())
{ {
@ -2178,12 +2172,12 @@ void Normalizer::intersectExternTypes(NormalizedExternType& heres, const Normali
// declare the result of the intersection operation to be never. // declare the result of the intersection operation to be never.
for (const TypeId thereTy : theres.ordering) for (const TypeId thereTy : theres.ordering)
{ {
const TypeIds& thereNegations = theres.externTypes.at(thereTy); const TypeIds& thereNegations = theres.classes.at(thereTy);
for (auto it = heres.ordering.begin(); it != heres.ordering.end();) for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{ {
TypeId hereTy = *it; TypeId hereTy = *it;
TypeIds& hereNegations = heres.externTypes.at(hereTy); TypeIds& hereNegations = heres.classes.at(hereTy);
if (isSubclass(thereTy, hereTy)) if (isSubclass(thereTy, hereTy))
{ {
@ -2206,10 +2200,10 @@ void Normalizer::intersectExternTypes(NormalizedExternType& heres, const Normali
} }
} }
unionExternTypes(negations, thereNegations); unionClasses(negations, thereNegations);
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy); heres.classes.erase(hereTy);
heres.pushPair(thereTy, std::move(negations)); heres.pushPair(thereTy, std::move(negations));
break; break;
} }
@ -2234,15 +2228,15 @@ void Normalizer::intersectExternTypes(NormalizedExternType& heres, const Normali
{ {
if (isSubclass(hereTy, *nIt)) if (isSubclass(hereTy, *nIt))
{ {
// eg SomeExternType & (class & ~SomeExternType) // eg SomeClass & (class & ~SomeClass)
// or SomeExternType & (class & ~ParentExternType) // or SomeClass & (class & ~ParentClass)
heres.externTypes.erase(hereTy); heres.classes.erase(hereTy);
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
erasedHere = true; erasedHere = true;
break; break;
} }
// eg SomeExternType & (class & ~Unrelated) // eg SomeClass & (class & ~Unrelated)
if (!isSubclass(*nIt, hereTy)) if (!isSubclass(*nIt, hereTy))
nIt = negations.erase(nIt); nIt = negations.erase(nIt);
else else
@ -2251,30 +2245,30 @@ void Normalizer::intersectExternTypes(NormalizedExternType& heres, const Normali
if (!erasedHere) if (!erasedHere)
{ {
unionExternTypes(hereNegations, negations); unionClasses(hereNegations, negations);
++it; ++it;
} }
} }
else if (hereTy == thereTy) else if (hereTy == thereTy)
{ {
unionExternTypes(hereNegations, thereNegations); unionClasses(hereNegations, thereNegations);
break; break;
} }
else else
{ {
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy); heres.classes.erase(hereTy);
} }
} }
} }
} }
void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there) void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId there)
{ {
for (auto it = heres.ordering.begin(); it != heres.ordering.end();) for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{ {
TypeId hereTy = *it; TypeId hereTy = *it;
const TypeIds& hereNegations = heres.externTypes.at(hereTy); const TypeIds& hereNegations = heres.classes.at(hereTy);
// If the incoming class _is_ the current class, we skip it. Maybe // If the incoming class _is_ the current class, we skip it. Maybe
// another entry will have a different story. We check for this first // another entry will have a different story. We check for this first
@ -2290,24 +2284,9 @@ void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres,
else if (isSubclass(there, hereTy)) else if (isSubclass(there, hereTy))
{ {
TypeIds negations = std::move(hereNegations); TypeIds negations = std::move(hereNegations);
bool emptyIntersectWithNegation = false;
for (auto nIt = negations.begin(); nIt != negations.end();) for (auto nIt = negations.begin(); nIt != negations.end();)
{ {
if (isSubclass(there, *nIt))
{
// Hitting this block means that the incoming class is a
// subclass of this type, _and_ one of its negations is a
// superclass of this type, e.g.:
//
// Dog & ~Animal
//
// Clearly this intersects to never, so we mark this class as
// being removed from the normalized class type.
emptyIntersectWithNegation = true;
break;
}
if (!isSubclass(*nIt, there)) if (!isSubclass(*nIt, there))
{ {
nIt = negations.erase(nIt); nIt = negations.erase(nIt);
@ -2319,9 +2298,8 @@ void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres,
} }
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy); heres.classes.erase(hereTy);
if (!emptyIntersectWithNegation) heres.pushPair(there, std::move(negations));
heres.pushPair(there, std::move(negations));
break; break;
} }
// If the incoming class is a superclass of the current class, we don't // If the incoming class is a superclass of the current class, we don't
@ -2335,7 +2313,7 @@ void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres,
else else
{ {
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy); heres.classes.erase(hereTy);
} }
} }
} }
@ -2606,31 +2584,11 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
{ {
if (tprop.readTy.has_value()) if (tprop.readTy.has_value())
{ {
if (FFlag::LuauFixInfiniteRecursionInNormalization) // if the intersection of the read types of a property is uninhabited, the whole table is `never`.
// We've seen these table prop elements before and we're about to ask if their intersection
// is inhabited
if (FFlag::LuauNormalizationTracksCyclicPairsThroughInhabitance)
{ {
TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result;
// If any property is going to get mapped to `never`, we can just call the entire table `never`.
// Since this check is syntactic, we may sometimes miss simplifying tables with complex uninhabited properties.
// Prior versions of this code attempted to do this semantically using the normalization machinery, but this
// mistakenly causes infinite loops when giving more complex recursive table types. As it stands, this approach
// will continue to scale as simplification is improved, but we may wish to reintroduce the semantic approach
// once we have revisited the usage of seen sets systematically (and possibly with some additional guarding to recognize
// when types are infinitely-recursive with non-pointer identical instances of them, or some guard to prevent that
// construction altogether). See also: `gh1632_no_infinite_recursion_in_normalization`
if (get<NeverType>(ty))
return {builtinTypes->neverType};
prop.readTy = ty;
hereSubThere &= (ty == hprop.readTy);
thereSubHere &= (ty == tprop.readTy);
}
else
{
// if the intersection of the read types of a property is uninhabited, the whole table is `never`.
// We've seen these table prop elements before and we're about to ask if their intersection
// is inhabited
auto pair1 = std::pair{*hprop.readTy, *tprop.readTy}; auto pair1 = std::pair{*hprop.readTy, *tprop.readTy};
auto pair2 = std::pair{*tprop.readTy, *hprop.readTy}; auto pair2 = std::pair{*tprop.readTy, *hprop.readTy};
if (seenTablePropPairs.contains(pair1) || seenTablePropPairs.contains(pair2)) if (seenTablePropPairs.contains(pair1) || seenTablePropPairs.contains(pair2))
@ -2645,8 +2603,6 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
seenTablePropPairs.insert(pair2); seenTablePropPairs.insert(pair2);
} }
// FIXME(ariel): this is being added in a flag removal, so not changing the semantics here, but worth noting that this
// fresh `seenSet` is definitely a bug. we already have `seenSet` from the parameter that _should_ have been used here.
Set<TypeId> seenSet{nullptr}; Set<TypeId> seenSet{nullptr};
NormalizationResult res = isIntersectionInhabited(*hprop.readTy, *tprop.readTy, seenTablePropPairs, seenSet); NormalizationResult res = isIntersectionInhabited(*hprop.readTy, *tprop.readTy, seenTablePropPairs, seenSet);
@ -2660,6 +2616,34 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
hereSubThere &= (ty == hprop.readTy); hereSubThere &= (ty == hprop.readTy);
thereSubHere &= (ty == tprop.readTy); thereSubHere &= (ty == tprop.readTy);
} }
else
{
if (seenSet.contains(*hprop.readTy) && seenSet.contains(*tprop.readTy))
{
seenSet.erase(*hprop.readTy);
seenSet.erase(*tprop.readTy);
return {builtinTypes->neverType};
}
else
{
seenSet.insert(*hprop.readTy);
seenSet.insert(*tprop.readTy);
}
NormalizationResult res = isIntersectionInhabited(*hprop.readTy, *tprop.readTy);
seenSet.erase(*hprop.readTy);
seenSet.erase(*tprop.readTy);
if (NormalizationResult::True != res)
return {builtinTypes->neverType};
TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result;
prop.readTy = ty;
hereSubThere &= (ty == hprop.readTy);
thereSubHere &= (ty == tprop.readTy);
}
} }
else else
{ {
@ -3055,12 +3039,15 @@ NormalizationResult Normalizer::intersectTyvarsWithTy(
return NormalizationResult::True; return NormalizationResult::True;
} }
// See above for an explanation of `ignoreSmallerTyvars`. // See above for an explaination of `ignoreSmallerTyvars`.
NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars) NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars)
{ {
RecursionCounter _rc(&sharedState->counters.recursionCount); if (FFlag::LuauIntersectNormalsNeedsToTrackResourceLimits)
if (!withinResourceLimits()) {
return NormalizationResult::HitLimits; RecursionCounter _rc(&sharedState->counters.recursionCount);
if (!withinResourceLimits())
return NormalizationResult::HitLimits;
}
if (!get<NeverType>(there.tops)) if (!get<NeverType>(there.tops))
{ {
@ -3073,17 +3060,14 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
return unionNormals(here, there, ignoreSmallerTyvars); return unionNormals(here, there, ignoreSmallerTyvars);
} }
// Limit based on worst-case expansion of the table/function intersections // Limit based on worst-case expansion of the table intersection
// This restriction can be relaxed when table intersection simplification is improved // This restriction can be relaxed when table intersection simplification is improved
if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit)) if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
return NormalizationResult::HitLimits; return NormalizationResult::HitLimits;
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
return NormalizationResult::HitLimits;
here.booleans = intersectionOfBools(here.booleans, there.booleans); here.booleans = intersectionOfBools(here.booleans, there.booleans);
intersectExternTypes(here.externTypes, there.externTypes); intersectClasses(here.classes, there.classes);
here.errors = (get<NeverType>(there.errors) ? there.errors : here.errors); here.errors = (get<NeverType>(there.errors) ? there.errors : here.errors);
here.nils = (get<NeverType>(there.nils) ? there.nils : here.nils); here.nils = (get<NeverType>(there.nils) ? there.nils : here.nils);
here.numbers = (get<NeverType>(there.numbers) ? there.numbers : here.numbers); here.numbers = (get<NeverType>(there.numbers) ? there.numbers : here.numbers);
@ -3178,8 +3162,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
} }
return NormalizationResult::True; return NormalizationResult::True;
} }
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) || else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) || get<TypeFunctionInstanceType>(there))
get<TypeFunctionInstanceType>(there))
{ {
NormalizedType thereNorm{builtinTypes}; NormalizedType thereNorm{builtinTypes};
NormalizedType topNorm{builtinTypes}; NormalizedType topNorm{builtinTypes};
@ -3205,18 +3188,18 @@ NormalizationResult Normalizer::intersectNormalWithTy(
intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes); intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes);
here.tables = std::move(tables); here.tables = std::move(tables);
} }
else if (get<ExternType>(there)) else if (get<ClassType>(there))
{ {
NormalizedExternType nct = std::move(here.externTypes); NormalizedClassType nct = std::move(here.classes);
clearNormal(here); clearNormal(here);
intersectExternTypesWithExternType(nct, there); intersectClassesWithClass(nct, there);
here.externTypes = std::move(nct); here.classes = std::move(nct);
} }
else if (get<ErrorType>(there)) else if (get<ErrorType>(there))
{ {
TypeId errors = here.errors; TypeId errors = here.errors;
clearNormal(here); clearNormal(here);
here.errors = get<ErrorType>(errors) ? errors : there; here.errors = errors;
} }
else if (const PrimitiveType* ptv = get<PrimitiveType>(there)) else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
{ {
@ -3274,7 +3257,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
subtractPrimitive(here, ntv->ty); subtractPrimitive(here, ntv->ty);
else if (const SingletonType* stv = get<SingletonType>(t)) else if (const SingletonType* stv = get<SingletonType>(t))
subtractSingleton(here, follow(ntv->ty)); subtractSingleton(here, follow(ntv->ty));
else if (get<ExternType>(t)) else if (get<ClassType>(t))
{ {
NormalizationResult res = intersectNormalWithNegationTy(t, here); NormalizationResult res = intersectNormalWithNegationTy(t, here);
if (shouldEarlyExit(res)) if (shouldEarlyExit(res))
@ -3313,18 +3296,8 @@ NormalizationResult Normalizer::intersectNormalWithTy(
clearNormal(here); clearNormal(here);
return NormalizationResult::True; return NormalizationResult::True;
} }
else if (get<ErrorType>(t))
{
// ~error is still an error, so intersecting with the negation is the same as intersecting with a type
TypeId errors = here.errors;
clearNormal(here);
here.errors = get<ErrorType>(errors) ? errors : t;
}
else if (auto nt = get<NegationType>(t)) else if (auto nt = get<NegationType>(t))
{
here.tyvars = std::move(tyvars);
return intersectNormalWithTy(here, nt->ty, seenTablePropPairs, seenSetTypes); return intersectNormalWithTy(here, nt->ty, seenTablePropPairs, seenSetTypes);
}
else else
{ {
// TODO negated unions, intersections, table, and function. // TODO negated unions, intersections, table, and function.
@ -3334,7 +3307,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
} }
else if (get<NeverType>(there)) else if (get<NeverType>(there))
{ {
here.externTypes.resetToNever(); here.classes.resetToNever();
} }
else if (get<NoRefineType>(there)) else if (get<NoRefineType>(there))
{ {
@ -3352,43 +3325,19 @@ NormalizationResult Normalizer::intersectNormalWithTy(
return NormalizationResult::True; return NormalizationResult::True;
} }
void makeTableShared_DEPRECATED(TypeId ty)
{
ty = follow(ty);
if (auto tableTy = getMutable<TableType>(ty))
{
for (auto& [_, prop] : tableTy->props)
prop.makeShared();
}
else if (auto metatableTy = get<MetatableType>(ty))
{
makeTableShared_DEPRECATED(metatableTy->metatable);
makeTableShared_DEPRECATED(metatableTy->table);
}
}
void makeTableShared(TypeId ty, DenseHashSet<TypeId>& seen)
{
ty = follow(ty);
if (seen.contains(ty))
return;
seen.insert(ty);
if (auto tableTy = getMutable<TableType>(ty))
{
for (auto& [_, prop] : tableTy->props)
prop.makeShared();
}
else if (auto metatableTy = get<MetatableType>(ty))
{
makeTableShared(metatableTy->metatable, seen);
makeTableShared(metatableTy->table, seen);
}
}
void makeTableShared(TypeId ty) void makeTableShared(TypeId ty)
{ {
DenseHashSet<TypeId> seen{nullptr}; ty = follow(ty);
makeTableShared(ty, seen); if (auto tableTy = getMutable<TableType>(ty))
{
for (auto& [_, prop] : tableTy->props)
prop.makeShared();
}
else if (auto metatableTy = get<MetatableType>(ty))
{
makeTableShared(metatableTy->metatable);
makeTableShared(metatableTy->table);
}
} }
// -------- Convert back from a normalized type to a type // -------- Convert back from a normalized type to a type
@ -3403,18 +3352,18 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
if (!get<NeverType>(norm.booleans)) if (!get<NeverType>(norm.booleans))
result.push_back(norm.booleans); result.push_back(norm.booleans);
if (isTop(builtinTypes, norm.externTypes)) if (isTop(builtinTypes, norm.classes))
{ {
result.push_back(builtinTypes->externType); result.push_back(builtinTypes->classType);
} }
else if (!norm.externTypes.isNever()) else if (!norm.classes.isNever())
{ {
std::vector<TypeId> parts; std::vector<TypeId> parts;
parts.reserve(norm.externTypes.externTypes.size()); parts.reserve(norm.classes.classes.size());
for (const TypeId normTy : norm.externTypes.ordering) for (const TypeId normTy : norm.classes.ordering)
{ {
const TypeIds& normNegations = norm.externTypes.externTypes.at(normTy); const TypeIds& normNegations = norm.classes.classes.at(normTy);
if (normNegations.empty()) if (normNegations.empty())
{ {
@ -3490,10 +3439,7 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
result.reserve(result.size() + norm.tables.size()); result.reserve(result.size() + norm.tables.size());
for (auto table : norm.tables) for (auto table : norm.tables)
{ {
if (FFlag::LuauNormalizationCatchMetatableCycles) makeTableShared(table);
makeTableShared(table);
else
makeTableShared_DEPRECATED(table);
result.push_back(table); result.push_back(table);
} }
} }
@ -3519,14 +3465,7 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
return arena->addType(UnionType{std::move(result)}); return arena->addType(UnionType{std::move(result)});
} }
bool isSubtype( bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice)
TypeId subTy,
TypeId superTy,
NotNull<Scope> scope,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
InternalErrorReporter& ice
)
{ {
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
TypeArena arena; TypeArena arena;
@ -3539,7 +3478,7 @@ bool isSubtype(
// Subtyping under DCR is not implemented using unification! // Subtyping under DCR is not implemented using unification!
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}}; Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}};
return subtyping.isSubtype(subTy, superTy, scope).isSubtype; return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
} }
@ -3552,14 +3491,7 @@ bool isSubtype(
} }
} }
bool isSubtype( bool isSubtype(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice)
TypePackId subPack,
TypePackId superPack,
NotNull<Scope> scope,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
InternalErrorReporter& ice
)
{ {
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
TypeArena arena; TypeArena arena;
@ -3572,7 +3504,7 @@ bool isSubtype(
// Subtyping under DCR is not implemented using unification! // Subtyping under DCR is not implemented using unification!
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}}; Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}};
return subtyping.isSubtype(subPack, superPack, scope).isSubtype; return subtyping.isSubtype(subPack, superPack, scope).isSubtype;
} }

View file

@ -10,15 +10,12 @@
#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<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<Scope> scope, NotNull<Scope> scope,
@ -28,13 +25,12 @@ OverloadResolver::OverloadResolver(
) )
: builtinTypes(builtinTypes) : builtinTypes(builtinTypes)
, arena(arena) , arena(arena)
, simplifier(simplifier)
, normalizer(normalizer) , normalizer(normalizer)
, typeFunctionRuntime(typeFunctionRuntime) , 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, typeFunctionRuntime, ice})
, callLoc(callLocation) , callLoc(callLocation)
{ {
} }
@ -206,7 +202,7 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
) )
{ {
FunctionGraphReductionResult result = reduceTypeFunctions( FunctionGraphReductionResult result = reduceTypeFunctions(
fnTy, callLoc, TypeFunctionContext{arena, builtinTypes, scope, simplifier, normalizer, typeFunctionRuntime, ice, limits}, /*force=*/true fnTy, callLoc, TypeFunctionContext{arena, builtinTypes, scope, normalizer, typeFunctionRuntime, ice, limits}, /*force=*/true
); );
if (!result.errors.empty()) if (!result.errors.empty())
return {OverloadIsNonviable, result.errors}; return {OverloadIsNonviable, result.errors};
@ -256,32 +252,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}};
}
} }
} }
@ -425,10 +404,9 @@ 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<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<Scope> scope, NotNull<Scope> scope,
@ -439,9 +417,8 @@ static std::optional<TypeId> selectOverload(
TypePackId argsPack TypePackId argsPack
) )
{ {
auto resolver = OverloadResolver resolver{builtinTypes, arena, normalizer, typeFunctionRuntime, 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,7 +432,6 @@ 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<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> iceReporter, NotNull<InternalErrorReporter> iceReporter,
@ -467,22 +443,22 @@ SolveResult solveFunctionCall(
) )
{ {
std::optional<TypeId> overloadToUse = std::optional<TypeId> overloadToUse =
selectOverload(builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, scope, iceReporter, limits, location, fn, argsPack); selectOverload(builtinTypes, arena, 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

@ -6,7 +6,6 @@
#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 +14,8 @@
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(LuauFlagBasicIntersectFollows, false);
LUAU_FASTFLAGVARIABLE(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType)
namespace Luau namespace Luau
{ {
@ -34,27 +31,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);
@ -318,14 +313,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 +328,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 +353,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 +361,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 +444,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 +499,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 +512,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 +535,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 +573,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 +707,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 +722,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 +762,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 +840,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,8 +1066,11 @@ 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); if (FFlag::LuauFlagBasicIntersectFollows)
right = follow(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;
@ -1310,25 +1179,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 +1411,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 +1426,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 +1437,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)>;
@ -58,25 +57,8 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
} }
else if constexpr (std::is_same_v<T, ErrorType>) else if constexpr (std::is_same_v<T, ErrorType>)
{ {
if (FFlag::LuauSyntheticErrors) LUAU_ASSERT(ty->persistent);
{ return ty;
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>)
{ {
@ -94,15 +76,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 +118,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};
return dest.addType(std::move(clone)); return dest.addType(std::move(clone));
} }
else else
@ -252,21 +239,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 +527,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 +537,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 +637,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 +799,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 +853,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

View file

@ -7,11 +7,13 @@
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/StringUtils.h"
#include "Luau/Substitution.h" #include "Luau/Substitution.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/TxnLog.h" #include "Luau/TxnLog.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFunction.h" #include "Luau/TypeFunction.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/TypePath.h" #include "Luau/TypePath.h"
@ -19,9 +21,8 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity, false);
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit)
namespace Luau namespace Luau
{ {
@ -31,7 +32,7 @@ struct VarianceFlipper
Subtyping::Variance* variance; Subtyping::Variance* variance;
Subtyping::Variance oldValue; Subtyping::Variance oldValue;
explicit VarianceFlipper(Subtyping::Variance* v) VarianceFlipper(Subtyping::Variance* v)
: variance(v) : variance(v)
, oldValue(*v) , oldValue(*v)
{ {
@ -99,9 +100,6 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
else else
result.insert(r); result.insert(r);
} }
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
return result;
} }
for (const SubtypingReasoning& r : b) for (const SubtypingReasoning& r : b)
@ -118,9 +116,6 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
else else
result.insert(r); result.insert(r);
} }
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
return result;
} }
return result; return result;
@ -313,7 +308,7 @@ struct ApplyMappedGenerics : Substitution
bool ignoreChildren(TypeId ty) override bool ignoreChildren(TypeId ty) override
{ {
if (get<ExternType>(ty)) if (get<ClassType>(ty))
return true; return true;
return ty->persistent; return ty->persistent;
@ -401,14 +396,12 @@ TypePackId* SubtypingEnvironment::getMappedPackBounds(TypePackId tp)
Subtyping::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<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> iceReporter NotNull<InternalErrorReporter> iceReporter
) )
: builtinTypes(builtinTypes) : builtinTypes(builtinTypes)
, arena(typeArena) , arena(typeArena)
, simplifier(simplifier)
, normalizer(normalizer) , normalizer(normalizer)
, typeFunctionRuntime(typeFunctionRuntime) , typeFunctionRuntime(typeFunctionRuntime)
, iceReporter(iceReporter) , iceReporter(iceReporter)
@ -421,14 +414,6 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope); SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
if (result.normalizationTooComplex)
{
if (result.isCacheable)
resultCache[{subTy, superTy}] = result;
return result;
}
for (const auto& [subTy, bounds] : env.mappedGenerics) for (const auto& [subTy, bounds] : env.mappedGenerics)
{ {
const auto& lb = bounds.lowerBound; const auto& lb = bounds.lowerBound;
@ -606,12 +591,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
if (!result.isSubtype && !result.normalizationTooComplex) if (!result.isSubtype && !result.normalizationTooComplex)
{ {
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
if (semantic.isSubtype)
if (semantic.normalizationTooComplex)
{
result = semantic;
}
else if (semantic.isSubtype)
{ {
semantic.reasoning.clear(); semantic.reasoning.clear();
result = semantic; result = semantic;
@ -626,12 +606,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
if (!result.isSubtype && !result.normalizationTooComplex) if (!result.isSubtype && !result.normalizationTooComplex)
{ {
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
if (semantic.isSubtype)
if (semantic.normalizationTooComplex)
{
result = semantic;
}
else if (semantic.isSubtype)
{ {
// Clear the semantic reasoning, as any reasonings within // Clear the semantic reasoning, as any reasonings within
// potentially contain invalid paths. // potentially contain invalid paths.
@ -742,9 +717,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
else if (auto p = get2<MetatableType, TableType>(subTy, superTy)) else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
else if (auto p = get2<ExternType, ExternType>(subTy, superTy)) else if (auto p = get2<ClassType, ClassType>(subTy, superTy))
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
else if (auto p = get2<ExternType, TableType>(subTy, superTy)) else if (auto p = get2<ClassType, TableType>(subTy, superTy))
result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope); result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope);
else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy)) else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy))
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
@ -777,8 +752,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
// Match head types pairwise // Match head types pairwise
for (size_t i = 0; i < headSize; ++i) for (size_t i = 0; i < headSize; ++i)
results.push_back(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}) results.push_back(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i}));
);
// Handle mismatched head sizes // Handle mismatched head sizes
@ -791,7 +765,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
for (size_t i = headSize; i < superHead.size(); ++i) for (size_t i = headSize; i < superHead.size(); ++i)
results.push_back(isCovariantWith(env, vt->ty, superHead[i], scope) results.push_back(isCovariantWith(env, vt->ty, superHead[i], scope)
.withSubPath(TypePath::PathBuilder().tail().variadic().build()) .withSubPath(TypePath::PathBuilder().tail().variadic().build())
.withSuperComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})); .withSuperComponent(TypePath::Index{i}));
} }
else if (auto gt = get<GenericTypePack>(*subTail)) else if (auto gt = get<GenericTypePack>(*subTail))
{ {
@ -845,7 +819,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
{ {
for (size_t i = headSize; i < subHead.size(); ++i) for (size_t i = headSize; i < subHead.size(); ++i)
results.push_back(isCovariantWith(env, subHead[i], vt->ty, scope) results.push_back(isCovariantWith(env, subHead[i], vt->ty, scope)
.withSubComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}) .withSubComponent(TypePath::Index{i})
.withSuperPath(TypePath::PathBuilder().tail().variadic().build())); .withSuperPath(TypePath::PathBuilder().tail().variadic().build()));
} }
else if (auto gt = get<GenericTypePack>(*superTail)) else if (auto gt = get<GenericTypePack>(*superTail))
@ -883,7 +857,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
else else
return SubtypingResult{false} return SubtypingResult{false}
.withSuperComponent(TypePath::PackField::Tail) .withSuperComponent(TypePath::PackField::Tail)
.withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}}); .withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}});
} }
else else
return {false}; return {false};
@ -1106,10 +1080,6 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
for (TypeId ty : superUnion) for (TypeId ty : superUnion)
{ {
SubtypingResult next = isCovariantWith(env, subTy, ty, scope); SubtypingResult next = isCovariantWith(env, subTy, ty, scope);
if (next.normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
if (next.isSubtype) if (next.isSubtype)
return SubtypingResult{true}; return SubtypingResult{true};
} }
@ -1128,13 +1098,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Unio
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
size_t i = 0; size_t i = 0;
for (TypeId ty : subUnion) for (TypeId ty : subUnion)
{ subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++}));
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union}));
if (subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
return SubtypingResult::all(subtypings); return SubtypingResult::all(subtypings);
} }
@ -1144,13 +1108,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
size_t i = 0; size_t i = 0;
for (TypeId ty : superIntersection) for (TypeId ty : superIntersection)
{ subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++}));
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
return SubtypingResult::all(subtypings); return SubtypingResult::all(subtypings);
} }
@ -1160,13 +1118,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Inte
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
size_t i = 0; size_t i = 0;
for (TypeId ty : subIntersection) for (TypeId ty : subIntersection)
{ subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++}));
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
return SubtypingResult::any(subtypings); return SubtypingResult::any(subtypings);
} }
@ -1334,7 +1286,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
} }
// the top class type is not actually a primitive type, so the negation of // the top class type is not actually a primitive type, so the negation of
// any one of them includes the top class type. // any one of them includes the top class type.
else if (auto p = get2<ExternType, PrimitiveType>(subTy, negatedTy)) else if (auto p = get2<ClassType, PrimitiveType>(subTy, negatedTy))
result = {true}; result = {true};
else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy)) else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy))
result = {p->type != PrimitiveType::Table}; result = {p->type != PrimitiveType::Table};
@ -1342,9 +1294,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
result = {p.second->type != PrimitiveType::Function}; result = {p.second->type != PrimitiveType::Function};
else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy)) else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy))
result = {*p.first != *p.second}; result = {*p.first != *p.second};
else if (auto p = get2<ExternType, ExternType>(subTy, negatedTy)) else if (auto p = get2<ClassType, ClassType>(subTy, negatedTy))
result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope)); result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope));
else if (get2<FunctionType, ExternType>(subTy, negatedTy)) else if (get2<FunctionType, ClassType>(subTy, negatedTy))
result = {true}; result = {true};
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy)) else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
iceReporter->ice("attempting to negate a non-testable type"); iceReporter->ice("attempting to negate a non-testable type");
@ -1442,9 +1394,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope) SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope)
{ {
return isCovariantWith(env, subMt->table, superMt->table, scope) if (DFInt::LuauTypeSolverRelease >= 646)
.withBothComponent(TypePath::TypeField::Table) {
.andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable)); return isCovariantWith(env, subMt->table, superMt->table, scope)
.withBothComponent(TypePath::TypeField::Table)
.andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable));
}
else
{
return isCovariantWith(env, subMt->table, superMt->table, scope)
.andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable));
}
} }
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope) SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope)
@ -1456,7 +1416,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
// of the supertype table. // of the supertype table.
// //
// There's a flaw here in that if the __index metamethod contributes a new // There's a flaw here in that if the __index metamethod contributes a new
// field that would satisfy the subtyping relationship, we'll erroneously say // field that would satisfy the subtyping relationship, we'll erronously say
// that the metatable isn't a subtype of the table, even though they have // that the metatable isn't a subtype of the table, even though they have
// compatible properties/shapes. We'll revisit this later when we have a // compatible properties/shapes. We'll revisit this later when we have a
// better understanding of how important this is. // better understanding of how important this is.
@ -1469,15 +1429,15 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
} }
} }
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull<Scope> scope) SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope)
{ {
return {isSubclass(subExternType, superExternType)}; return {isSubclass(subClass, superClass)};
} }
SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
TypeId subTy, TypeId subTy,
const ExternType* subExternType, const ClassType* subClass,
TypeId superTy, TypeId superTy,
const TableType* superTable, const TableType* superTable,
NotNull<Scope> scope NotNull<Scope> scope
@ -1489,7 +1449,7 @@ SubtypingResult Subtyping::isCovariantWith(
for (const auto& [name, prop] : superTable->props) for (const auto& [name, prop] : superTable->props)
{ {
if (auto classProp = lookupExternTypeProp(subExternType, name)) if (auto classProp = lookupClassProp(subClass, name))
{ {
result.andAlso(isCovariantWith(env, *classProp, prop, name, scope)); result.andAlso(isCovariantWith(env, *classProp, prop, name, scope));
} }
@ -1517,19 +1477,6 @@ SubtypingResult Subtyping::isCovariantWith(
result.orElse( result.orElse(
isContravariantWith(env, subFunction->argTypes, superFunction->argTypes, scope).withBothComponent(TypePath::PackField::Arguments) isContravariantWith(env, subFunction->argTypes, superFunction->argTypes, scope).withBothComponent(TypePath::PackField::Arguments)
); );
// If subtyping failed in the argument packs, we should check if there's a hidden variadic tail and try ignoring it.
// This might cause subtyping correctly because the sub type here may not have a hidden variadic tail or equivalent.
if (!result.isSubtype)
{
auto [arguments, tail] = flatten(superFunction->argTypes);
if (auto variadic = get<VariadicTypePack>(tail); variadic && variadic->hidden)
{
result.orElse(isContravariantWith(env, subFunction->argTypes, arena->addTypePack(TypePack{arguments}), scope)
.withBothComponent(TypePath::PackField::Arguments));
}
}
} }
result.andAlso(isCovariantWith(env, subFunction->retTypes, superFunction->retTypes, scope).withBothComponent(TypePath::PackField::Returns)); result.andAlso(isCovariantWith(env, subFunction->retTypes, superFunction->retTypes, scope).withBothComponent(TypePath::PackField::Returns));
@ -1659,7 +1606,7 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope); SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope);
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope)); result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope));
result.andAlso( result.andAlso(
isCovariantWith(env, subNorm->externTypes, superNorm->externTypes, scope).orElse(isCovariantWith(env, subNorm->externTypes, superNorm->tables, scope)) isCovariantWith(env, subNorm->classes, superNorm->classes, scope).orElse(isCovariantWith(env, subNorm->classes, superNorm->tables, scope))
); );
result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope)); result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope));
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope)); result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope));
@ -1676,24 +1623,24 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const NormalizedExternType& subExternType, const NormalizedClassType& subClass,
const NormalizedExternType& superExternType, const NormalizedClassType& superClass,
NotNull<Scope> scope NotNull<Scope> scope
) )
{ {
for (const auto& [subExternTypeTy, _] : subExternType.externTypes) for (const auto& [subClassTy, _] : subClass.classes)
{ {
SubtypingResult result; SubtypingResult result;
for (const auto& [superExternTypeTy, superNegations] : superExternType.externTypes) for (const auto& [superClassTy, superNegations] : superClass.classes)
{ {
result.orElse(isCovariantWith(env, subExternTypeTy, superExternTypeTy, scope)); result.orElse(isCovariantWith(env, subClassTy, superClassTy, scope));
if (!result.isSubtype) if (!result.isSubtype)
continue; continue;
for (TypeId negation : superNegations) for (TypeId negation : superNegations)
{ {
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subExternTypeTy, negation, scope))); result.andAlso(SubtypingResult::negate(isCovariantWith(env, subClassTy, negation, scope)));
if (result.isSubtype) if (result.isSubtype)
break; break;
} }
@ -1708,17 +1655,17 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const NormalizedExternType& subExternType, const NormalizedClassType& subClass,
const TypeIds& superTables, const TypeIds& superTables,
NotNull<Scope> scope NotNull<Scope> scope
) )
{ {
for (const auto& [subExternTypeTy, _] : subExternType.externTypes) for (const auto& [subClassTy, _] : subClass.classes)
{ {
SubtypingResult result; SubtypingResult result;
for (TypeId superTableTy : superTables) for (TypeId superTableTy : superTables)
result.orElse(isCovariantWith(env, subExternTypeTy, superTableTy, scope)); result.orElse(isCovariantWith(env, subClassTy, superTableTy, scope));
if (!result.isSubtype) if (!result.isSubtype)
return result; return result;
@ -1806,12 +1753,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
{ {
results.emplace_back(); results.emplace_back();
for (TypeId superTy : superTypes) for (TypeId superTy : superTypes)
{
results.back().orElse(isCovariantWith(env, subTy, superTy, scope)); results.back().orElse(isCovariantWith(env, subTy, superTy, scope));
if (results.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
} }
return SubtypingResult::all(results); return SubtypingResult::all(results);
@ -1913,7 +1855,7 @@ TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse)
std::pair<TypeId, ErrorVec> Subtyping::handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance, NotNull<Scope> scope) std::pair<TypeId, ErrorVec> Subtyping::handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance, NotNull<Scope> scope)
{ {
TypeFunctionContext context{arena, builtinTypes, scope, simplifier, normalizer, typeFunctionRuntime, iceReporter, NotNull{&limits}}; TypeFunctionContext context{arena, builtinTypes, scope, normalizer, typeFunctionRuntime, iceReporter, NotNull{&limits}};
TypeId function = arena->addType(*functionInstance); TypeId function = arena->addType(*functionInstance);
FunctionGraphReductionResult result = reduceTypeFunctions(function, {}, context, true); FunctionGraphReductionResult result = reduceTypeFunctions(function, {}, context, true);
ErrorVec errors; ErrorVec errors;

View file

@ -14,8 +14,10 @@ bool Symbol::operator==(const Symbol& rhs) const
return local == rhs.local; return local == rhs.local;
else if (global.value) else if (global.value)
return rhs.global.value && global == rhs.global.value; // Subtlety: AstName::operator==(const char*) uses strcmp, not pointer identity. return rhs.global.value && global == rhs.global.value; // Subtlety: AstName::operator==(const char*) uses strcmp, not pointer identity.
else else if (FFlag::LuauSolverV2)
return !rhs.local && !rhs.global.value; // Reflexivity: we already know `this` Symbol is empty, so check that rhs is. return !rhs.local && !rhs.global.value; // Reflexivity: we already know `this` Symbol is empty, so check that rhs is.
else
return false;
} }
std::string toString(const Symbol& name) std::string toString(const Symbol& name)

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