mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/648 (#1477)
Some checks failed
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Has been cancelled
build / ${{matrix.os.name}} (map[name:macos version:macos-latest]) (push) Has been cancelled
build / ${{matrix.os.name}} (map[name:macos-arm version:macos-14]) (push) Has been cancelled
build / ${{matrix.os.name}} (map[name:ubuntu version:ubuntu-latest]) (push) Has been cancelled
build / windows (Win32) (push) Has been cancelled
build / windows (x64) (push) Has been cancelled
build / coverage (push) Has been cancelled
build / web (push) Has been cancelled
release / ${{matrix.os.name}} (map[name:macos version:macos-latest]) (push) Has been cancelled
release / ${{matrix.os.name}} (map[name:ubuntu version:ubuntu-20.04]) (push) Has been cancelled
release / ${{matrix.os.name}} (map[name:windows version:windows-latest]) (push) Has been cancelled
release / web (push) Has been cancelled
Some checks failed
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Has been cancelled
build / ${{matrix.os.name}} (map[name:macos version:macos-latest]) (push) Has been cancelled
build / ${{matrix.os.name}} (map[name:macos-arm version:macos-14]) (push) Has been cancelled
build / ${{matrix.os.name}} (map[name:ubuntu version:ubuntu-latest]) (push) Has been cancelled
build / windows (Win32) (push) Has been cancelled
build / windows (x64) (push) Has been cancelled
build / coverage (push) Has been cancelled
build / web (push) Has been cancelled
release / ${{matrix.os.name}} (map[name:macos version:macos-latest]) (push) Has been cancelled
release / ${{matrix.os.name}} (map[name:ubuntu version:ubuntu-20.04]) (push) Has been cancelled
release / ${{matrix.os.name}} (map[name:windows version:windows-latest]) (push) Has been cancelled
release / web (push) Has been cancelled
## What's new * Added `math.map` function to the standard library, based on https://rfcs.luau-lang.org/function-math-map.html * `FileResolver` can provide an implementation of `getRequireSuggestions` to provide auto-complete suggestions for require-by-string ## New Solver * In user-defined type functions, `readproperty` and `writeproperty` will return `nil` instead of erroring if property is not found * Fixed incorrect scope of variadic arguments in the data-flow graph * Fixed multiple assertion failures --- Internal Contributors: Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Varun Saini <vsaini@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
parent
d7842e08ae
commit
e491128f95
@ -39,6 +39,7 @@ enum class AutocompleteEntryKind
|
||||
Type,
|
||||
Module,
|
||||
GeneratedFunction,
|
||||
RequirePath,
|
||||
};
|
||||
|
||||
enum class ParenthesesRecommendation
|
||||
|
@ -9,6 +9,8 @@
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static constexpr char kRequireTagName[] = "require";
|
||||
|
||||
struct Frontend;
|
||||
struct GlobalTypes;
|
||||
struct TypeChecker;
|
||||
|
@ -68,7 +68,6 @@ private:
|
||||
DenseHashMap<const AstExpr*, const Def*> compoundAssignDefs{nullptr};
|
||||
|
||||
DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr};
|
||||
|
||||
friend struct DataFlowGraphBuilder;
|
||||
};
|
||||
|
||||
@ -83,6 +82,7 @@ struct DfgScope
|
||||
|
||||
DfgScope* parent;
|
||||
ScopeType scopeType;
|
||||
Location location;
|
||||
|
||||
using Bindings = DenseHashMap<Symbol, const Def*>;
|
||||
using Props = DenseHashMap<const Def*, std::unordered_map<std::string, const Def*>>;
|
||||
@ -105,10 +105,44 @@ struct DataFlowResult
|
||||
const RefinementKey* parent = nullptr;
|
||||
};
|
||||
|
||||
using ScopeStack = std::vector<DfgScope*>;
|
||||
|
||||
struct DataFlowGraphBuilder
|
||||
{
|
||||
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,
|
||||
NotNull<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:
|
||||
DataFlowGraphBuilder() = default;
|
||||
|
||||
@ -120,10 +154,15 @@ private:
|
||||
NotNull<RefinementKeyArena> keyArena{&graph.keyArena};
|
||||
|
||||
struct InternalErrorReporter* handle = nullptr;
|
||||
DfgScope* moduleScope = nullptr;
|
||||
|
||||
/// The arena owning all of the scope allocations for the dataflow graph being built.
|
||||
std::vector<std::unique_ptr<DfgScope>> scopes;
|
||||
|
||||
/// A stack of scopes used by the visitor to see where we are.
|
||||
ScopeStack scopeStack;
|
||||
|
||||
DfgScope* currentScope();
|
||||
|
||||
struct FunctionCapture
|
||||
{
|
||||
std::vector<DefId> captureDefs;
|
||||
@ -134,81 +173,81 @@ private:
|
||||
DenseHashMap<Symbol, FunctionCapture> captures{Symbol{}};
|
||||
void resolveCaptures();
|
||||
|
||||
DfgScope* childScope(DfgScope* scope, DfgScope::ScopeType scopeType = DfgScope::Linear);
|
||||
DfgScope* makeChildScope(Location loc, DfgScope::ScopeType scopeType = DfgScope::Linear);
|
||||
|
||||
void join(DfgScope* p, DfgScope* a, DfgScope* b);
|
||||
void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b);
|
||||
void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b);
|
||||
|
||||
DefId lookup(DfgScope* scope, Symbol symbol);
|
||||
DefId lookup(DfgScope* scope, DefId def, const std::string& key);
|
||||
DefId lookup(Symbol symbol);
|
||||
DefId lookup(DefId def, const std::string& key);
|
||||
|
||||
ControlFlow visit(DfgScope* scope, AstStatBlock* b);
|
||||
ControlFlow visitBlockWithoutChildScope(DfgScope* scope, AstStatBlock* b);
|
||||
ControlFlow visit(AstStatBlock* b);
|
||||
ControlFlow visitBlockWithoutChildScope(AstStatBlock* b);
|
||||
|
||||
ControlFlow visit(DfgScope* scope, AstStat* s);
|
||||
ControlFlow visit(DfgScope* scope, AstStatIf* i);
|
||||
ControlFlow visit(DfgScope* scope, AstStatWhile* w);
|
||||
ControlFlow visit(DfgScope* scope, AstStatRepeat* r);
|
||||
ControlFlow visit(DfgScope* scope, AstStatBreak* b);
|
||||
ControlFlow visit(DfgScope* scope, AstStatContinue* c);
|
||||
ControlFlow visit(DfgScope* scope, AstStatReturn* r);
|
||||
ControlFlow visit(DfgScope* scope, AstStatExpr* e);
|
||||
ControlFlow visit(DfgScope* scope, AstStatLocal* l);
|
||||
ControlFlow visit(DfgScope* scope, AstStatFor* f);
|
||||
ControlFlow visit(DfgScope* scope, AstStatForIn* f);
|
||||
ControlFlow visit(DfgScope* scope, AstStatAssign* a);
|
||||
ControlFlow visit(DfgScope* scope, AstStatCompoundAssign* c);
|
||||
ControlFlow visit(DfgScope* scope, AstStatFunction* f);
|
||||
ControlFlow visit(DfgScope* scope, AstStatLocalFunction* l);
|
||||
ControlFlow visit(DfgScope* scope, AstStatTypeAlias* t);
|
||||
ControlFlow visit(DfgScope* scope, AstStatTypeFunction* f);
|
||||
ControlFlow visit(DfgScope* scope, AstStatDeclareGlobal* d);
|
||||
ControlFlow visit(DfgScope* scope, AstStatDeclareFunction* d);
|
||||
ControlFlow visit(DfgScope* scope, AstStatDeclareClass* d);
|
||||
ControlFlow visit(DfgScope* scope, AstStatError* error);
|
||||
ControlFlow visit(AstStat* s);
|
||||
ControlFlow visit(AstStatIf* i);
|
||||
ControlFlow visit(AstStatWhile* w);
|
||||
ControlFlow visit(AstStatRepeat* r);
|
||||
ControlFlow visit(AstStatBreak* b);
|
||||
ControlFlow visit(AstStatContinue* c);
|
||||
ControlFlow visit(AstStatReturn* r);
|
||||
ControlFlow visit(AstStatExpr* e);
|
||||
ControlFlow visit(AstStatLocal* l);
|
||||
ControlFlow visit(AstStatFor* f);
|
||||
ControlFlow visit(AstStatForIn* f);
|
||||
ControlFlow visit(AstStatAssign* a);
|
||||
ControlFlow visit(AstStatCompoundAssign* c);
|
||||
ControlFlow visit(AstStatFunction* f);
|
||||
ControlFlow visit(AstStatLocalFunction* l);
|
||||
ControlFlow visit(AstStatTypeAlias* t);
|
||||
ControlFlow visit(AstStatTypeFunction* f);
|
||||
ControlFlow visit(AstStatDeclareGlobal* d);
|
||||
ControlFlow visit(AstStatDeclareFunction* d);
|
||||
ControlFlow visit(AstStatDeclareClass* d);
|
||||
ControlFlow visit(AstStatError* error);
|
||||
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExpr* e);
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprGroup* group);
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprLocal* l);
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprGlobal* g);
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprCall* c);
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprIndexName* i);
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprIndexExpr* i);
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprFunction* f);
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprTable* t);
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprUnary* u);
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprBinary* b);
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprTypeAssertion* t);
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprIfElse* i);
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprInterpString* i);
|
||||
DataFlowResult visitExpr(DfgScope* scope, AstExprError* error);
|
||||
DataFlowResult visitExpr(AstExpr* e);
|
||||
DataFlowResult visitExpr(AstExprGroup* group);
|
||||
DataFlowResult visitExpr(AstExprLocal* l);
|
||||
DataFlowResult visitExpr(AstExprGlobal* g);
|
||||
DataFlowResult visitExpr(AstExprCall* c);
|
||||
DataFlowResult visitExpr(AstExprIndexName* i);
|
||||
DataFlowResult visitExpr(AstExprIndexExpr* i);
|
||||
DataFlowResult visitExpr(AstExprFunction* f);
|
||||
DataFlowResult visitExpr(AstExprTable* t);
|
||||
DataFlowResult visitExpr(AstExprUnary* u);
|
||||
DataFlowResult visitExpr(AstExprBinary* b);
|
||||
DataFlowResult visitExpr(AstExprTypeAssertion* t);
|
||||
DataFlowResult visitExpr(AstExprIfElse* i);
|
||||
DataFlowResult visitExpr(AstExprInterpString* i);
|
||||
DataFlowResult visitExpr(AstExprError* error);
|
||||
|
||||
void visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef);
|
||||
DefId visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef);
|
||||
DefId visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef);
|
||||
DefId visitLValue(DfgScope* scope, AstExprIndexName* i, DefId incomingDef);
|
||||
DefId visitLValue(DfgScope* scope, AstExprIndexExpr* i, DefId incomingDef);
|
||||
DefId visitLValue(DfgScope* scope, AstExprError* e, DefId incomingDef);
|
||||
void visitLValue(AstExpr* e, DefId incomingDef);
|
||||
DefId visitLValue(AstExprLocal* l, DefId incomingDef);
|
||||
DefId visitLValue(AstExprGlobal* g, DefId incomingDef);
|
||||
DefId visitLValue(AstExprIndexName* i, DefId incomingDef);
|
||||
DefId visitLValue(AstExprIndexExpr* i, DefId incomingDef);
|
||||
DefId visitLValue(AstExprError* e, DefId incomingDef);
|
||||
|
||||
void visitType(DfgScope* scope, AstType* t);
|
||||
void visitType(DfgScope* scope, AstTypeReference* r);
|
||||
void visitType(DfgScope* scope, AstTypeTable* t);
|
||||
void visitType(DfgScope* scope, AstTypeFunction* f);
|
||||
void visitType(DfgScope* scope, AstTypeTypeof* t);
|
||||
void visitType(DfgScope* scope, AstTypeUnion* u);
|
||||
void visitType(DfgScope* scope, AstTypeIntersection* i);
|
||||
void visitType(DfgScope* scope, AstTypeError* error);
|
||||
void visitType(AstType* t);
|
||||
void visitType(AstTypeReference* r);
|
||||
void visitType(AstTypeTable* t);
|
||||
void visitType(AstTypeFunction* f);
|
||||
void visitType(AstTypeTypeof* t);
|
||||
void visitType(AstTypeUnion* u);
|
||||
void visitType(AstTypeIntersection* i);
|
||||
void visitType(AstTypeError* error);
|
||||
|
||||
void visitTypePack(DfgScope* scope, AstTypePack* p);
|
||||
void visitTypePack(DfgScope* scope, AstTypePackExplicit* e);
|
||||
void visitTypePack(DfgScope* scope, AstTypePackVariadic* v);
|
||||
void visitTypePack(DfgScope* scope, AstTypePackGeneric* g);
|
||||
void visitTypePack(AstTypePack* p);
|
||||
void visitTypePack(AstTypePackExplicit* e);
|
||||
void visitTypePack(AstTypePackVariadic* v);
|
||||
void visitTypePack(AstTypePackGeneric* g);
|
||||
|
||||
void visitTypeList(DfgScope* scope, AstTypeList l);
|
||||
void visitTypeList(AstTypeList l);
|
||||
|
||||
void visitGenerics(DfgScope* scope, AstArray<AstGenericType> g);
|
||||
void visitGenericPacks(DfgScope* scope, AstArray<AstGenericTypePack> g);
|
||||
void visitGenerics(AstArray<AstGenericType> g);
|
||||
void visitGenericPacks(AstArray<AstGenericTypePack> g);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -31,6 +32,9 @@ struct ModuleInfo
|
||||
bool optional = false;
|
||||
};
|
||||
|
||||
using RequireSuggestion = std::string;
|
||||
using RequireSuggestions = std::vector<RequireSuggestion>;
|
||||
|
||||
struct FileResolver
|
||||
{
|
||||
virtual ~FileResolver() {}
|
||||
@ -51,6 +55,11 @@ struct FileResolver
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
struct NullFileResolver : FileResolver
|
||||
|
@ -1,12 +1,15 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/Autocomplete.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Module.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -15,9 +18,28 @@ struct FragmentAutocompleteAncestryResult
|
||||
DenseHashMap<AstName, AstLocal*> localMap{AstName()};
|
||||
std::vector<AstLocal*> localStack;
|
||||
std::vector<AstNode*> ancestry;
|
||||
AstStat* nearestStatement;
|
||||
AstStat* nearestStatement = nullptr;
|
||||
};
|
||||
|
||||
struct FragmentParseResult
|
||||
{
|
||||
std::string fragmentToParse;
|
||||
AstStatBlock* root = nullptr;
|
||||
std::vector<AstNode*> ancestry;
|
||||
std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>();
|
||||
};
|
||||
|
||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* root, const Position& cursorPos);
|
||||
|
||||
FragmentParseResult parseFragment(const SourceModule& srcModule, std::string_view src, const Position& cursorPos);
|
||||
|
||||
AutocompleteResult fragmentAutocomplete(
|
||||
Frontend& frontend,
|
||||
std::string_view src,
|
||||
const ModuleName& moduleName,
|
||||
Position& cursorPosition,
|
||||
StringCompletionCallback callback
|
||||
);
|
||||
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/AnyTypeSummary.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@ -131,6 +132,9 @@ struct Module
|
||||
|
||||
TypePackId returnType = nullptr;
|
||||
std::unordered_map<Name, TypeFun> exportedTypeBindings;
|
||||
// We also need to keep DFG data alive between runs
|
||||
std::shared_ptr<DataFlowGraph> dataFlowGraph = nullptr;
|
||||
std::vector<std::unique_ptr<DfgScope>> dfgScopes;
|
||||
|
||||
bool hasModuleScope() const;
|
||||
ScopePtr getModuleScope() const;
|
||||
|
@ -667,6 +667,11 @@ struct AnyType
|
||||
{
|
||||
};
|
||||
|
||||
// A special, trivial type for the refinement system that is always eliminated from intersections.
|
||||
struct NoRefineType
|
||||
{
|
||||
};
|
||||
|
||||
// `T | U`
|
||||
struct UnionType
|
||||
{
|
||||
@ -755,6 +760,7 @@ using TypeVariant = Unifiable::Variant<
|
||||
UnknownType,
|
||||
NeverType,
|
||||
NegationType,
|
||||
NoRefineType,
|
||||
TypeFunctionInstanceType>;
|
||||
|
||||
struct Type final
|
||||
@ -949,6 +955,7 @@ public:
|
||||
const TypeId unknownType;
|
||||
const TypeId neverType;
|
||||
const TypeId errorType;
|
||||
const TypeId noRefineType;
|
||||
const TypeId falsyType;
|
||||
const TypeId truthyType;
|
||||
|
||||
|
@ -248,4 +248,36 @@ std::optional<Ty> follow(std::optional<Ty> ty)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not expr is a literal expression, for example:
|
||||
* - Scalar literals (numbers, booleans, strings, nil)
|
||||
* - Table literals
|
||||
* - Lambdas (a "function literal")
|
||||
*/
|
||||
bool isLiteral(const AstExpr* expr);
|
||||
|
||||
/**
|
||||
* Given a table literal and a mapping from expression to type, determine
|
||||
* whether any literal expression in this table depends on any blocked types.
|
||||
* This is used as a precondition for bidirectional inference: be warned that
|
||||
* the behavior of this algorithm is tightly coupled to that of bidirectional
|
||||
* inference.
|
||||
* @param expr Expression to search
|
||||
* @param astTypes Mapping from AST node to TypeID
|
||||
* @returns A vector of blocked types
|
||||
*/
|
||||
std::vector<TypeId> findBlockedTypesIn(AstExprTable* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes);
|
||||
|
||||
/**
|
||||
* Given a function call and a mapping from expression to type, determine
|
||||
* whether the type of any argument in said call in depends on a blocked types.
|
||||
* This is used as a precondition for bidirectional inference: be warned that
|
||||
* the behavior of this algorithm is tightly coupled to that of bidirectional
|
||||
* inference.
|
||||
* @param expr Expression to search
|
||||
* @param astTypes Mapping from AST node to TypeID
|
||||
* @returns A vector of blocked types
|
||||
*/
|
||||
std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes);
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -133,6 +133,10 @@ struct GenericTypeVisitor
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const NoRefineType& nrt)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const UnknownType& utv)
|
||||
{
|
||||
return visit(ty);
|
||||
@ -345,6 +349,8 @@ struct GenericTypeVisitor
|
||||
}
|
||||
else if (auto atv = get<AnyType>(ty))
|
||||
visit(ty, *atv);
|
||||
else if (auto nrt = get<NoRefineType>(ty))
|
||||
visit(ty, *nrt);
|
||||
else if (auto utv = get<UnionType>(ty))
|
||||
{
|
||||
if (visit(ty, *utv))
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#include "Luau/AstQuery.h"
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
@ -15,6 +17,7 @@
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauAutocompleteNewSolverLimit)
|
||||
LUAU_FASTFLAGVARIABLE(AutocompleteRequirePathSuggestions, false)
|
||||
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
LUAU_FASTINT(LuauTypeInferIterationLimit)
|
||||
@ -215,7 +218,6 @@ static TypeCorrectKind checkTypeCorrectKind(
|
||||
{
|
||||
for (TypeId id : itv->parts)
|
||||
{
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
id = follow(id);
|
||||
|
||||
if (const FunctionType* ftv = get<FunctionType>(id); ftv && checkFunctionType(ftv))
|
||||
@ -1444,11 +1446,25 @@ static std::optional<std::string> getStringContents(const AstNode* node)
|
||||
}
|
||||
}
|
||||
|
||||
static std::optional<AutocompleteEntryMap> convertRequireSuggestionsToAutocompleteEntryMap(std::optional<RequireSuggestions> suggestions)
|
||||
{
|
||||
if (!suggestions)
|
||||
return std::nullopt;
|
||||
|
||||
AutocompleteEntryMap result;
|
||||
for (const RequireSuggestion& suggestion : *suggestions)
|
||||
{
|
||||
result[suggestion] = {AutocompleteEntryKind::RequirePath};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::optional<AutocompleteEntryMap> autocompleteStringParams(
|
||||
const SourceModule& sourceModule,
|
||||
const ModulePtr& module,
|
||||
const std::vector<AstNode*>& nodes,
|
||||
Position position,
|
||||
FileResolver* fileResolver,
|
||||
StringCompletionCallback callback
|
||||
)
|
||||
{
|
||||
@ -1495,6 +1511,13 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(
|
||||
{
|
||||
for (const std::string& tag : funcType->tags)
|
||||
{
|
||||
if (FFlag::AutocompleteRequirePathSuggestions)
|
||||
{
|
||||
if (tag == kRequireTagName && fileResolver)
|
||||
{
|
||||
return convertRequireSuggestionsToAutocompleteEntryMap(fileResolver->getRequireSuggestions(module->name, candidateString));
|
||||
}
|
||||
}
|
||||
if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString))
|
||||
{
|
||||
return ret;
|
||||
@ -1679,6 +1702,7 @@ static AutocompleteResult autocomplete(
|
||||
TypeArena* typeArena,
|
||||
Scope* globalScope,
|
||||
Position position,
|
||||
FileResolver* fileResolver,
|
||||
StringCompletionCallback callback
|
||||
)
|
||||
{
|
||||
@ -1922,7 +1946,7 @@ static AutocompleteResult autocomplete(
|
||||
else if (isIdentifier(node) && (parent->is<AstStatExpr>() || parent->is<AstStatError>()))
|
||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
||||
|
||||
if (std::optional<AutocompleteEntryMap> ret = autocompleteStringParams(sourceModule, module, ancestry, position, callback))
|
||||
if (std::optional<AutocompleteEntryMap> ret = autocompleteStringParams(sourceModule, module, ancestry, position, fileResolver, callback))
|
||||
{
|
||||
return {*ret, ancestry, AutocompleteContext::String};
|
||||
}
|
||||
@ -1999,7 +2023,7 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName
|
||||
globalScope = frontend.globalsForAutocomplete.globalScope.get();
|
||||
|
||||
TypeArena typeArena;
|
||||
return autocomplete(*sourceModule, module, builtinTypes, &typeArena, globalScope, position, callback);
|
||||
return autocomplete(*sourceModule, module, builtinTypes, &typeArena, globalScope, position, frontend.fileResolver, callback);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -27,6 +27,8 @@
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
|
||||
LUAU_FASTFLAG(AutocompleteRequirePathSuggestions);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -413,8 +415,18 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
||||
attachDcrMagicFunction(ttv->props["pack"].type(), dcrMagicFunctionPack);
|
||||
}
|
||||
|
||||
if (FFlag::AutocompleteRequirePathSuggestions)
|
||||
{
|
||||
TypeId requireTy = getGlobalBinding(globals, "require");
|
||||
attachTag(requireTy, kRequireTagName);
|
||||
attachMagicFunction(requireTy, magicFunctionRequire);
|
||||
attachDcrMagicFunction(requireTy, dcrMagicFunctionRequire);
|
||||
}
|
||||
else
|
||||
{
|
||||
attachMagicFunction(getGlobalBinding(globals, "require"), magicFunctionRequire);
|
||||
attachDcrMagicFunction(getGlobalBinding(globals, "require"), dcrMagicFunctionRequire);
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<TypeId> parseFormatString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size)
|
||||
|
@ -359,6 +359,11 @@ private:
|
||||
// noop.
|
||||
}
|
||||
|
||||
void cloneChildren(NoRefineType* t)
|
||||
{
|
||||
// noop.
|
||||
}
|
||||
|
||||
void cloneChildren(UnionType* t)
|
||||
{
|
||||
for (TypeId& ty : t->options)
|
||||
|
@ -26,10 +26,10 @@
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
LUAU_FASTINT(LuauCheckRecursionLimit);
|
||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease);
|
||||
LUAU_FASTINT(LuauCheckRecursionLimit)
|
||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson)
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -2883,9 +2883,45 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
|
||||
{
|
||||
Unifier2 unifier{arena, builtinTypes, NotNull{scope.get()}, ice};
|
||||
std::vector<TypeId> toBlock;
|
||||
if (DFInt::LuauTypeSolverRelease >= 648)
|
||||
{
|
||||
// This logic is incomplete as we want to re-run this
|
||||
// _after_ blocked types have resolved, but this
|
||||
// allows us to do some bidirectional inference.
|
||||
toBlock = findBlockedTypesIn(expr, NotNull{&module->astTypes});
|
||||
if (toBlock.empty())
|
||||
{
|
||||
matchLiteralType(
|
||||
NotNull{&module->astTypes}, NotNull{&module->astExpectedTypes}, builtinTypes, arena, NotNull{&unifier}, *expectedType, ty, expr, toBlock
|
||||
NotNull{&module->astTypes},
|
||||
NotNull{&module->astExpectedTypes},
|
||||
builtinTypes,
|
||||
arena,
|
||||
NotNull{&unifier},
|
||||
*expectedType,
|
||||
ty,
|
||||
expr,
|
||||
toBlock
|
||||
);
|
||||
// The visitor we ran prior should ensure that there are no
|
||||
// blocked types that we would encounter while matching on
|
||||
// this expression.
|
||||
LUAU_ASSERT(toBlock.empty());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
matchLiteralType(
|
||||
NotNull{&module->astTypes},
|
||||
NotNull{&module->astExpectedTypes},
|
||||
builtinTypes,
|
||||
arena,
|
||||
NotNull{&unifier},
|
||||
*expectedType,
|
||||
ty,
|
||||
expr,
|
||||
toBlock
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Inference{ty};
|
||||
|
@ -32,6 +32,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings, false)
|
||||
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRemoveNotAnyHack, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -1238,6 +1239,13 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FFlag::LuauRemoveNotAnyHack)
|
||||
{
|
||||
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
|
||||
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We use `any` here because the discriminant type may be pointed at by both branches,
|
||||
// where the discriminant type is not negated, and the other where it is negated, i.e.
|
||||
// `unknown ~ unknown` and `~unknown ~ never`, so `T & unknown ~ T` and `T & ~unknown ~ never`
|
||||
@ -1247,6 +1255,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||
// In practice, users cannot negate `any`, so this is an implementation detail we can always change.
|
||||
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->anyType);
|
||||
}
|
||||
}
|
||||
|
||||
OverloadResolver resolver{
|
||||
builtinTypes,
|
||||
@ -1322,6 +1331,22 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
||||
if (isBlocked(argsPack))
|
||||
return true;
|
||||
|
||||
if (DFInt::LuauTypeSolverRelease >= 648)
|
||||
{
|
||||
// This is expensive as we need to traverse a (potentially large)
|
||||
// literal up front in order to determine if there are any blocked
|
||||
// types, otherwise we may run `matchTypeLiteral` multiple times,
|
||||
// which right now may fail due to being non-idempotent (it
|
||||
// destructively updates the underlying literal type).
|
||||
auto blockedTypes = findBlockedArgTypesIn(c.callSite, c.astTypes);
|
||||
for (const auto ty : blockedTypes)
|
||||
{
|
||||
block(ty, constraint);
|
||||
}
|
||||
if (!blockedTypes.empty())
|
||||
return false;
|
||||
}
|
||||
|
||||
// We know the type of the function and the arguments it expects to receive.
|
||||
// We also know the TypeIds of the actual arguments that will be passed.
|
||||
//
|
||||
@ -1384,7 +1409,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
||||
{
|
||||
const TypeId expectedArgTy = follow(expectedArgs[i + typeOffset]);
|
||||
const TypeId actualArgTy = follow(argPackHead[i + typeOffset]);
|
||||
const AstExpr* expr = unwrapGroup(c.callSite->args.data[i]);
|
||||
AstExpr* expr = unwrapGroup(c.callSite->args.data[i]);
|
||||
|
||||
(*c.astExpectedTypes)[expr] = expectedArgTy;
|
||||
|
||||
@ -1416,12 +1441,19 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
||||
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
||||
std::vector<TypeId> toBlock;
|
||||
(void)matchLiteralType(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, expectedArgTy, actualArgTy, expr, toBlock);
|
||||
if (DFInt::LuauTypeSolverRelease >= 648)
|
||||
{
|
||||
LUAU_ASSERT(toBlock.empty());
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto t : toBlock)
|
||||
block(t, constraint);
|
||||
if (!toBlock.empty())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -1748,8 +1780,9 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
|
||||
|
||||
if (auto lhsFree = getMutable<FreeType>(lhsType))
|
||||
{
|
||||
if (get<TableType>(lhsFree->upperBound) || get<MetatableType>(lhsFree->upperBound))
|
||||
lhsType = lhsFree->upperBound;
|
||||
auto lhsFreeUpperBound = DFInt::LuauTypeSolverRelease >= 648 ? follow(lhsFree->upperBound) : lhsFree->upperBound;
|
||||
if (get<TableType>(lhsFreeUpperBound) || get<MetatableType>(lhsFreeUpperBound))
|
||||
lhsType = lhsFreeUpperBound;
|
||||
else
|
||||
{
|
||||
TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope});
|
||||
@ -1759,7 +1792,7 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
|
||||
upperTable->props[c.propName] = rhsType;
|
||||
|
||||
// Food for thought: Could we block if simplification encounters a blocked type?
|
||||
lhsFree->upperBound = simplifyIntersection(builtinTypes, arena, lhsFree->upperBound, newUpperBound).result;
|
||||
lhsFree->upperBound = simplifyIntersection(builtinTypes, arena, lhsFreeUpperBound, newUpperBound).result;
|
||||
|
||||
bind(constraint, c.propType, rhsType);
|
||||
return true;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,7 @@
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
std::string DiffPathNode::toString() const
|
||||
{
|
||||
switch (kind)
|
||||
@ -944,12 +945,14 @@ std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator DifferEnvironment
|
||||
return visitingStack.crend();
|
||||
}
|
||||
|
||||
|
||||
DifferResult diff(TypeId ty1, TypeId ty2)
|
||||
{
|
||||
DifferEnvironment differEnv{ty1, ty2, std::nullopt, std::nullopt};
|
||||
return diffUsingEnv(differEnv, ty1, ty2);
|
||||
}
|
||||
|
||||
|
||||
DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional<std::string> symbol1, std::optional<std::string> symbol2)
|
||||
{
|
||||
DifferEnvironment differEnv{ty1, ty2, symbol1, symbol2};
|
||||
|
@ -1,10 +1,13 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauMathMap)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static const std::string kBuiltinDefinitionLuaSrcChecked = 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,
|
||||
@ -195,6 +198,228 @@ declare utf8: {
|
||||
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";
|
||||
|
||||
static const std::string kBuiltinDefinitionLuaSrcChecked = R"BUILTIN_SRC(
|
||||
|
||||
declare bit32: {
|
||||
band: @checked (...number) -> number,
|
||||
bor: @checked (...number) -> number,
|
||||
bxor: @checked (...number) -> number,
|
||||
btest: @checked (number, ...number) -> boolean,
|
||||
rrotate: @checked (x: number, disp: number) -> number,
|
||||
lrotate: @checked (x: number, disp: number) -> number,
|
||||
lshift: @checked (x: number, disp: number) -> number,
|
||||
arshift: @checked (x: number, disp: number) -> number,
|
||||
rshift: @checked (x: number, disp: number) -> number,
|
||||
bnot: @checked (x: number) -> number,
|
||||
extract: @checked (n: number, field: number, width: number?) -> number,
|
||||
replace: @checked (n: number, v: number, field: number, width: number?) -> number,
|
||||
countlz: @checked (n: number) -> number,
|
||||
countrz: @checked (n: number) -> number,
|
||||
byteswap: @checked (n: number) -> number,
|
||||
}
|
||||
|
||||
declare math: {
|
||||
frexp: @checked (n: number) -> (number, number),
|
||||
ldexp: @checked (s: number, e: number) -> number,
|
||||
fmod: @checked (x: number, y: number) -> number,
|
||||
modf: @checked (n: number) -> (number, number),
|
||||
pow: @checked (x: number, y: number) -> number,
|
||||
exp: @checked (n: number) -> number,
|
||||
|
||||
ceil: @checked (n: number) -> number,
|
||||
floor: @checked (n: number) -> number,
|
||||
abs: @checked (n: number) -> number,
|
||||
sqrt: @checked (n: number) -> number,
|
||||
|
||||
log: @checked (n: number, base: number?) -> number,
|
||||
log10: @checked (n: number) -> number,
|
||||
|
||||
rad: @checked (n: number) -> number,
|
||||
deg: @checked (n: number) -> number,
|
||||
|
||||
sin: @checked (n: number) -> number,
|
||||
cos: @checked (n: number) -> number,
|
||||
tan: @checked (n: number) -> number,
|
||||
sinh: @checked (n: number) -> number,
|
||||
cosh: @checked (n: number) -> number,
|
||||
tanh: @checked (n: number) -> number,
|
||||
atan: @checked (n: number) -> number,
|
||||
acos: @checked (n: number) -> number,
|
||||
asin: @checked (n: number) -> number,
|
||||
atan2: @checked (y: number, x: number) -> number,
|
||||
|
||||
min: @checked (number, ...number) -> number,
|
||||
max: @checked (number, ...number) -> number,
|
||||
|
||||
pi: number,
|
||||
huge: number,
|
||||
|
||||
randomseed: @checked (seed: number) -> (),
|
||||
random: @checked (number?, number?) -> number,
|
||||
|
||||
sign: @checked (n: number) -> number,
|
||||
clamp: @checked (n: number, min: number, max: number) -> number,
|
||||
noise: @checked (x: number, y: number?, z: number?) -> number,
|
||||
round: @checked (n: number) -> number,
|
||||
map: @checked (x: number, inmin: number, inmax: number, outmin: number, outmax: 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 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: {
|
||||
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.
|
||||
declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
|
||||
|
||||
|
||||
--- Buffer API
|
||||
declare buffer: {
|
||||
create: @checked (size: number) -> buffer,
|
||||
@ -227,7 +452,7 @@ declare buffer: {
|
||||
|
||||
std::string getBuiltinDefinitionSource()
|
||||
{
|
||||
std::string result = kBuiltinDefinitionLuaSrcChecked;
|
||||
std::string result = FFlag::LuauMathMap ? kBuiltinDefinitionLuaSrcChecked : kBuiltinDefinitionLuaSrcChecked_DEPRECATED;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,11 @@
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/AstQuery.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
#include "Luau/Module.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -10,6 +15,8 @@ namespace Luau
|
||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* root, const Position& cursorPos)
|
||||
{
|
||||
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(root, cursorPos);
|
||||
// Should always contain the root AstStat
|
||||
LUAU_ASSERT(ancestry.size() >= 1);
|
||||
DenseHashMap<AstName, AstLocal*> localMap{AstName()};
|
||||
std::vector<AstLocal*> localStack;
|
||||
AstStat* nearestStatement = nullptr;
|
||||
@ -21,7 +28,7 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* ro
|
||||
{
|
||||
if (stat->location.begin <= cursorPos)
|
||||
nearestStatement = stat;
|
||||
if (stat->location.begin <= cursorPos)
|
||||
if (stat->location.begin < cursorPos && stat->location.begin.line < cursorPos.line)
|
||||
{
|
||||
// This statement precedes the current one
|
||||
if (auto loc = stat->as<AstStatLocal>())
|
||||
@ -42,7 +49,116 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* ro
|
||||
}
|
||||
}
|
||||
|
||||
if (!nearestStatement)
|
||||
nearestStatement = ancestry[0]->asStat();
|
||||
LUAU_ASSERT(nearestStatement);
|
||||
return {std::move(localMap), std::move(localStack), std::move(ancestry), std::move(nearestStatement)};
|
||||
}
|
||||
|
||||
std::pair<unsigned int, unsigned int> getDocumentOffsets(const std::string_view& src, const Position& startPos, const Position& endPos)
|
||||
{
|
||||
unsigned int lineCount = 0;
|
||||
unsigned int colCount = 0;
|
||||
|
||||
unsigned int docOffset = 0;
|
||||
unsigned int startOffset = 0;
|
||||
unsigned int endOffset = 0;
|
||||
bool foundStart = false;
|
||||
bool foundEnd = false;
|
||||
for (char c : src)
|
||||
{
|
||||
if (foundStart && foundEnd)
|
||||
break;
|
||||
|
||||
if (startPos.line == lineCount && startPos.column == colCount)
|
||||
{
|
||||
foundStart = true;
|
||||
startOffset = docOffset;
|
||||
}
|
||||
|
||||
if (endPos.line == lineCount && endPos.column == colCount)
|
||||
{
|
||||
endOffset = docOffset;
|
||||
foundEnd = true;
|
||||
}
|
||||
|
||||
if (c == '\n')
|
||||
{
|
||||
lineCount++;
|
||||
colCount = 0;
|
||||
}
|
||||
else
|
||||
colCount++;
|
||||
docOffset++;
|
||||
}
|
||||
|
||||
|
||||
unsigned int min = std::min(startOffset, endOffset);
|
||||
unsigned int len = std::max(startOffset, endOffset) - min;
|
||||
return {min, len};
|
||||
}
|
||||
|
||||
ScopePtr findClosestScope(const ModulePtr& module, const Position& cursorPos)
|
||||
{
|
||||
LUAU_ASSERT(module->hasModuleScope());
|
||||
|
||||
ScopePtr closest = module->getModuleScope();
|
||||
for (auto [loc, sc] : module->scopes)
|
||||
{
|
||||
if (loc.begin <= cursorPos && closest->location.begin <= loc.begin)
|
||||
closest = sc;
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
FragmentParseResult parseFragment(const SourceModule& srcModule, std::string_view src, const Position& cursorPos)
|
||||
{
|
||||
FragmentAutocompleteAncestryResult result = findAncestryForFragmentParse(srcModule.root, cursorPos);
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = false;
|
||||
opts.captureComments = false;
|
||||
opts.parseFragment = FragmentParseResumeSettings{std::move(result.localMap), std::move(result.localStack)};
|
||||
AstStat* enclosingStatement = result.nearestStatement;
|
||||
|
||||
const Position& endPos = cursorPos;
|
||||
// If the statement starts on a previous line, grab the statement beginning
|
||||
// otherwise, grab the statement end to whatever is being typed right now
|
||||
const Position& startPos =
|
||||
enclosingStatement->location.begin.line == cursorPos.line ? enclosingStatement->location.begin : enclosingStatement->location.end;
|
||||
|
||||
auto [offsetStart, parseLength] = getDocumentOffsets(src, startPos, endPos);
|
||||
|
||||
const char* srcStart = src.data() + offsetStart;
|
||||
std::string_view dbg = src.substr(offsetStart, parseLength);
|
||||
const std::shared_ptr<AstNameTable>& nameTbl = srcModule.names;
|
||||
FragmentParseResult fragmentResult;
|
||||
fragmentResult.fragmentToParse = std::string(dbg.data(), parseLength);
|
||||
// For the duration of the incremental parse, we want to allow the name table to re-use duplicate names
|
||||
ParseResult p = Luau::Parser::parse(srcStart, parseLength, *nameTbl, *fragmentResult.alloc.get(), opts);
|
||||
|
||||
std::vector<AstNode*> fabricatedAncestry = std::move(result.ancestry);
|
||||
std::vector<AstNode*> fragmentAncestry = findAncestryAtPositionForAutocomplete(p.root, p.root->location.end);
|
||||
fabricatedAncestry.insert(fabricatedAncestry.end(), fragmentAncestry.begin(), fragmentAncestry.end());
|
||||
if (enclosingStatement == nullptr)
|
||||
enclosingStatement = p.root;
|
||||
fragmentResult.root = std::move(p.root);
|
||||
fragmentResult.ancestry = std::move(fabricatedAncestry);
|
||||
return fragmentResult;
|
||||
}
|
||||
|
||||
|
||||
AutocompleteResult fragmentAutocomplete(
|
||||
Frontend& frontend,
|
||||
std::string_view src,
|
||||
const ModuleName& moduleName,
|
||||
Position& cursorPosition,
|
||||
StringCompletionCallback callback
|
||||
)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauSolverV2);
|
||||
// TODO
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -49,6 +49,7 @@ LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMoreThoroughCycleDetection, false)
|
||||
|
||||
LUAU_FASTFLAG(StudioReportLuauAny2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStoreDFGOnModule, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -1315,6 +1316,18 @@ ModulePtr check(
|
||||
}
|
||||
|
||||
DataFlowGraph dfg = DataFlowGraphBuilder::build(sourceModule.root, iceHandler);
|
||||
DataFlowGraph* dfgForConstraintGeneration = nullptr;
|
||||
if (FFlag::LuauStoreDFGOnModule)
|
||||
{
|
||||
auto [dfg, scopes] = DataFlowGraphBuilder::buildShared(sourceModule.root, iceHandler);
|
||||
result->dataFlowGraph = std::move(dfg);
|
||||
result->dfgScopes = std::move(scopes);
|
||||
dfgForConstraintGeneration = result->dataFlowGraph.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
dfgForConstraintGeneration = &dfg;
|
||||
}
|
||||
|
||||
UnifierSharedState unifierState{iceHandler};
|
||||
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
||||
@ -1336,7 +1349,7 @@ ModulePtr check(
|
||||
parentScope,
|
||||
std::move(prepareModuleScope),
|
||||
logger.get(),
|
||||
NotNull{&dfg},
|
||||
NotNull{dfgForConstraintGeneration},
|
||||
requireCycles
|
||||
};
|
||||
|
||||
|
@ -801,6 +801,12 @@ struct TypeCacher : TypeOnceVisitor
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const NoRefineType&) override
|
||||
{
|
||||
cache(ty);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const UnionType& ut) override
|
||||
{
|
||||
if (isUncacheable(ty) || isCached(ty))
|
||||
|
@ -594,7 +594,7 @@ struct NonStrictTypeChecker
|
||||
std::shared_ptr<const NormalizedType> norm = normalizer.normalize(expectedArgType);
|
||||
DefId def = dfg->getDef(arg);
|
||||
TypeId runTimeErrorTy;
|
||||
// If we're dealing with any, negating any will cause all subtype tests to fail, since ~any is any
|
||||
// If we're dealing with any, negating any will cause all subtype tests to fail
|
||||
// However, when someone calls this function, they're going to want to be able to pass it anything,
|
||||
// for that reason, we manually inject never into the context so that the runtime test will always pass.
|
||||
if (!norm)
|
||||
|
@ -1872,7 +1872,7 @@ NormalizationResult Normalizer::unionNormalWithTy(NormalizedType& here, TypeId t
|
||||
if (res != NormalizationResult::True)
|
||||
return res;
|
||||
}
|
||||
else if (get<PendingExpansionType>(there) || get<TypeFunctionInstanceType>(there))
|
||||
else if (get<PendingExpansionType>(there) || get<TypeFunctionInstanceType>(there) || get<NoRefineType>(there))
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
@ -3217,6 +3217,11 @@ NormalizationResult Normalizer::intersectNormalWithTy(NormalizedType& here, Type
|
||||
// assumption that it is the same as any.
|
||||
return NormalizationResult::True;
|
||||
}
|
||||
else if (get<NoRefineType>(t))
|
||||
{
|
||||
// `*no-refine*` means we will never do anything to affect the intersection.
|
||||
return NormalizationResult::True;
|
||||
}
|
||||
else if (get<NeverType>(t))
|
||||
{
|
||||
// if we're intersecting with `~never`, this is equivalent to intersecting with `unknown`
|
||||
@ -3243,6 +3248,11 @@ NormalizationResult Normalizer::intersectNormalWithTy(NormalizedType& here, Type
|
||||
{
|
||||
here.classes.resetToNever();
|
||||
}
|
||||
else if (get<NoRefineType>(there))
|
||||
{
|
||||
// `*no-refine*` means we will never do anything to affect the intersection.
|
||||
return NormalizationResult::True;
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
|
||||
|
@ -50,6 +50,11 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
|
||||
LUAU_ASSERT(ty->persistent);
|
||||
return ty;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, NoRefineType>)
|
||||
{
|
||||
LUAU_ASSERT(ty->persistent);
|
||||
return ty;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, ErrorType>)
|
||||
{
|
||||
LUAU_ASSERT(ty->persistent);
|
||||
|
@ -261,52 +261,30 @@ SubtypingResult SubtypingResult::any(const std::vector<SubtypingResult>& results
|
||||
|
||||
struct ApplyMappedGenerics : Substitution
|
||||
{
|
||||
using MappedGenerics = DenseHashMap<TypeId, SubtypingEnvironment::GenericBounds>;
|
||||
using MappedGenericPacks = DenseHashMap<TypePackId, TypePackId>;
|
||||
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<TypeArena> arena;
|
||||
|
||||
SubtypingEnvironment& env;
|
||||
|
||||
MappedGenerics& mappedGenerics_DEPRECATED;
|
||||
MappedGenericPacks& mappedGenericPacks_DEPRECATED;
|
||||
|
||||
ApplyMappedGenerics(
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeArena> arena,
|
||||
SubtypingEnvironment& env,
|
||||
MappedGenerics& mappedGenerics,
|
||||
MappedGenericPacks& mappedGenericPacks
|
||||
)
|
||||
ApplyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, SubtypingEnvironment& env)
|
||||
: Substitution(TxnLog::empty(), arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
, arena(arena)
|
||||
, env(env)
|
||||
, mappedGenerics_DEPRECATED(mappedGenerics)
|
||||
, mappedGenericPacks_DEPRECATED(mappedGenericPacks)
|
||||
{
|
||||
}
|
||||
|
||||
bool isDirty(TypeId ty) override
|
||||
{
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
return env.containsMappedType(ty);
|
||||
else
|
||||
return mappedGenerics_DEPRECATED.contains(ty);
|
||||
}
|
||||
|
||||
bool isDirty(TypePackId tp) override
|
||||
{
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
return env.containsMappedPack(tp);
|
||||
else
|
||||
return mappedGenericPacks_DEPRECATED.contains(tp);
|
||||
}
|
||||
|
||||
TypeId clean(TypeId ty) override
|
||||
{
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
const auto& bounds = env.getMappedTypeBounds(ty);
|
||||
|
||||
@ -318,23 +296,8 @@ struct ApplyMappedGenerics : Substitution
|
||||
|
||||
return arena->addType(IntersectionType{std::vector<TypeId>(begin(bounds.upperBound), end(bounds.upperBound))});
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& bounds = mappedGenerics_DEPRECATED[ty];
|
||||
|
||||
if (bounds.upperBound.empty())
|
||||
return builtinTypes->unknownType;
|
||||
|
||||
if (bounds.upperBound.size() == 1)
|
||||
return *begin(bounds.upperBound);
|
||||
|
||||
return arena->addType(IntersectionType{std::vector<TypeId>(begin(bounds.upperBound), end(bounds.upperBound))});
|
||||
}
|
||||
}
|
||||
|
||||
TypePackId clean(TypePackId tp) override
|
||||
{
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
if (auto it = env.getMappedPackBounds(tp))
|
||||
return *it;
|
||||
@ -343,11 +306,6 @@ struct ApplyMappedGenerics : Substitution
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return mappedGenericPacks_DEPRECATED[tp];
|
||||
}
|
||||
}
|
||||
|
||||
bool ignoreChildren(TypeId ty) override
|
||||
{
|
||||
@ -364,7 +322,7 @@ struct ApplyMappedGenerics : Substitution
|
||||
|
||||
std::optional<TypeId> SubtypingEnvironment::applyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty)
|
||||
{
|
||||
ApplyMappedGenerics amg{builtinTypes, arena, *this, mappedGenerics, mappedGenericPacks};
|
||||
ApplyMappedGenerics amg{builtinTypes, arena, *this};
|
||||
return amg.substitute(ty);
|
||||
}
|
||||
|
||||
@ -489,8 +447,6 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
|
||||
}
|
||||
|
||||
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
SubtypingEnvironment boundsEnv;
|
||||
boundsEnv.parent = &env;
|
||||
SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope);
|
||||
@ -498,14 +454,6 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
|
||||
|
||||
result.andAlso(boundsResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
SubtypingResult boundsResult = isCovariantWith(env, lowerBound, upperBound, scope);
|
||||
boundsResult.reasoning.clear();
|
||||
|
||||
result.andAlso(boundsResult);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: We presently don't store subtype test results in the persistent
|
||||
* cache if the left-side type is a generic function.
|
||||
@ -582,18 +530,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||
subTy = follow(subTy);
|
||||
superTy = follow(superTy);
|
||||
|
||||
if (const TypeId* subIt = (DFInt::LuauTypeSolverRelease >= 644 ? env.tryFindSubstitution(subTy) : env.substitutions.find(subTy)); subIt && *subIt)
|
||||
if (const TypeId* subIt = env.tryFindSubstitution(subTy); subIt && *subIt)
|
||||
subTy = *subIt;
|
||||
|
||||
if (const TypeId* superIt = (DFInt::LuauTypeSolverRelease >= 644 ? env.tryFindSubstitution(superTy) : env.substitutions.find(superTy));
|
||||
superIt && *superIt)
|
||||
if (const TypeId* superIt = env.tryFindSubstitution(superTy); superIt && *superIt)
|
||||
superTy = *superIt;
|
||||
|
||||
const SubtypingResult* cachedResult = resultCache.find({subTy, superTy});
|
||||
if (cachedResult)
|
||||
return *cachedResult;
|
||||
|
||||
cachedResult = DFInt::LuauTypeSolverRelease >= 644 ? env.tryFindSubtypingResult({subTy, superTy}) : env.ephemeralCache.find({subTy, superTy});
|
||||
cachedResult = env.tryFindSubtypingResult({subTy, superTy});
|
||||
if (cachedResult)
|
||||
return *cachedResult;
|
||||
|
||||
@ -838,8 +785,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
std::vector<TypeId> headSlice(begin(superHead), begin(superHead) + headSize);
|
||||
TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail);
|
||||
|
||||
if (TypePackId* other =
|
||||
(DFInt::LuauTypeSolverRelease >= 644 ? env.getMappedPackBounds(*subTail) : env.mappedGenericPacks.find(*subTail)))
|
||||
if (TypePackId* other = env.getMappedPackBounds(*subTail))
|
||||
// TODO: TypePath can't express "slice of a pack + its tail".
|
||||
results.push_back(isCovariantWith(env, *other, superTailPack, scope).withSubComponent(TypePath::PackField::Tail));
|
||||
else
|
||||
@ -894,8 +840,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
std::vector<TypeId> headSlice(begin(subHead), begin(subHead) + headSize);
|
||||
TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail);
|
||||
|
||||
if (TypePackId* other =
|
||||
(DFInt::LuauTypeSolverRelease >= 644 ? env.getMappedPackBounds(*superTail) : env.mappedGenericPacks.find(*superTail)))
|
||||
if (TypePackId* other = env.getMappedPackBounds(*superTail))
|
||||
// TODO: TypePath can't express "slice of a pack + its tail".
|
||||
results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail));
|
||||
else
|
||||
@ -1837,11 +1782,8 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId supe
|
||||
if (!get<GenericType>(subTy))
|
||||
return false;
|
||||
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
if (!env.mappedGenerics.find(subTy) && env.containsMappedType(subTy))
|
||||
iceReporter->ice("attempting to modify bounds of a potentially visited generic");
|
||||
}
|
||||
|
||||
env.mappedGenerics[subTy].upperBound.insert(superTy);
|
||||
}
|
||||
@ -1850,11 +1792,8 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId supe
|
||||
if (!get<GenericType>(superTy))
|
||||
return false;
|
||||
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
if (!env.mappedGenerics.find(superTy) && env.containsMappedType(superTy))
|
||||
iceReporter->ice("attempting to modify bounds of a potentially visited generic");
|
||||
}
|
||||
|
||||
env.mappedGenerics[superTy].lowerBound.insert(subTy);
|
||||
}
|
||||
@ -1901,7 +1840,7 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePac
|
||||
if (!get<GenericTypePack>(subTp))
|
||||
return false;
|
||||
|
||||
if (TypePackId* m = (DFInt::LuauTypeSolverRelease >= 644 ? env.getMappedPackBounds(subTp) : env.mappedGenericPacks.find(subTp)))
|
||||
if (TypePackId* m = env.getMappedPackBounds(subTp))
|
||||
return *m == superTp;
|
||||
|
||||
env.mappedGenericPacks[subTp] = superTp;
|
||||
|
@ -6,19 +6,14 @@
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier2.h"
|
||||
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static bool isLiteral(const AstExpr* expr)
|
||||
{
|
||||
return (
|
||||
expr->is<AstExprTable>() || expr->is<AstExprFunction>() || expr->is<AstExprConstantNumber>() || expr->is<AstExprConstantString>() ||
|
||||
expr->is<AstExprConstantBool>() || expr->is<AstExprConstantNil>()
|
||||
);
|
||||
}
|
||||
|
||||
// A fast approximation of subTy <: superTy
|
||||
static bool fastIsSubtype(TypeId subTy, TypeId superTy)
|
||||
{
|
||||
@ -381,15 +376,21 @@ TypeId matchLiteralType(
|
||||
const TypeId* keyTy = astTypes->find(item.key);
|
||||
LUAU_ASSERT(keyTy);
|
||||
TypeId tKey = follow(*keyTy);
|
||||
if (get<BlockedType>(tKey))
|
||||
if (DFInt::LuauTypeSolverRelease >= 648)
|
||||
{
|
||||
LUAU_ASSERT(!is<BlockedType>(tKey));
|
||||
}
|
||||
else if (get<BlockedType>(tKey))
|
||||
toBlock.push_back(tKey);
|
||||
|
||||
const TypeId* propTy = astTypes->find(item.value);
|
||||
LUAU_ASSERT(propTy);
|
||||
TypeId tProp = follow(*propTy);
|
||||
if (get<BlockedType>(tProp))
|
||||
if (DFInt::LuauTypeSolverRelease >= 648)
|
||||
{
|
||||
LUAU_ASSERT(!is<BlockedType>(tKey));
|
||||
}
|
||||
else if (get<BlockedType>(tProp))
|
||||
toBlock.push_back(tProp);
|
||||
|
||||
// Populate expected types for non-string keys declared with [] (the code below will handle the case where they are strings)
|
||||
if (!item.key->as<AstExprConstantString>() && expectedTableTy->indexer)
|
||||
(*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType;
|
||||
|
@ -269,6 +269,12 @@ void StateDot::visitChildren(TypeId ty, int index)
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, NoRefineType>)
|
||||
{
|
||||
formatAppend(result, "NoRefineType %d", index);
|
||||
finishNodeLabel(ty);
|
||||
finishNode();
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, UnknownType>)
|
||||
{
|
||||
formatAppend(result, "UnknownType %d", index);
|
||||
|
@ -856,6 +856,11 @@ struct TypeStringifier
|
||||
state.emit("any");
|
||||
}
|
||||
|
||||
void operator()(TypeId, const NoRefineType&)
|
||||
{
|
||||
state.emit("*no-refine*");
|
||||
}
|
||||
|
||||
void operator()(TypeId, const UnionType& uv)
|
||||
{
|
||||
if (state.hasSeen(&uv))
|
||||
|
@ -1030,6 +1030,7 @@ BuiltinTypes::BuiltinTypes()
|
||||
, unknownType(arena->addType(Type{UnknownType{}, /*persistent*/ true}))
|
||||
, neverType(arena->addType(Type{NeverType{}, /*persistent*/ true}))
|
||||
, errorType(arena->addType(Type{ErrorType{}, /*persistent*/ true}))
|
||||
, noRefineType(arena->addType(Type{NoRefineType{}, /*persistent*/ true}))
|
||||
, falsyType(arena->addType(Type{UnionType{{falseType, nilType}}, /*persistent*/ true}))
|
||||
, truthyType(arena->addType(Type{NegationType{falsyType}, /*persistent*/ true}))
|
||||
, optionalNumberType(arena->addType(Type{UnionType{{numberType, nilType}}, /*persistent*/ true}))
|
||||
|
@ -145,6 +145,12 @@ public:
|
||||
{
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("any"), std::nullopt, Location());
|
||||
}
|
||||
|
||||
AstType* operator()(const NoRefineType&)
|
||||
{
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("*no-refine*"), std::nullopt, Location());
|
||||
}
|
||||
|
||||
AstType* operator()(const TableType& ttv)
|
||||
{
|
||||
RecursionCounter counter(&count);
|
||||
|
@ -3022,21 +3022,10 @@ PropertyType TypeChecker2::hasIndexTypeFromType(
|
||||
if (tt->indexer)
|
||||
{
|
||||
TypeId indexType = follow(tt->indexer->indexType);
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
TypeId givenType = module->internalTypes.addType(SingletonType{StringSingleton{prop}});
|
||||
if (isSubtype(givenType, indexType, NotNull{module->getModuleScope().get()}, builtinTypes, *ice))
|
||||
return {NormalizationResult::True, {tt->indexer->indexResultType}};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isPrim(indexType, PrimitiveType::String))
|
||||
return {NormalizationResult::True, {tt->indexer->indexResultType}};
|
||||
// If the indexer looks like { [any] : _} - the prop lookup should be allowed!
|
||||
else if (get<AnyType>(indexType) || get<UnknownType>(indexType))
|
||||
return {NormalizationResult::True, {tt->indexer->indexResultType}};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if we are in a conditional context, we treat the property as present and `unknown` because
|
||||
|
@ -49,6 +49,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctions2, false)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionNoEvaluation)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunFixRegister)
|
||||
LUAU_FASTFLAG(LuauRemoveNotAnyHack)
|
||||
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
|
||||
@ -777,16 +778,8 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
|
||||
if (normTy->hasTopTable() || get<TableType>(normalizedOperand))
|
||||
return {ctx->builtins->numberType, false, {}, {}};
|
||||
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
if (auto result = tryDistributeTypeFunctionApp(lenTypeFunction, instance, typeParams, packParams, ctx))
|
||||
return *result;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
|
||||
return *result;
|
||||
}
|
||||
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
@ -874,16 +867,8 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
|
||||
if (normTy->isExactlyNumber())
|
||||
return {ctx->builtins->numberType, false, {}, {}};
|
||||
|
||||
if (DFInt::LuauTypeSolverRelease >= 644)
|
||||
{
|
||||
if (auto result = tryDistributeTypeFunctionApp(unmTypeFunction, instance, typeParams, packParams, ctx))
|
||||
return *result;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
|
||||
return *result;
|
||||
}
|
||||
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
@ -1810,7 +1795,6 @@ struct FindRefinementBlockers : TypeOnceVisitor
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
||||
TypeId instance,
|
||||
const std::vector<TypeId>& typeParams,
|
||||
@ -1878,8 +1862,18 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
||||
* We need to treat T & ~any as T in this case.
|
||||
*/
|
||||
if (auto nt = get<NegationType>(discriminant))
|
||||
{
|
||||
if (FFlag::LuauRemoveNotAnyHack)
|
||||
{
|
||||
if (get<NoRefineType>(follow(nt->ty)))
|
||||
return {target, {}};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (get<AnyType>(follow(nt->ty)))
|
||||
return {target, {}};
|
||||
}
|
||||
}
|
||||
|
||||
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
|
||||
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type.
|
||||
@ -2059,6 +2053,15 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
|
||||
for (auto ty : typeParams)
|
||||
types.emplace_back(follow(ty));
|
||||
|
||||
if (FFlag::LuauRemoveNotAnyHack)
|
||||
{
|
||||
// if we only have two parameters and one is `*no-refine*`, we're all done.
|
||||
if (types.size() == 2 && get<NoRefineType>(types[1]))
|
||||
return {types[0], false, {}, {}};
|
||||
else if (types.size() == 2 && get<NoRefineType>(types[0]))
|
||||
return {types[1], false, {}, {}};
|
||||
}
|
||||
|
||||
// check to see if the operand types are resolved enough, and wait to reduce if not
|
||||
// if any of them are `never`, the intersection will always be `never`, so we can reduce directly.
|
||||
for (auto ty : types)
|
||||
@ -2073,6 +2076,10 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
|
||||
TypeId resultTy = ctx->builtins->unknownType;
|
||||
for (auto ty : types)
|
||||
{
|
||||
// skip any `*no-refine*` types.
|
||||
if (FFlag::LuauRemoveNotAnyHack && get<NoRefineType>(ty))
|
||||
continue;
|
||||
|
||||
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty);
|
||||
if (!result.blockedTypes.empty())
|
||||
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
|
||||
|
@ -16,6 +16,7 @@
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixRegister, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixNoReadWrite, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -634,6 +635,8 @@ static int readTableProp(lua_State* L)
|
||||
auto prop = tftt->props.at(tfsst->value);
|
||||
if (prop.readTy)
|
||||
allocTypeUserData(L, (*prop.readTy)->type);
|
||||
else if (FFlag::LuauUserTypeFunFixNoReadWrite)
|
||||
lua_pushnil(L);
|
||||
else
|
||||
luaL_error(L, "type.readproperty: property %s is write-only, and therefore does not have a read type.", tfsst->value.c_str());
|
||||
|
||||
@ -672,6 +675,8 @@ static int writeTableProp(lua_State* L)
|
||||
auto prop = tftt->props.at(tfsst->value);
|
||||
if (prop.writeTy)
|
||||
allocTypeUserData(L, (*prop.writeTy)->type);
|
||||
else if (FFlag::LuauUserTypeFunFixNoReadWrite)
|
||||
lua_pushnil(L);
|
||||
else
|
||||
luaL_error(L, "type.writeproperty: property %s is read-only, and therefore does not have a write type.", tfsst->value.c_str());
|
||||
|
||||
|
@ -479,4 +479,68 @@ ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypePackId
|
||||
return result;
|
||||
}
|
||||
|
||||
bool isLiteral(const AstExpr* expr)
|
||||
{
|
||||
return (
|
||||
expr->is<AstExprTable>() || expr->is<AstExprFunction>() || expr->is<AstExprConstantNumber>() || expr->is<AstExprConstantString>() ||
|
||||
expr->is<AstExprConstantBool>() || expr->is<AstExprConstantNil>()
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Visitor which, given an expression and a mapping from expression to TypeId,
|
||||
* determines if there are any literal expressions that contain blocked types.
|
||||
* This is used for bi-directional inference: we want to "apply" a type from
|
||||
* a function argument or a type annotation to a literal.
|
||||
*/
|
||||
class BlockedTypeInLiteralVisitor : public AstVisitor
|
||||
{
|
||||
public:
|
||||
explicit BlockedTypeInLiteralVisitor(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes, NotNull<std::vector<TypeId>> toBlock)
|
||||
: astTypes_{astTypes}
|
||||
, toBlock_{toBlock}
|
||||
{
|
||||
}
|
||||
bool visit(AstNode*) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExpr* e) override
|
||||
{
|
||||
auto ty = astTypes_->find(e);
|
||||
if (ty && (get<BlockedType>(follow(*ty)) != nullptr))
|
||||
{
|
||||
toBlock_->push_back(*ty);
|
||||
}
|
||||
return isLiteral(e) || e->is<AstExprGroup>();
|
||||
}
|
||||
|
||||
private:
|
||||
NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes_;
|
||||
NotNull<std::vector<TypeId>> toBlock_;
|
||||
};
|
||||
|
||||
std::vector<TypeId> findBlockedTypesIn(AstExprTable* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes)
|
||||
{
|
||||
std::vector<TypeId> toBlock;
|
||||
BlockedTypeInLiteralVisitor v{astTypes, NotNull{&toBlock}};
|
||||
expr->visit(&v);
|
||||
return toBlock;
|
||||
}
|
||||
|
||||
std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes)
|
||||
{
|
||||
std::vector<TypeId> toBlock;
|
||||
BlockedTypeInLiteralVisitor v{astTypes, NotNull{&toBlock}};
|
||||
for (auto arg: expr->args)
|
||||
{
|
||||
if (isLiteral(arg) || arg->is<AstExprGroup>())
|
||||
{
|
||||
arg->visit(&v);
|
||||
}
|
||||
}
|
||||
return toBlock;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -188,9 +188,18 @@ Parser::Parser(const char* buffer, size_t bufferSize, AstNameTable& names, Alloc
|
||||
functionStack.reserve(8);
|
||||
functionStack.push_back(top);
|
||||
|
||||
if (FFlag::LuauAllowFragmentParsing)
|
||||
{
|
||||
nameSelf = names.getOrAdd("self");
|
||||
nameNumber = names.getOrAdd("number");
|
||||
nameError = names.getOrAdd(kParseNameError);
|
||||
}
|
||||
else
|
||||
{
|
||||
nameSelf = names.addStatic("self");
|
||||
nameNumber = names.addStatic("number");
|
||||
nameError = names.addStatic(kParseNameError);
|
||||
}
|
||||
nameNil = names.getOrAdd("nil"); // nil is a reserved keyword
|
||||
|
||||
matchRecoveryStopOnToken.assign(Lexeme::Type::Reserved_END, 0);
|
||||
|
@ -17,8 +17,6 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauErrorResumeCleanupArgs, false)
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** Error-recovery functions
|
||||
@ -430,11 +428,7 @@ static void resume_handle(lua_State* L, void* ud)
|
||||
|
||||
static int resume_error(lua_State* L, const char* msg, int narg)
|
||||
{
|
||||
if (FFlag::LuauErrorResumeCleanupArgs)
|
||||
L->top -= narg;
|
||||
else
|
||||
L->top = L->ci->base;
|
||||
|
||||
setsvalue(L, L->top, luaS_new(L, msg));
|
||||
incr_top(L);
|
||||
return LUA_ERRRUN;
|
||||
|
@ -7,6 +7,8 @@
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauMathMap, false)
|
||||
|
||||
#undef PI
|
||||
#define PI (3.14159265358979323846)
|
||||
#define RADIANS_PER_DEGREE (PI / 180.0)
|
||||
@ -403,6 +405,19 @@ static int math_round(lua_State* L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_map(lua_State* L)
|
||||
{
|
||||
double x = luaL_checknumber(L, 1);
|
||||
double inmin = luaL_checknumber(L, 2);
|
||||
double inmax = luaL_checknumber(L, 3);
|
||||
double outmin = luaL_checknumber(L, 4);
|
||||
double outmax = luaL_checknumber(L, 5);
|
||||
|
||||
double result = outmin + (x - inmin) * (outmax - outmin) / (inmax - inmin);
|
||||
lua_pushnumber(L, result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const luaL_Reg mathlib[] = {
|
||||
{"abs", math_abs},
|
||||
{"acos", math_acos},
|
||||
@ -455,5 +470,12 @@ int luaopen_math(lua_State* L)
|
||||
lua_setfield(L, -2, "pi");
|
||||
lua_pushnumber(L, HUGE_VAL);
|
||||
lua_setfield(L, -2, "huge");
|
||||
|
||||
if (FFlag::LuauMathMap)
|
||||
{
|
||||
lua_pushcfunction(L, math_map, "map");
|
||||
lua_setfield(L, -2, "map");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -508,9 +508,6 @@ def runTest(subdir, filename, filepath):
|
||||
filepath = os.path.abspath(filepath)
|
||||
|
||||
mainVm = os.path.abspath(arguments.vm)
|
||||
if not os.path.isfile(mainVm):
|
||||
print(f"{colored(Color.RED, 'ERROR')}: VM executable '{mainVm}' does not exist.")
|
||||
sys.exit(1)
|
||||
|
||||
# Process output will contain the test name and execution times
|
||||
mainOutput = getVmOutput(substituteArguments(mainVm, getExtraArguments(filepath)) + " " + filepath)
|
||||
@ -890,11 +887,9 @@ def run(args, argsubcb):
|
||||
analyzeResult('', mainResult, compareResults)
|
||||
else:
|
||||
all_files = [subdir + os.sep + filename for subdir, dirs, files in os.walk(arguments.folder) for filename in files]
|
||||
if len(all_files) == 0:
|
||||
print(f"{colored(Color.YELLOW, 'WARNING')}: No test files found in '{arguments.folder}'.")
|
||||
for filepath in sorted(all_files):
|
||||
subdir, filename = os.path.split(filepath)
|
||||
if filename.endswith(".lua") or filename.endswith(".luau"):
|
||||
if filename.endswith(".lua"):
|
||||
if arguments.run_test == None or re.match(arguments.run_test, filename[:-4]):
|
||||
runTest(subdir, filename, filepath)
|
||||
|
||||
|
@ -31,6 +31,7 @@ extern int optimizationLevel;
|
||||
void luaC_fullgc(lua_State* L);
|
||||
void luaC_validate(lua_State* L);
|
||||
|
||||
LUAU_FASTFLAG(LuauMathMap)
|
||||
LUAU_FASTFLAG(DebugLuauAbortingChecks)
|
||||
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
|
||||
LUAU_FASTFLAG(LuauNativeAttribute)
|
||||
@ -652,6 +653,8 @@ TEST_CASE("Buffers")
|
||||
|
||||
TEST_CASE("Math")
|
||||
{
|
||||
ScopedFastFlag LuauMathMap{FFlag::LuauMathMap, true};
|
||||
|
||||
runConformance("math.lua");
|
||||
}
|
||||
|
||||
|
@ -4,10 +4,13 @@
|
||||
#include "Fixture.h"
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/AstQuery.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauAllowFragmentParsing);
|
||||
|
||||
struct FragmentAutocompleteFixture : Fixture
|
||||
{
|
||||
|
||||
@ -17,9 +20,25 @@ struct FragmentAutocompleteFixture : Fixture
|
||||
REQUIRE(p.root);
|
||||
return findAncestryForFragmentParse(p.root, cursorPos);
|
||||
}
|
||||
|
||||
CheckResult checkBase(const std::string& document)
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
FrontendOptions opts;
|
||||
opts.retainFullTypeGraphs = true;
|
||||
return this->frontend.check("MainModule", opts);
|
||||
}
|
||||
|
||||
FragmentParseResult parseFragment(const std::string& document, const Position& cursorPos)
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauAllowFragmentParsing, true}, {FFlag::LuauSolverV2, true}};
|
||||
SourceModule* srcModule = this->getMainSourceModule();
|
||||
std::string_view srcString = document;
|
||||
return Luau::parseFragment(*srcModule, srcString, cursorPos);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_SUITE_BEGIN("FragmentAutocompleteTraversalTest");
|
||||
TEST_SUITE_BEGIN("FragmentAutocompleteTraversalTests");
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "just_two_locals")
|
||||
{
|
||||
@ -32,7 +51,7 @@ local y = 5
|
||||
);
|
||||
|
||||
CHECK_EQ(3, result.ancestry.size());
|
||||
CHECK_EQ(2, result.localStack.size());
|
||||
CHECK_EQ(1, result.localStack.size());
|
||||
CHECK_EQ(result.localMap.size(), result.localStack.size());
|
||||
REQUIRE(result.nearestStatement);
|
||||
|
||||
@ -56,10 +75,10 @@ end
|
||||
);
|
||||
|
||||
CHECK_EQ(5, result.ancestry.size());
|
||||
CHECK_EQ(3, result.localStack.size());
|
||||
CHECK_EQ(2, result.localStack.size());
|
||||
CHECK_EQ(result.localMap.size(), result.localStack.size());
|
||||
REQUIRE(result.nearestStatement);
|
||||
CHECK_EQ("e", std::string(result.localStack.back()->name.value));
|
||||
CHECK_EQ("y", std::string(result.localStack.back()->name.value));
|
||||
|
||||
AstStatLocal* local = result.nearestStatement->as<AstStatLocal>();
|
||||
REQUIRE(local);
|
||||
@ -85,10 +104,10 @@ end
|
||||
);
|
||||
|
||||
CHECK_EQ(6, result.ancestry.size());
|
||||
CHECK_EQ(4, result.localStack.size());
|
||||
CHECK_EQ(3, result.localStack.size());
|
||||
CHECK_EQ(result.localMap.size(), result.localStack.size());
|
||||
REQUIRE(result.nearestStatement);
|
||||
CHECK_EQ("q", std::string(result.localStack.back()->name.value));
|
||||
CHECK_EQ("z", std::string(result.localStack.back()->name.value));
|
||||
|
||||
AstStatLocal* local = result.nearestStatement->as<AstStatLocal>();
|
||||
REQUIRE(local);
|
||||
@ -129,11 +148,122 @@ local function bar() return x + foo() end
|
||||
);
|
||||
|
||||
CHECK_EQ(8, result.ancestry.size());
|
||||
CHECK_EQ(3, result.localStack.size());
|
||||
CHECK_EQ(2, result.localStack.size());
|
||||
CHECK_EQ(result.localMap.size(), result.localStack.size());
|
||||
CHECK_EQ("bar", std::string(result.localStack.back()->name.value));
|
||||
CHECK_EQ("x", std::string(result.localStack.back()->name.value));
|
||||
auto returnSt = result.nearestStatement->as<AstStatReturn>();
|
||||
CHECK(returnSt != nullptr);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
|
||||
TEST_SUITE_BEGIN("FragmentAutocompleteParserTests");
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "statement_in_empty_fragment_is_non_null")
|
||||
{
|
||||
auto res = check(R"(
|
||||
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(res);
|
||||
|
||||
auto fragment = parseFragment(
|
||||
R"(
|
||||
|
||||
)",
|
||||
Position(1, 0)
|
||||
);
|
||||
CHECK_EQ("\n", fragment.fragmentToParse);
|
||||
CHECK_EQ(2, fragment.ancestry.size());
|
||||
REQUIRE(fragment.root);
|
||||
CHECK_EQ(0, fragment.root->body.size);
|
||||
auto statBody = fragment.root->as<AstStatBlock>();
|
||||
CHECK(statBody != nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "can_parse_complete_fragments")
|
||||
{
|
||||
auto res = check(
|
||||
R"(
|
||||
local x = 4
|
||||
local y = 5
|
||||
)"
|
||||
);
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(res);
|
||||
|
||||
auto fragment = parseFragment(
|
||||
R"(
|
||||
local x = 4
|
||||
local y = 5
|
||||
local z = x + y
|
||||
)",
|
||||
Position{3, 15}
|
||||
);
|
||||
|
||||
CHECK_EQ("\nlocal z = x + y", fragment.fragmentToParse);
|
||||
CHECK_EQ(5, fragment.ancestry.size());
|
||||
REQUIRE(fragment.root);
|
||||
CHECK_EQ(1, fragment.root->body.size);
|
||||
auto stat = fragment.root->body.data[0]->as<AstStatLocal>();
|
||||
REQUIRE(stat);
|
||||
CHECK_EQ(1, stat->vars.size);
|
||||
CHECK_EQ(1, stat->values.size);
|
||||
CHECK_EQ("z", std::string(stat->vars.data[0]->name.value));
|
||||
|
||||
auto bin = stat->values.data[0]->as<AstExprBinary>();
|
||||
REQUIRE(bin);
|
||||
CHECK_EQ(AstExprBinary::Op::Add, bin->op);
|
||||
|
||||
auto lhs = bin->left->as<AstExprLocal>();
|
||||
auto rhs = bin->right->as<AstExprLocal>();
|
||||
REQUIRE(lhs);
|
||||
REQUIRE(rhs);
|
||||
CHECK_EQ("x", std::string(lhs->local->name.value));
|
||||
CHECK_EQ("y", std::string(rhs->local->name.value));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "can_parse_fragments_in_line")
|
||||
{
|
||||
auto res = check(
|
||||
R"(
|
||||
local x = 4
|
||||
local y = 5
|
||||
)"
|
||||
);
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(res);
|
||||
|
||||
auto fragment = parseFragment(
|
||||
R"(
|
||||
local x = 4
|
||||
local z = x + y
|
||||
local y = 5
|
||||
)",
|
||||
Position{2, 15}
|
||||
);
|
||||
|
||||
CHECK_EQ("local z = x + y", fragment.fragmentToParse);
|
||||
CHECK_EQ(5, fragment.ancestry.size());
|
||||
REQUIRE(fragment.root);
|
||||
CHECK_EQ(1, fragment.root->body.size);
|
||||
auto stat = fragment.root->body.data[0]->as<AstStatLocal>();
|
||||
REQUIRE(stat);
|
||||
CHECK_EQ(1, stat->vars.size);
|
||||
CHECK_EQ(1, stat->values.size);
|
||||
CHECK_EQ("z", std::string(stat->vars.data[0]->name.value));
|
||||
|
||||
auto bin = stat->values.data[0]->as<AstExprBinary>();
|
||||
REQUIRE(bin);
|
||||
CHECK_EQ(AstExprBinary::Op::Add, bin->op);
|
||||
|
||||
auto lhs = bin->left->as<AstExprLocal>();
|
||||
auto rhs = bin->right->as<AstExprGlobal>();
|
||||
REQUIRE(lhs);
|
||||
REQUIRE(rhs);
|
||||
CHECK_EQ("x", std::string(lhs->local->name.value));
|
||||
CHECK_EQ("y", std::string(rhs->name.value));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "lualib.h"
|
||||
|
||||
#include "Repl.h"
|
||||
#include "ScopedFlags.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
@ -12,6 +13,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
LUAU_FASTFLAG(LuauMathMap)
|
||||
|
||||
struct Completion
|
||||
{
|
||||
std::string completion;
|
||||
@ -172,15 +175,17 @@ TEST_CASE_FIXTURE(ReplFixture, "CompleteGlobalVariables")
|
||||
CHECK(checkCompletion(completions, prefix, "myvariable1"));
|
||||
CHECK(checkCompletion(completions, prefix, "myvariable2"));
|
||||
}
|
||||
if (FFlag::LuauMathMap)
|
||||
{
|
||||
// Try completing some builtin functions
|
||||
CompletionSet completions = getCompletionSet("math.m");
|
||||
|
||||
std::string prefix = "math.";
|
||||
CHECK(completions.size() == 3);
|
||||
CHECK(completions.size() == 4);
|
||||
CHECK(checkCompletion(completions, prefix, "max("));
|
||||
CHECK(checkCompletion(completions, prefix, "min("));
|
||||
CHECK(checkCompletion(completions, prefix, "modf("));
|
||||
CHECK(checkCompletion(completions, prefix, "map("));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionNoEvaluation)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunFixRegister)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunFixNoReadWrite)
|
||||
|
||||
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
|
||||
|
||||
@ -674,6 +675,36 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_methods_works")
|
||||
CHECK(toString(tpm->givenTp) == "{ BaseField: number, read BaseMethod: (BaseClass, number) -> (), read Touched: Connection }");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "write_of_readonly_is_nil")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
ScopedFastFlag udtfRwFix{FFlag::LuauUserTypeFunFixNoReadWrite, true};
|
||||
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getclass(arg)
|
||||
local props = arg:properties()
|
||||
local table = types.newtable(props)
|
||||
local singleton = types.singleton("BaseMethod")
|
||||
|
||||
if table:writeproperty(singleton) then
|
||||
return types.singleton(true)
|
||||
else
|
||||
return types.singleton(false)
|
||||
end
|
||||
end
|
||||
-- forcing an error here to check the exact type of the metatable
|
||||
local function ok(idx: getclass<BaseClass>): nil return idx end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
||||
REQUIRE(tpm);
|
||||
CHECK(toString(tpm->givenTp) == "false");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_check_mutability")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
|
@ -4891,4 +4891,41 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type")
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
|
||||
// CLI-121540: All of these examples should have no errors.
|
||||
|
||||
LUAU_CHECK_ERROR_COUNT(3, check(R"(
|
||||
local function doTheThing(_: { [string]: unknown }) end
|
||||
doTheThing({
|
||||
['foo'] = 5,
|
||||
['bar'] = 'heyo',
|
||||
})
|
||||
)"));
|
||||
|
||||
LUAU_CHECK_ERROR_COUNT(1, check(R"(
|
||||
type Input = { [string]: unknown }
|
||||
|
||||
local i : Input = {
|
||||
[('%s'):format('3.14')]=5,
|
||||
['stringField']='Heyo'
|
||||
}
|
||||
)"));
|
||||
|
||||
// This example previously asserted due to eagerly mutating the underlying
|
||||
// table type.
|
||||
LUAU_CHECK_ERROR_COUNT(3, check(R"(
|
||||
type Input = { [string]: unknown }
|
||||
|
||||
local function doTheThing(_: Input) end
|
||||
|
||||
doTheThing({
|
||||
[('%s'):format('3.14')]=5,
|
||||
['stringField']='Heyo'
|
||||
})
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -1683,4 +1683,27 @@ TEST_CASE_FIXTURE(Fixture, "leading_ampersand_no_type")
|
||||
CHECK("*error-type*" == toString(requireTypeAlias("Amp")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "react_lua_follow_free_type_ub")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
return function(Roact)
|
||||
local Tree = Roact.Component:extend("Tree")
|
||||
|
||||
function Tree:render()
|
||||
local breadth, components, depth, id, wrap =
|
||||
self.props.breadth, self.props.components, self.props.depth, self.props.id, self.props.wrap
|
||||
local Box = components.Box
|
||||
if depth == 0 then
|
||||
Roact.createElement(Box, {})
|
||||
else
|
||||
Roact.createElement(Tree, {})
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -388,6 +388,20 @@ assert(math.pow(noinline(2), 2) == 4)
|
||||
assert(math.pow(noinline(4), 0.5) == 2)
|
||||
assert(math.pow(noinline(-2), 2) == 4)
|
||||
|
||||
-- map
|
||||
assert(math.map(0, -1, 1, 0, 2) == 1)
|
||||
assert(math.map(1, 1, 4, 0, 2) == 0)
|
||||
assert(math.map(2.5, 1, 4, 0, 2) == 1)
|
||||
assert(math.map(4, 1, 4, 0, 2) == 2)
|
||||
assert(math.map(1, 1, 4, 2, 0) == 2)
|
||||
assert(math.map(2.5, 1, 4, 2, 0) == 1)
|
||||
assert(math.map(4, 1, 4, 2, 0) == 0)
|
||||
assert(math.map(1, 4, 1, 2, 0) == 0)
|
||||
assert(math.map(2.5, 4, 1, 2, 0) == 1)
|
||||
assert(math.map(4, 4, 1, 2, 0) == 2)
|
||||
assert(math.map(-8, 0, 4, 0, 2) == -4)
|
||||
assert(math.map(16, 0, 4, 0, 2) == 8)
|
||||
|
||||
assert(tostring(math.pow(-2, 0.5)) == "nan")
|
||||
|
||||
-- test that fastcalls return correct number of results
|
||||
|
Loading…
Reference in New Issue
Block a user