mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-12 13:00:38 +00:00
Sync to upstream/release/588 (#992)
Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
This commit is contained in:
parent
087be529b0
commit
76f67e0733
96 changed files with 3057 additions and 991 deletions
|
@ -38,6 +38,7 @@ enum class AutocompleteEntryKind
|
|||
String,
|
||||
Type,
|
||||
Module,
|
||||
GeneratedFunction,
|
||||
};
|
||||
|
||||
enum class ParenthesesRecommendation
|
||||
|
@ -70,6 +71,10 @@ struct AutocompleteEntry
|
|||
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>;
|
||||
|
@ -94,4 +99,6 @@ using StringCompletionCallback =
|
|||
|
||||
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback);
|
||||
|
||||
constexpr char kGeneratedAnonymousFunctionEntryName[] = "function (anonymous autofilled)";
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -19,7 +19,7 @@ struct Config
|
|||
{
|
||||
Config();
|
||||
|
||||
Mode mode;
|
||||
Mode mode = Mode::Nonstrict;
|
||||
|
||||
ParseOptions parseOptions;
|
||||
|
||||
|
|
|
@ -94,12 +94,13 @@ struct ConstraintGraphBuilder
|
|||
ScopePtr globalScope;
|
||||
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
|
||||
std::vector<RequireCycle> requireCycles;
|
||||
|
||||
DcrLogger* logger;
|
||||
|
||||
ConstraintGraphBuilder(ModulePtr module, TypeArena* arena, NotNull<ModuleResolver> moduleResolver, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
DcrLogger* logger, NotNull<DataFlowGraph> dfg);
|
||||
DcrLogger* logger, NotNull<DataFlowGraph> dfg, std::vector<RequireCycle> requireCycles);
|
||||
|
||||
/**
|
||||
* Fabricates a new free type belonging to a given scope.
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "Luau/Normalize.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <vector>
|
||||
|
@ -81,9 +82,11 @@ struct ConstraintSolver
|
|||
std::vector<RequireCycle> requireCycles;
|
||||
|
||||
DcrLogger* logger;
|
||||
TypeCheckLimits limits;
|
||||
|
||||
explicit ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, std::vector<NotNull<Constraint>> constraints,
|
||||
ModuleName moduleName, NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger);
|
||||
ModuleName moduleName, NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger,
|
||||
TypeCheckLimits limits);
|
||||
|
||||
// Randomize the order in which to dispatch constraints
|
||||
void randomize(unsigned seed);
|
||||
|
@ -280,6 +283,9 @@ private:
|
|||
|
||||
TypePackId anyifyModuleReturnTypePackGenerics(TypePackId tp);
|
||||
|
||||
void throwTimeLimitError();
|
||||
void throwUserCancelError();
|
||||
|
||||
ToStringOptions opts;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
// 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/Type.h"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -17,6 +19,7 @@ struct DiffPathNode
|
|||
FunctionReturn,
|
||||
Union,
|
||||
Intersection,
|
||||
Negation,
|
||||
};
|
||||
Kind kind;
|
||||
// non-null when TableProperty
|
||||
|
@ -54,11 +57,15 @@ struct DiffPathNodeLeaf
|
|||
std::optional<Name> tableProperty;
|
||||
std::optional<int> minLength;
|
||||
bool isVariadic;
|
||||
DiffPathNodeLeaf(std::optional<TypeId> ty, std::optional<Name> tableProperty, std::optional<int> minLength, bool isVariadic)
|
||||
// TODO: Rename to anonymousIndex, for both union and Intersection
|
||||
std::optional<size_t> unionIndex;
|
||||
DiffPathNodeLeaf(
|
||||
std::optional<TypeId> ty, std::optional<Name> tableProperty, std::optional<int> minLength, bool isVariadic, std::optional<size_t> unionIndex)
|
||||
: ty(ty)
|
||||
, tableProperty(tableProperty)
|
||||
, minLength(minLength)
|
||||
, isVariadic(isVariadic)
|
||||
, unionIndex(unionIndex)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -66,6 +73,8 @@ struct DiffPathNodeLeaf
|
|||
|
||||
static DiffPathNodeLeaf detailsTableProperty(TypeId ty, Name tableProperty);
|
||||
|
||||
static DiffPathNodeLeaf detailsUnionIndex(TypeId ty, size_t index);
|
||||
|
||||
static DiffPathNodeLeaf detailsLength(int minLength, bool isVariadic);
|
||||
|
||||
static DiffPathNodeLeaf nullopts();
|
||||
|
@ -82,11 +91,12 @@ struct DiffError
|
|||
enum Kind
|
||||
{
|
||||
Normal,
|
||||
MissingProperty,
|
||||
MissingTableProperty,
|
||||
MissingUnionMember,
|
||||
MissingIntersectionMember,
|
||||
IncompatibleGeneric,
|
||||
LengthMismatchInFnArgs,
|
||||
LengthMismatchInFnRets,
|
||||
LengthMismatchInUnion,
|
||||
LengthMismatchInIntersection,
|
||||
};
|
||||
Kind kind;
|
||||
|
||||
|
@ -141,6 +151,8 @@ struct DifferEnvironment
|
|||
{
|
||||
TypeId rootLeft;
|
||||
TypeId rootRight;
|
||||
|
||||
DenseHashMap<TypeId, TypeId> genericMatchedPairs;
|
||||
};
|
||||
DifferResult diff(TypeId ty1, TypeId ty2);
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/RequireTracer.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
|
@ -189,14 +190,6 @@ struct Frontend
|
|||
std::optional<CheckResult> getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete = false);
|
||||
|
||||
private:
|
||||
struct TypeCheckLimits
|
||||
{
|
||||
std::optional<double> finishTime;
|
||||
std::optional<int> instantiationChildLimit;
|
||||
std::optional<int> unifierIterationLimit;
|
||||
std::shared_ptr<FrontendCancellationToken> cancellationToken;
|
||||
};
|
||||
|
||||
ModulePtr check(const SourceModule& sourceModule, Mode mode, std::vector<RequireCycle> requireCycles, std::optional<ScopePtr> environmentScope,
|
||||
bool forAutocomplete, bool recordJsonLog, TypeCheckLimits typeCheckLimits);
|
||||
|
||||
|
@ -248,11 +241,12 @@ public:
|
|||
|
||||
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& globalScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options);
|
||||
const ScopePtr& globalScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
|
||||
TypeCheckLimits limits);
|
||||
|
||||
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& globalScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
|
||||
bool recordJsonLog);
|
||||
TypeCheckLimits limits, bool recordJsonLog);
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -277,6 +277,9 @@ struct NormalizedType
|
|||
/// Returns true if this type should result in error suppressing behavior.
|
||||
bool shouldSuppressErrors() const;
|
||||
|
||||
/// Returns true if this type contains the primitve top table type, `table`.
|
||||
bool hasTopTable() const;
|
||||
|
||||
// Helpers that improve readability of the above (they just say if the component is present)
|
||||
bool hasTops() const;
|
||||
bool hasBooleans() const;
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -42,17 +40,7 @@ struct Symbol
|
|||
return local != nullptr || global.value != nullptr;
|
||||
}
|
||||
|
||||
bool operator==(const Symbol& rhs) const
|
||||
{
|
||||
if (local)
|
||||
return local == rhs.local;
|
||||
else if (global.value)
|
||||
return rhs.global.value && global == rhs.global.value; // Subtlety: AstName::operator==(const char*) uses strcmp, not pointer identity.
|
||||
else if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return !rhs.local && !rhs.global.value; // Reflexivity: we already know `this` Symbol is empty, so check that rhs is.
|
||||
else
|
||||
return false;
|
||||
}
|
||||
bool operator==(const Symbol& rhs) const;
|
||||
|
||||
bool operator!=(const Symbol& rhs) const
|
||||
{
|
||||
|
|
|
@ -9,12 +9,16 @@
|
|||
|
||||
namespace Luau
|
||||
{
|
||||
struct Module;
|
||||
|
||||
struct TypeArena
|
||||
{
|
||||
TypedAllocator<Type> types;
|
||||
TypedAllocator<TypePackVar> typePacks;
|
||||
|
||||
// Owning module, if any
|
||||
Module* owningModule = nullptr;
|
||||
|
||||
void clear();
|
||||
|
||||
template<typename T>
|
||||
|
|
41
Analysis/include/Luau/TypeCheckLimits.h
Normal file
41
Analysis/include/Luau/TypeCheckLimits.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Cancellation.h"
|
||||
#include "Luau/Error.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class TimeLimitError : public InternalCompilerError
|
||||
{
|
||||
public:
|
||||
explicit TimeLimitError(const std::string& moduleName)
|
||||
: InternalCompilerError("Typeinfer failed to complete in allotted time", moduleName)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class UserCancelError : public InternalCompilerError
|
||||
{
|
||||
public:
|
||||
explicit UserCancelError(const std::string& moduleName)
|
||||
: InternalCompilerError("Analysis has been cancelled by user", moduleName)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeCheckLimits
|
||||
{
|
||||
std::optional<double> finishTime;
|
||||
std::optional<int> instantiationChildLimit;
|
||||
std::optional<int> unifierIterationLimit;
|
||||
|
||||
std::shared_ptr<FrontendCancellationToken> cancellationToken;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -10,6 +10,7 @@
|
|||
#include "Luau/Symbol.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier.h"
|
||||
|
@ -56,24 +57,6 @@ struct HashBoolNamePair
|
|||
size_t operator()(const std::pair<bool, Name>& pair) const;
|
||||
};
|
||||
|
||||
class TimeLimitError : public InternalCompilerError
|
||||
{
|
||||
public:
|
||||
explicit TimeLimitError(const std::string& moduleName)
|
||||
: InternalCompilerError("Typeinfer failed to complete in allotted time", moduleName)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class UserCancelError : public InternalCompilerError
|
||||
{
|
||||
public:
|
||||
explicit UserCancelError(const std::string& moduleName)
|
||||
: InternalCompilerError("Analysis has been cancelled by user", moduleName)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct GlobalTypes
|
||||
{
|
||||
GlobalTypes(NotNull<BuiltinTypes> builtinTypes);
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Luau
|
|||
|
||||
struct TxnLog;
|
||||
struct TypeArena;
|
||||
class Normalizer;
|
||||
|
||||
enum class ValueContext
|
||||
{
|
||||
|
@ -55,6 +56,51 @@ std::vector<TypeId> reduceUnion(const std::vector<TypeId>& types);
|
|||
*/
|
||||
TypeId stripNil(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, TypeId ty);
|
||||
|
||||
enum class ErrorSuppression
|
||||
{
|
||||
Suppress,
|
||||
DoNotSuppress,
|
||||
NormalizationFailed
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalizes the given type using the normalizer to determine if the type
|
||||
* should suppress any errors that would be reported involving it.
|
||||
* @param normalizer the normalizer to use
|
||||
* @param ty the type to check for error suppression
|
||||
* @returns an enum indicating whether or not to suppress the error or to signal a normalization failure
|
||||
*/
|
||||
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty);
|
||||
|
||||
/**
|
||||
* Flattens and normalizes the given typepack using the normalizer to determine if the type
|
||||
* should suppress any errors that would be reported involving it.
|
||||
* @param normalizer the normalizer to use
|
||||
* @param tp the typepack to check for error suppression
|
||||
* @returns an enum indicating whether or not to suppress the error or to signal a normalization failure
|
||||
*/
|
||||
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypePackId tp);
|
||||
|
||||
/**
|
||||
* Normalizes the two given type using the normalizer to determine if either type
|
||||
* should suppress any errors that would be reported involving it.
|
||||
* @param normalizer the normalizer to use
|
||||
* @param ty1 the first type to check for error suppression
|
||||
* @param ty2 the second type to check for error suppression
|
||||
* @returns an enum indicating whether or not to suppress the error or to signal a normalization failure
|
||||
*/
|
||||
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty1, TypeId ty2);
|
||||
|
||||
/**
|
||||
* Flattens and normalizes the two given typepacks using the normalizer to determine if either type
|
||||
* should suppress any errors that would be reported involving it.
|
||||
* @param normalizer the normalizer to use
|
||||
* @param tp1 the first typepack to check for error suppression
|
||||
* @param tp2 the second typepack to check for error suppression
|
||||
* @returns an enum indicating whether or not to suppress the error or to signal a normalization failure
|
||||
*/
|
||||
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypePackId tp1, TypePackId tp2);
|
||||
|
||||
template<typename T, typename Ty>
|
||||
const T* get(std::optional<Ty> ty)
|
||||
{
|
||||
|
|
|
@ -43,6 +43,21 @@ struct Widen : Substitution
|
|||
TypePackId operator()(TypePackId ty);
|
||||
};
|
||||
|
||||
/**
|
||||
* Normally, when we unify table properties, we must do so invariantly, but we
|
||||
* can introduce a special exception: If the table property in the subtype
|
||||
* position arises from a literal expression, it is safe to instead perform a
|
||||
* covariant check.
|
||||
*
|
||||
* This is very useful for typechecking cases where table literals (and trees of
|
||||
* table literals) are passed directly to functions.
|
||||
*
|
||||
* In this case, we know that the property has no other name referring to it and
|
||||
* so it is perfectly safe for the function to mutate the table any way it
|
||||
* wishes.
|
||||
*/
|
||||
using LiteralProperties = DenseHashSet<Name>;
|
||||
|
||||
// TODO: Use this more widely.
|
||||
struct UnifierOptions
|
||||
{
|
||||
|
@ -80,7 +95,7 @@ struct Unifier
|
|||
|
||||
// Configure the Unifier to test for scope subsumption via embedded Scope
|
||||
// pointers rather than TypeLevels.
|
||||
void enableScopeTests();
|
||||
void enableNewSolver();
|
||||
|
||||
// Test whether the two type vars unify. Never commits the result.
|
||||
ErrorVec canUnify(TypeId subTy, TypeId superTy);
|
||||
|
@ -90,10 +105,10 @@ struct Unifier
|
|||
* Populate the vector errors with any type errors that may arise.
|
||||
* Populate the transaction log with the set of TypeIds that need to be reset to undo the unification attempt.
|
||||
*/
|
||||
void tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false);
|
||||
void tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
|
||||
|
||||
private:
|
||||
void tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false);
|
||||
void tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
|
||||
void tryUnifyUnionWithType(TypeId subTy, const UnionType* uv, TypeId superTy);
|
||||
|
||||
// Traverse the two types provided and block on any BlockedTypes we find.
|
||||
|
@ -108,7 +123,7 @@ private:
|
|||
void tryUnifyPrimitives(TypeId subTy, TypeId superTy);
|
||||
void tryUnifySingletons(TypeId subTy, TypeId superTy);
|
||||
void tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCall = false);
|
||||
void tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false);
|
||||
void tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
|
||||
void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed);
|
||||
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
|
||||
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
|
||||
|
@ -163,8 +178,10 @@ private:
|
|||
// Available after regular type pack unification errors
|
||||
std::optional<int> firstPackErrorPos;
|
||||
|
||||
// If true, we use the scope hierarchy rather than TypeLevels
|
||||
bool useScopes = false;
|
||||
// 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);
|
||||
|
|
|
@ -13,6 +13,10 @@
|
|||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDisableCompletionOutsideQuotes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAnonymousAutofilled, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteLastTypecheck, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteHideSelfArg, false)
|
||||
|
||||
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
|
||||
|
@ -280,18 +284,38 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul
|
|||
ParenthesesRecommendation parens =
|
||||
indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect);
|
||||
|
||||
result[name] = AutocompleteEntry{
|
||||
AutocompleteEntryKind::Property,
|
||||
type,
|
||||
prop.deprecated,
|
||||
isWrongIndexer(type),
|
||||
typeCorrect,
|
||||
containingClass,
|
||||
&prop,
|
||||
prop.documentationSymbol,
|
||||
{},
|
||||
parens,
|
||||
};
|
||||
if (FFlag::LuauAutocompleteHideSelfArg)
|
||||
{
|
||||
result[name] = AutocompleteEntry{
|
||||
AutocompleteEntryKind::Property,
|
||||
type,
|
||||
prop.deprecated,
|
||||
isWrongIndexer(type),
|
||||
typeCorrect,
|
||||
containingClass,
|
||||
&prop,
|
||||
prop.documentationSymbol,
|
||||
{},
|
||||
parens,
|
||||
{},
|
||||
indexType == PropIndexType::Colon
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
result[name] = AutocompleteEntry{
|
||||
AutocompleteEntryKind::Property,
|
||||
type,
|
||||
prop.deprecated,
|
||||
isWrongIndexer(type),
|
||||
typeCorrect,
|
||||
containingClass,
|
||||
&prop,
|
||||
prop.documentationSymbol,
|
||||
{},
|
||||
parens
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -591,14 +615,14 @@ std::optional<TypeId> getLocalTypeInScopeAt(const Module& module, Position posit
|
|||
return {};
|
||||
}
|
||||
|
||||
static std::optional<Name> tryGetTypeNameInScope(ScopePtr scope, TypeId ty)
|
||||
template <typename T>
|
||||
static std::optional<std::string> tryToStringDetailed(const ScopePtr& scope, T ty, bool functionTypeArguments)
|
||||
{
|
||||
if (!canSuggestInferredType(scope, ty))
|
||||
return std::nullopt;
|
||||
|
||||
LUAU_ASSERT(FFlag::LuauAnonymousAutofilled);
|
||||
ToStringOptions opts;
|
||||
opts.useLineBreaks = false;
|
||||
opts.hideTableKind = true;
|
||||
opts.functionTypeArguments = functionTypeArguments;
|
||||
opts.scope = scope;
|
||||
ToStringResult name = toStringDetailed(ty, opts);
|
||||
|
||||
|
@ -608,6 +632,30 @@ static std::optional<Name> tryGetTypeNameInScope(ScopePtr scope, TypeId ty)
|
|||
return name.name;
|
||||
}
|
||||
|
||||
static std::optional<Name> tryGetTypeNameInScope(ScopePtr scope, TypeId ty, bool functionTypeArguments = false)
|
||||
{
|
||||
if (!canSuggestInferredType(scope, ty))
|
||||
return std::nullopt;
|
||||
|
||||
if (FFlag::LuauAnonymousAutofilled)
|
||||
{
|
||||
return tryToStringDetailed(scope, ty, functionTypeArguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToStringOptions opts;
|
||||
opts.useLineBreaks = false;
|
||||
opts.hideTableKind = true;
|
||||
opts.scope = scope;
|
||||
ToStringResult name = toStringDetailed(ty, opts);
|
||||
|
||||
if (name.error || name.invalid || name.cycle || name.truncated)
|
||||
return std::nullopt;
|
||||
|
||||
return name.name;
|
||||
}
|
||||
}
|
||||
|
||||
static bool tryAddTypeCorrectSuggestion(AutocompleteEntryMap& result, ScopePtr scope, AstType* topType, TypeId inferredType, Position position)
|
||||
{
|
||||
std::optional<TypeId> ty;
|
||||
|
@ -1297,6 +1345,14 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(const Source
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (FFlag::LuauDisableCompletionOutsideQuotes && !nodes.back()->is<AstExprError>())
|
||||
{
|
||||
if (nodes.back()->location.end == position || nodes.back()->location.begin == position)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
AstExprCall* candidate = nodes.at(nodes.size() - 2)->as<AstExprCall>();
|
||||
if (!candidate)
|
||||
{
|
||||
|
@ -1361,6 +1417,140 @@ static AutocompleteResult autocompleteWhileLoopKeywords(std::vector<AstNode*> an
|
|||
return {std::move(ret), std::move(ancestry), AutocompleteContext::Keyword};
|
||||
}
|
||||
|
||||
static std::string makeAnonymous(const ScopePtr& scope, const FunctionType& funcTy)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauAnonymousAutofilled);
|
||||
std::string result = "function(";
|
||||
|
||||
auto [args, tail] = Luau::flatten(funcTy.argTypes);
|
||||
|
||||
bool first = true;
|
||||
// Skip the implicit 'self' argument if call is indexed with ':'
|
||||
for (size_t argIdx = 0; argIdx < args.size(); ++argIdx)
|
||||
{
|
||||
if (!first)
|
||||
result += ", ";
|
||||
else
|
||||
first = false;
|
||||
|
||||
std::string name;
|
||||
if (argIdx < funcTy.argNames.size() && funcTy.argNames[argIdx])
|
||||
name = funcTy.argNames[argIdx]->name;
|
||||
else
|
||||
name = "a" + std::to_string(argIdx);
|
||||
|
||||
if (std::optional<Name> type = tryGetTypeNameInScope(scope, args[argIdx], true))
|
||||
result += name + ": " + *type;
|
||||
else
|
||||
result += name;
|
||||
}
|
||||
|
||||
if (tail && (Luau::isVariadic(*tail) || Luau::get<Luau::FreeTypePack>(Luau::follow(*tail))))
|
||||
{
|
||||
if (!first)
|
||||
result += ", ";
|
||||
|
||||
std::optional<std::string> varArgType;
|
||||
if (const VariadicTypePack* pack = get<VariadicTypePack>(follow(*tail)))
|
||||
{
|
||||
if (std::optional<std::string> res = tryToStringDetailed(scope, pack->ty, true))
|
||||
varArgType = std::move(res);
|
||||
}
|
||||
|
||||
if (varArgType)
|
||||
result += "...: " + *varArgType;
|
||||
else
|
||||
result += "...";
|
||||
}
|
||||
|
||||
result += ")";
|
||||
|
||||
auto [rets, retTail] = Luau::flatten(funcTy.retTypes);
|
||||
if (const size_t totalRetSize = rets.size() + (retTail ? 1 : 0); totalRetSize > 0)
|
||||
{
|
||||
if (std::optional<std::string> returnTypes = tryToStringDetailed(scope, funcTy.retTypes, true))
|
||||
{
|
||||
result += ": ";
|
||||
bool wrap = totalRetSize != 1;
|
||||
if (wrap)
|
||||
result += "(";
|
||||
result += *returnTypes;
|
||||
if (wrap)
|
||||
result += ")";
|
||||
}
|
||||
}
|
||||
result += " end";
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::optional<AutocompleteEntry> makeAnonymousAutofilled(const ModulePtr& module, Position position, const AstNode* node, const std::vector<AstNode*>& ancestry)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauAnonymousAutofilled);
|
||||
const AstExprCall* call = node->as<AstExprCall>();
|
||||
if (!call && ancestry.size() > 1)
|
||||
call = ancestry[ancestry.size() - 2]->as<AstExprCall>();
|
||||
|
||||
if (!call)
|
||||
return std::nullopt;
|
||||
|
||||
if (!call->location.containsClosed(position) || call->func->location.containsClosed(position))
|
||||
return std::nullopt;
|
||||
|
||||
TypeId* typeIter = module->astTypes.find(call->func);
|
||||
if (!typeIter)
|
||||
return std::nullopt;
|
||||
|
||||
const FunctionType* outerFunction = get<FunctionType>(follow(*typeIter));
|
||||
if (!outerFunction)
|
||||
return std::nullopt;
|
||||
|
||||
size_t argument = 0;
|
||||
for (size_t i = 0; i < call->args.size; ++i)
|
||||
{
|
||||
if (call->args.data[i]->location.containsClosed(position))
|
||||
{
|
||||
argument = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (call->self)
|
||||
argument++;
|
||||
|
||||
std::optional<TypeId> argType;
|
||||
auto [args, tail] = flatten(outerFunction->argTypes);
|
||||
if (argument < args.size())
|
||||
argType = args[argument];
|
||||
|
||||
if (!argType)
|
||||
return std::nullopt;
|
||||
|
||||
TypeId followed = follow(*argType);
|
||||
const FunctionType* type = get<FunctionType>(followed);
|
||||
if (!type)
|
||||
{
|
||||
if (const UnionType* unionType = get<UnionType>(followed))
|
||||
{
|
||||
if (std::optional<const FunctionType*> nonnullFunction = returnFirstNonnullOptionOfType<FunctionType>(unionType))
|
||||
type = *nonnullFunction;
|
||||
}
|
||||
}
|
||||
|
||||
if (!type)
|
||||
return std::nullopt;
|
||||
|
||||
const ScopePtr scope = findScopeAtPosition(*module, position);
|
||||
if (!scope)
|
||||
return std::nullopt;
|
||||
|
||||
AutocompleteEntry entry;
|
||||
entry.kind = AutocompleteEntryKind::GeneratedFunction;
|
||||
entry.typeCorrect = TypeCorrectKind::Correct;
|
||||
entry.type = argType;
|
||||
entry.insertText = makeAnonymous(scope, *type);
|
||||
return std::make_optional(std::move(entry));
|
||||
}
|
||||
|
||||
static AutocompleteResult autocomplete(const SourceModule& sourceModule, const ModulePtr& module, NotNull<BuiltinTypes> builtinTypes,
|
||||
TypeArena* typeArena, Scope* globalScope, Position position, StringCompletionCallback callback)
|
||||
{
|
||||
|
@ -1612,7 +1802,19 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||
return {};
|
||||
|
||||
if (node->asExpr())
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
||||
{
|
||||
if (FFlag::LuauAnonymousAutofilled)
|
||||
{
|
||||
AutocompleteResult ret = autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
||||
if (std::optional<AutocompleteEntry> generated = makeAnonymousAutofilled(module, position, node, ancestry))
|
||||
ret.entryMap[kGeneratedAnonymousFunctionEntryName] = std::move(*generated);
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
||||
}
|
||||
}
|
||||
else if (node->asStat())
|
||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
||||
|
||||
|
@ -1621,11 +1823,14 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||
|
||||
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback)
|
||||
{
|
||||
// FIXME: We can improve performance here by parsing without checking.
|
||||
// The old type graph is probably fine. (famous last words!)
|
||||
FrontendOptions opts;
|
||||
opts.forAutocomplete = true;
|
||||
frontend.check(moduleName, opts);
|
||||
if (!FFlag::LuauAutocompleteLastTypecheck)
|
||||
{
|
||||
// FIXME: We can improve performance here by parsing without checking.
|
||||
// The old type graph is probably fine. (famous last words!)
|
||||
FrontendOptions opts;
|
||||
opts.forAutocomplete = true;
|
||||
frontend.check(moduleName, opts);
|
||||
}
|
||||
|
||||
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
|
||||
if (!sourceModule)
|
||||
|
|
|
@ -4,15 +4,12 @@
|
|||
#include "Luau/Lexer.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauEnableNonstrictByDefaultForLuauConfig, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using Error = std::optional<std::string>;
|
||||
|
||||
Config::Config()
|
||||
: mode(FFlag::LuauEnableNonstrictByDefaultForLuauConfig ? Mode::Nonstrict : Mode::NoCheck)
|
||||
{
|
||||
enabledLint.setDefaults();
|
||||
}
|
||||
|
|
|
@ -139,7 +139,8 @@ void forEachConstraint(const Checkpoint& start, const Checkpoint& end, const Con
|
|||
|
||||
ConstraintGraphBuilder::ConstraintGraphBuilder(ModulePtr module, TypeArena* arena, NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, DcrLogger* logger, NotNull<DataFlowGraph> dfg)
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, DcrLogger* logger, NotNull<DataFlowGraph> dfg,
|
||||
std::vector<RequireCycle> requireCycles)
|
||||
: module(module)
|
||||
, builtinTypes(builtinTypes)
|
||||
, arena(arena)
|
||||
|
@ -149,6 +150,7 @@ ConstraintGraphBuilder::ConstraintGraphBuilder(ModulePtr module, TypeArena* aren
|
|||
, ice(ice)
|
||||
, globalScope(globalScope)
|
||||
, prepareModuleScope(std::move(prepareModuleScope))
|
||||
, requireCycles(std::move(requireCycles))
|
||||
, logger(logger)
|
||||
{
|
||||
LUAU_ASSERT(module);
|
||||
|
@ -703,6 +705,16 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* l
|
|||
{
|
||||
scope->importedTypeBindings[name] = module->exportedTypeBindings;
|
||||
scope->importedModules[name] = moduleInfo->name;
|
||||
|
||||
// Imported types of requires that transitively refer to current module have to be replaced with 'any'
|
||||
for (const auto& [location, path] : requireCycles)
|
||||
{
|
||||
if (!path.empty() && path.front() == moduleInfo->name)
|
||||
{
|
||||
for (auto& [name, tf] : scope->importedTypeBindings[name])
|
||||
tf = TypeFun{{}, {}, builtinTypes->anyType};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1913,6 +1925,9 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGraphBuilder::checkBinary(
|
|||
NullableBreadcrumbId bc = dfg->getBreadcrumb(typeguard->target);
|
||||
if (!bc)
|
||||
return {leftType, rightType, nullptr};
|
||||
auto augmentForErrorSupression = [&](TypeId ty) -> TypeId {
|
||||
return arena->addType(UnionType{{ty, builtinTypes->errorType}});
|
||||
};
|
||||
|
||||
TypeId discriminantTy = builtinTypes->neverType;
|
||||
if (typeguard->type == "nil")
|
||||
|
@ -1926,9 +1941,9 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGraphBuilder::checkBinary(
|
|||
else if (typeguard->type == "thread")
|
||||
discriminantTy = builtinTypes->threadType;
|
||||
else if (typeguard->type == "table")
|
||||
discriminantTy = builtinTypes->tableType;
|
||||
discriminantTy = augmentForErrorSupression(builtinTypes->tableType);
|
||||
else if (typeguard->type == "function")
|
||||
discriminantTy = builtinTypes->functionType;
|
||||
discriminantTy = augmentForErrorSupression(builtinTypes->functionType);
|
||||
else if (typeguard->type == "userdata")
|
||||
{
|
||||
// For now, we don't really care about being accurate with userdata if the typeguard was using typeof.
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/Quantify.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/TimeTrace.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeFamily.h"
|
||||
|
@ -259,7 +260,7 @@ struct InstantiationQueuer : TypeOnceVisitor
|
|||
};
|
||||
|
||||
ConstraintSolver::ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, std::vector<NotNull<Constraint>> constraints,
|
||||
ModuleName moduleName, NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger)
|
||||
ModuleName moduleName, NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger, TypeCheckLimits limits)
|
||||
: arena(normalizer->arena)
|
||||
, builtinTypes(normalizer->builtinTypes)
|
||||
, normalizer(normalizer)
|
||||
|
@ -269,6 +270,7 @@ ConstraintSolver::ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope
|
|||
, moduleResolver(moduleResolver)
|
||||
, requireCycles(requireCycles)
|
||||
, logger(logger)
|
||||
, limits(std::move(limits))
|
||||
{
|
||||
opts.exhaustive = true;
|
||||
|
||||
|
@ -334,6 +336,11 @@ void ConstraintSolver::run()
|
|||
continue;
|
||||
}
|
||||
|
||||
if (limits.finishTime && TimeTrace::getClock() > *limits.finishTime)
|
||||
throwTimeLimitError();
|
||||
if (limits.cancellationToken && limits.cancellationToken->requested())
|
||||
throwUserCancelError();
|
||||
|
||||
std::string saveMe = FFlag::DebugLuauLogSolver ? toString(*c, opts) : std::string{};
|
||||
StepSnapshot snapshot;
|
||||
|
||||
|
@ -555,6 +562,9 @@ bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNull<con
|
|||
|
||||
Instantiation inst(TxnLog::empty(), arena, TypeLevel{}, constraint->scope);
|
||||
|
||||
if (limits.instantiationChildLimit)
|
||||
inst.childLimit = *limits.instantiationChildLimit;
|
||||
|
||||
std::optional<TypeId> instantiated = inst.substitute(c.superType);
|
||||
|
||||
LUAU_ASSERT(get<BlockedType>(c.subType));
|
||||
|
@ -586,7 +596,7 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull<const Const
|
|||
if (isBlocked(operandType))
|
||||
return block(operandType, constraint);
|
||||
|
||||
if (get<FreeType>(operandType))
|
||||
if (!force && get<FreeType>(operandType))
|
||||
return block(operandType, constraint);
|
||||
|
||||
LUAU_ASSERT(get<BlockedType>(c.resultType));
|
||||
|
@ -713,6 +723,10 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
if (mm)
|
||||
{
|
||||
Instantiation instantiation{TxnLog::empty(), arena, TypeLevel{}, constraint->scope};
|
||||
|
||||
if (limits.instantiationChildLimit)
|
||||
instantiation.childLimit = *limits.instantiationChildLimit;
|
||||
|
||||
std::optional<TypeId> instantiatedMm = instantiation.substitute(*mm);
|
||||
if (!instantiatedMm)
|
||||
{
|
||||
|
@ -1318,6 +1332,9 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
|
||||
Instantiation inst(TxnLog::empty(), arena, TypeLevel{}, constraint->scope);
|
||||
|
||||
if (limits.instantiationChildLimit)
|
||||
inst.childLimit = *limits.instantiationChildLimit;
|
||||
|
||||
std::vector<TypeId> arityMatchingOverloads;
|
||||
std::optional<TxnLog> bestOverloadLog;
|
||||
|
||||
|
@ -1334,7 +1351,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
}
|
||||
|
||||
Unifier u{normalizer, constraint->scope, Location{}, Covariant};
|
||||
u.enableScopeTests();
|
||||
u.enableNewSolver();
|
||||
|
||||
u.tryUnify(*instantiated, inferredTy, /* isFunctionCall */ true);
|
||||
|
||||
|
@ -1384,7 +1401,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
if (!bestOverloadLog)
|
||||
{
|
||||
Unifier u{normalizer, constraint->scope, Location{}, Covariant};
|
||||
u.enableScopeTests();
|
||||
u.enableNewSolver();
|
||||
|
||||
u.tryUnify(inferredTy, builtinTypes->anyType);
|
||||
u.tryUnify(fn, builtinTypes->anyType);
|
||||
|
@ -1505,6 +1522,7 @@ static void updateTheTableType(
|
|||
|
||||
for (size_t i = 0; i < path.size() - 1; ++i)
|
||||
{
|
||||
t = follow(t);
|
||||
auto propTy = findTablePropertyRespectingMeta(builtinTypes, dummy, t, path[i], Location{});
|
||||
dummy.clear();
|
||||
|
||||
|
@ -1885,19 +1903,20 @@ bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNull<const Cons
|
|||
if (!force && !blockedTypes.empty())
|
||||
return block(blockedTypes, constraint);
|
||||
|
||||
const NormalizedType* normType = normalizer->normalize(c.type);
|
||||
|
||||
if (!normType)
|
||||
reportError(NormalizationTooComplex{}, constraint->location);
|
||||
|
||||
if (normType && normType->shouldSuppressErrors())
|
||||
switch (shouldSuppressErrors(normalizer, c.type))
|
||||
{
|
||||
case ErrorSuppression::Suppress:
|
||||
{
|
||||
auto resultOrError = simplifyUnion(builtinTypes, arena, result, builtinTypes->errorType).result;
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(resultOrError);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
case ErrorSuppression::DoNotSuppress:
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(result);
|
||||
break;
|
||||
case ErrorSuppression::NormalizationFailed:
|
||||
reportError(NormalizationTooComplex{}, constraint->location);
|
||||
break;
|
||||
}
|
||||
|
||||
unblock(c.resultType, constraint->location);
|
||||
|
@ -1983,6 +2002,15 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
|||
unify(*anyified, ty, constraint->scope);
|
||||
};
|
||||
|
||||
auto unknownify = [&](auto ty) {
|
||||
Anyification anyify{arena, constraint->scope, builtinTypes, &iceReporter, builtinTypes->unknownType, builtinTypes->anyTypePack};
|
||||
std::optional anyified = anyify.substitute(ty);
|
||||
if (!anyified)
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
else
|
||||
unify(*anyified, ty, constraint->scope);
|
||||
};
|
||||
|
||||
auto errorify = [&](auto ty) {
|
||||
Anyification anyify{arena, constraint->scope, builtinTypes, &iceReporter, errorRecoveryType(), errorRecoveryTypePack()};
|
||||
std::optional errorified = anyify.substitute(ty);
|
||||
|
@ -2051,6 +2079,9 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
|||
|
||||
Instantiation instantiation(TxnLog::empty(), arena, TypeLevel{}, constraint->scope);
|
||||
|
||||
if (limits.instantiationChildLimit)
|
||||
instantiation.childLimit = *limits.instantiationChildLimit;
|
||||
|
||||
if (std::optional<TypeId> instantiatedIterFn = instantiation.substitute(*iterFn))
|
||||
{
|
||||
if (auto iterFtv = get<FunctionType>(*instantiatedIterFn))
|
||||
|
@ -2107,6 +2138,8 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
|||
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
else if (auto primitiveTy = get<PrimitiveType>(iteratorTy); primitiveTy && primitiveTy->type == PrimitiveType::Type::Table)
|
||||
unknownify(c.variables);
|
||||
else
|
||||
errorify(c.variables);
|
||||
|
||||
|
@ -2357,7 +2390,7 @@ template<typename TID>
|
|||
bool ConstraintSolver::tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
|
||||
{
|
||||
Unifier u{normalizer, constraint->scope, constraint->location, Covariant};
|
||||
u.enableScopeTests();
|
||||
u.enableNewSolver();
|
||||
|
||||
u.tryUnify(subTy, superTy);
|
||||
|
||||
|
@ -2606,7 +2639,7 @@ bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
|
|||
ErrorVec ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull<Scope> scope)
|
||||
{
|
||||
Unifier u{normalizer, scope, Location{}, Covariant};
|
||||
u.enableScopeTests();
|
||||
u.enableNewSolver();
|
||||
|
||||
u.tryUnify(subType, superType);
|
||||
|
||||
|
@ -2631,7 +2664,7 @@ ErrorVec ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNu
|
|||
{
|
||||
UnifierSharedState sharedState{&iceReporter};
|
||||
Unifier u{normalizer, scope, Location{}, Covariant};
|
||||
u.enableScopeTests();
|
||||
u.enableNewSolver();
|
||||
|
||||
u.tryUnify(subPack, superPack);
|
||||
|
||||
|
@ -2728,7 +2761,7 @@ TypeId ConstraintSolver::unionOfTypes(TypeId a, TypeId b, NotNull<Scope> scope,
|
|||
if (unifyFreeTypes && (get<FreeType>(a) || get<FreeType>(b)))
|
||||
{
|
||||
Unifier u{normalizer, scope, Location{}, Covariant};
|
||||
u.enableScopeTests();
|
||||
u.enableNewSolver();
|
||||
u.tryUnify(b, a);
|
||||
|
||||
if (u.errors.empty())
|
||||
|
@ -2785,4 +2818,14 @@ TypePackId ConstraintSolver::anyifyModuleReturnTypePackGenerics(TypePackId tp)
|
|||
return arena->addTypePack(resultTypes, resultTail);
|
||||
}
|
||||
|
||||
LUAU_NOINLINE void ConstraintSolver::throwTimeLimitError()
|
||||
{
|
||||
throw TimeLimitError(currentModuleName);
|
||||
}
|
||||
|
||||
LUAU_NOINLINE void ConstraintSolver::throwUserCancelError()
|
||||
{
|
||||
throw UserCancelError(currentModuleName);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Differ.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -34,6 +38,10 @@ std::string DiffPathNode::toString() const
|
|||
// Add 1 because Lua is 1-indexed
|
||||
return "Ret[" + std::to_string(*index + 1) + "]";
|
||||
}
|
||||
case DiffPathNode::Kind::Negation:
|
||||
{
|
||||
return "Negation";
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw InternalCompilerError{"DiffPathNode::toString is not exhaustive"};
|
||||
|
@ -58,22 +66,27 @@ DiffPathNode DiffPathNode::constructWithKind(Kind kind)
|
|||
|
||||
DiffPathNodeLeaf DiffPathNodeLeaf::detailsNormal(TypeId ty)
|
||||
{
|
||||
return DiffPathNodeLeaf{ty, std::nullopt, std::nullopt, false};
|
||||
return DiffPathNodeLeaf{ty, std::nullopt, std::nullopt, false, std::nullopt};
|
||||
}
|
||||
|
||||
DiffPathNodeLeaf DiffPathNodeLeaf::detailsTableProperty(TypeId ty, Name tableProperty)
|
||||
{
|
||||
return DiffPathNodeLeaf{ty, tableProperty, std::nullopt, false};
|
||||
return DiffPathNodeLeaf{ty, tableProperty, std::nullopt, false, std::nullopt};
|
||||
}
|
||||
|
||||
DiffPathNodeLeaf DiffPathNodeLeaf::detailsUnionIndex(TypeId ty, size_t index)
|
||||
{
|
||||
return DiffPathNodeLeaf{ty, std::nullopt, std::nullopt, false, index};
|
||||
}
|
||||
|
||||
DiffPathNodeLeaf DiffPathNodeLeaf::detailsLength(int minLength, bool isVariadic)
|
||||
{
|
||||
return DiffPathNodeLeaf{std::nullopt, std::nullopt, minLength, isVariadic};
|
||||
return DiffPathNodeLeaf{std::nullopt, std::nullopt, minLength, isVariadic, std::nullopt};
|
||||
}
|
||||
|
||||
DiffPathNodeLeaf DiffPathNodeLeaf::nullopts()
|
||||
{
|
||||
return DiffPathNodeLeaf{std::nullopt, std::nullopt, std::nullopt, false};
|
||||
return DiffPathNodeLeaf{std::nullopt, std::nullopt, std::nullopt, false, std::nullopt};
|
||||
}
|
||||
|
||||
std::string DiffPath::toString(bool prependDot) const
|
||||
|
@ -104,7 +117,7 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
|
|||
checkNonMissingPropertyLeavesHaveNulloptTableProperty();
|
||||
return pathStr + " has type " + Luau::toString(*leaf.ty);
|
||||
}
|
||||
case DiffError::Kind::MissingProperty:
|
||||
case DiffError::Kind::MissingTableProperty:
|
||||
{
|
||||
if (leaf.ty.has_value())
|
||||
{
|
||||
|
@ -120,6 +133,38 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
|
|||
}
|
||||
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
|
||||
}
|
||||
case DiffError::Kind::MissingUnionMember:
|
||||
{
|
||||
// TODO: do normal case
|
||||
if (leaf.ty.has_value())
|
||||
{
|
||||
if (!leaf.unionIndex.has_value())
|
||||
throw InternalCompilerError{"leaf.unionIndex is nullopt"};
|
||||
return pathStr + " is a union containing type " + Luau::toString(*leaf.ty);
|
||||
}
|
||||
else if (otherLeaf.ty.has_value())
|
||||
{
|
||||
return pathStr + " is a union missing type " + Luau::toString(*otherLeaf.ty);
|
||||
}
|
||||
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
|
||||
}
|
||||
case DiffError::Kind::MissingIntersectionMember:
|
||||
{
|
||||
// TODO: better message for intersections
|
||||
// An intersection of just functions is always an "overloaded function"
|
||||
// An intersection of just tables is always a "joined table"
|
||||
if (leaf.ty.has_value())
|
||||
{
|
||||
if (!leaf.unionIndex.has_value())
|
||||
throw InternalCompilerError{"leaf.unionIndex is nullopt"};
|
||||
return pathStr + " is an intersection containing type " + Luau::toString(*leaf.ty);
|
||||
}
|
||||
else if (otherLeaf.ty.has_value())
|
||||
{
|
||||
return pathStr + " is an intersection missing type " + Luau::toString(*otherLeaf.ty);
|
||||
}
|
||||
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
|
||||
}
|
||||
case DiffError::Kind::LengthMismatchInFnArgs:
|
||||
{
|
||||
if (!leaf.minLength.has_value())
|
||||
|
@ -163,9 +208,20 @@ std::string getDevFixFriendlyName(TypeId ty)
|
|||
|
||||
std::string DiffError::toString() const
|
||||
{
|
||||
std::string msg = "DiffError: these two types are not equal because the left type at " + toStringALeaf(leftRootName, left, right) +
|
||||
", while the right type at " + toStringALeaf(rightRootName, right, left);
|
||||
return msg;
|
||||
switch (kind)
|
||||
{
|
||||
case DiffError::Kind::IncompatibleGeneric:
|
||||
{
|
||||
std::string diffPathStr{diffPath.toString(true)};
|
||||
return "DiffError: these two types are not equal because the left generic at " + leftRootName + diffPathStr +
|
||||
" cannot be the same type parameter as the right generic at " + rightRootName + diffPathStr;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return "DiffError: these two types are not equal because the left type at " + toStringALeaf(leftRootName, left, right) +
|
||||
", while the right type at " + toStringALeaf(rightRootName, right, left);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiffError::checkValidInitialization(const DiffPathNodeLeaf& left, const DiffPathNodeLeaf& right)
|
||||
|
@ -193,6 +249,19 @@ static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right)
|
|||
static DifferResult diffPrimitive(DifferEnvironment& env, TypeId left, TypeId right);
|
||||
static DifferResult diffSingleton(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 diffNegation(DifferEnvironment& env, TypeId left, TypeId right);
|
||||
struct FindSeteqCounterexampleResult
|
||||
{
|
||||
// nullopt if no counterexample found
|
||||
std::optional<size_t> mismatchIdx;
|
||||
// true if counterexample is in the left, false if cex is in the right
|
||||
bool inLeft;
|
||||
};
|
||||
static FindSeteqCounterexampleResult findSeteqCounterexample(
|
||||
DifferEnvironment& env, const std::vector<TypeId>& left, const std::vector<TypeId>& right);
|
||||
static DifferResult diffUnion(DifferEnvironment& env, TypeId left, TypeId right);
|
||||
static DifferResult diffIntersection(DifferEnvironment& env, TypeId left, TypeId right);
|
||||
/**
|
||||
* The last argument gives context info on which complex type contained the TypePack.
|
||||
*/
|
||||
|
@ -205,6 +274,8 @@ static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right)
|
|||
{
|
||||
const TableType* leftTable = get<TableType>(left);
|
||||
const TableType* rightTable = get<TableType>(right);
|
||||
LUAU_ASSERT(leftTable);
|
||||
LUAU_ASSERT(rightTable);
|
||||
|
||||
for (auto const& [field, value] : leftTable->props)
|
||||
{
|
||||
|
@ -212,7 +283,7 @@ static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right)
|
|||
{
|
||||
// left has a field the right doesn't
|
||||
return DifferResult{DiffError{
|
||||
DiffError::Kind::MissingProperty,
|
||||
DiffError::Kind::MissingTableProperty,
|
||||
DiffPathNodeLeaf::detailsTableProperty(value.type(), field),
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
|
@ -225,9 +296,9 @@ static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right)
|
|||
if (leftTable->props.find(field) == leftTable->props.end())
|
||||
{
|
||||
// right has a field the left doesn't
|
||||
return DifferResult{
|
||||
DiffError{DiffError::Kind::MissingProperty, DiffPathNodeLeaf::nullopts(), DiffPathNodeLeaf::detailsTableProperty(value.type(), field),
|
||||
getDevFixFriendlyName(env.rootLeft), getDevFixFriendlyName(env.rootRight)}};
|
||||
return DifferResult{DiffError{DiffError::Kind::MissingTableProperty, DiffPathNodeLeaf::nullopts(),
|
||||
DiffPathNodeLeaf::detailsTableProperty(value.type(), field), getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight)}};
|
||||
}
|
||||
}
|
||||
// left and right have the same set of keys
|
||||
|
@ -248,6 +319,8 @@ static DifferResult diffPrimitive(DifferEnvironment& env, TypeId left, TypeId ri
|
|||
{
|
||||
const PrimitiveType* leftPrimitive = get<PrimitiveType>(left);
|
||||
const PrimitiveType* rightPrimitive = get<PrimitiveType>(right);
|
||||
LUAU_ASSERT(leftPrimitive);
|
||||
LUAU_ASSERT(rightPrimitive);
|
||||
|
||||
if (leftPrimitive->type != rightPrimitive->type)
|
||||
{
|
||||
|
@ -266,6 +339,8 @@ static DifferResult diffSingleton(DifferEnvironment& env, TypeId left, TypeId ri
|
|||
{
|
||||
const SingletonType* leftSingleton = get<SingletonType>(left);
|
||||
const SingletonType* rightSingleton = get<SingletonType>(right);
|
||||
LUAU_ASSERT(leftSingleton);
|
||||
LUAU_ASSERT(rightSingleton);
|
||||
|
||||
if (*leftSingleton != *rightSingleton)
|
||||
{
|
||||
|
@ -284,6 +359,8 @@ static DifferResult diffFunction(DifferEnvironment& env, TypeId left, TypeId rig
|
|||
{
|
||||
const FunctionType* leftFunction = get<FunctionType>(left);
|
||||
const FunctionType* rightFunction = get<FunctionType>(right);
|
||||
LUAU_ASSERT(leftFunction);
|
||||
LUAU_ASSERT(rightFunction);
|
||||
|
||||
DifferResult differResult = diffTpi(env, DiffError::Kind::LengthMismatchInFnArgs, leftFunction->argTypes, rightFunction->argTypes);
|
||||
if (differResult.diffError.has_value())
|
||||
|
@ -291,6 +368,157 @@ static DifferResult diffFunction(DifferEnvironment& env, TypeId left, TypeId rig
|
|||
return diffTpi(env, DiffError::Kind::LengthMismatchInFnRets, leftFunction->retTypes, rightFunction->retTypes);
|
||||
}
|
||||
|
||||
static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId right)
|
||||
{
|
||||
LUAU_ASSERT(get<GenericType>(left));
|
||||
LUAU_ASSERT(get<GenericType>(right));
|
||||
// Try to pair up the generics
|
||||
bool isLeftFree = !env.genericMatchedPairs.contains(left);
|
||||
bool isRightFree = !env.genericMatchedPairs.contains(right);
|
||||
if (isLeftFree && isRightFree)
|
||||
{
|
||||
env.genericMatchedPairs[left] = right;
|
||||
env.genericMatchedPairs[right] = left;
|
||||
return DifferResult{};
|
||||
}
|
||||
else if (isLeftFree || isRightFree)
|
||||
{
|
||||
return DifferResult{DiffError{
|
||||
DiffError::Kind::IncompatibleGeneric,
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
}};
|
||||
}
|
||||
|
||||
// Both generics are already paired up
|
||||
if (*env.genericMatchedPairs.find(left) == right)
|
||||
return DifferResult{};
|
||||
|
||||
return DifferResult{DiffError{
|
||||
DiffError::Kind::IncompatibleGeneric,
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
}};
|
||||
}
|
||||
|
||||
static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId right)
|
||||
{
|
||||
const NegationType* leftNegation = get<NegationType>(left);
|
||||
const NegationType* rightNegation = get<NegationType>(right);
|
||||
LUAU_ASSERT(leftNegation);
|
||||
LUAU_ASSERT(rightNegation);
|
||||
|
||||
DifferResult differResult = diffUsingEnv(env, leftNegation->ty, rightNegation->ty);
|
||||
if (!differResult.diffError.has_value())
|
||||
return DifferResult{};
|
||||
|
||||
differResult.wrapDiffPath(DiffPathNode::constructWithKind(DiffPathNode::Kind::Negation));
|
||||
return differResult;
|
||||
}
|
||||
|
||||
static FindSeteqCounterexampleResult findSeteqCounterexample(
|
||||
DifferEnvironment& env, const std::vector<TypeId>& left, const std::vector<TypeId>& right)
|
||||
{
|
||||
std::unordered_set<size_t> unmatchedRightIdxes;
|
||||
for (size_t i = 0; i < right.size(); i++)
|
||||
unmatchedRightIdxes.insert(i);
|
||||
for (size_t leftIdx = 0; leftIdx < left.size(); leftIdx++)
|
||||
{
|
||||
bool leftIdxIsMatched = false;
|
||||
auto unmatchedRightIdxIt = unmatchedRightIdxes.begin();
|
||||
while (unmatchedRightIdxIt != unmatchedRightIdxes.end())
|
||||
{
|
||||
DifferResult differResult = diffUsingEnv(env, left[leftIdx], right[*unmatchedRightIdxIt]);
|
||||
if (differResult.diffError.has_value())
|
||||
{
|
||||
unmatchedRightIdxIt++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// unmatchedRightIdxIt is matched with current leftIdx
|
||||
leftIdxIsMatched = true;
|
||||
unmatchedRightIdxIt = unmatchedRightIdxes.erase(unmatchedRightIdxIt);
|
||||
}
|
||||
if (!leftIdxIsMatched)
|
||||
{
|
||||
return FindSeteqCounterexampleResult{leftIdx, true};
|
||||
}
|
||||
}
|
||||
if (unmatchedRightIdxes.empty())
|
||||
return FindSeteqCounterexampleResult{std::nullopt, false};
|
||||
return FindSeteqCounterexampleResult{*unmatchedRightIdxes.begin(), false};
|
||||
}
|
||||
|
||||
static DifferResult diffUnion(DifferEnvironment& env, TypeId left, TypeId right)
|
||||
{
|
||||
const UnionType* leftUnion = get<UnionType>(left);
|
||||
const UnionType* rightUnion = get<UnionType>(right);
|
||||
LUAU_ASSERT(leftUnion);
|
||||
LUAU_ASSERT(rightUnion);
|
||||
|
||||
FindSeteqCounterexampleResult findSeteqCexResult = findSeteqCounterexample(env, leftUnion->options, rightUnion->options);
|
||||
if (findSeteqCexResult.mismatchIdx.has_value())
|
||||
{
|
||||
if (findSeteqCexResult.inLeft)
|
||||
return DifferResult{DiffError{
|
||||
DiffError::Kind::MissingUnionMember,
|
||||
DiffPathNodeLeaf::detailsUnionIndex(leftUnion->options[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx),
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
}};
|
||||
else
|
||||
return DifferResult{DiffError{
|
||||
DiffError::Kind::MissingUnionMember,
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
DiffPathNodeLeaf::detailsUnionIndex(rightUnion->options[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
}};
|
||||
}
|
||||
|
||||
// TODO: somehow detect mismatch index, likely using heuristics
|
||||
|
||||
return DifferResult{};
|
||||
}
|
||||
|
||||
static DifferResult diffIntersection(DifferEnvironment& env, TypeId left, TypeId right)
|
||||
{
|
||||
const IntersectionType* leftIntersection = get<IntersectionType>(left);
|
||||
const IntersectionType* rightIntersection = get<IntersectionType>(right);
|
||||
LUAU_ASSERT(leftIntersection);
|
||||
LUAU_ASSERT(rightIntersection);
|
||||
|
||||
FindSeteqCounterexampleResult findSeteqCexResult = findSeteqCounterexample(env, leftIntersection->parts, rightIntersection->parts);
|
||||
if (findSeteqCexResult.mismatchIdx.has_value())
|
||||
{
|
||||
if (findSeteqCexResult.inLeft)
|
||||
return DifferResult{DiffError{
|
||||
DiffError::Kind::MissingIntersectionMember,
|
||||
DiffPathNodeLeaf::detailsUnionIndex(leftIntersection->parts[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx),
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
}};
|
||||
else
|
||||
return DifferResult{DiffError{
|
||||
DiffError::Kind::MissingIntersectionMember,
|
||||
DiffPathNodeLeaf::nullopts(),
|
||||
DiffPathNodeLeaf::detailsUnionIndex(rightIntersection->parts[*findSeteqCexResult.mismatchIdx], *findSeteqCexResult.mismatchIdx),
|
||||
getDevFixFriendlyName(env.rootLeft),
|
||||
getDevFixFriendlyName(env.rootRight),
|
||||
}};
|
||||
}
|
||||
|
||||
// TODO: somehow detect mismatch index, likely using heuristics
|
||||
|
||||
return DifferResult{};
|
||||
}
|
||||
|
||||
static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId right)
|
||||
{
|
||||
left = follow(left);
|
||||
|
@ -322,6 +550,10 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig
|
|||
// Both left and right must be Any if either is Any for them to be equal!
|
||||
return DifferResult{};
|
||||
}
|
||||
else if (auto ln = get<NegationType>(left))
|
||||
{
|
||||
return diffNegation(env, left, right);
|
||||
}
|
||||
|
||||
throw InternalCompilerError{"Unimplemented Simple TypeId variant for diffing"};
|
||||
}
|
||||
|
@ -336,6 +568,24 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig
|
|||
{
|
||||
return diffFunction(env, left, right);
|
||||
}
|
||||
if (auto lg = get<GenericType>(left))
|
||||
{
|
||||
return diffGeneric(env, left, right);
|
||||
}
|
||||
if (auto lu = get<UnionType>(left))
|
||||
{
|
||||
return diffUnion(env, left, right);
|
||||
}
|
||||
if (auto li = get<IntersectionType>(left))
|
||||
{
|
||||
return diffIntersection(env, left, right);
|
||||
}
|
||||
if (auto le = get<Luau::Unifiable::Error>(left))
|
||||
{
|
||||
// TODO: return debug-friendly result state
|
||||
return DifferResult{};
|
||||
}
|
||||
|
||||
throw InternalCompilerError{"Unimplemented non-simple TypeId variant for diffing"};
|
||||
}
|
||||
|
||||
|
@ -444,7 +694,7 @@ static DifferResult diffHandleFlattenedTail(DifferEnvironment& env, DiffError::K
|
|||
|
||||
DifferResult diff(TypeId ty1, TypeId ty2)
|
||||
{
|
||||
DifferEnvironment differEnv{ty1, ty2};
|
||||
DifferEnvironment differEnv{ty1, ty2, DenseHashMap<TypeId, TypeId>{nullptr}};
|
||||
return diffUsingEnv(differEnv, ty1, ty2);
|
||||
}
|
||||
|
||||
|
@ -452,7 +702,7 @@ bool isSimple(TypeId ty)
|
|||
{
|
||||
ty = follow(ty);
|
||||
// TODO: think about GenericType, etc.
|
||||
return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty);
|
||||
return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty) || get<NegationType>(ty);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -1141,22 +1141,26 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons
|
|||
|
||||
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& parentScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options)
|
||||
const ScopePtr& parentScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
|
||||
TypeCheckLimits limits)
|
||||
{
|
||||
const bool recordJsonLog = FFlag::DebugLuauLogSolverToJson;
|
||||
return check(sourceModule, requireCycles, builtinTypes, iceHandler, moduleResolver, fileResolver, parentScope, std::move(prepareModuleScope),
|
||||
options, recordJsonLog);
|
||||
options, limits, recordJsonLog);
|
||||
}
|
||||
|
||||
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& parentScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
|
||||
bool recordJsonLog)
|
||||
TypeCheckLimits limits, bool recordJsonLog)
|
||||
{
|
||||
ModulePtr result = std::make_shared<Module>();
|
||||
result->name = sourceModule.name;
|
||||
result->humanReadableName = sourceModule.humanReadableName;
|
||||
|
||||
result->internalTypes.owningModule = result.get();
|
||||
result->interfaceTypes.owningModule = result.get();
|
||||
|
||||
iceHandler->moduleName = sourceModule.name;
|
||||
|
||||
std::unique_ptr<DcrLogger> logger;
|
||||
|
@ -1174,32 +1178,34 @@ ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle
|
|||
|
||||
UnifierSharedState unifierState{iceHandler};
|
||||
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
||||
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
|
||||
unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit);
|
||||
|
||||
Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}};
|
||||
|
||||
ConstraintGraphBuilder cgb{
|
||||
result,
|
||||
&result->internalTypes,
|
||||
moduleResolver,
|
||||
builtinTypes,
|
||||
iceHandler,
|
||||
parentScope,
|
||||
std::move(prepareModuleScope),
|
||||
logger.get(),
|
||||
NotNull{&dfg},
|
||||
};
|
||||
ConstraintGraphBuilder cgb{result, &result->internalTypes, moduleResolver, builtinTypes, iceHandler, parentScope, std::move(prepareModuleScope),
|
||||
logger.get(), NotNull{&dfg}, requireCycles};
|
||||
|
||||
cgb.visit(sourceModule.root);
|
||||
result->errors = std::move(cgb.errors);
|
||||
|
||||
ConstraintSolver cs{
|
||||
NotNull{&normalizer}, NotNull(cgb.rootScope), borrowConstraints(cgb.constraints), result->name, moduleResolver, requireCycles, logger.get()};
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cgb.rootScope), borrowConstraints(cgb.constraints), result->humanReadableName, moduleResolver,
|
||||
requireCycles, logger.get(), limits};
|
||||
|
||||
if (options.randomizeConstraintResolutionSeed)
|
||||
cs.randomize(*options.randomizeConstraintResolutionSeed);
|
||||
|
||||
cs.run();
|
||||
try
|
||||
{
|
||||
cs.run();
|
||||
}
|
||||
catch (const TimeLimitError&)
|
||||
{
|
||||
result->timeout = true;
|
||||
}
|
||||
catch (const UserCancelError&)
|
||||
{
|
||||
result->cancelled = true;
|
||||
}
|
||||
|
||||
for (TypeError& e : cs.errors)
|
||||
result->errors.emplace_back(std::move(e));
|
||||
|
@ -1209,7 +1215,22 @@ ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle
|
|||
|
||||
result->clonePublicInterface(builtinTypes, *iceHandler);
|
||||
|
||||
Luau::check(builtinTypes, NotNull{&unifierState}, logger.get(), sourceModule, result.get());
|
||||
if (result->timeout || result->cancelled)
|
||||
{
|
||||
// If solver was interrupted, skip typechecking and replace all module results with error-supressing types to avoid leaking blocked/pending types
|
||||
ScopePtr moduleScope = result->getModuleScope();
|
||||
moduleScope->returnType = builtinTypes->errorRecoveryTypePack();
|
||||
|
||||
for (auto& [name, ty] : result->declaredGlobals)
|
||||
ty = builtinTypes->errorRecoveryType();
|
||||
|
||||
for (auto& [name, tf] : result->exportedTypeBindings)
|
||||
tf.type = builtinTypes->errorRecoveryType();
|
||||
}
|
||||
else
|
||||
{
|
||||
Luau::check(builtinTypes, NotNull{&unifierState}, logger.get(), sourceModule, result.get());
|
||||
}
|
||||
|
||||
// It would be nice if we could freeze the arenas before doing type
|
||||
// checking, but we'll have to do some work to get there.
|
||||
|
@ -1248,7 +1269,7 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vect
|
|||
{
|
||||
return Luau::check(sourceModule, requireCycles, builtinTypes, NotNull{&iceHandler},
|
||||
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{fileResolver},
|
||||
environmentScope ? *environmentScope : globals.globalScope, prepareModuleScopeWrap, options, recordJsonLog);
|
||||
environmentScope ? *environmentScope : globals.globalScope, prepareModuleScopeWrap, options, typeCheckLimits, recordJsonLog);
|
||||
}
|
||||
catch (const InternalCompilerError& err)
|
||||
{
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauLintNativeComment, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -2825,6 +2827,12 @@ static void lintComments(LintContext& context, const std::vector<HotComment>& ho
|
|||
"optimize directive uses unknown optimization level '%s', 0..2 expected", level);
|
||||
}
|
||||
}
|
||||
else if (FFlag::LuauLintNativeComment && first == "native")
|
||||
{
|
||||
if (space != std::string::npos)
|
||||
emitWarning(context, LintWarning::Code_CommentDirective, hc.location,
|
||||
"native directive has extra symbols at the end of the line");
|
||||
}
|
||||
else
|
||||
{
|
||||
static const char* kHotComments[] = {
|
||||
|
@ -2833,6 +2841,7 @@ static void lintComments(LintContext& context, const std::vector<HotComment>& ho
|
|||
"nonstrict",
|
||||
"strict",
|
||||
"optimize",
|
||||
"native",
|
||||
};
|
||||
|
||||
if (const char* suggestion = fuzzyMatch(first, kHotComments, std::size(kHotComments)))
|
||||
|
|
|
@ -19,7 +19,6 @@ LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
|||
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeBlockedTypes, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeCyclicUnions, false);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauTransitiveSubtyping)
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
|
||||
|
@ -253,6 +252,14 @@ bool NormalizedType::shouldSuppressErrors() const
|
|||
return hasErrors() || get<AnyType>(tops);
|
||||
}
|
||||
|
||||
bool NormalizedType::hasTopTable() const
|
||||
{
|
||||
return hasTables() && std::any_of(tables.begin(), tables.end(), [&](TypeId ty) {
|
||||
auto primTy = get<PrimitiveType>(ty);
|
||||
return primTy && primTy->type == PrimitiveType::Type::Table;
|
||||
});
|
||||
}
|
||||
|
||||
bool NormalizedType::hasTops() const
|
||||
{
|
||||
return !get<NeverType>(tops);
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauSharedSelf)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include "Luau/Scope.h"
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -160,14 +162,12 @@ void Scope::inheritRefinements(const ScopePtr& childScope)
|
|||
dcrRefinements[k] = a;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
for (const auto& [k, a] : childScope->refinements)
|
||||
{
|
||||
for (const auto& [k, a] : childScope->refinements)
|
||||
{
|
||||
Symbol symbol = getBaseSymbol(k);
|
||||
if (lookup(symbol))
|
||||
refinements[k] = a;
|
||||
}
|
||||
Symbol symbol = getBaseSymbol(k);
|
||||
if (lookup(symbol))
|
||||
refinements[k] = a;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -364,7 +364,13 @@ Relation relate(TypeId left, TypeId right)
|
|||
if (auto ut = get<UnionType>(left))
|
||||
return Relation::Intersects;
|
||||
else if (auto ut = get<UnionType>(right))
|
||||
{
|
||||
std::vector<Relation> opts;
|
||||
for (TypeId part : ut)
|
||||
if (relate(left, part) == Relation::Subset)
|
||||
return Relation::Subset;
|
||||
return Relation::Intersects;
|
||||
}
|
||||
|
||||
if (auto rnt = get<NegationType>(right))
|
||||
{
|
||||
|
|
|
@ -3,9 +3,23 @@
|
|||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
bool Symbol::operator==(const Symbol& rhs) const
|
||||
{
|
||||
if (local)
|
||||
return local == rhs.local;
|
||||
else if (global.value)
|
||||
return rhs.global.value && global == rhs.global.value; // Subtlety: AstName::operator==(const char*) uses strcmp, not pointer identity.
|
||||
else if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
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)
|
||||
{
|
||||
if (name.local)
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
|
|
@ -283,7 +283,8 @@ struct TypeChecker2
|
|||
if (errors.empty())
|
||||
noTypeFamilyErrors.insert(instance);
|
||||
|
||||
reportErrors(std::move(errors));
|
||||
if (!isErrorSuppressing(location, instance))
|
||||
reportErrors(std::move(errors));
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
@ -488,7 +489,7 @@ struct TypeChecker2
|
|||
u.hideousFixMeGenericsAreActuallyFree = true;
|
||||
|
||||
u.tryUnify(actualRetType, expectedRetType);
|
||||
const bool ok = u.errors.empty() && u.log.empty();
|
||||
const bool ok = (u.errors.empty() && u.log.empty()) || isErrorSuppressing(ret->location, actualRetType, ret->location, expectedRetType);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
|
@ -526,9 +527,7 @@ struct TypeChecker2
|
|||
TypeId valueType = value ? lookupType(value) : nullptr;
|
||||
if (valueType)
|
||||
{
|
||||
ErrorVec errors = tryUnify(stack.back(), value->location, valueType, annotationType);
|
||||
if (!errors.empty())
|
||||
reportErrors(std::move(errors));
|
||||
reportErrors(tryUnify(stack.back(), value->location, valueType, annotationType));
|
||||
}
|
||||
|
||||
visit(var->annotation);
|
||||
|
@ -554,9 +553,7 @@ struct TypeChecker2
|
|||
if (var->annotation)
|
||||
{
|
||||
TypeId varType = lookupAnnotation(var->annotation);
|
||||
ErrorVec errors = tryUnify(stack.back(), value->location, valueTypes.head[j - i], varType);
|
||||
if (!errors.empty())
|
||||
reportErrors(std::move(errors));
|
||||
reportErrors(tryUnify(stack.back(), value->location, valueTypes.head[j - i], varType));
|
||||
|
||||
visit(var->annotation);
|
||||
}
|
||||
|
@ -764,6 +761,11 @@ struct TypeChecker2
|
|||
}
|
||||
};
|
||||
|
||||
const NormalizedType* iteratorNorm = normalizer.normalize(iteratorTy);
|
||||
|
||||
if (!iteratorNorm)
|
||||
reportError(NormalizationTooComplex{}, firstValue->location);
|
||||
|
||||
/*
|
||||
* If the first iterator argument is a function
|
||||
* * There must be 1 to 3 iterator arguments. Name them (nextTy,
|
||||
|
@ -798,7 +800,7 @@ struct TypeChecker2
|
|||
{
|
||||
// nothing
|
||||
}
|
||||
else if (isOptional(iteratorTy))
|
||||
else if (isOptional(iteratorTy) && !(iteratorNorm && iteratorNorm->shouldSuppressErrors()))
|
||||
{
|
||||
reportError(OptionalValueAccess{iteratorTy}, forInStatement->values.data[0]->location);
|
||||
}
|
||||
|
@ -833,7 +835,7 @@ struct TypeChecker2
|
|||
{
|
||||
checkFunction(nextFtv, instantiatedIteratorTypes, true);
|
||||
}
|
||||
else
|
||||
else if (!isErrorSuppressing(forInStatement->values.data[0]->location, *instantiatedNextFn))
|
||||
{
|
||||
reportError(CannotCallNonFunction{*instantiatedNextFn}, forInStatement->values.data[0]->location);
|
||||
}
|
||||
|
@ -843,7 +845,7 @@ struct TypeChecker2
|
|||
reportError(UnificationTooComplex{}, forInStatement->values.data[0]->location);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (!isErrorSuppressing(forInStatement->values.data[0]->location, *iterMmTy))
|
||||
{
|
||||
// TODO: This will not tell the user that this is because the
|
||||
// metamethod isn't callable. This is not ideal, and we should
|
||||
|
@ -859,7 +861,11 @@ struct TypeChecker2
|
|||
reportError(UnificationTooComplex{}, forInStatement->values.data[0]->location);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (iteratorNorm && iteratorNorm->hasTopTable())
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
else if (!iteratorNorm || !iteratorNorm->shouldSuppressErrors())
|
||||
{
|
||||
reportError(CannotCallNonFunction{iteratorTy}, forInStatement->values.data[0]->location);
|
||||
}
|
||||
|
@ -882,7 +888,9 @@ struct TypeChecker2
|
|||
if (get<NeverType>(lhsType))
|
||||
continue;
|
||||
|
||||
if (!isSubtype(rhsType, lhsType, stack.back()))
|
||||
|
||||
if (!isSubtype(rhsType, lhsType, stack.back()) &&
|
||||
!isErrorSuppressing(assign->vars.data[i]->location, lhsType, assign->values.data[i]->location, rhsType))
|
||||
{
|
||||
reportError(TypeMismatch{lhsType, rhsType}, rhs->location);
|
||||
}
|
||||
|
@ -1064,8 +1072,8 @@ struct TypeChecker2
|
|||
void visitCall(AstExprCall* call)
|
||||
{
|
||||
TypePack args;
|
||||
std::vector<Location> argLocs;
|
||||
argLocs.reserve(call->args.size + 1);
|
||||
std::vector<AstExpr*> argExprs;
|
||||
argExprs.reserve(call->args.size + 1);
|
||||
|
||||
TypeId* originalCallTy = module->astOriginalCallTypes.find(call);
|
||||
TypeId* selectedOverloadTy = module->astOverloadResolvedTypes.find(call);
|
||||
|
@ -1088,18 +1096,18 @@ struct TypeChecker2
|
|||
ice->ice("method call expression has no 'self'");
|
||||
|
||||
args.head.push_back(lookupType(indexExpr->expr));
|
||||
argLocs.push_back(indexExpr->expr->location);
|
||||
argExprs.push_back(indexExpr->expr);
|
||||
}
|
||||
else if (findMetatableEntry(builtinTypes, module->errors, *originalCallTy, "__call", call->func->location))
|
||||
{
|
||||
args.head.insert(args.head.begin(), lookupType(call->func));
|
||||
argLocs.push_back(call->func->location);
|
||||
argExprs.push_back(call->func);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < call->args.size; ++i)
|
||||
{
|
||||
AstExpr* arg = call->args.data[i];
|
||||
argLocs.push_back(arg->location);
|
||||
argExprs.push_back(arg);
|
||||
TypeId* argTy = module->astTypes.find(arg);
|
||||
if (argTy)
|
||||
args.head.push_back(*argTy);
|
||||
|
@ -1127,20 +1135,20 @@ struct TypeChecker2
|
|||
call->location,
|
||||
};
|
||||
|
||||
resolver.resolve(fnTy, &args, call->func->location, &argLocs);
|
||||
resolver.resolve(fnTy, &args, call->func, &argExprs);
|
||||
auto norm = normalizer.normalize(fnTy);
|
||||
if (!norm)
|
||||
reportError(NormalizationTooComplex{}, call->func->location);
|
||||
|
||||
if (!resolver.ok.empty())
|
||||
if (norm && norm->shouldSuppressErrors())
|
||||
return; // error suppressing function type!
|
||||
else if (!resolver.ok.empty())
|
||||
return; // We found a call that works, so this is ok.
|
||||
else if (auto norm = normalizer.normalize(fnTy); !norm || !normalizer.isInhabited(norm))
|
||||
{
|
||||
if (!norm)
|
||||
reportError(NormalizationTooComplex{}, call->func->location);
|
||||
else
|
||||
return; // Ok. Calling an uninhabited type is no-op.
|
||||
}
|
||||
else if (!norm || !normalizer.isInhabited(norm))
|
||||
return; // Ok. Calling an uninhabited type is no-op.
|
||||
else if (!resolver.nonviableOverloads.empty())
|
||||
{
|
||||
if (resolver.nonviableOverloads.size() == 1)
|
||||
if (resolver.nonviableOverloads.size() == 1 && !isErrorSuppressing(call->func->location, resolver.nonviableOverloads.front().first))
|
||||
reportErrors(resolver.nonviableOverloads.front().second);
|
||||
else
|
||||
{
|
||||
|
@ -1224,13 +1232,26 @@ struct TypeChecker2
|
|||
InsertionOrderedMap<TypeId, std::pair<Analysis, size_t>> resolution;
|
||||
|
||||
private:
|
||||
template<typename Ty>
|
||||
std::optional<ErrorVec> tryUnify(const Location& location, Ty subTy, Ty superTy)
|
||||
std::optional<ErrorVec> tryUnify(const Location& location, TypeId subTy, TypeId superTy, const LiteralProperties* literalProperties = nullptr)
|
||||
{
|
||||
Unifier u{normalizer, scope, location, Covariant};
|
||||
u.ctx = CountMismatch::Arg;
|
||||
u.hideousFixMeGenericsAreActuallyFree = true;
|
||||
u.enableScopeTests();
|
||||
u.enableNewSolver();
|
||||
u.tryUnify(subTy, superTy, /*isFunctionCall*/ false, /*isIntersection*/ false, literalProperties);
|
||||
|
||||
if (u.errors.empty())
|
||||
return std::nullopt;
|
||||
|
||||
return std::move(u.errors);
|
||||
}
|
||||
|
||||
std::optional<ErrorVec> tryUnify(const Location& location, TypePackId subTy, TypePackId superTy)
|
||||
{
|
||||
Unifier u{normalizer, scope, location, Covariant};
|
||||
u.ctx = CountMismatch::Arg;
|
||||
u.hideousFixMeGenericsAreActuallyFree = true;
|
||||
u.enableNewSolver();
|
||||
u.tryUnify(subTy, superTy);
|
||||
|
||||
if (u.errors.empty())
|
||||
|
@ -1240,7 +1261,7 @@ struct TypeChecker2
|
|||
}
|
||||
|
||||
std::pair<Analysis, ErrorVec> checkOverload(
|
||||
TypeId fnTy, const TypePack* args, Location fnLoc, const std::vector<Location>* argLocs, bool callMetamethodOk = true)
|
||||
TypeId fnTy, const TypePack* args, AstExpr* fnLoc, const std::vector<AstExpr*>* argExprs, bool callMetamethodOk = true)
|
||||
{
|
||||
fnTy = follow(fnTy);
|
||||
|
||||
|
@ -1248,25 +1269,64 @@ struct TypeChecker2
|
|||
if (get<AnyType>(fnTy) || get<ErrorType>(fnTy) || get<NeverType>(fnTy))
|
||||
return {Ok, {}};
|
||||
else if (auto fn = get<FunctionType>(fnTy))
|
||||
return checkOverload_(fnTy, fn, args, fnLoc, argLocs); // Intentionally split to reduce the stack pressure of this function.
|
||||
return checkOverload_(fnTy, fn, args, fnLoc, argExprs); // Intentionally split to reduce the stack pressure of this function.
|
||||
else if (auto callMm = findMetatableEntry(builtinTypes, discard, fnTy, "__call", callLoc); callMm && callMetamethodOk)
|
||||
{
|
||||
// Calling a metamethod forwards the `fnTy` as self.
|
||||
TypePack withSelf = *args;
|
||||
withSelf.head.insert(withSelf.head.begin(), fnTy);
|
||||
|
||||
std::vector<Location> withSelfLocs = *argLocs;
|
||||
withSelfLocs.insert(withSelfLocs.begin(), fnLoc);
|
||||
std::vector<AstExpr*> withSelfExprs = *argExprs;
|
||||
withSelfExprs.insert(withSelfExprs.begin(), fnLoc);
|
||||
|
||||
return checkOverload(*callMm, &withSelf, fnLoc, &withSelfLocs, /*callMetamethodOk=*/false);
|
||||
return checkOverload(*callMm, &withSelf, fnLoc, &withSelfExprs, /*callMetamethodOk=*/false);
|
||||
}
|
||||
else
|
||||
return {TypeIsNotAFunction, {}}; // Intentionally empty. We can just fabricate the type error later on.
|
||||
}
|
||||
|
||||
static bool isLiteral(AstExpr* expr)
|
||||
{
|
||||
if (auto group = expr->as<AstExprGroup>())
|
||||
return isLiteral(group->expr);
|
||||
else if (auto assertion = expr->as<AstExprTypeAssertion>())
|
||||
return isLiteral(assertion->expr);
|
||||
|
||||
return
|
||||
expr->is<AstExprConstantNil>() ||
|
||||
expr->is<AstExprConstantBool>() ||
|
||||
expr->is<AstExprConstantNumber>() ||
|
||||
expr->is<AstExprConstantString>() ||
|
||||
expr->is<AstExprFunction>() ||
|
||||
expr->is<AstExprTable>();
|
||||
}
|
||||
|
||||
static std::unique_ptr<LiteralProperties> buildLiteralPropertiesSet(AstExpr* expr)
|
||||
{
|
||||
const AstExprTable* table = expr->as<AstExprTable>();
|
||||
if (!table)
|
||||
return nullptr;
|
||||
|
||||
std::unique_ptr<LiteralProperties> result = std::make_unique<LiteralProperties>(Name{});
|
||||
|
||||
for (const AstExprTable::Item& item : table->items)
|
||||
{
|
||||
if (item.kind != AstExprTable::Item::Record)
|
||||
continue;
|
||||
|
||||
AstExprConstantString* keyExpr = item.key->as<AstExprConstantString>();
|
||||
LUAU_ASSERT(keyExpr);
|
||||
|
||||
if (isLiteral(item.value))
|
||||
result->insert(Name{keyExpr->value.begin(), keyExpr->value.end()});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
LUAU_NOINLINE
|
||||
std::pair<Analysis, ErrorVec> checkOverload_(
|
||||
TypeId fnTy, const FunctionType* fn, const TypePack* args, Location fnLoc, const std::vector<Location>* argLocs)
|
||||
TypeId fnTy, const FunctionType* fn, const TypePack* args, AstExpr* fnExpr, const std::vector<AstExpr*>* argExprs)
|
||||
{
|
||||
TxnLog fake;
|
||||
FamilyGraphReductionResult result = reduceFamilies(fnTy, callLoc, arena, builtinTypes, scope, normalizer, &fake, /*force=*/true);
|
||||
|
@ -1286,9 +1346,11 @@ struct TypeChecker2
|
|||
|
||||
TypeId paramTy = *paramIter;
|
||||
TypeId argTy = args->head[argOffset];
|
||||
Location argLoc = argLocs->at(argOffset >= argLocs->size() ? argLocs->size() - 1 : argOffset);
|
||||
AstExpr* argLoc = argExprs->at(argOffset >= argExprs->size() ? argExprs->size() - 1 : argOffset);
|
||||
|
||||
if (auto errors = tryUnify(argLoc, argTy, paramTy))
|
||||
std::unique_ptr<LiteralProperties> literalProperties{buildLiteralPropertiesSet(argLoc)};
|
||||
|
||||
if (auto errors = tryUnify(argLoc->location, argTy, paramTy, literalProperties.get()))
|
||||
{
|
||||
// Since we're stopping right here, we need to decide if this is a nonviable overload or if there is an arity mismatch.
|
||||
// If it's a nonviable overload, then we need to keep going to get all type errors.
|
||||
|
@ -1308,19 +1370,21 @@ struct TypeChecker2
|
|||
// If we can iterate over the head of arguments, then we have exhausted the head of the parameters.
|
||||
LUAU_ASSERT(paramIter == end(fn->argTypes));
|
||||
|
||||
Location argLoc = argLocs->at(argOffset >= argLocs->size() ? argLocs->size() - 1 : argOffset);
|
||||
AstExpr* argExpr = argExprs->at(argOffset >= argExprs->size() ? argExprs->size() - 1 : argOffset);
|
||||
|
||||
if (!paramIter.tail())
|
||||
{
|
||||
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
|
||||
TypeError error{argLoc, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, false}};
|
||||
TypeError error{argExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, false}};
|
||||
return {ArityMismatch, {error}};
|
||||
}
|
||||
else if (auto vtp = get<VariadicTypePack>(follow(paramIter.tail())))
|
||||
{
|
||||
if (auto errors = tryUnify(argLoc, args->head[argOffset], vtp->ty))
|
||||
if (auto errors = tryUnify(argExpr->location, args->head[argOffset], vtp->ty))
|
||||
argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end());
|
||||
}
|
||||
else if (get<GenericTypePack>(follow(paramIter.tail())))
|
||||
argumentErrors.push_back(TypeError{argExpr->location, TypePackMismatch{fn->argTypes, arena->addTypePack(*args)}});
|
||||
|
||||
++argOffset;
|
||||
}
|
||||
|
@ -1333,18 +1397,18 @@ struct TypeChecker2
|
|||
// It may have a tail, however, so check that.
|
||||
if (auto vtp = get<VariadicTypePack>(follow(args->tail)))
|
||||
{
|
||||
Location argLoc = argLocs->at(argLocs->size() - 1);
|
||||
AstExpr* argExpr = argExprs->at(argExprs->size() - 1);
|
||||
|
||||
if (auto errors = tryUnify(argLoc, vtp->ty, *paramIter))
|
||||
if (auto errors = tryUnify(argExpr->location, vtp->ty, *paramIter))
|
||||
argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end());
|
||||
}
|
||||
else if (!isOptional(*paramIter))
|
||||
{
|
||||
Location argLoc = argLocs->empty() ? fnLoc : argLocs->at(argLocs->size() - 1);
|
||||
AstExpr* argExpr = argExprs->empty() ? fnExpr : argExprs->at(argExprs->size() - 1);
|
||||
|
||||
// It is ok to have excess parameters as long as they are all optional.
|
||||
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
|
||||
TypeError error{argLoc, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, false}};
|
||||
TypeError error{argExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, false}};
|
||||
return {ArityMismatch, {error}};
|
||||
}
|
||||
|
||||
|
@ -1355,13 +1419,27 @@ struct TypeChecker2
|
|||
LUAU_ASSERT(paramIter == end(fn->argTypes));
|
||||
LUAU_ASSERT(argOffset == args->head.size());
|
||||
|
||||
const Location argLoc = argExprs->empty() ? Location{} // TODO
|
||||
: argExprs->at(argExprs->size() - 1)->location;
|
||||
|
||||
if (paramIter.tail() && args->tail)
|
||||
{
|
||||
Location argLoc = argLocs->at(argLocs->size() - 1);
|
||||
|
||||
if (auto errors = tryUnify(argLoc, *args->tail, *paramIter.tail()))
|
||||
argumentErrors.insert(argumentErrors.end(), errors->begin(), errors->end());
|
||||
}
|
||||
else if (paramIter.tail())
|
||||
{
|
||||
const TypePackId paramTail = follow(*paramIter.tail());
|
||||
|
||||
if (get<GenericTypePack>(paramTail))
|
||||
{
|
||||
argumentErrors.push_back(TypeError{argLoc, TypePackMismatch{fn->argTypes, arena->addTypePack(*args)}});
|
||||
}
|
||||
else if (get<VariadicTypePack>(paramTail))
|
||||
{
|
||||
// Nothing. This is ok.
|
||||
}
|
||||
}
|
||||
|
||||
return {argumentErrors.empty() ? Ok : OverloadIsNonviable, argumentErrors};
|
||||
}
|
||||
|
@ -1409,14 +1487,14 @@ struct TypeChecker2
|
|||
}
|
||||
|
||||
public:
|
||||
void resolve(TypeId fnTy, const TypePack* args, Location selfLoc, const std::vector<Location>* argLocs)
|
||||
void resolve(TypeId fnTy, const TypePack* args, AstExpr* selfExpr, const std::vector<AstExpr*>* argExprs)
|
||||
{
|
||||
fnTy = follow(fnTy);
|
||||
|
||||
auto it = get<IntersectionType>(fnTy);
|
||||
if (!it)
|
||||
{
|
||||
auto [analysis, errors] = checkOverload(fnTy, args, selfLoc, argLocs);
|
||||
auto [analysis, errors] = checkOverload(fnTy, args, selfExpr, argExprs);
|
||||
add(analysis, fnTy, std::move(errors));
|
||||
return;
|
||||
}
|
||||
|
@ -1426,7 +1504,7 @@ struct TypeChecker2
|
|||
if (resolution.find(ty) != resolution.end())
|
||||
continue;
|
||||
|
||||
auto [analysis, errors] = checkOverload(ty, args, selfLoc, argLocs);
|
||||
auto [analysis, errors] = checkOverload(ty, args, selfExpr, argExprs);
|
||||
add(analysis, ty, std::move(errors));
|
||||
}
|
||||
}
|
||||
|
@ -1508,8 +1586,7 @@ struct TypeChecker2
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO!
|
||||
visit(indexExpr->expr, ValueContext::LValue);
|
||||
visit(indexExpr->expr, ValueContext::RValue);
|
||||
visit(indexExpr->index, ValueContext::RValue);
|
||||
|
||||
NotNull<Scope> scope = stack.back();
|
||||
|
@ -1576,7 +1653,8 @@ struct TypeChecker2
|
|||
TypeId inferredArgTy = *argIt;
|
||||
TypeId annotatedArgTy = lookupAnnotation(arg->annotation);
|
||||
|
||||
if (!isSubtype(inferredArgTy, annotatedArgTy, stack.back()))
|
||||
if (!isSubtype(inferredArgTy, annotatedArgTy, stack.back()) &&
|
||||
!isErrorSuppressing(arg->location, inferredArgTy, arg->annotation->location, annotatedArgTy))
|
||||
{
|
||||
reportError(TypeMismatch{inferredArgTy, annotatedArgTy}, arg->location);
|
||||
}
|
||||
|
@ -1610,7 +1688,7 @@ struct TypeChecker2
|
|||
TypeId operandType = lookupType(expr->expr);
|
||||
TypeId resultType = lookupType(expr);
|
||||
|
||||
if (get<AnyType>(operandType) || get<ErrorType>(operandType) || get<NeverType>(operandType))
|
||||
if (isErrorSuppressing(expr->expr->location, operandType))
|
||||
return;
|
||||
|
||||
if (auto it = kUnaryOpMetamethods.find(expr->op); it != kUnaryOpMetamethods.end())
|
||||
|
@ -1645,7 +1723,7 @@ struct TypeChecker2
|
|||
TypeId expectedFunction = testArena.addType(FunctionType{expectedArgs, expectedRet});
|
||||
|
||||
ErrorVec errors = tryUnify(scope, expr->location, *mm, expectedFunction);
|
||||
if (!errors.empty())
|
||||
if (!errors.empty() && !isErrorSuppressing(expr->expr->location, *firstArg, expr->expr->location, operandType))
|
||||
{
|
||||
reportError(TypeMismatch{*firstArg, operandType}, expr->location);
|
||||
return;
|
||||
|
@ -1660,7 +1738,10 @@ struct TypeChecker2
|
|||
{
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
int recursionCount = 0;
|
||||
const NormalizedType* nty = normalizer.normalize(operandType);
|
||||
|
||||
if (nty && nty->shouldSuppressErrors())
|
||||
return;
|
||||
|
||||
if (!hasLength(operandType, seen, &recursionCount))
|
||||
{
|
||||
|
@ -1719,6 +1800,8 @@ struct TypeChecker2
|
|||
return leftType;
|
||||
else if (get<AnyType>(rightType) || get<ErrorType>(rightType) || get<NeverType>(rightType))
|
||||
return rightType;
|
||||
else if ((normLeft && normLeft->shouldSuppressErrors()) || (normRight && normRight->shouldSuppressErrors()))
|
||||
return builtinTypes->anyType; // we can't say anything better if it's error suppressing but not any or error alone.
|
||||
|
||||
if ((get<BlockedType>(leftType) || get<FreeType>(leftType) || get<GenericType>(leftType)) && !isEquality && !isLogical)
|
||||
{
|
||||
|
@ -1933,6 +2016,9 @@ struct TypeChecker2
|
|||
case AstExprBinary::Op::CompareLe:
|
||||
case AstExprBinary::Op::CompareLt:
|
||||
{
|
||||
if (normLeft && normLeft->shouldSuppressErrors())
|
||||
return builtinTypes->numberType;
|
||||
|
||||
if (normLeft && normLeft->isExactlyNumber())
|
||||
{
|
||||
reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->numberType));
|
||||
|
@ -2324,7 +2410,7 @@ struct TypeChecker2
|
|||
TypeArena arena;
|
||||
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
|
||||
u.hideousFixMeGenericsAreActuallyFree = genericsOkay;
|
||||
u.enableScopeTests();
|
||||
u.enableNewSolver();
|
||||
|
||||
u.tryUnify(subTy, superTy);
|
||||
const bool ok = u.errors.empty() && u.log.empty();
|
||||
|
@ -2338,9 +2424,12 @@ struct TypeChecker2
|
|||
Unifier u{NotNull{&normalizer}, scope, location, Covariant};
|
||||
u.ctx = context;
|
||||
u.hideousFixMeGenericsAreActuallyFree = genericsOkay;
|
||||
u.enableScopeTests();
|
||||
u.enableNewSolver();
|
||||
u.tryUnify(subTy, superTy);
|
||||
|
||||
if (isErrorSuppressing(location, subTy, location, superTy))
|
||||
return {};
|
||||
|
||||
return std::move(u.errors);
|
||||
}
|
||||
|
||||
|
@ -2376,6 +2465,7 @@ struct TypeChecker2
|
|||
return;
|
||||
}
|
||||
|
||||
// if the type is error suppressing, we don't actually have any work left to do.
|
||||
if (norm->shouldSuppressErrors())
|
||||
return;
|
||||
|
||||
|
@ -2542,6 +2632,50 @@ struct TypeChecker2
|
|||
if (!candidates.empty())
|
||||
data = TypeErrorData(UnknownPropButFoundLikeProp{utk->table, utk->key, candidates});
|
||||
}
|
||||
|
||||
bool isErrorSuppressing(Location loc, TypeId ty)
|
||||
{
|
||||
switch (shouldSuppressErrors(NotNull{&normalizer}, ty))
|
||||
{
|
||||
case ErrorSuppression::DoNotSuppress:
|
||||
return false;
|
||||
case ErrorSuppression::Suppress:
|
||||
return true;
|
||||
case ErrorSuppression::NormalizationFailed:
|
||||
reportError(NormalizationTooComplex{}, loc);
|
||||
return false;
|
||||
};
|
||||
|
||||
LUAU_ASSERT(false);
|
||||
return false; // UNREACHABLE
|
||||
}
|
||||
|
||||
bool isErrorSuppressing(Location loc1, TypeId ty1, Location loc2, TypeId ty2)
|
||||
{
|
||||
return isErrorSuppressing(loc1, ty1) || isErrorSuppressing(loc2, ty2);
|
||||
}
|
||||
|
||||
bool isErrorSuppressing(Location loc, TypePackId tp)
|
||||
{
|
||||
switch (shouldSuppressErrors(NotNull{&normalizer}, tp))
|
||||
{
|
||||
case ErrorSuppression::DoNotSuppress:
|
||||
return false;
|
||||
case ErrorSuppression::Suppress:
|
||||
return true;
|
||||
case ErrorSuppression::NormalizationFailed:
|
||||
reportError(NormalizationTooComplex{}, loc);
|
||||
return false;
|
||||
};
|
||||
|
||||
LUAU_ASSERT(false);
|
||||
return false; // UNREACHABLE
|
||||
}
|
||||
|
||||
bool isErrorSuppressing(Location loc1, TypePackId tp1, Location loc2, TypePackId tp2)
|
||||
{
|
||||
return isErrorSuppressing(loc1, tp1) || isErrorSuppressing(loc2, tp2);
|
||||
}
|
||||
};
|
||||
|
||||
void check(
|
||||
|
|
|
@ -36,6 +36,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixCyclicModuleExports, false)
|
||||
LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckTypeguards, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
|
||||
|
@ -269,6 +270,8 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
|||
currentModule.reset(new Module);
|
||||
currentModule->name = module.name;
|
||||
currentModule->humanReadableName = module.humanReadableName;
|
||||
currentModule->internalTypes.owningModule = currentModule.get();
|
||||
currentModule->interfaceTypes.owningModule = currentModule.get();
|
||||
currentModule->type = module.type;
|
||||
currentModule->allocator = module.allocator;
|
||||
currentModule->names = module.names;
|
||||
|
@ -1193,6 +1196,19 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatLocal& local)
|
|||
{
|
||||
scope->importedTypeBindings[name] = module->exportedTypeBindings;
|
||||
scope->importedModules[name] = moduleInfo->name;
|
||||
|
||||
if (FFlag::LuauFixCyclicModuleExports)
|
||||
{
|
||||
// Imported types of requires that transitively refer to current module have to be replaced with 'any'
|
||||
for (const auto& [location, path] : requireCycles)
|
||||
{
|
||||
if (!path.empty() && path.front() == moduleInfo->name)
|
||||
{
|
||||
for (auto& [name, tf] : scope->importedTypeBindings[name])
|
||||
tf = TypeFun{{}, {}, anyType};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In non-strict mode we force the module type on the variable, in strict mode it is already unified
|
||||
|
|
|
@ -295,4 +295,58 @@ TypeId stripNil(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, TypeId ty)
|
|||
return follow(ty);
|
||||
}
|
||||
|
||||
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty)
|
||||
{
|
||||
const NormalizedType* normType = normalizer->normalize(ty);
|
||||
|
||||
if (!normType)
|
||||
return ErrorSuppression::NormalizationFailed;
|
||||
|
||||
return (normType->shouldSuppressErrors()) ? ErrorSuppression::Suppress : ErrorSuppression::DoNotSuppress;
|
||||
}
|
||||
|
||||
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypePackId tp)
|
||||
{
|
||||
auto [tys, tail] = flatten(tp);
|
||||
|
||||
// check the head, one type at a time
|
||||
for (TypeId ty : tys)
|
||||
{
|
||||
auto result = shouldSuppressErrors(normalizer, ty);
|
||||
if (result != ErrorSuppression::DoNotSuppress)
|
||||
return result;
|
||||
}
|
||||
|
||||
// check the tail if we have one and it's finite
|
||||
if (tail && finite(*tail))
|
||||
return shouldSuppressErrors(normalizer, *tail);
|
||||
|
||||
return ErrorSuppression::DoNotSuppress;
|
||||
}
|
||||
|
||||
// This is a useful helper because it is often the case that we are looking at specifically a pair of types that might suppress.
|
||||
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty1, TypeId ty2)
|
||||
{
|
||||
auto result = shouldSuppressErrors(normalizer, ty1);
|
||||
|
||||
// if ty1 is do not suppress, ty2 determines our overall behavior
|
||||
if (result == ErrorSuppression::DoNotSuppress)
|
||||
return shouldSuppressErrors(normalizer, ty2);
|
||||
|
||||
// otherwise, ty1 is either suppress or normalization failure which are both the appropriate overarching result
|
||||
return result;
|
||||
}
|
||||
|
||||
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypePackId tp1, TypePackId tp2)
|
||||
{
|
||||
auto result = shouldSuppressErrors(normalizer, tp1);
|
||||
|
||||
// if tp1 is do not suppress, tp2 determines our overall behavior
|
||||
if (result == ErrorSuppression::DoNotSuppress)
|
||||
return shouldSuppressErrors(normalizer, tp2);
|
||||
|
||||
// otherwise, tp1 is either suppress or normalization failure which are both the appropriate overarching result
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -22,7 +22,6 @@ LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
|
|||
LUAU_FASTFLAGVARIABLE(LuauMaintainScopesInUnifier, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOccursIsntAlwaysFailure, false)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauNormalizeBlockedTypes)
|
||||
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls)
|
||||
|
||||
|
@ -50,7 +49,7 @@ struct PromoteTypeLevels final : TypeOnceVisitor
|
|||
template<typename TID, typename T>
|
||||
void promote(TID ty, T* t)
|
||||
{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution && !t)
|
||||
if (useScopes && !t)
|
||||
return;
|
||||
|
||||
LUAU_ASSERT(t);
|
||||
|
@ -369,7 +368,6 @@ static std::optional<std::pair<Luau::Name, const SingletonType*>> getTableMatchT
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
// TODO: Inline and clip with FFlag::DebugLuauDeferredConstraintResolution
|
||||
template<typename TY_A, typename TY_B>
|
||||
static bool subsumes(bool useScopes, TY_A* left, TY_B* right)
|
||||
{
|
||||
|
@ -406,11 +404,11 @@ Unifier::Unifier(NotNull<Normalizer> normalizer, NotNull<Scope> scope, const Loc
|
|||
LUAU_ASSERT(sharedState.iceHandler);
|
||||
}
|
||||
|
||||
void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection)
|
||||
void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection, const LiteralProperties* literalProperties)
|
||||
{
|
||||
sharedState.counters.iterationCount = 0;
|
||||
|
||||
tryUnify_(subTy, superTy, isFunctionCall, isIntersection);
|
||||
tryUnify_(subTy, superTy, isFunctionCall, isIntersection, literalProperties);
|
||||
}
|
||||
|
||||
static bool isBlocked(const TxnLog& log, TypeId ty)
|
||||
|
@ -425,7 +423,7 @@ static bool isBlocked(const TxnLog& log, TypePackId tp)
|
|||
return get<BlockedTypePack>(tp);
|
||||
}
|
||||
|
||||
void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection)
|
||||
void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection, const LiteralProperties* literalProperties)
|
||||
{
|
||||
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
|
||||
|
||||
|
@ -443,6 +441,16 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
if (superTy == subTy)
|
||||
return;
|
||||
|
||||
if (isBlocked(log, subTy) && isBlocked(log, superTy))
|
||||
{
|
||||
blockedTypes.push_back(subTy);
|
||||
blockedTypes.push_back(superTy);
|
||||
}
|
||||
else if (isBlocked(log, subTy))
|
||||
blockedTypes.push_back(subTy);
|
||||
else if (isBlocked(log, superTy))
|
||||
blockedTypes.push_back(superTy);
|
||||
|
||||
if (log.get<TypeFamilyInstanceType>(superTy))
|
||||
{
|
||||
// We do not report errors from reducing here. This is because we will
|
||||
|
@ -470,7 +478,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
auto superFree = log.getMutable<FreeType>(superTy);
|
||||
auto subFree = log.getMutable<FreeType>(subTy);
|
||||
|
||||
if (superFree && subFree && subsumes(useScopes, superFree, subFree))
|
||||
if (superFree && subFree && subsumes(useNewSolver, superFree, subFree))
|
||||
{
|
||||
if (!occursCheck(subTy, superTy, /* reversed = */ false))
|
||||
log.replace(subTy, BoundType(superTy));
|
||||
|
@ -481,7 +489,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
{
|
||||
if (!occursCheck(superTy, subTy, /* reversed = */ true))
|
||||
{
|
||||
if (subsumes(useScopes, superFree, subFree))
|
||||
if (subsumes(useNewSolver, superFree, subFree))
|
||||
{
|
||||
log.changeLevel(subTy, superFree->level);
|
||||
}
|
||||
|
@ -495,7 +503,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
{
|
||||
// Unification can't change the level of a generic.
|
||||
auto subGeneric = log.getMutable<GenericType>(subTy);
|
||||
if (subGeneric && !subsumes(useScopes, subGeneric, superFree))
|
||||
if (subGeneric && !subsumes(useNewSolver, subGeneric, superFree))
|
||||
{
|
||||
// TODO: a more informative error message? CLI-39912
|
||||
reportError(location, GenericError{"Generic subtype escaping scope"});
|
||||
|
@ -504,7 +512,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
|
||||
if (!occursCheck(superTy, subTy, /* reversed = */ true))
|
||||
{
|
||||
promoteTypeLevels(log, types, superFree->level, superFree->scope, useScopes, subTy);
|
||||
promoteTypeLevels(log, types, superFree->level, superFree->scope, useNewSolver, subTy);
|
||||
|
||||
Widen widen{types, builtinTypes};
|
||||
log.replace(superTy, BoundType(widen(subTy)));
|
||||
|
@ -521,7 +529,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
|
||||
// Unification can't change the level of a generic.
|
||||
auto superGeneric = log.getMutable<GenericType>(superTy);
|
||||
if (superGeneric && !subsumes(useScopes, superGeneric, subFree))
|
||||
if (superGeneric && !subsumes(useNewSolver, superGeneric, subFree))
|
||||
{
|
||||
// TODO: a more informative error message? CLI-39912
|
||||
reportError(location, GenericError{"Generic supertype escaping scope"});
|
||||
|
@ -530,7 +538,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
|
||||
if (!occursCheck(subTy, superTy, /* reversed = */ false))
|
||||
{
|
||||
promoteTypeLevels(log, types, subFree->level, subFree->scope, useScopes, superTy);
|
||||
promoteTypeLevels(log, types, subFree->level, subFree->scope, useNewSolver, superTy);
|
||||
log.replace(subTy, BoundType(superTy));
|
||||
}
|
||||
|
||||
|
@ -542,7 +550,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
auto superGeneric = log.getMutable<GenericType>(superTy);
|
||||
auto subGeneric = log.getMutable<GenericType>(subTy);
|
||||
|
||||
if (superGeneric && subGeneric && subsumes(useScopes, superGeneric, subGeneric))
|
||||
if (superGeneric && subGeneric && subsumes(useNewSolver, superGeneric, subGeneric))
|
||||
{
|
||||
if (!occursCheck(subTy, superTy, /* reversed = */ false))
|
||||
log.replace(subTy, BoundType(superTy));
|
||||
|
@ -637,16 +645,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
|
||||
size_t errorCount = errors.size();
|
||||
|
||||
if (isBlocked(log, subTy) && isBlocked(log, superTy))
|
||||
{
|
||||
blockedTypes.push_back(subTy);
|
||||
blockedTypes.push_back(superTy);
|
||||
}
|
||||
else if (isBlocked(log, subTy))
|
||||
blockedTypes.push_back(subTy);
|
||||
else if (isBlocked(log, superTy))
|
||||
blockedTypes.push_back(superTy);
|
||||
else if (const UnionType* subUnion = log.getMutable<UnionType>(subTy))
|
||||
if (const UnionType* subUnion = log.getMutable<UnionType>(subTy))
|
||||
{
|
||||
tryUnifyUnionWithType(subTy, subUnion, superTy);
|
||||
}
|
||||
|
@ -717,7 +716,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
|
||||
else if (log.getMutable<TableType>(superTy) && log.getMutable<TableType>(subTy))
|
||||
{
|
||||
tryUnifyTables(subTy, superTy, isIntersection);
|
||||
tryUnifyTables(subTy, superTy, isIntersection, literalProperties);
|
||||
}
|
||||
else if (log.get<TableType>(superTy) && (log.get<PrimitiveType>(subTy) || log.get<SingletonType>(subTy)))
|
||||
{
|
||||
|
@ -772,7 +771,7 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionType* subUnion, Typ
|
|||
Unifier innerState = makeChildUnifier();
|
||||
innerState.tryUnify_(type, superTy);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
if (useNewSolver)
|
||||
logs.push_back(std::move(innerState.log));
|
||||
|
||||
if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||
|
@ -955,7 +954,7 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
|
|||
if (FFlag::LuauTransitiveSubtyping ? !innerState.failure : innerState.errors.empty())
|
||||
{
|
||||
found = true;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
if (useNewSolver)
|
||||
logs.push_back(std::move(innerState.log));
|
||||
else
|
||||
{
|
||||
|
@ -980,7 +979,7 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
|
|||
}
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
if (useNewSolver)
|
||||
log.concatAsUnion(combineLogsIntoUnion(std::move(logs)), NotNull{types});
|
||||
|
||||
if (unificationTooComplex)
|
||||
|
@ -1061,14 +1060,14 @@ void Unifier::tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const I
|
|||
firstFailedOption = {innerState.errors.front()};
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
if (useNewSolver)
|
||||
logs.push_back(std::move(innerState.log));
|
||||
else
|
||||
log.concat(std::move(innerState.log));
|
||||
failure |= innerState.failure;
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
if (useNewSolver)
|
||||
log.concat(combineLogsIntoIntersection(std::move(logs)));
|
||||
|
||||
if (unificationTooComplex)
|
||||
|
@ -1118,7 +1117,7 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType*
|
|||
}
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution && normalize)
|
||||
if (useNewSolver && normalize)
|
||||
{
|
||||
// We cannot normalize a type that contains blocked types. We have to
|
||||
// stop for now if we find any.
|
||||
|
@ -1161,7 +1160,7 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType*
|
|||
{
|
||||
found = true;
|
||||
errorsSuppressed = innerState.failure;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution || (FFlag::LuauTransitiveSubtyping && innerState.failure))
|
||||
if (useNewSolver || (FFlag::LuauTransitiveSubtyping && innerState.failure))
|
||||
logs.push_back(std::move(innerState.log));
|
||||
else
|
||||
{
|
||||
|
@ -1176,7 +1175,7 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType*
|
|||
}
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
if (useNewSolver)
|
||||
log.concat(combineLogsIntoIntersection(std::move(logs)));
|
||||
else if (FFlag::LuauTransitiveSubtyping && errorsSuppressed)
|
||||
log.concat(std::move(logs.front()));
|
||||
|
@ -1296,7 +1295,7 @@ void Unifier::tryUnifyNormalizedTypes(
|
|||
}
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
if (useNewSolver)
|
||||
{
|
||||
for (TypeId superTable : superNorm.tables)
|
||||
{
|
||||
|
@ -1527,6 +1526,15 @@ struct WeirdIter
|
|||
return pack != nullptr && index < pack->head.size();
|
||||
}
|
||||
|
||||
std::optional<TypePackId> tail() const
|
||||
{
|
||||
if (!pack)
|
||||
return packId;
|
||||
|
||||
LUAU_ASSERT(index == pack->head.size());
|
||||
return pack->tail;
|
||||
}
|
||||
|
||||
bool advance()
|
||||
{
|
||||
if (!pack)
|
||||
|
@ -1588,9 +1596,9 @@ struct WeirdIter
|
|||
}
|
||||
};
|
||||
|
||||
void Unifier::enableScopeTests()
|
||||
void Unifier::enableNewSolver()
|
||||
{
|
||||
useScopes = true;
|
||||
useNewSolver = true;
|
||||
log.useScopes = true;
|
||||
}
|
||||
|
||||
|
@ -1664,25 +1672,26 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||
blockedTypePacks.push_back(superTp);
|
||||
}
|
||||
else if (isBlocked(log, subTp))
|
||||
{
|
||||
blockedTypePacks.push_back(subTp);
|
||||
}
|
||||
else if (isBlocked(log, superTp))
|
||||
{
|
||||
blockedTypePacks.push_back(superTp);
|
||||
}
|
||||
if (log.getMutable<FreeTypePack>(superTp))
|
||||
|
||||
if (auto superFree = log.getMutable<FreeTypePack>(superTp))
|
||||
{
|
||||
if (!occursCheck(superTp, subTp, /* reversed = */ true))
|
||||
{
|
||||
Widen widen{types, builtinTypes};
|
||||
if (useNewSolver)
|
||||
promoteTypeLevels(log, types, superFree->level, superFree->scope, /*useScopes*/ true, subTp);
|
||||
log.replace(superTp, Unifiable::Bound<TypePackId>(widen(subTp)));
|
||||
}
|
||||
}
|
||||
else if (log.getMutable<FreeTypePack>(subTp))
|
||||
else if (auto subFree = log.getMutable<FreeTypePack>(subTp))
|
||||
{
|
||||
if (!occursCheck(subTp, superTp, /* reversed = */ false))
|
||||
{
|
||||
if (useNewSolver)
|
||||
promoteTypeLevels(log, types, subFree->level, subFree->scope, /*useScopes*/ true, superTp);
|
||||
log.replace(subTp, Unifiable::Bound<TypePackId>(superTp));
|
||||
}
|
||||
}
|
||||
|
@ -1771,28 +1780,74 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||
// If both are at the end, we're done
|
||||
if (!superIter.good() && !subIter.good())
|
||||
{
|
||||
const bool lFreeTail = superTpv->tail && log.getMutable<FreeTypePack>(log.follow(*superTpv->tail)) != nullptr;
|
||||
const bool rFreeTail = subTpv->tail && log.getMutable<FreeTypePack>(log.follow(*subTpv->tail)) != nullptr;
|
||||
if (lFreeTail && rFreeTail)
|
||||
if (useNewSolver)
|
||||
{
|
||||
tryUnify_(*subTpv->tail, *superTpv->tail);
|
||||
}
|
||||
else if (lFreeTail)
|
||||
{
|
||||
tryUnify_(emptyTp, *superTpv->tail);
|
||||
}
|
||||
else if (rFreeTail)
|
||||
{
|
||||
tryUnify_(emptyTp, *subTpv->tail);
|
||||
}
|
||||
else if (subTpv->tail && superTpv->tail)
|
||||
{
|
||||
if (log.getMutable<VariadicTypePack>(superIter.packId))
|
||||
tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index));
|
||||
else if (log.getMutable<VariadicTypePack>(subIter.packId))
|
||||
tryUnifyVariadics(superIter.packId, subIter.packId, true, int(superIter.index));
|
||||
if (subIter.tail() && superIter.tail())
|
||||
tryUnify_(*subIter.tail(), *superIter.tail());
|
||||
else if (subIter.tail())
|
||||
{
|
||||
const TypePackId subTail = log.follow(*subIter.tail());
|
||||
|
||||
if (log.get<FreeTypePack>(subTail))
|
||||
tryUnify_(subTail, emptyTp);
|
||||
else if (log.get<GenericTypePack>(subTail))
|
||||
reportError(location, TypePackMismatch{subTail, emptyTp});
|
||||
else if (log.get<VariadicTypePack>(subTail) || log.get<ErrorTypePack>(subTail))
|
||||
{
|
||||
// Nothing. This is ok.
|
||||
}
|
||||
else
|
||||
{
|
||||
ice("Unexpected subtype tail pack " + toString(subTail), location);
|
||||
}
|
||||
}
|
||||
else if (superIter.tail())
|
||||
{
|
||||
const TypePackId superTail = log.follow(*superIter.tail());
|
||||
|
||||
if (log.get<FreeTypePack>(superTail))
|
||||
tryUnify_(emptyTp, superTail);
|
||||
else if (log.get<GenericTypePack>(superTail))
|
||||
reportError(location, TypePackMismatch{emptyTp, superTail});
|
||||
else if (log.get<VariadicTypePack>(superTail) || log.get<ErrorTypePack>(superTail))
|
||||
{
|
||||
// Nothing. This is ok.
|
||||
}
|
||||
else
|
||||
{
|
||||
ice("Unexpected supertype tail pack " + toString(superTail), location);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Nothing. This is ok.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool lFreeTail = superTpv->tail && log.getMutable<FreeTypePack>(log.follow(*superTpv->tail)) != nullptr;
|
||||
const bool rFreeTail = subTpv->tail && log.getMutable<FreeTypePack>(log.follow(*subTpv->tail)) != nullptr;
|
||||
if (lFreeTail && rFreeTail)
|
||||
{
|
||||
tryUnify_(*subTpv->tail, *superTpv->tail);
|
||||
}
|
||||
else if (lFreeTail)
|
||||
{
|
||||
tryUnify_(emptyTp, *superTpv->tail);
|
||||
}
|
||||
else if (rFreeTail)
|
||||
{
|
||||
tryUnify_(emptyTp, *subTpv->tail);
|
||||
}
|
||||
else if (subTpv->tail && superTpv->tail)
|
||||
{
|
||||
if (log.getMutable<VariadicTypePack>(superIter.packId))
|
||||
tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index));
|
||||
else if (log.getMutable<VariadicTypePack>(subIter.packId))
|
||||
tryUnifyVariadics(superIter.packId, subIter.packId, true, int(superIter.index));
|
||||
else
|
||||
tryUnify_(*subTpv->tail, *superTpv->tail);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -2049,7 +2104,7 @@ struct Resetter
|
|||
|
||||
} // namespace
|
||||
|
||||
void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
||||
void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection, const LiteralProperties* literalProperties)
|
||||
{
|
||||
if (isPrim(log.follow(subTy), PrimitiveType::Table))
|
||||
subTy = builtinTypes->emptyTableType;
|
||||
|
@ -2134,7 +2189,8 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
{
|
||||
// TODO: read-only properties don't need invariance
|
||||
Resetter resetter{&variance};
|
||||
variance = Invariant;
|
||||
if (!literalProperties || !literalProperties->contains(name))
|
||||
variance = Invariant;
|
||||
|
||||
Unifier innerState = makeChildUnifier();
|
||||
innerState.tryUnify_(r->second.type(), prop.type());
|
||||
|
@ -2150,7 +2206,8 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
// TODO: read-only indexers don't need invariance
|
||||
// TODO: really we should only allow this if prop.type is optional.
|
||||
Resetter resetter{&variance};
|
||||
variance = Invariant;
|
||||
if (!literalProperties || !literalProperties->contains(name))
|
||||
variance = Invariant;
|
||||
|
||||
Unifier innerState = makeChildUnifier();
|
||||
innerState.tryUnify_(subTable->indexer->indexResultType, prop.type());
|
||||
|
@ -2213,10 +2270,17 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
// TODO: read-only indexers don't need invariance
|
||||
// TODO: really we should only allow this if prop.type is optional.
|
||||
Resetter resetter{&variance};
|
||||
variance = Invariant;
|
||||
if (!literalProperties || !literalProperties->contains(name))
|
||||
variance = Invariant;
|
||||
|
||||
Unifier innerState = makeChildUnifier();
|
||||
innerState.tryUnify_(superTable->indexer->indexResultType, prop.type());
|
||||
if (useNewSolver)
|
||||
innerState.tryUnify_(prop.type(), superTable->indexer->indexResultType);
|
||||
else
|
||||
{
|
||||
// Incredibly, the old solver depends on this bug somehow.
|
||||
innerState.tryUnify_(superTable->indexer->indexResultType, prop.type());
|
||||
}
|
||||
|
||||
checkChildUnifierTypeMismatch(innerState.errors, name, superTy, subTy);
|
||||
|
||||
|
@ -2478,7 +2542,7 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed)
|
|||
{
|
||||
case TableState::Free:
|
||||
{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
if (useNewSolver)
|
||||
{
|
||||
Unifier innerState = makeChildUnifier();
|
||||
bool missingProperty = false;
|
||||
|
@ -2843,8 +2907,8 @@ std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, N
|
|||
|
||||
TxnLog Unifier::combineLogsIntoIntersection(std::vector<TxnLog> logs)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
||||
TxnLog result(useScopes);
|
||||
LUAU_ASSERT(useNewSolver);
|
||||
TxnLog result(useNewSolver);
|
||||
for (TxnLog& log : logs)
|
||||
result.concatAsIntersections(std::move(log), NotNull{types});
|
||||
return result;
|
||||
|
@ -2852,7 +2916,7 @@ TxnLog Unifier::combineLogsIntoIntersection(std::vector<TxnLog> logs)
|
|||
|
||||
TxnLog Unifier::combineLogsIntoUnion(std::vector<TxnLog> logs)
|
||||
{
|
||||
TxnLog result(useScopes);
|
||||
TxnLog result(useNewSolver);
|
||||
for (TxnLog& log : logs)
|
||||
result.concatAsUnion(std::move(log), NotNull{types});
|
||||
return result;
|
||||
|
@ -3012,8 +3076,8 @@ Unifier Unifier::makeChildUnifier()
|
|||
u.normalize = normalize;
|
||||
u.checkInhabited = checkInhabited;
|
||||
|
||||
if (useScopes)
|
||||
u.enableScopeTests();
|
||||
if (useNewSolver)
|
||||
u.enableNewSolver();
|
||||
|
||||
return u;
|
||||
}
|
||||
|
|
|
@ -274,9 +274,6 @@ std::string runCode(lua_State* L, const std::string& source)
|
|||
return error;
|
||||
}
|
||||
|
||||
if (codegen)
|
||||
Luau::CodeGen::compile(L, -1);
|
||||
|
||||
lua_State* T = lua_newthread(L);
|
||||
|
||||
lua_pushvalue(L, -2);
|
||||
|
|
|
@ -12,12 +12,18 @@ namespace Luau
|
|||
namespace CodeGen
|
||||
{
|
||||
|
||||
enum CodeGenFlags
|
||||
{
|
||||
// Only run native codegen for modules that have been marked with --!native
|
||||
CodeGen_OnlyNativeModules = 1 << 0,
|
||||
};
|
||||
|
||||
bool isSupported();
|
||||
|
||||
void create(lua_State* L);
|
||||
|
||||
// Builds target function and all inner functions
|
||||
void compile(lua_State* L, int idx);
|
||||
void compile(lua_State* L, int idx, unsigned int flags = 0);
|
||||
|
||||
using AnnotatorFn = void (*)(void* context, std::string& result, int fid, int instpos);
|
||||
|
||||
|
|
|
@ -77,6 +77,12 @@ enum class IrCmd : uint8_t
|
|||
// B: unsigned int (hash)
|
||||
GET_HASH_NODE_ADDR,
|
||||
|
||||
// Get pointer (TValue) to Closure upvalue.
|
||||
// A: pointer or undef (Closure)
|
||||
// B: UPn
|
||||
// When undef is specified, uses current function Closure.
|
||||
GET_CLOSURE_UPVAL_ADDR,
|
||||
|
||||
// Store a tag into TValue
|
||||
// A: Rn
|
||||
// B: tag
|
||||
|
@ -542,10 +548,10 @@ enum class IrCmd : uint8_t
|
|||
FALLBACK_GETVARARGS,
|
||||
|
||||
// Create closure from a child proto
|
||||
// A: unsigned int (bytecode instruction index)
|
||||
// B: Rn (dest)
|
||||
// A: unsigned int (nups)
|
||||
// B: pointer (table)
|
||||
// C: unsigned int (protoid)
|
||||
FALLBACK_NEWCLOSURE,
|
||||
NEWCLOSURE,
|
||||
|
||||
// Create closure from a pre-created function object (reusing it unless environments diverge)
|
||||
// A: unsigned int (bytecode instruction index)
|
||||
|
@ -600,6 +606,10 @@ enum class IrCmd : uint8_t
|
|||
// Returns the string name of a type either from a __type metatable field or just based on the tag, alternative for typeof(x)
|
||||
// A: Rn
|
||||
GET_TYPEOF,
|
||||
|
||||
// Find or create an upval at the given level
|
||||
// A: Rn (level)
|
||||
FINDUPVAL,
|
||||
};
|
||||
|
||||
enum class IrConstKind : uint8_t
|
||||
|
|
|
@ -150,6 +150,7 @@ inline bool hasResult(IrCmd cmd)
|
|||
case IrCmd::GET_ARR_ADDR:
|
||||
case IrCmd::GET_SLOT_NODE_ADDR:
|
||||
case IrCmd::GET_HASH_NODE_ADDR:
|
||||
case IrCmd::GET_CLOSURE_UPVAL_ADDR:
|
||||
case IrCmd::ADD_INT:
|
||||
case IrCmd::SUB_INT:
|
||||
case IrCmd::ADD_NUM:
|
||||
|
@ -192,6 +193,8 @@ inline bool hasResult(IrCmd cmd)
|
|||
case IrCmd::INVOKE_LIBM:
|
||||
case IrCmd::GET_TYPE:
|
||||
case IrCmd::GET_TYPEOF:
|
||||
case IrCmd::NEWCLOSURE:
|
||||
case IrCmd::FINDUPVAL:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#define LUACODEGEN_API extern
|
||||
#endif
|
||||
|
||||
struct lua_State;
|
||||
typedef struct lua_State lua_State;
|
||||
|
||||
// returns 1 if Luau code generator is supported, 0 otherwise
|
||||
LUACODEGEN_API int luau_codegen_supported(void);
|
||||
|
|
|
@ -239,7 +239,7 @@ void create(lua_State* L)
|
|||
ecb->enter = onEnter;
|
||||
}
|
||||
|
||||
void compile(lua_State* L, int idx)
|
||||
void compile(lua_State* L, int idx, unsigned int flags)
|
||||
{
|
||||
LUAU_ASSERT(lua_isLfunction(L, idx));
|
||||
const TValue* func = luaA_toobject(L, idx);
|
||||
|
@ -249,6 +249,13 @@ void compile(lua_State* L, int idx)
|
|||
if (!data)
|
||||
return;
|
||||
|
||||
Proto* root = clvalue(func)->l.p;
|
||||
if ((flags & CodeGen_OnlyNativeModules) != 0 && (root->flags & LPF_NATIVE_MODULE) == 0)
|
||||
return;
|
||||
|
||||
std::vector<Proto*> protos;
|
||||
gatherFunctions(protos, root);
|
||||
|
||||
#if defined(__aarch64__)
|
||||
static unsigned int cpuFeatures = getCpuFeaturesA64();
|
||||
A64::AssemblyBuilderA64 build(/* logText= */ false, cpuFeatures);
|
||||
|
@ -256,9 +263,6 @@ void compile(lua_State* L, int idx)
|
|||
X64::AssemblyBuilderX64 build(/* logText= */ false);
|
||||
#endif
|
||||
|
||||
std::vector<Proto*> protos;
|
||||
gatherFunctions(protos, clvalue(func)->l.p);
|
||||
|
||||
ModuleHelpers helpers;
|
||||
#if defined(__aarch64__)
|
||||
A64::assembleHelpers(build, helpers);
|
||||
|
|
|
@ -427,9 +427,6 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
|||
case IrCmd::FALLBACK_GETVARARGS:
|
||||
defRange(vmRegOp(inst.b), function.intOp(inst.c));
|
||||
break;
|
||||
case IrCmd::FALLBACK_NEWCLOSURE:
|
||||
def(inst.b);
|
||||
break;
|
||||
case IrCmd::FALLBACK_DUPCLOSURE:
|
||||
def(inst.b);
|
||||
break;
|
||||
|
@ -448,6 +445,10 @@ static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock&
|
|||
use(inst.a);
|
||||
break;
|
||||
|
||||
case IrCmd::FINDUPVAL:
|
||||
use(inst.a);
|
||||
break;
|
||||
|
||||
default:
|
||||
// All instructions which reference registers have to be handled explicitly
|
||||
LUAU_ASSERT(inst.a.kind != IrOpKind::VmReg);
|
||||
|
|
|
@ -452,7 +452,7 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
|
|||
inst(IrCmd::FALLBACK_GETVARARGS, constUint(i), vmReg(LUAU_INSN_A(*pc)), constInt(LUAU_INSN_B(*pc) - 1));
|
||||
break;
|
||||
case LOP_NEWCLOSURE:
|
||||
inst(IrCmd::FALLBACK_NEWCLOSURE, constUint(i), vmReg(LUAU_INSN_A(*pc)), constUint(LUAU_INSN_D(*pc)));
|
||||
translateInstNewClosure(*this, pc, i);
|
||||
break;
|
||||
case LOP_DUPCLOSURE:
|
||||
inst(IrCmd::FALLBACK_DUPCLOSURE, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmConst(LUAU_INSN_D(*pc)));
|
||||
|
|
|
@ -61,6 +61,12 @@ static const char* getTagName(uint8_t tag)
|
|||
return "tuserdata";
|
||||
case LUA_TTHREAD:
|
||||
return "tthread";
|
||||
case LUA_TPROTO:
|
||||
return "tproto";
|
||||
case LUA_TUPVAL:
|
||||
return "tupval";
|
||||
case LUA_TDEADKEY:
|
||||
return "tdeadkey";
|
||||
default:
|
||||
LUAU_ASSERT(!"Unknown type tag");
|
||||
LUAU_UNREACHABLE();
|
||||
|
@ -93,6 +99,8 @@ const char* getCmdName(IrCmd cmd)
|
|||
return "GET_SLOT_NODE_ADDR";
|
||||
case IrCmd::GET_HASH_NODE_ADDR:
|
||||
return "GET_HASH_NODE_ADDR";
|
||||
case IrCmd::GET_CLOSURE_UPVAL_ADDR:
|
||||
return "GET_CLOSURE_UPVAL_ADDR";
|
||||
case IrCmd::STORE_TAG:
|
||||
return "STORE_TAG";
|
||||
case IrCmd::STORE_POINTER:
|
||||
|
@ -267,8 +275,8 @@ const char* getCmdName(IrCmd cmd)
|
|||
return "FALLBACK_PREPVARARGS";
|
||||
case IrCmd::FALLBACK_GETVARARGS:
|
||||
return "FALLBACK_GETVARARGS";
|
||||
case IrCmd::FALLBACK_NEWCLOSURE:
|
||||
return "FALLBACK_NEWCLOSURE";
|
||||
case IrCmd::NEWCLOSURE:
|
||||
return "NEWCLOSURE";
|
||||
case IrCmd::FALLBACK_DUPCLOSURE:
|
||||
return "FALLBACK_DUPCLOSURE";
|
||||
case IrCmd::FALLBACK_FORGPREP:
|
||||
|
@ -303,6 +311,8 @@ const char* getCmdName(IrCmd cmd)
|
|||
return "GET_TYPE";
|
||||
case IrCmd::GET_TYPEOF:
|
||||
return "GET_TYPEOF";
|
||||
case IrCmd::FINDUPVAL:
|
||||
return "FINDUPVAL";
|
||||
}
|
||||
|
||||
LUAU_UNREACHABLE();
|
||||
|
|
|
@ -314,6 +314,14 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
|||
build.add(inst.regA64, inst.regA64, zextReg(temp2), kLuaNodeSizeLog2);
|
||||
break;
|
||||
}
|
||||
case IrCmd::GET_CLOSURE_UPVAL_ADDR:
|
||||
{
|
||||
inst.regA64 = regs.allocReuse(KindA64::x, index, {inst.a});
|
||||
RegisterA64 cl = inst.a.kind == IrOpKind::Undef ? rClosure : regOp(inst.a);
|
||||
|
||||
build.add(inst.regA64, cl, uint16_t(offsetof(Closure, l.uprefs) + sizeof(TValue) * vmUpvalueOp(inst.b)));
|
||||
break;
|
||||
}
|
||||
case IrCmd::STORE_TAG:
|
||||
{
|
||||
RegisterA64 temp = regs.allocTemp(KindA64::w);
|
||||
|
@ -1044,13 +1052,20 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
|||
bool continueInVm = (inst.d.kind == IrOpKind::Constant && intOp(inst.d));
|
||||
Label fresh; // used when guard aborts execution or jumps to a VM exit
|
||||
Label& fail = continueInVm ? helpers.exitContinueVmClearNativeFlag : getTargetLabel(inst.c, fresh);
|
||||
|
||||
// To support DebugLuauAbortingChecks, CHECK_TAG with VmReg has to be handled
|
||||
RegisterA64 tag = inst.a.kind == IrOpKind::VmReg ? regs.allocTemp(KindA64::w) : regOp(inst.a);
|
||||
|
||||
if (inst.a.kind == IrOpKind::VmReg)
|
||||
build.ldr(tag, mem(rBase, vmRegOp(inst.a) * sizeof(TValue) + offsetof(TValue, tt)));
|
||||
|
||||
if (tagOp(inst.b) == 0)
|
||||
{
|
||||
build.cbnz(regOp(inst.a), fail);
|
||||
build.cbnz(tag, fail);
|
||||
}
|
||||
else
|
||||
{
|
||||
build.cmp(regOp(inst.a), tagOp(inst.b));
|
||||
build.cmp(tag, tagOp(inst.b));
|
||||
build.b(ConditionA64::NotEqual, fail);
|
||||
}
|
||||
if (!continueInVm)
|
||||
|
@ -1517,13 +1532,26 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
|||
regs.spill(build, index);
|
||||
emitFallback(build, offsetof(NativeContext, executeGETVARARGS), uintOp(inst.a));
|
||||
break;
|
||||
case IrCmd::FALLBACK_NEWCLOSURE:
|
||||
LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg);
|
||||
LUAU_ASSERT(inst.c.kind == IrOpKind::Constant);
|
||||
case IrCmd::NEWCLOSURE:
|
||||
{
|
||||
RegisterA64 reg = regOp(inst.b); // note: we need to call regOp before spill so that we don't do redundant reloads
|
||||
|
||||
regs.spill(build, index);
|
||||
emitFallback(build, offsetof(NativeContext, executeNEWCLOSURE), uintOp(inst.a));
|
||||
regs.spill(build, index, {reg});
|
||||
build.mov(x2, reg);
|
||||
|
||||
build.mov(x0, rState);
|
||||
build.mov(w1, uintOp(inst.a));
|
||||
|
||||
build.ldr(x3, mem(rClosure, offsetof(Closure, l.p)));
|
||||
build.ldr(x3, mem(x3, offsetof(Proto, p)));
|
||||
build.ldr(x3, mem(x3, sizeof(Proto*) * uintOp(inst.c)));
|
||||
|
||||
build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaF_newLclosure)));
|
||||
build.blr(x4);
|
||||
|
||||
inst.regA64 = regs.takeReg(x0, index);
|
||||
break;
|
||||
}
|
||||
case IrCmd::FALLBACK_DUPCLOSURE:
|
||||
LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg);
|
||||
LUAU_ASSERT(inst.c.kind == IrOpKind::VmConst);
|
||||
|
@ -1743,6 +1771,18 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
|||
break;
|
||||
}
|
||||
|
||||
case IrCmd::FINDUPVAL:
|
||||
{
|
||||
regs.spill(build, index);
|
||||
build.mov(x0, rState);
|
||||
build.add(x1, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue)));
|
||||
build.ldr(x2, mem(rNativeContext, offsetof(NativeContext, luaF_findupval)));
|
||||
build.blr(x2);
|
||||
|
||||
inst.regA64 = regs.takeReg(x0, index);
|
||||
break;
|
||||
}
|
||||
|
||||
// To handle unsupported instructions, add "case IrCmd::OP" and make sure to set error = true!
|
||||
}
|
||||
|
||||
|
|
|
@ -186,14 +186,40 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
|||
build.add(inst.regX64, tmp.reg);
|
||||
break;
|
||||
};
|
||||
case IrCmd::GET_CLOSURE_UPVAL_ADDR:
|
||||
{
|
||||
inst.regX64 = regs.allocRegOrReuse(SizeX64::qword, index, {inst.a});
|
||||
|
||||
if (inst.a.kind == IrOpKind::Undef)
|
||||
{
|
||||
build.mov(inst.regX64, sClosure);
|
||||
}
|
||||
else
|
||||
{
|
||||
RegisterX64 cl = regOp(inst.a);
|
||||
if (inst.regX64 != cl)
|
||||
build.mov(inst.regX64, cl);
|
||||
}
|
||||
|
||||
build.add(inst.regX64, offsetof(Closure, l.uprefs) + sizeof(TValue) * vmUpvalueOp(inst.b));
|
||||
break;
|
||||
}
|
||||
case IrCmd::STORE_TAG:
|
||||
if (inst.b.kind == IrOpKind::Constant)
|
||||
build.mov(luauRegTag(vmRegOp(inst.a)), tagOp(inst.b));
|
||||
{
|
||||
if (inst.a.kind == IrOpKind::Inst)
|
||||
build.mov(dword[regOp(inst.a) + offsetof(TValue, tt)], tagOp(inst.b));
|
||||
else
|
||||
build.mov(luauRegTag(vmRegOp(inst.a)), tagOp(inst.b));
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(!"Unsupported instruction form");
|
||||
break;
|
||||
case IrCmd::STORE_POINTER:
|
||||
build.mov(luauRegValue(vmRegOp(inst.a)), regOp(inst.b));
|
||||
if (inst.a.kind == IrOpKind::Inst)
|
||||
build.mov(qword[regOp(inst.a) + offsetof(TValue, value)], regOp(inst.b));
|
||||
else
|
||||
build.mov(luauRegValue(vmRegOp(inst.a)), regOp(inst.b));
|
||||
break;
|
||||
case IrCmd::STORE_DOUBLE:
|
||||
if (inst.b.kind == IrOpKind::Constant)
|
||||
|
@ -1207,12 +1233,25 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
|||
|
||||
emitFallback(regs, build, offsetof(NativeContext, executeGETVARARGS), uintOp(inst.a));
|
||||
break;
|
||||
case IrCmd::FALLBACK_NEWCLOSURE:
|
||||
LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg);
|
||||
LUAU_ASSERT(inst.c.kind == IrOpKind::Constant);
|
||||
case IrCmd::NEWCLOSURE:
|
||||
{
|
||||
ScopedRegX64 tmp2{regs, SizeX64::qword};
|
||||
build.mov(tmp2.reg, sClosure);
|
||||
build.mov(tmp2.reg, qword[tmp2.reg + offsetof(Closure, l.p)]);
|
||||
build.mov(tmp2.reg, qword[tmp2.reg + offsetof(Proto, p)]);
|
||||
build.mov(tmp2.reg, qword[tmp2.reg + sizeof(Proto*) * uintOp(inst.c)]);
|
||||
|
||||
emitFallback(regs, build, offsetof(NativeContext, executeNEWCLOSURE), uintOp(inst.a));
|
||||
IrCallWrapperX64 callWrap(regs, build, index);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::dword, uintOp(inst.a), inst.a);
|
||||
callWrap.addArgument(SizeX64::qword, regOp(inst.b), inst.b);
|
||||
callWrap.addArgument(SizeX64::qword, tmp2);
|
||||
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaF_newLclosure)]);
|
||||
|
||||
inst.regX64 = regs.takeReg(rax, index);
|
||||
break;
|
||||
}
|
||||
case IrCmd::FALLBACK_DUPCLOSURE:
|
||||
LUAU_ASSERT(inst.b.kind == IrOpKind::VmReg);
|
||||
LUAU_ASSERT(inst.c.kind == IrOpKind::VmConst);
|
||||
|
@ -1412,6 +1451,17 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
|||
break;
|
||||
}
|
||||
|
||||
case IrCmd::FINDUPVAL:
|
||||
{
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::qword, luauRegAddress(vmRegOp(inst.a)));
|
||||
callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaF_findupval)]);
|
||||
|
||||
inst.regX64 = regs.takeReg(rax, index);
|
||||
break;
|
||||
}
|
||||
|
||||
// Pseudo instructions
|
||||
case IrCmd::NOP:
|
||||
case IrCmd::SUBSTITUTE:
|
||||
|
|
|
@ -1213,5 +1213,61 @@ void translateInstOrX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp c
|
|||
}
|
||||
}
|
||||
|
||||
void translateInstNewClosure(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
{
|
||||
LUAU_ASSERT(unsigned(LUAU_INSN_D(*pc)) < unsigned(build.function.proto->sizep));
|
||||
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
Proto* pv = build.function.proto->p[LUAU_INSN_D(*pc)];
|
||||
|
||||
build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1));
|
||||
|
||||
IrOp env = build.inst(IrCmd::LOAD_ENV);
|
||||
IrOp ncl = build.inst(IrCmd::NEWCLOSURE, build.constUint(pv->nups), env, build.constUint(LUAU_INSN_D(*pc)));
|
||||
|
||||
build.inst(IrCmd::STORE_POINTER, build.vmReg(ra), ncl);
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TFUNCTION));
|
||||
|
||||
for (int ui = 0; ui < pv->nups; ++ui)
|
||||
{
|
||||
Instruction uinsn = pc[ui + 1];
|
||||
LUAU_ASSERT(LUAU_INSN_OP(uinsn) == LOP_CAPTURE);
|
||||
|
||||
IrOp dst = build.inst(IrCmd::GET_CLOSURE_UPVAL_ADDR, ncl, build.vmUpvalue(ui));
|
||||
|
||||
switch (LUAU_INSN_A(uinsn))
|
||||
{
|
||||
case LCT_VAL:
|
||||
{
|
||||
IrOp src = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(LUAU_INSN_B(uinsn)));
|
||||
build.inst(IrCmd::STORE_TVALUE, dst, src);
|
||||
break;
|
||||
}
|
||||
|
||||
case LCT_REF:
|
||||
{
|
||||
IrOp src = build.inst(IrCmd::FINDUPVAL, build.vmReg(LUAU_INSN_B(uinsn)));
|
||||
build.inst(IrCmd::STORE_POINTER, dst, src);
|
||||
build.inst(IrCmd::STORE_TAG, dst, build.constTag(LUA_TUPVAL));
|
||||
break;
|
||||
}
|
||||
|
||||
case LCT_UPVAL:
|
||||
{
|
||||
IrOp src = build.inst(IrCmd::GET_CLOSURE_UPVAL_ADDR, build.undef(), build.vmUpvalue(LUAU_INSN_B(uinsn)));
|
||||
IrOp load = build.inst(IrCmd::LOAD_TVALUE, src);
|
||||
build.inst(IrCmd::STORE_TVALUE, dst, load);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
LUAU_ASSERT(!"Unknown upvalue capture type");
|
||||
LUAU_UNREACHABLE(); // improves switch() codegen by eliding opcode bounds checks
|
||||
}
|
||||
}
|
||||
|
||||
build.inst(IrCmd::CHECK_GC);
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
|
|
@ -65,6 +65,7 @@ void translateInstCapture(IrBuilder& build, const Instruction* pc, int pcpos);
|
|||
void translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos);
|
||||
void translateInstAndX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp c);
|
||||
void translateInstOrX(IrBuilder& build, const Instruction* pc, int pcpos, IrOp c);
|
||||
void translateInstNewClosure(IrBuilder& build, const Instruction* pc, int pcpos);
|
||||
|
||||
inline int getOpLength(LuauOpcode op)
|
||||
{
|
||||
|
|
|
@ -38,6 +38,7 @@ IrValueKind getCmdValueKind(IrCmd cmd)
|
|||
case IrCmd::GET_ARR_ADDR:
|
||||
case IrCmd::GET_SLOT_NODE_ADDR:
|
||||
case IrCmd::GET_HASH_NODE_ADDR:
|
||||
case IrCmd::GET_CLOSURE_UPVAL_ADDR:
|
||||
return IrValueKind::Pointer;
|
||||
case IrCmd::STORE_TAG:
|
||||
case IrCmd::STORE_POINTER:
|
||||
|
@ -141,7 +142,9 @@ IrValueKind getCmdValueKind(IrCmd cmd)
|
|||
case IrCmd::FALLBACK_NAMECALL:
|
||||
case IrCmd::FALLBACK_PREPVARARGS:
|
||||
case IrCmd::FALLBACK_GETVARARGS:
|
||||
case IrCmd::FALLBACK_NEWCLOSURE:
|
||||
return IrValueKind::None;
|
||||
case IrCmd::NEWCLOSURE:
|
||||
return IrValueKind::Pointer;
|
||||
case IrCmd::FALLBACK_DUPCLOSURE:
|
||||
case IrCmd::FALLBACK_FORGPREP:
|
||||
return IrValueKind::None;
|
||||
|
@ -164,6 +167,8 @@ IrValueKind getCmdValueKind(IrCmd cmd)
|
|||
case IrCmd::GET_TYPE:
|
||||
case IrCmd::GET_TYPEOF:
|
||||
return IrValueKind::Pointer;
|
||||
case IrCmd::FINDUPVAL:
|
||||
return IrValueKind::Pointer;
|
||||
}
|
||||
|
||||
LUAU_UNREACHABLE();
|
||||
|
|
|
@ -77,7 +77,6 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst)
|
|||
case IrCmd::FALLBACK_GETVARARGS:
|
||||
invalidateRestoreVmRegs(vmRegOp(inst.b), function.intOp(inst.c));
|
||||
break;
|
||||
case IrCmd::FALLBACK_NEWCLOSURE:
|
||||
case IrCmd::FALLBACK_DUPCLOSURE:
|
||||
invalidateRestoreOp(inst.b);
|
||||
break;
|
||||
|
@ -109,6 +108,8 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst)
|
|||
case IrCmd::FALLBACK_PREPVARARGS:
|
||||
case IrCmd::ADJUST_STACK_TO_TOP:
|
||||
case IrCmd::GET_TYPEOF:
|
||||
case IrCmd::NEWCLOSURE:
|
||||
case IrCmd::FINDUPVAL:
|
||||
break;
|
||||
|
||||
// These instrucitons read VmReg only after optimizeMemoryOperandsX64
|
||||
|
|
|
@ -56,6 +56,8 @@ void initFunctions(NativeState& data)
|
|||
data.context.luaC_step = luaC_step;
|
||||
|
||||
data.context.luaF_close = luaF_close;
|
||||
data.context.luaF_findupval = luaF_findupval;
|
||||
data.context.luaF_newLclosure = luaF_newLclosure;
|
||||
|
||||
data.context.luaT_gettm = luaT_gettm;
|
||||
data.context.luaT_objtypenamestr = luaT_objtypenamestr;
|
||||
|
|
|
@ -52,6 +52,8 @@ struct NativeContext
|
|||
size_t (*luaC_step)(lua_State* L, bool assist) = nullptr;
|
||||
|
||||
void (*luaF_close)(lua_State* L, StkId level) = nullptr;
|
||||
UpVal* (*luaF_findupval)(lua_State* L, StkId level) = nullptr;
|
||||
Closure* (*luaF_newLclosure)(lua_State* L, int nelems, Table* e, Proto* p) = nullptr;
|
||||
|
||||
const TValue* (*luaT_gettm)(Table* events, TMS event, TString* ename) = nullptr;
|
||||
const TString* (*luaT_objtypenamestr)(lua_State* L, const TValue* o) = nullptr;
|
||||
|
|
|
@ -415,6 +415,8 @@ static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid
|
|||
case LBF_RAWLEN:
|
||||
case LBF_BIT32_EXTRACTK:
|
||||
case LBF_GETMETATABLE:
|
||||
case LBF_TONUMBER:
|
||||
case LBF_TOSTRING:
|
||||
break;
|
||||
case LBF_SETMETATABLE:
|
||||
state.invalidateHeap(); // TODO: only knownNoMetatable is affected and we might know which one
|
||||
|
@ -760,6 +762,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
|||
case IrCmd::GET_ARR_ADDR:
|
||||
case IrCmd::GET_SLOT_NODE_ADDR:
|
||||
case IrCmd::GET_HASH_NODE_ADDR:
|
||||
case IrCmd::GET_CLOSURE_UPVAL_ADDR:
|
||||
break;
|
||||
case IrCmd::ADD_INT:
|
||||
case IrCmd::SUB_INT:
|
||||
|
@ -823,6 +826,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
|||
case IrCmd::BITXOR_UINT:
|
||||
case IrCmd::BITOR_UINT:
|
||||
case IrCmd::BITNOT_UINT:
|
||||
break;
|
||||
case IrCmd::BITLSHIFT_UINT:
|
||||
case IrCmd::BITRSHIFT_UINT:
|
||||
case IrCmd::BITARSHIFT_UINT:
|
||||
|
@ -833,6 +837,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
|||
case IrCmd::INVOKE_LIBM:
|
||||
case IrCmd::GET_TYPE:
|
||||
case IrCmd::GET_TYPEOF:
|
||||
case IrCmd::FINDUPVAL:
|
||||
break;
|
||||
|
||||
case IrCmd::JUMP_CMP_ANY:
|
||||
|
@ -923,8 +928,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
|||
case IrCmd::FALLBACK_GETVARARGS:
|
||||
state.invalidateRegisterRange(vmRegOp(inst.b), function.intOp(inst.c));
|
||||
break;
|
||||
case IrCmd::FALLBACK_NEWCLOSURE:
|
||||
state.invalidate(inst.b);
|
||||
case IrCmd::NEWCLOSURE:
|
||||
break;
|
||||
case IrCmd::FALLBACK_DUPCLOSURE:
|
||||
state.invalidate(inst.b);
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
// Version 1: Baseline version for the open-source release. Supported until 0.521.
|
||||
// Version 2: Adds Proto::linedefined. Supported until 0.544.
|
||||
// Version 3: Adds FORGPREP/JUMPXEQK* and enhances AUX encoding for FORGLOOP. Removes FORGLOOP_NEXT/INEXT and JUMPIFEQK/JUMPIFNOTEQK. Currently supported.
|
||||
// Version 4: Adds Proto::flags and typeinfo. Currently supported.
|
||||
|
||||
// Bytecode opcode, part of the instruction header
|
||||
enum LuauOpcode
|
||||
|
@ -543,6 +544,10 @@ enum LuauBuiltinFunction
|
|||
// get/setmetatable
|
||||
LBF_GETMETATABLE,
|
||||
LBF_SETMETATABLE,
|
||||
|
||||
// tonumber/tostring
|
||||
LBF_TONUMBER,
|
||||
LBF_TOSTRING,
|
||||
};
|
||||
|
||||
// Capture type, used in LOP_CAPTURE
|
||||
|
@ -552,3 +557,10 @@ enum LuauCaptureType
|
|||
LCT_REF,
|
||||
LCT_UPVAL,
|
||||
};
|
||||
|
||||
// Proto flag bitmask, stored in Proto::flags
|
||||
enum LuauProtoFlag
|
||||
{
|
||||
// used to tag main proto for modules with --!native
|
||||
LPF_NATIVE_MODULE = 1 << 0,
|
||||
};
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
#include "Luau/Bytecode.h"
|
||||
#include "Luau/Compiler.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileBuiltinTonumber, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileBuiltinTostring, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace Compile
|
||||
|
@ -69,6 +72,11 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op
|
|||
if (builtin.isGlobal("setmetatable"))
|
||||
return LBF_SETMETATABLE;
|
||||
|
||||
if (FFlag::LuauCompileBuiltinTonumber && builtin.isGlobal("tonumber"))
|
||||
return LBF_TONUMBER;
|
||||
if (FFlag::LuauCompileBuiltinTostring && builtin.isGlobal("tostring"))
|
||||
return LBF_TOSTRING;
|
||||
|
||||
if (builtin.object == "math")
|
||||
{
|
||||
if (builtin.method == "abs")
|
||||
|
@ -257,10 +265,10 @@ BuiltinInfo getBuiltinInfo(int bfid)
|
|||
case LBF_MATH_ABS:
|
||||
case LBF_MATH_ACOS:
|
||||
case LBF_MATH_ASIN:
|
||||
return {1, 1};
|
||||
return {1, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_MATH_ATAN2:
|
||||
return {2, 1};
|
||||
return {2, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_MATH_ATAN:
|
||||
case LBF_MATH_CEIL:
|
||||
|
@ -269,19 +277,19 @@ BuiltinInfo getBuiltinInfo(int bfid)
|
|||
case LBF_MATH_DEG:
|
||||
case LBF_MATH_EXP:
|
||||
case LBF_MATH_FLOOR:
|
||||
return {1, 1};
|
||||
return {1, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_MATH_FMOD:
|
||||
return {2, 1};
|
||||
return {2, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_MATH_FREXP:
|
||||
return {1, 2};
|
||||
return {1, 2, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_MATH_LDEXP:
|
||||
return {2, 1};
|
||||
return {2, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_MATH_LOG10:
|
||||
return {1, 1};
|
||||
return {1, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_MATH_LOG:
|
||||
return {-1, 1}; // 1 or 2 parameters
|
||||
|
@ -291,10 +299,10 @@ BuiltinInfo getBuiltinInfo(int bfid)
|
|||
return {-1, 1}; // variadic
|
||||
|
||||
case LBF_MATH_MODF:
|
||||
return {1, 2};
|
||||
return {1, 2, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_MATH_POW:
|
||||
return {2, 1};
|
||||
return {2, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_MATH_RAD:
|
||||
case LBF_MATH_SINH:
|
||||
|
@ -302,16 +310,16 @@ BuiltinInfo getBuiltinInfo(int bfid)
|
|||
case LBF_MATH_SQRT:
|
||||
case LBF_MATH_TANH:
|
||||
case LBF_MATH_TAN:
|
||||
return {1, 1};
|
||||
return {1, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_BIT32_ARSHIFT:
|
||||
return {2, 1};
|
||||
return {2, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_BIT32_BAND:
|
||||
return {-1, 1}; // variadic
|
||||
|
||||
case LBF_BIT32_BNOT:
|
||||
return {1, 1};
|
||||
return {1, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_BIT32_BOR:
|
||||
case LBF_BIT32_BXOR:
|
||||
|
@ -323,14 +331,14 @@ BuiltinInfo getBuiltinInfo(int bfid)
|
|||
|
||||
case LBF_BIT32_LROTATE:
|
||||
case LBF_BIT32_LSHIFT:
|
||||
return {2, 1};
|
||||
return {2, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_BIT32_REPLACE:
|
||||
return {-1, 1}; // 3 or 4 parameters
|
||||
|
||||
case LBF_BIT32_RROTATE:
|
||||
case LBF_BIT32_RSHIFT:
|
||||
return {2, 1};
|
||||
return {2, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_TYPE:
|
||||
return {1, 1};
|
||||
|
@ -342,7 +350,7 @@ BuiltinInfo getBuiltinInfo(int bfid)
|
|||
return {-1, 1}; // variadic
|
||||
|
||||
case LBF_STRING_LEN:
|
||||
return {1, 1};
|
||||
return {1, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_TYPEOF:
|
||||
return {1, 1};
|
||||
|
@ -351,11 +359,11 @@ BuiltinInfo getBuiltinInfo(int bfid)
|
|||
return {-1, 1}; // 2 or 3 parameters
|
||||
|
||||
case LBF_MATH_CLAMP:
|
||||
return {3, 1};
|
||||
return {3, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_MATH_SIGN:
|
||||
case LBF_MATH_ROUND:
|
||||
return {1, 1};
|
||||
return {1, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_RAWSET:
|
||||
return {3, 1};
|
||||
|
@ -375,22 +383,28 @@ BuiltinInfo getBuiltinInfo(int bfid)
|
|||
|
||||
case LBF_BIT32_COUNTLZ:
|
||||
case LBF_BIT32_COUNTRZ:
|
||||
return {1, 1};
|
||||
return {1, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_SELECT_VARARG:
|
||||
return {-1, -1}; // variadic
|
||||
|
||||
case LBF_RAWLEN:
|
||||
return {1, 1};
|
||||
return {1, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_BIT32_EXTRACTK:
|
||||
return {3, 1};
|
||||
return {3, 1, BuiltinInfo::Flag_NoneSafe};
|
||||
|
||||
case LBF_GETMETATABLE:
|
||||
return {1, 1};
|
||||
|
||||
case LBF_SETMETATABLE:
|
||||
return {2, 1};
|
||||
|
||||
case LBF_TONUMBER:
|
||||
return {-1, 1}; // 1 or 2 parameters
|
||||
|
||||
case LBF_TOSTRING:
|
||||
return {1, 1};
|
||||
};
|
||||
|
||||
LUAU_UNREACHABLE();
|
||||
|
|
|
@ -41,8 +41,17 @@ void analyzeBuiltins(DenseHashMap<AstExprCall*, int>& result, const DenseHashMap
|
|||
|
||||
struct BuiltinInfo
|
||||
{
|
||||
enum Flags
|
||||
{
|
||||
// none-safe builtins are builtins that have the same behavior for arguments that are nil or none
|
||||
// this allows the compiler to compile calls to builtins more efficiently in certain cases
|
||||
// for example, math.abs(x()) may compile x() as if it returns one value; if it returns no values, abs() will get nil instead of none
|
||||
Flag_NoneSafe = 1 << 0,
|
||||
};
|
||||
|
||||
int params;
|
||||
int results;
|
||||
unsigned int flags;
|
||||
};
|
||||
|
||||
BuiltinInfo getBuiltinInfo(int bfid);
|
||||
|
|
|
@ -27,6 +27,9 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
|||
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileFunctionType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileNativeComment, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileFixBuiltinArity, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -187,7 +190,7 @@ struct Compiler
|
|||
return node->as<AstExprFunction>();
|
||||
}
|
||||
|
||||
uint32_t compileFunction(AstExprFunction* func)
|
||||
uint32_t compileFunction(AstExprFunction* func, uint8_t protoflags)
|
||||
{
|
||||
LUAU_TIMETRACE_SCOPE("Compiler::compileFunction", "Compiler");
|
||||
|
||||
|
@ -262,7 +265,7 @@ struct Compiler
|
|||
if (bytecode.getInstructionCount() > kMaxInstructionCount)
|
||||
CompileError::raise(func->location, "Exceeded function instruction limit; split the function into parts to compile");
|
||||
|
||||
bytecode.endFunction(uint8_t(stackSize), uint8_t(upvals.size()));
|
||||
bytecode.endFunction(uint8_t(stackSize), uint8_t(upvals.size()), protoflags);
|
||||
|
||||
Function& f = functions[func];
|
||||
f.id = fid;
|
||||
|
@ -792,8 +795,19 @@ struct Compiler
|
|||
{
|
||||
if (!isExprMultRet(expr->args.data[expr->args.size - 1]))
|
||||
return compileExprFastcallN(expr, target, targetCount, targetTop, multRet, regs, bfid);
|
||||
else if (options.optimizationLevel >= 2 && int(expr->args.size) == getBuiltinInfo(bfid).params)
|
||||
return compileExprFastcallN(expr, target, targetCount, targetTop, multRet, regs, bfid);
|
||||
else if (options.optimizationLevel >= 2)
|
||||
{
|
||||
if (FFlag::LuauCompileFixBuiltinArity)
|
||||
{
|
||||
// when a builtin is none-safe with matching arity, even if the last expression returns 0 or >1 arguments,
|
||||
// we can rely on the behavior of the function being the same (none-safe means nil and none are interchangeable)
|
||||
BuiltinInfo info = getBuiltinInfo(bfid);
|
||||
if (int(expr->args.size) == info.params && (info.flags & BuiltinInfo::Flag_NoneSafe) != 0)
|
||||
return compileExprFastcallN(expr, target, targetCount, targetTop, multRet, regs, bfid);
|
||||
}
|
||||
else if (int(expr->args.size) == getBuiltinInfo(bfid).params)
|
||||
return compileExprFastcallN(expr, target, targetCount, targetTop, multRet, regs, bfid);
|
||||
}
|
||||
}
|
||||
|
||||
if (expr->self)
|
||||
|
@ -3147,7 +3161,7 @@ struct Compiler
|
|||
}
|
||||
}
|
||||
|
||||
// compute expressions with side effects for lulz
|
||||
// compute expressions with side effects
|
||||
for (size_t i = stat->vars.size; i < stat->values.size; ++i)
|
||||
{
|
||||
RegScope rsi(this);
|
||||
|
@ -3834,11 +3848,20 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
|
|||
LUAU_ASSERT(parseResult.errors.empty());
|
||||
|
||||
CompileOptions options = inputOptions;
|
||||
uint8_t mainFlags = 0;
|
||||
|
||||
for (const HotComment& hc : parseResult.hotcomments)
|
||||
{
|
||||
if (hc.header && hc.content.compare(0, 9, "optimize ") == 0)
|
||||
options.optimizationLevel = std::max(0, std::min(2, atoi(hc.content.c_str() + 9)));
|
||||
|
||||
if (FFlag::LuauCompileNativeComment && hc.header && hc.content == "native")
|
||||
{
|
||||
mainFlags |= LPF_NATIVE_MODULE;
|
||||
options.optimizationLevel = 2; // note: this might be removed in the future in favor of --!optimize
|
||||
}
|
||||
}
|
||||
|
||||
AstStatBlock* root = parseResult.root;
|
||||
|
||||
Compiler compiler(bytecode, options);
|
||||
|
@ -3884,12 +3907,12 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
|
|||
root->visit(&functionVisitor);
|
||||
|
||||
for (AstExprFunction* expr : functions)
|
||||
compiler.compileFunction(expr);
|
||||
compiler.compileFunction(expr, 0);
|
||||
|
||||
AstExprFunction main(root->location, /*generics= */ AstArray<AstGenericType>(), /*genericPacks= */ AstArray<AstGenericTypePack>(),
|
||||
/* self= */ nullptr, AstArray<AstLocal*>(), /* vararg= */ true, /* varargLocation= */ Luau::Location(), root, /* functionDepth= */ 0,
|
||||
/* debugname= */ AstName());
|
||||
uint32_t mainid = compiler.compileFunction(&main);
|
||||
uint32_t mainid = compiler.compileFunction(&main, mainFlags);
|
||||
|
||||
const Compiler::Function* mainf = compiler.functions.find(&main);
|
||||
LUAU_ASSERT(mainf && mainf->upvals.empty());
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
#include <limits.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauAssignmentHasCost, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace Compile
|
||||
|
@ -302,12 +304,14 @@ struct CostVisitor : AstVisitor
|
|||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStat* node) override
|
||||
bool visit(AstStatIf* node) override
|
||||
{
|
||||
if (node->is<AstStatIf>())
|
||||
// unconditional 'else' may require a jump after the 'if' body
|
||||
// note: this ignores cases when 'then' always terminates and also assumes comparison requires an extra instruction which may be false
|
||||
if (!FFlag::LuauAssignmentHasCost)
|
||||
result += 2;
|
||||
else if (node->is<AstStatBreak>() || node->is<AstStatContinue>())
|
||||
result += 1;
|
||||
else
|
||||
result += 1 + (node->elsebody && !node->elsebody->is<AstStatIf>());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -333,7 +337,21 @@ struct CostVisitor : AstVisitor
|
|||
for (size_t i = 0; i < node->vars.size; ++i)
|
||||
assign(node->vars.data[i]);
|
||||
|
||||
return true;
|
||||
if (!FFlag::LuauAssignmentHasCost)
|
||||
return true;
|
||||
|
||||
for (size_t i = 0; i < node->vars.size || i < node->values.size; ++i)
|
||||
{
|
||||
Cost ac;
|
||||
if (i < node->vars.size)
|
||||
ac += model(node->vars.data[i]);
|
||||
if (i < node->values.size)
|
||||
ac += model(node->values.data[i]);
|
||||
// local->local or constant->local assignment is not free
|
||||
result += ac.model == 0 ? Cost(1) : ac;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStatCompoundAssign* node) override
|
||||
|
@ -345,6 +363,20 @@ struct CostVisitor : AstVisitor
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatBreak* node) override
|
||||
{
|
||||
result += 1;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStatContinue* node) override
|
||||
{
|
||||
result += 1;
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount, const DenseHashMap<AstExprCall*, int>& builtins)
|
||||
|
|
|
@ -186,6 +186,7 @@ target_sources(Luau.Analysis PRIVATE
|
|||
Analysis/include/Luau/TypeArena.h
|
||||
Analysis/include/Luau/TypeAttach.h
|
||||
Analysis/include/Luau/TypeChecker2.h
|
||||
Analysis/include/Luau/TypeCheckLimits.h
|
||||
Analysis/include/Luau/TypedAllocator.h
|
||||
Analysis/include/Luau/TypeFamily.h
|
||||
Analysis/include/Luau/TypeInfer.h
|
||||
|
|
|
@ -123,7 +123,7 @@
|
|||
|
||||
// enables callbacks to redirect code execution from Luau VM to a custom implementation
|
||||
#ifndef LUA_CUSTOM_EXECUTION
|
||||
#define LUA_CUSTOM_EXECUTION 0
|
||||
#define LUA_CUSTOM_EXECUTION 1
|
||||
#endif
|
||||
|
||||
// }==================================================================
|
||||
|
|
|
@ -336,7 +336,7 @@ const char* luaL_findtable(lua_State* L, int idx, const char* fname, int szhint)
|
|||
const char* luaL_typename(lua_State* L, int idx)
|
||||
{
|
||||
const TValue* obj = luaA_toobject(L, idx);
|
||||
return luaT_objtypename(L, obj);
|
||||
return obj ? luaT_objtypename(L, obj) : "no value";
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#endif
|
||||
#endif
|
||||
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauFastcallGC, false)
|
||||
|
||||
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM
|
||||
// The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack.
|
||||
// If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path
|
||||
|
@ -830,6 +832,8 @@ static int luauF_char(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
|||
|
||||
if (nparams < int(sizeof(buffer)) && nresults <= 1)
|
||||
{
|
||||
if (DFFlag::LuauFastcallGC && luaC_needsGC(L))
|
||||
return -1; // we can't call luaC_checkGC so fall back to C implementation
|
||||
|
||||
if (nparams >= 1)
|
||||
{
|
||||
|
@ -900,6 +904,9 @@ static int luauF_sub(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
|||
int i = int(nvalue(args));
|
||||
int j = int(nvalue(args + 1));
|
||||
|
||||
if (DFFlag::LuauFastcallGC && luaC_needsGC(L))
|
||||
return -1; // we can't call luaC_checkGC so fall back to C implementation
|
||||
|
||||
if (i >= 1 && j >= i && unsigned(j - 1) < unsigned(ts->len))
|
||||
{
|
||||
setsvalue(L, res, luaS_newlstr(L, getstr(ts) + (i - 1), j - i + 1));
|
||||
|
@ -1247,6 +1254,73 @@ static int luauF_setmetatable(lua_State* L, StkId res, TValue* arg0, int nresult
|
|||
return -1;
|
||||
}
|
||||
|
||||
static int luauF_tonumber(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams == 1 && nresults <= 1)
|
||||
{
|
||||
double num;
|
||||
|
||||
if (ttisnumber(arg0))
|
||||
{
|
||||
setnvalue(res, nvalue(arg0));
|
||||
return 1;
|
||||
}
|
||||
else if (ttisstring(arg0) && luaO_str2d(svalue(arg0), &num))
|
||||
{
|
||||
setnvalue(res, num);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
setnilvalue(res);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int luauF_tostring(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1)
|
||||
{
|
||||
switch (ttype(arg0))
|
||||
{
|
||||
case LUA_TNIL:
|
||||
{
|
||||
TString* s = L->global->ttname[LUA_TNIL];
|
||||
setsvalue(L, res, s);
|
||||
return 1;
|
||||
}
|
||||
case LUA_TBOOLEAN:
|
||||
{
|
||||
TString* s = bvalue(arg0) ? luaS_newliteral(L, "true") : luaS_newliteral(L, "false");
|
||||
setsvalue(L, res, s);
|
||||
return 1;
|
||||
}
|
||||
case LUA_TNUMBER:
|
||||
{
|
||||
if (DFFlag::LuauFastcallGC && luaC_needsGC(L))
|
||||
return -1; // we can't call luaC_checkGC so fall back to C implementation
|
||||
|
||||
char s[LUAI_MAXNUM2STR];
|
||||
char* e = luai_num2str(s, nvalue(arg0));
|
||||
setsvalue(L, res, luaS_newlstr(L, s, e - s));
|
||||
return 1;
|
||||
}
|
||||
case LUA_TSTRING:
|
||||
{
|
||||
setsvalue(L, res, tsvalue(arg0));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// fall back to generic C implementation
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int luauF_missing(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
return -1;
|
||||
|
@ -1411,6 +1485,9 @@ const luau_FastFunction luauF_table[256] = {
|
|||
luauF_getmetatable,
|
||||
luauF_setmetatable,
|
||||
|
||||
luauF_tonumber,
|
||||
luauF_tostring,
|
||||
|
||||
// When adding builtins, add them above this line; what follows is 64 "dummy" entries with luauF_missing fallback.
|
||||
// This is important so that older versions of the runtime that don't support newer builtins automatically fall back via luauF_missing.
|
||||
// Given the builtin addition velocity this should always provide a larger compatibility window than bytecode versions suggest.
|
||||
|
|
|
@ -36,6 +36,7 @@ Proto* luaF_newproto(lua_State* L)
|
|||
f->execdata = NULL;
|
||||
f->exectarget = 0;
|
||||
f->typeinfo = NULL;
|
||||
f->userdata = NULL;
|
||||
|
||||
return f;
|
||||
}
|
||||
|
|
|
@ -73,10 +73,12 @@
|
|||
|
||||
#define luaC_white(g) cast_to(uint8_t, ((g)->currentwhite) & WHITEBITS)
|
||||
|
||||
#define luaC_needsGC(L) (L->global->totalbytes >= L->global->GCthreshold)
|
||||
|
||||
#define luaC_checkGC(L) \
|
||||
{ \
|
||||
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK)); \
|
||||
if (L->global->totalbytes >= L->global->GCthreshold) \
|
||||
if (luaC_needsGC(L)) \
|
||||
{ \
|
||||
condhardmemtests(luaC_validate(L), 1); \
|
||||
luaC_step(L, true); \
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include <math.h>
|
||||
#include <time.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFasterNoise, false)
|
||||
|
||||
#undef PI
|
||||
#define PI (3.14159265358979323846)
|
||||
#define RADIANS_PER_DEGREE (PI / 180.0)
|
||||
|
@ -275,6 +277,7 @@ static int math_randomseed(lua_State* L)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// TODO: Delete with LuauFasterNoise
|
||||
static const unsigned char kPerlin[512] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99,
|
||||
37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174,
|
||||
20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41,
|
||||
|
@ -295,18 +298,32 @@ static const unsigned char kPerlin[512] = {151, 160, 137, 91, 90, 15, 131, 13, 2
|
|||
106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61,
|
||||
156, 180};
|
||||
|
||||
static float fade(float t)
|
||||
static const unsigned char kPerlinHash[257] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99,
|
||||
37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174,
|
||||
20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41,
|
||||
55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86,
|
||||
164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17,
|
||||
182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110,
|
||||
79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14,
|
||||
239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24,
|
||||
72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 151};
|
||||
|
||||
const float kPerlinGrad[16][3] = {{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, {0, 1, 1},
|
||||
{0, -1, 1}, {0, 1, -1}, {0, -1, -1}, {1, 1, 0}, {0, -1, 1}, {-1, 1, 0}, {0, -1, -1}};
|
||||
|
||||
static float perlin_fade(float t)
|
||||
{
|
||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||
}
|
||||
|
||||
static float math_lerp(float t, float a, float b)
|
||||
static float perlin_lerp(float t, float a, float b)
|
||||
{
|
||||
return a + t * (b - a);
|
||||
}
|
||||
|
||||
static float grad(unsigned char hash, float x, float y, float z)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauFasterNoise);
|
||||
unsigned char h = hash & 15;
|
||||
float u = (h < 8) ? x : y;
|
||||
float v = (h < 4) ? y : (h == 12 || h == 14) ? x : z;
|
||||
|
@ -314,8 +331,15 @@ static float grad(unsigned char hash, float x, float y, float z)
|
|||
return (h & 1 ? -u : u) + (h & 2 ? -v : v);
|
||||
}
|
||||
|
||||
static float perlin(float x, float y, float z)
|
||||
static float perlin_grad(int hash, float x, float y, float z)
|
||||
{
|
||||
const float* g = kPerlinGrad[hash & 15];
|
||||
return g[0] * x + g[1] * y + g[2] * z;
|
||||
}
|
||||
|
||||
static float perlin_dep(float x, float y, float z)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauFasterNoise);
|
||||
float xflr = floorf(x);
|
||||
float yflr = floorf(y);
|
||||
float zflr = floorf(z);
|
||||
|
@ -328,9 +352,9 @@ static float perlin(float x, float y, float z)
|
|||
float yf = y - yflr;
|
||||
float zf = z - zflr;
|
||||
|
||||
float u = fade(xf);
|
||||
float v = fade(yf);
|
||||
float w = fade(zf);
|
||||
float u = perlin_fade(xf);
|
||||
float v = perlin_fade(yf);
|
||||
float w = perlin_fade(zf);
|
||||
|
||||
const unsigned char* p = kPerlin;
|
||||
|
||||
|
@ -342,24 +366,79 @@ static float perlin(float x, float y, float z)
|
|||
int ba = p[b] + zi;
|
||||
int bb = p[b + 1] + zi;
|
||||
|
||||
return math_lerp(w,
|
||||
math_lerp(v, math_lerp(u, grad(p[aa], xf, yf, zf), grad(p[ba], xf - 1, yf, zf)),
|
||||
math_lerp(u, grad(p[ab], xf, yf - 1, zf), grad(p[bb], xf - 1, yf - 1, zf))),
|
||||
math_lerp(v, math_lerp(u, grad(p[aa + 1], xf, yf, zf - 1), grad(p[ba + 1], xf - 1, yf, zf - 1)),
|
||||
math_lerp(u, grad(p[ab + 1], xf, yf - 1, zf - 1), grad(p[bb + 1], xf - 1, yf - 1, zf - 1))));
|
||||
return perlin_lerp(w,
|
||||
perlin_lerp(v, perlin_lerp(u, grad(p[aa], xf, yf, zf), grad(p[ba], xf - 1, yf, zf)),
|
||||
perlin_lerp(u, grad(p[ab], xf, yf - 1, zf), grad(p[bb], xf - 1, yf - 1, zf))),
|
||||
perlin_lerp(v, perlin_lerp(u, grad(p[aa + 1], xf, yf, zf - 1), grad(p[ba + 1], xf - 1, yf, zf - 1)),
|
||||
perlin_lerp(u, grad(p[ab + 1], xf, yf - 1, zf - 1), grad(p[bb + 1], xf - 1, yf - 1, zf - 1))));
|
||||
}
|
||||
|
||||
static float perlin(float x, float y, float z)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauFasterNoise);
|
||||
float xflr = floorf(x);
|
||||
float yflr = floorf(y);
|
||||
float zflr = floorf(z);
|
||||
|
||||
int xi = int(xflr) & 255;
|
||||
int yi = int(yflr) & 255;
|
||||
int zi = int(zflr) & 255;
|
||||
|
||||
float xf = x - xflr;
|
||||
float yf = y - yflr;
|
||||
float zf = z - zflr;
|
||||
|
||||
float u = perlin_fade(xf);
|
||||
float v = perlin_fade(yf);
|
||||
float w = perlin_fade(zf);
|
||||
|
||||
const unsigned char* p = kPerlinHash;
|
||||
|
||||
int a = (p[xi] + yi) & 255;
|
||||
int aa = (p[a] + zi) & 255;
|
||||
int ab = (p[a + 1] + zi) & 255;
|
||||
|
||||
int b = (p[xi + 1] + yi) & 255;
|
||||
int ba = (p[b] + zi) & 255;
|
||||
int bb = (p[b + 1] + zi) & 255;
|
||||
|
||||
float la = perlin_lerp(u, perlin_grad(p[aa], xf, yf, zf), perlin_grad(p[ba], xf - 1, yf, zf));
|
||||
float lb = perlin_lerp(u, perlin_grad(p[ab], xf, yf - 1, zf), perlin_grad(p[bb], xf - 1, yf - 1, zf));
|
||||
float la1 = perlin_lerp(u, perlin_grad(p[aa + 1], xf, yf, zf - 1), perlin_grad(p[ba + 1], xf - 1, yf, zf - 1));
|
||||
float lb1 = perlin_lerp(u, perlin_grad(p[ab + 1], xf, yf - 1, zf - 1), perlin_grad(p[bb + 1], xf - 1, yf - 1, zf - 1));
|
||||
|
||||
return perlin_lerp(w, perlin_lerp(v, la, lb), perlin_lerp(v, la1, lb1));
|
||||
}
|
||||
|
||||
static int math_noise(lua_State* L)
|
||||
{
|
||||
double x = luaL_checknumber(L, 1);
|
||||
double y = luaL_optnumber(L, 2, 0.0);
|
||||
double z = luaL_optnumber(L, 3, 0.0);
|
||||
if (FFlag::LuauFasterNoise)
|
||||
{
|
||||
int nx, ny, nz;
|
||||
double x = lua_tonumberx(L, 1, &nx);
|
||||
double y = lua_tonumberx(L, 2, &ny);
|
||||
double z = lua_tonumberx(L, 3, &nz);
|
||||
|
||||
double r = perlin((float)x, (float)y, (float)z);
|
||||
luaL_argexpected(L, nx, 1, "number");
|
||||
luaL_argexpected(L, ny || lua_isnoneornil(L, 2), 2, "number");
|
||||
luaL_argexpected(L, nz || lua_isnoneornil(L, 3), 3, "number");
|
||||
|
||||
lua_pushnumber(L, r);
|
||||
double r = perlin((float)x, (float)y, (float)z);
|
||||
|
||||
return 1;
|
||||
lua_pushnumber(L, r);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
double x = luaL_checknumber(L, 1);
|
||||
double y = luaL_optnumber(L, 2, 0.0);
|
||||
double z = luaL_optnumber(L, 3, 0.0);
|
||||
|
||||
double r = perlin_dep((float)x, (float)y, (float)z);
|
||||
|
||||
lua_pushnumber(L, r);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int math_clamp(lua_State* L)
|
||||
|
|
|
@ -290,6 +290,8 @@ typedef struct Proto
|
|||
|
||||
uint8_t* typeinfo;
|
||||
|
||||
void* userdata;
|
||||
|
||||
GCObject* gclist;
|
||||
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include "ldebug.h"
|
||||
#include "lvm.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFasterTableConcat, false)
|
||||
|
||||
static int foreachi(lua_State* L)
|
||||
{
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
|
@ -219,8 +221,8 @@ static int tmove(lua_State* L)
|
|||
|
||||
static void addfield(lua_State* L, luaL_Buffer* b, int i)
|
||||
{
|
||||
lua_rawgeti(L, 1, i);
|
||||
if (!lua_isstring(L, -1))
|
||||
int tt = lua_rawgeti(L, 1, i);
|
||||
if (FFlag::LuauFasterTableConcat ? (tt != LUA_TSTRING && tt != LUA_TNUMBER) : !lua_isstring(L, -1))
|
||||
luaL_error(L, "invalid value (%s) at index %d in table for 'concat'", luaL_typename(L, -1), i);
|
||||
luaL_addvalue(b);
|
||||
}
|
||||
|
|
|
@ -131,6 +131,9 @@
|
|||
goto dispatchContinue
|
||||
#endif
|
||||
|
||||
// Does VM support native execution via ExecutionCallbacks? We mostly assume it does but keep the define to make it easy to quantify the cost.
|
||||
#define VM_HAS_NATIVE LUA_CUSTOM_EXECUTION
|
||||
|
||||
LUAU_NOINLINE void luau_callhook(lua_State* L, lua_Hook hook, void* userdata)
|
||||
{
|
||||
ptrdiff_t base = savestack(L, L->base);
|
||||
|
@ -207,7 +210,7 @@ static void luau_execute(lua_State* L)
|
|||
LUAU_ASSERT(L->isactive);
|
||||
LUAU_ASSERT(!isblack(obj2gco(L))); // we don't use luaC_threadbarrier because active threads never turn black
|
||||
|
||||
#if LUA_CUSTOM_EXECUTION
|
||||
#if VM_HAS_NATIVE
|
||||
if ((L->ci->flags & LUA_CALLINFO_NATIVE) && !SingleStep)
|
||||
{
|
||||
Proto* p = clvalue(L->ci->func)->l.p;
|
||||
|
@ -1036,7 +1039,7 @@ reentry:
|
|||
Closure* nextcl = clvalue(cip->func);
|
||||
Proto* nextproto = nextcl->l.p;
|
||||
|
||||
#if LUA_CUSTOM_EXECUTION
|
||||
#if VM_HAS_NATIVE
|
||||
if (LUAU_UNLIKELY((cip->flags & LUA_CALLINFO_NATIVE) && !SingleStep))
|
||||
{
|
||||
if (L->global->ecb.enter(L, nextproto) == 1)
|
||||
|
@ -2371,7 +2374,7 @@ reentry:
|
|||
ci->flags = LUA_CALLINFO_NATIVE;
|
||||
ci->savedpc = p->code;
|
||||
|
||||
#if LUA_CUSTOM_EXECUTION
|
||||
#if VM_HAS_NATIVE
|
||||
if (L->global->ecb.enter(L, p) == 1)
|
||||
goto reentry;
|
||||
else
|
||||
|
@ -2890,7 +2893,7 @@ int luau_precall(lua_State* L, StkId func, int nresults)
|
|||
|
||||
ci->savedpc = p->code;
|
||||
|
||||
#if LUA_CUSTOM_EXECUTION
|
||||
#if VM_HAS_NATIVE
|
||||
if (p->execdata)
|
||||
ci->flags = LUA_CALLINFO_NATIVE;
|
||||
#endif
|
||||
|
|
22
bench/micro_tests/test_ToNumberString.lua
Normal file
22
bench/micro_tests/test_ToNumberString.lua
Normal file
|
@ -0,0 +1,22 @@
|
|||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
||||
|
||||
bench.runCode(function()
|
||||
for j=1,1e6 do
|
||||
tonumber("42")
|
||||
tonumber(42)
|
||||
end
|
||||
end, "tonumber")
|
||||
|
||||
bench.runCode(function()
|
||||
for j=1,1e6 do
|
||||
tostring(nil)
|
||||
tostring("test")
|
||||
tostring(42)
|
||||
end
|
||||
end, "tostring")
|
||||
|
||||
bench.runCode(function()
|
||||
for j=1,1e6 do
|
||||
tostring(j)
|
||||
end
|
||||
end, "tostring-gc")
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
|
||||
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
||||
LUAU_FASTFLAG(LuauAutocompleteLastTypecheck)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -33,14 +34,40 @@ struct ACFixtureImpl : BaseType
|
|||
|
||||
AutocompleteResult autocomplete(unsigned row, unsigned column)
|
||||
{
|
||||
if (FFlag::LuauAutocompleteLastTypecheck)
|
||||
{
|
||||
FrontendOptions opts;
|
||||
opts.forAutocomplete = true;
|
||||
this->frontend.check("MainModule", opts);
|
||||
}
|
||||
|
||||
return Luau::autocomplete(this->frontend, "MainModule", Position{row, column}, nullCallback);
|
||||
}
|
||||
|
||||
AutocompleteResult autocomplete(char marker, StringCompletionCallback callback = nullCallback)
|
||||
{
|
||||
if (FFlag::LuauAutocompleteLastTypecheck)
|
||||
{
|
||||
FrontendOptions opts;
|
||||
opts.forAutocomplete = true;
|
||||
this->frontend.check("MainModule", opts);
|
||||
}
|
||||
|
||||
return Luau::autocomplete(this->frontend, "MainModule", getPosition(marker), callback);
|
||||
}
|
||||
|
||||
AutocompleteResult autocomplete(const ModuleName& name, Position pos, StringCompletionCallback callback = nullCallback)
|
||||
{
|
||||
if (FFlag::LuauAutocompleteLastTypecheck)
|
||||
{
|
||||
FrontendOptions opts;
|
||||
opts.forAutocomplete = true;
|
||||
this->frontend.check(name, opts);
|
||||
}
|
||||
|
||||
return Luau::autocomplete(this->frontend, name, pos, callback);
|
||||
}
|
||||
|
||||
CheckResult check(const std::string& source)
|
||||
{
|
||||
markerPosition.clear();
|
||||
|
@ -99,7 +126,7 @@ struct ACFixtureImpl : BaseType
|
|||
LUAU_ASSERT(i != markerPosition.end());
|
||||
return i->second;
|
||||
}
|
||||
|
||||
ScopedFastFlag flag{"LuauAutocompleteHideSelfArg", true};
|
||||
// Maps a marker character (0-9 inclusive) to a position in the source code.
|
||||
std::map<char, Position> markerPosition;
|
||||
};
|
||||
|
@ -1319,7 +1346,7 @@ local a: aa
|
|||
|
||||
frontend.check("Module/B");
|
||||
|
||||
auto ac = Luau::autocomplete(frontend, "Module/B", Position{2, 11}, nullCallback);
|
||||
auto ac = autocomplete("Module/B", Position{2, 11});
|
||||
|
||||
CHECK(ac.entryMap.count("aaa"));
|
||||
CHECK_EQ(ac.context, AutocompleteContext::Type);
|
||||
|
@ -1342,7 +1369,7 @@ local a: aaa.
|
|||
|
||||
frontend.check("Module/B");
|
||||
|
||||
auto ac = Luau::autocomplete(frontend, "Module/B", Position{2, 13}, nullCallback);
|
||||
auto ac = autocomplete("Module/B", Position{2, 13});
|
||||
|
||||
CHECK_EQ(2, ac.entryMap.size());
|
||||
CHECK(ac.entryMap.count("A"));
|
||||
|
@ -1999,7 +2026,7 @@ ex.a(function(x:
|
|||
|
||||
frontend.check("Module/B");
|
||||
|
||||
auto ac = Luau::autocomplete(frontend, "Module/B", Position{2, 16}, nullCallback);
|
||||
auto ac = autocomplete("Module/B", Position{2, 16});
|
||||
|
||||
CHECK(!ac.entryMap.count("done"));
|
||||
|
||||
|
@ -2010,7 +2037,7 @@ ex.b(function(x:
|
|||
|
||||
frontend.check("Module/C");
|
||||
|
||||
ac = Luau::autocomplete(frontend, "Module/C", Position{2, 16}, nullCallback);
|
||||
ac = autocomplete("Module/C", Position{2, 16});
|
||||
|
||||
CHECK(!ac.entryMap.count("(done) -> number"));
|
||||
}
|
||||
|
@ -2033,7 +2060,7 @@ ex.a(function(x:
|
|||
|
||||
frontend.check("Module/B");
|
||||
|
||||
auto ac = Luau::autocomplete(frontend, "Module/B", Position{2, 16}, nullCallback);
|
||||
auto ac = autocomplete("Module/B", Position{2, 16});
|
||||
|
||||
CHECK(!ac.entryMap.count("done"));
|
||||
CHECK(ac.entryMap.count("ex.done"));
|
||||
|
@ -2046,7 +2073,7 @@ ex.b(function(x:
|
|||
|
||||
frontend.check("Module/C");
|
||||
|
||||
ac = Luau::autocomplete(frontend, "Module/C", Position{2, 16}, nullCallback);
|
||||
ac = autocomplete("Module/C", Position{2, 16});
|
||||
|
||||
CHECK(!ac.entryMap.count("(done) -> number"));
|
||||
CHECK(ac.entryMap.count("(ex.done) -> number"));
|
||||
|
@ -2360,7 +2387,7 @@ local a: aaa.do
|
|||
|
||||
frontend.check("Module/B");
|
||||
|
||||
auto ac = Luau::autocomplete(frontend, "Module/B", Position{2, 15}, nullCallback);
|
||||
auto ac = autocomplete("Module/B", Position{2, 15});
|
||||
|
||||
CHECK_EQ(2, ac.entryMap.size());
|
||||
CHECK(ac.entryMap.count("done"));
|
||||
|
@ -2372,7 +2399,7 @@ TEST_CASE_FIXTURE(ACFixture, "comments")
|
|||
{
|
||||
fileResolver.source["Comments"] = "--!str";
|
||||
|
||||
auto ac = Luau::autocomplete(frontend, "Comments", Position{0, 6}, nullCallback);
|
||||
auto ac = autocomplete("Comments", Position{0, 6});
|
||||
CHECK_EQ(0, ac.entryMap.size());
|
||||
}
|
||||
|
||||
|
@ -2391,7 +2418,7 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocompleteProp_index_function_metamethod
|
|||
-- | Column 20
|
||||
)";
|
||||
|
||||
auto ac = Luau::autocomplete(frontend, "Module/A", Position{9, 20}, nullCallback);
|
||||
auto ac = autocomplete("Module/A", Position{9, 20});
|
||||
REQUIRE_EQ(1, ac.entryMap.size());
|
||||
CHECK(ac.entryMap.count("x"));
|
||||
}
|
||||
|
@ -2484,7 +2511,7 @@ TEST_CASE_FIXTURE(ACFixture, "not_the_var_we_are_defining")
|
|||
{
|
||||
fileResolver.source["Module/A"] = "abc,de";
|
||||
|
||||
auto ac = Luau::autocomplete(frontend, "Module/A", Position{0, 6}, nullCallback);
|
||||
auto ac = autocomplete("Module/A", Position{0, 6});
|
||||
CHECK(!ac.entryMap.count("de"));
|
||||
}
|
||||
|
||||
|
@ -2495,7 +2522,7 @@ TEST_CASE_FIXTURE(ACFixture, "recursive_function_global")
|
|||
end
|
||||
)";
|
||||
|
||||
auto ac = Luau::autocomplete(frontend, "global", Position{1, 0}, nullCallback);
|
||||
auto ac = autocomplete("global", Position{1, 0});
|
||||
CHECK(ac.entryMap.count("abc"));
|
||||
}
|
||||
|
||||
|
@ -2508,7 +2535,7 @@ TEST_CASE_FIXTURE(ACFixture, "recursive_function_local")
|
|||
end
|
||||
)";
|
||||
|
||||
auto ac = Luau::autocomplete(frontend, "local", Position{1, 0}, nullCallback);
|
||||
auto ac = autocomplete("local", Position{1, 0});
|
||||
CHECK(ac.entryMap.count("abc"));
|
||||
}
|
||||
|
||||
|
@ -3147,6 +3174,8 @@ t:@1
|
|||
REQUIRE(ac.entryMap.count("two"));
|
||||
CHECK(!ac.entryMap["one"].wrongIndexType);
|
||||
CHECK(ac.entryMap["two"].wrongIndexType);
|
||||
CHECK(ac.entryMap["one"].indexedWithSelf);
|
||||
CHECK(ac.entryMap["two"].indexedWithSelf);
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -3161,6 +3190,8 @@ t.@1
|
|||
REQUIRE(ac.entryMap.count("two"));
|
||||
CHECK(ac.entryMap["one"].wrongIndexType);
|
||||
CHECK(!ac.entryMap["two"].wrongIndexType);
|
||||
CHECK(!ac.entryMap["one"].indexedWithSelf);
|
||||
CHECK(!ac.entryMap["two"].indexedWithSelf);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3190,6 +3221,7 @@ t:@1
|
|||
|
||||
REQUIRE(ac.entryMap.count("m"));
|
||||
CHECK(!ac.entryMap["m"].wrongIndexType);
|
||||
CHECK(ac.entryMap["m"].indexedWithSelf);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls")
|
||||
|
@ -3204,6 +3236,7 @@ t:@1
|
|||
|
||||
REQUIRE(ac.entryMap.count("m"));
|
||||
CHECK(ac.entryMap["m"].wrongIndexType);
|
||||
CHECK(ac.entryMap["m"].indexedWithSelf);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls_2")
|
||||
|
@ -3219,6 +3252,7 @@ t:@1
|
|||
|
||||
REQUIRE(ac.entryMap.count("f"));
|
||||
CHECK(ac.entryMap["f"].wrongIndexType);
|
||||
CHECK(ac.entryMap["f"].indexedWithSelf);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "do_wrong_compatible_self_calls")
|
||||
|
@ -3234,6 +3268,22 @@ t:@1
|
|||
REQUIRE(ac.entryMap.count("m"));
|
||||
// We can make changes to mark this as a wrong way to call even though it's compatible
|
||||
CHECK(!ac.entryMap["m"].wrongIndexType);
|
||||
CHECK(ac.entryMap["m"].indexedWithSelf);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "do_wrong_compatible_nonself_calls")
|
||||
{
|
||||
check(R"(
|
||||
local t = {}
|
||||
function t:m(x: string) end
|
||||
t.@1
|
||||
)");
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count("m"));
|
||||
CHECK(!ac.entryMap["m"].wrongIndexType);
|
||||
CHECK(!ac.entryMap["m"].indexedWithSelf);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "no_wrong_compatible_self_calls_with_generics")
|
||||
|
@ -3249,6 +3299,7 @@ t:@1
|
|||
REQUIRE(ac.entryMap.count("m"));
|
||||
// While this call is compatible with the type, this requires instantiation of a generic type which we don't perform
|
||||
CHECK(ac.entryMap["m"].wrongIndexType);
|
||||
CHECK(ac.entryMap["m"].indexedWithSelf);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "string_prim_self_calls_are_fine")
|
||||
|
@ -3262,10 +3313,13 @@ s:@1
|
|||
|
||||
REQUIRE(ac.entryMap.count("byte"));
|
||||
CHECK(ac.entryMap["byte"].wrongIndexType == false);
|
||||
CHECK(ac.entryMap["byte"].indexedWithSelf);
|
||||
REQUIRE(ac.entryMap.count("char"));
|
||||
CHECK(ac.entryMap["char"].wrongIndexType == true);
|
||||
CHECK(ac.entryMap["char"].indexedWithSelf);
|
||||
REQUIRE(ac.entryMap.count("sub"));
|
||||
CHECK(ac.entryMap["sub"].wrongIndexType == false);
|
||||
CHECK(ac.entryMap["sub"].indexedWithSelf);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "string_prim_non_self_calls_are_avoided")
|
||||
|
@ -3279,8 +3333,10 @@ s.@1
|
|||
|
||||
REQUIRE(ac.entryMap.count("char"));
|
||||
CHECK(ac.entryMap["char"].wrongIndexType == false);
|
||||
CHECK(!ac.entryMap["char"].indexedWithSelf);
|
||||
REQUIRE(ac.entryMap.count("sub"));
|
||||
CHECK(ac.entryMap["sub"].wrongIndexType == true);
|
||||
CHECK(!ac.entryMap["sub"].indexedWithSelf);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACBuiltinsFixture, "library_non_self_calls_are_fine")
|
||||
|
@ -3293,10 +3349,13 @@ string.@1
|
|||
|
||||
REQUIRE(ac.entryMap.count("byte"));
|
||||
CHECK(ac.entryMap["byte"].wrongIndexType == false);
|
||||
CHECK(!ac.entryMap["byte"].indexedWithSelf);
|
||||
REQUIRE(ac.entryMap.count("char"));
|
||||
CHECK(ac.entryMap["char"].wrongIndexType == false);
|
||||
CHECK(!ac.entryMap["char"].indexedWithSelf);
|
||||
REQUIRE(ac.entryMap.count("sub"));
|
||||
CHECK(ac.entryMap["sub"].wrongIndexType == false);
|
||||
CHECK(!ac.entryMap["sub"].indexedWithSelf);
|
||||
|
||||
check(R"(
|
||||
table.@1
|
||||
|
@ -3306,10 +3365,13 @@ table.@1
|
|||
|
||||
REQUIRE(ac.entryMap.count("remove"));
|
||||
CHECK(ac.entryMap["remove"].wrongIndexType == false);
|
||||
CHECK(!ac.entryMap["remove"].indexedWithSelf);
|
||||
REQUIRE(ac.entryMap.count("getn"));
|
||||
CHECK(ac.entryMap["getn"].wrongIndexType == false);
|
||||
CHECK(!ac.entryMap["getn"].indexedWithSelf);
|
||||
REQUIRE(ac.entryMap.count("insert"));
|
||||
CHECK(ac.entryMap["insert"].wrongIndexType == false);
|
||||
CHECK(!ac.entryMap["insert"].indexedWithSelf);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACBuiltinsFixture, "library_self_calls_are_invalid")
|
||||
|
@ -3322,13 +3384,16 @@ string:@1
|
|||
|
||||
REQUIRE(ac.entryMap.count("byte"));
|
||||
CHECK(ac.entryMap["byte"].wrongIndexType == true);
|
||||
CHECK(ac.entryMap["byte"].indexedWithSelf);
|
||||
REQUIRE(ac.entryMap.count("char"));
|
||||
CHECK(ac.entryMap["char"].wrongIndexType == true);
|
||||
CHECK(ac.entryMap["char"].indexedWithSelf);
|
||||
|
||||
// We want the next test to evaluate to 'true', but we have to allow function defined with 'self' to be callable with ':'
|
||||
// We may change the definition of the string metatable to not use 'self' types in the future (like byte/char/pack/unpack)
|
||||
REQUIRE(ac.entryMap.count("sub"));
|
||||
CHECK(ac.entryMap["sub"].wrongIndexType == false);
|
||||
CHECK(ac.entryMap["sub"].indexedWithSelf);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "source_module_preservation_and_invalidation")
|
||||
|
@ -3489,4 +3554,480 @@ TEST_CASE_FIXTURE(ACFixture, "frontend_use_correct_global_scope")
|
|||
CHECK(ac.entryMap.count("Name"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "string_completion_outside_quotes")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauDisableCompletionOutsideQuotes", true};
|
||||
|
||||
loadDefinition(R"(
|
||||
declare function require(path: string): any
|
||||
)");
|
||||
|
||||
std::optional<Binding> require = frontend.globalsForAutocomplete.globalScope->linearSearchForBinding("require");
|
||||
REQUIRE(require);
|
||||
Luau::unfreeze(frontend.globalsForAutocomplete.globalTypes);
|
||||
attachTag(require->typeId, "RequireCall");
|
||||
Luau::freeze(frontend.globalsForAutocomplete.globalTypes);
|
||||
|
||||
check(R"(
|
||||
local x = require(@1"@2"@3)
|
||||
)");
|
||||
|
||||
StringCompletionCallback callback = [](std::string, std::optional<const ClassType*>,
|
||||
std::optional<std::string> contents) -> std::optional<AutocompleteEntryMap>
|
||||
{
|
||||
Luau::AutocompleteEntryMap results = {{"test", Luau::AutocompleteEntry{Luau::AutocompleteEntryKind::String, std::nullopt, false, false}}};
|
||||
return results;
|
||||
};
|
||||
|
||||
auto ac = autocomplete('2', callback);
|
||||
|
||||
CHECK_EQ(1, ac.entryMap.size());
|
||||
CHECK(ac.entryMap.count("test"));
|
||||
|
||||
ac = autocomplete('1', callback);
|
||||
|
||||
CHECK_EQ(0, ac.entryMap.size());
|
||||
|
||||
ac = autocomplete('3', callback);
|
||||
|
||||
CHECK_EQ(0, ac.entryMap.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_empty")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: () -> ())
|
||||
a()
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function() end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_args")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (number, string) -> ())
|
||||
a()
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(a0: number, a1: string) end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_args_single_return")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (number, string) -> (string))
|
||||
a()
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(a0: number, a1: string): string end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_args_multi_return")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (number, string) -> (string, number))
|
||||
a()
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(a0: number, a1: string): (string, number) end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled__noargs_multi_return")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: () -> (string, number))
|
||||
a()
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(): (string, number) end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled__varargs_multi_return")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (...number) -> (string, number))
|
||||
a()
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(...: number): (string, number) end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_multi_varargs_multi_return")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (string, ...number) -> (string, number))
|
||||
a()
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(a0: string, ...: number): (string, number) end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_multi_varargs_varargs_return")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (string, ...number) -> ...number)
|
||||
a()
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(a0: string, ...: number): ...number end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_multi_varargs_multi_varargs_return")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (string, ...number) -> (boolean, ...number))
|
||||
a()
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(a0: string, ...: number): (boolean, ...number) end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_named_args")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (foo: number, bar: string) -> (string, number))
|
||||
a()
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(foo: number, bar: string): (string, number) end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_partially_args")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (number, bar: string) -> (string, number))
|
||||
a()
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(a0: number, bar: string): (string, number) end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_partially_args_last")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (foo: number, string) -> (string, number))
|
||||
a()
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(foo: number, a1: string): (string, number) end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_typeof_args")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local t = { a = 1, b = 2 }
|
||||
|
||||
local function foo(a: (foo: typeof(t)) -> ())
|
||||
a()
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(foo) end"; // Cannot utter this type.
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_table_literal_args")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: (tbl: { x: number, y: number }) -> number) return a({x=2, y = 3}) end
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(tbl: { x: number, y: number }): number end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_typeof_returns")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local t = { a = 1, b = 2 }
|
||||
|
||||
local function foo(a: () -> typeof(t))
|
||||
a()
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function() end"; // Cannot utter this type.
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_table_literal_args")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: () -> { x: number, y: number }) return {x=2, y = 3} end
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(): { x: number, y: number } end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_typeof_vararg")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local t = { a = 1, b = 2 }
|
||||
|
||||
local function foo(a: (...typeof(t)) -> ())
|
||||
a()
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(...) end"; // Cannot utter this type.
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_generic_type_pack_vararg")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo<A>(a: (...A) -> number, ...: A)
|
||||
return a(...)
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(...): number end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "anonymous_autofilled_generic_on_argument_type_pack_vararg")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauAnonymousAutofilled", true};
|
||||
|
||||
check(R"(
|
||||
local function foo(a: <T...>(...: T...) -> number)
|
||||
return a(4, 5, 6)
|
||||
end
|
||||
|
||||
foo(@1)
|
||||
)");
|
||||
|
||||
const std::optional<std::string> EXPECTED_INSERT = "function(...): number end";
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
|
||||
REQUIRE(ac.entryMap.count(kGeneratedAnonymousFunctionEntryName) == 1);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].kind == Luau::AutocompleteEntryKind::GeneratedFunction);
|
||||
CHECK(ac.entryMap[kGeneratedAnonymousFunctionEntryName].typeCorrect == Luau::TypeCorrectKind::Correct);
|
||||
REQUIRE(ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
CHECK_EQ(EXPECTED_INSERT, *ac.entryMap[kGeneratedAnonymousFunctionEntryName].insertText);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -6978,6 +6978,8 @@ L3: RETURN R0 0
|
|||
|
||||
TEST_CASE("BuiltinArity")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileFixBuiltinArity", true);
|
||||
|
||||
// by default we can't assume that we know parameter/result count for builtins as they can be overridden at runtime
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
return math.abs(unknown())
|
||||
|
@ -7037,6 +7039,21 @@ FASTCALL 34 L0
|
|||
GETIMPORT R0 4 [bit32.extract]
|
||||
CALL R0 -1 1
|
||||
L0: RETURN R0 1
|
||||
)");
|
||||
|
||||
// some builtins are not variadic and have a fixed number of arguments but are not none-safe, meaning that we can't replace calls that may
|
||||
// return none with calls that will return nil
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
return type(unknown())
|
||||
)",
|
||||
0, 2),
|
||||
R"(
|
||||
GETIMPORT R1 1 [unknown]
|
||||
CALL R1 0 -1
|
||||
FASTCALL 40 L0
|
||||
GETIMPORT R0 3 [type]
|
||||
CALL R0 -1 1
|
||||
L0: RETURN R0 1
|
||||
)");
|
||||
|
||||
// importantly, this optimization also helps us get around the multret inlining restriction for builtin wrappers
|
||||
|
|
|
@ -273,6 +273,8 @@ TEST_CASE("Assert")
|
|||
|
||||
TEST_CASE("Basic")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileFixBuiltinArity", true);
|
||||
|
||||
runConformance("basic.lua");
|
||||
}
|
||||
|
||||
|
@ -326,6 +328,8 @@ TEST_CASE("Clear")
|
|||
|
||||
TEST_CASE("Strings")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileFixBuiltinArity", true);
|
||||
|
||||
runConformance("strings.lua");
|
||||
}
|
||||
|
||||
|
@ -1112,6 +1116,34 @@ static bool endsWith(const std::string& str, const std::string& suffix)
|
|||
return suffix == std::string_view(str.c_str() + str.length() - suffix.length(), suffix.length());
|
||||
}
|
||||
|
||||
TEST_CASE("ApiType")
|
||||
{
|
||||
StateRef globalState(luaL_newstate(), lua_close);
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
lua_pushnumber(L, 2);
|
||||
CHECK(strcmp(luaL_typename(L, -1), "number") == 0);
|
||||
CHECK(strcmp(luaL_typename(L, 1), "number") == 0);
|
||||
CHECK(lua_type(L, -1) == LUA_TNUMBER);
|
||||
CHECK(lua_type(L, 1) == LUA_TNUMBER);
|
||||
|
||||
CHECK(strcmp(luaL_typename(L, 2), "no value") == 0);
|
||||
CHECK(lua_type(L, 2) == LUA_TNONE);
|
||||
CHECK(strcmp(lua_typename(L, lua_type(L, 2)), "no value") == 0);
|
||||
|
||||
lua_newuserdata(L, 0);
|
||||
CHECK(strcmp(luaL_typename(L, -1), "userdata") == 0);
|
||||
CHECK(lua_type(L, -1) == LUA_TUSERDATA);
|
||||
|
||||
lua_newtable(L);
|
||||
lua_pushstring(L, "hello");
|
||||
lua_setfield(L, -2, "__type");
|
||||
lua_setmetatable(L, -2);
|
||||
|
||||
CHECK(strcmp(luaL_typename(L, -1), "hello") == 0);
|
||||
CHECK(lua_type(L, -1) == LUA_TUSERDATA);
|
||||
}
|
||||
|
||||
#if !LUA_USE_LONGJMP
|
||||
TEST_CASE("ExceptionObject")
|
||||
{
|
||||
|
|
|
@ -21,7 +21,7 @@ void ConstraintGraphBuilderFixture::generateConstraints(const std::string& code)
|
|||
AstStatBlock* root = parse(code);
|
||||
dfg = std::make_unique<DataFlowGraph>(DataFlowGraphBuilder::build(root, NotNull{&ice}));
|
||||
cgb = std::make_unique<ConstraintGraphBuilder>(mainModule, &arena, NotNull(&moduleResolver), builtinTypes, NotNull(&ice),
|
||||
frontend.globals.globalScope, /*prepareModuleScope*/ nullptr, &logger, NotNull{dfg.get()});
|
||||
frontend.globals.globalScope, /*prepareModuleScope*/ nullptr, &logger, NotNull{dfg.get()}, std::vector<RequireCycle>());
|
||||
cgb->visit(root);
|
||||
rootScope = cgb->rootScope;
|
||||
constraints = Luau::borrowConstraints(cgb->constraints);
|
||||
|
@ -30,7 +30,7 @@ void ConstraintGraphBuilderFixture::generateConstraints(const std::string& code)
|
|||
void ConstraintGraphBuilderFixture::solve(const std::string& code)
|
||||
{
|
||||
generateConstraints(code);
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull{rootScope}, constraints, "MainModule", NotNull(&moduleResolver), {}, &logger};
|
||||
ConstraintSolver cs{NotNull{&normalizer}, NotNull{rootScope}, constraints, "MainModule", NotNull(&moduleResolver), {}, &logger, {}};
|
||||
cs.run();
|
||||
}
|
||||
|
||||
|
|
|
@ -133,6 +133,8 @@ end
|
|||
|
||||
TEST_CASE("ControlFlow")
|
||||
{
|
||||
ScopedFastFlag sff("LuauAssignmentHasCost", true);
|
||||
|
||||
uint64_t model = modelFunction(R"(
|
||||
function test(a)
|
||||
while a < 0 do
|
||||
|
@ -156,8 +158,8 @@ end
|
|||
const bool args1[] = {false};
|
||||
const bool args2[] = {true};
|
||||
|
||||
CHECK_EQ(82, Luau::Compile::computeCost(model, args1, 1));
|
||||
CHECK_EQ(79, Luau::Compile::computeCost(model, args2, 1));
|
||||
CHECK_EQ(76, Luau::Compile::computeCost(model, args1, 1));
|
||||
CHECK_EQ(73, Luau::Compile::computeCost(model, args2, 1));
|
||||
}
|
||||
|
||||
TEST_CASE("Conditional")
|
||||
|
@ -240,4 +242,25 @@ end
|
|||
CHECK_EQ(3, Luau::Compile::computeCost(model, args2, 1));
|
||||
}
|
||||
|
||||
TEST_CASE("MultipleAssignments")
|
||||
{
|
||||
ScopedFastFlag sff("LuauAssignmentHasCost", true);
|
||||
|
||||
uint64_t model = modelFunction(R"(
|
||||
function test(a)
|
||||
local x = 0
|
||||
x = a
|
||||
x = a + 1
|
||||
x, x, x = a
|
||||
x = a, a, a
|
||||
end
|
||||
)");
|
||||
|
||||
const bool args1[] = {false};
|
||||
const bool args2[] = {true};
|
||||
|
||||
CHECK_EQ(8, Luau::Compile::computeCost(model, args1, 1));
|
||||
CHECK_EQ(7, Luau::Compile::computeCost(model, args2, 1));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -176,7 +176,7 @@ AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& pars
|
|||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
ModulePtr module = Luau::check(*sourceModule, {}, builtinTypes, NotNull{&ice}, NotNull{&moduleResolver}, NotNull{&fileResolver},
|
||||
frontend.globals.globalScope, /*prepareModuleScope*/ nullptr, frontend.options);
|
||||
frontend.globals.globalScope, /*prepareModuleScope*/ nullptr, frontend.options, {});
|
||||
|
||||
Luau::lint(sourceModule->root, *sourceModule->names, frontend.globals.globalScope, module.get(), sourceModule->hotcomments, {});
|
||||
}
|
||||
|
@ -415,6 +415,17 @@ TypeId Fixture::requireTypeAlias(const std::string& name)
|
|||
return *ty;
|
||||
}
|
||||
|
||||
TypeId Fixture::requireExportedType(const ModuleName& moduleName, const std::string& name)
|
||||
{
|
||||
ModulePtr module = frontend.moduleResolver.getModule(moduleName);
|
||||
REQUIRE(module);
|
||||
|
||||
auto it = module->exportedTypeBindings.find(name);
|
||||
REQUIRE(it != module->exportedTypeBindings.end());
|
||||
|
||||
return it->second.type;
|
||||
}
|
||||
|
||||
std::string Fixture::decorateWithTypes(const std::string& code)
|
||||
{
|
||||
fileResolver.source[mainModuleName] = code;
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "Luau/Config.h"
|
||||
#include "Luau/Differ.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/IostreamHelpers.h"
|
||||
|
@ -15,6 +17,7 @@
|
|||
#include "IostreamOptional.h"
|
||||
#include "ScopedFlags.h"
|
||||
|
||||
#include "doctest.h"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
|
@ -92,6 +95,7 @@ struct Fixture
|
|||
std::optional<TypeId> lookupType(const std::string& name);
|
||||
std::optional<TypeId> lookupImportedType(const std::string& moduleAlias, const std::string& name);
|
||||
TypeId requireTypeAlias(const std::string& name);
|
||||
TypeId requireExportedType(const ModuleName& moduleName, const std::string& name);
|
||||
|
||||
ScopedFastFlag sff_DebugLuauFreezeArena;
|
||||
|
||||
|
@ -153,6 +157,51 @@ std::optional<TypeId> linearSearchForBinding(Scope* scope, const char* name);
|
|||
void registerHiddenTypes(Frontend* frontend);
|
||||
void createSomeClasses(Frontend* frontend);
|
||||
|
||||
template<typename BaseFixture>
|
||||
struct DifferFixtureGeneric : BaseFixture
|
||||
{
|
||||
void compareNe(TypeId left, TypeId right, const std::string& expectedMessage)
|
||||
{
|
||||
std::string diffMessage;
|
||||
try
|
||||
{
|
||||
DifferResult diffRes = diff(left, right);
|
||||
REQUIRE_MESSAGE(diffRes.diffError.has_value(), "Differ did not report type error, even though types are unequal");
|
||||
diffMessage = diffRes.diffError->toString();
|
||||
}
|
||||
catch (const InternalCompilerError& e)
|
||||
{
|
||||
REQUIRE_MESSAGE(false, ("InternalCompilerError: " + e.message));
|
||||
}
|
||||
CHECK_EQ(expectedMessage, diffMessage);
|
||||
}
|
||||
|
||||
void compareTypesNe(const std::string& leftSymbol, const std::string& rightSymbol, const std::string& expectedMessage)
|
||||
{
|
||||
compareNe(BaseFixture::requireType(leftSymbol), BaseFixture::requireType(rightSymbol), expectedMessage);
|
||||
}
|
||||
|
||||
void compareEq(TypeId left, TypeId right)
|
||||
{
|
||||
try
|
||||
{
|
||||
DifferResult diffRes = diff(left, right);
|
||||
CHECK_MESSAGE(!diffRes.diffError.has_value(), diffRes.diffError->toString());
|
||||
}
|
||||
catch (const InternalCompilerError& e)
|
||||
{
|
||||
REQUIRE_MESSAGE(false, ("InternalCompilerError: " + e.message));
|
||||
}
|
||||
}
|
||||
|
||||
void compareTypesEq(const std::string& leftSymbol, const std::string& rightSymbol)
|
||||
{
|
||||
compareEq(BaseFixture::requireType(leftSymbol), BaseFixture::requireType(rightSymbol));
|
||||
}
|
||||
};
|
||||
using DifferFixture = DifferFixtureGeneric<Fixture>;
|
||||
using DifferFixtureWithBuiltins = DifferFixtureGeneric<BuiltinsFixture>;
|
||||
|
||||
} // namespace Luau
|
||||
|
||||
#define LUAU_REQUIRE_ERRORS(result) \
|
||||
|
|
|
@ -444,6 +444,53 @@ TEST_CASE_FIXTURE(FrontendFixture, "cycle_incremental_type_surface_longer")
|
|||
CHECK_EQ(toString(tyB), "any");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "cycle_incremental_type_surface_exports")
|
||||
{
|
||||
ScopedFastFlag luauFixCyclicModuleExports{"LuauFixCyclicModuleExports", true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
local b = require(game.B)
|
||||
export type atype = { x: b.btype }
|
||||
return {mod_a = 1}
|
||||
)";
|
||||
|
||||
fileResolver.source["game/B"] = R"(
|
||||
export type btype = { x: number }
|
||||
|
||||
local function bf()
|
||||
local a = require(game.A)
|
||||
local bfl : a.atype = nil
|
||||
return {bfl.x}
|
||||
end
|
||||
return {mod_b = 2}
|
||||
)";
|
||||
|
||||
ToStringOptions opts;
|
||||
opts.exhaustive = true;
|
||||
|
||||
CheckResult resultA = frontend.check("game/A");
|
||||
LUAU_REQUIRE_ERRORS(resultA);
|
||||
|
||||
CheckResult resultB = frontend.check("game/B");
|
||||
LUAU_REQUIRE_ERRORS(resultB);
|
||||
|
||||
TypeId tyB = requireExportedType("game/B", "btype");
|
||||
CHECK_EQ(toString(tyB, opts), "{| x: number |}");
|
||||
|
||||
TypeId tyA = requireExportedType("game/A", "atype");
|
||||
CHECK_EQ(toString(tyA, opts), "{| x: any |}");
|
||||
|
||||
frontend.markDirty("game/B");
|
||||
resultB = frontend.check("game/B");
|
||||
LUAU_REQUIRE_ERRORS(resultB);
|
||||
|
||||
tyB = requireExportedType("game/B", "btype");
|
||||
CHECK_EQ(toString(tyB, opts), "{| x: number |}");
|
||||
|
||||
tyA = requireExportedType("game/A", "atype");
|
||||
CHECK_EQ(toString(tyA, opts), "{| x: any |}");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "dont_reparse_clean_file_when_linting")
|
||||
{
|
||||
fileResolver.source["Modules/A"] = R"(
|
||||
|
|
|
@ -1657,6 +1657,8 @@ _ = (math.random() < 0.5 and false) or 42 -- currently ignored
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "WrongComment")
|
||||
{
|
||||
ScopedFastFlag sff("LuauLintNativeComment", true);
|
||||
|
||||
LintResult result = lint(R"(
|
||||
--!strict
|
||||
--!struct
|
||||
|
@ -1666,17 +1668,19 @@ TEST_CASE_FIXTURE(Fixture, "WrongComment")
|
|||
--!nolint UnknownGlobal
|
||||
--! no more lint
|
||||
--!strict here
|
||||
--!native on
|
||||
do end
|
||||
--!nolint
|
||||
)");
|
||||
|
||||
REQUIRE(6 == result.warnings.size());
|
||||
REQUIRE(7 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Unknown comment directive 'struct'; did you mean 'strict'?");
|
||||
CHECK_EQ(result.warnings[1].text, "Unknown comment directive 'nolintGlobal'");
|
||||
CHECK_EQ(result.warnings[2].text, "nolint directive refers to unknown lint rule 'Global'");
|
||||
CHECK_EQ(result.warnings[3].text, "nolint directive refers to unknown lint rule 'KnownGlobal'; did you mean 'UnknownGlobal'?");
|
||||
CHECK_EQ(result.warnings[4].text, "Comment directive with the type checking mode has extra symbols at the end of the line");
|
||||
CHECK_EQ(result.warnings[5].text, "Comment directive is ignored because it is placed after the first non-comment token");
|
||||
CHECK_EQ(result.warnings[5].text, "native directive has extra symbols at the end of the line");
|
||||
CHECK_EQ(result.warnings[6].text, "Comment directive is ignored because it is placed after the first non-comment token");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "WrongCommentMuteSelf")
|
||||
|
|
|
@ -114,6 +114,17 @@ struct SimplifyFixture : Fixture
|
|||
|
||||
TEST_SUITE_BEGIN("Simplify");
|
||||
|
||||
TEST_CASE_FIXTURE(SimplifyFixture, "overload_negation_refinement_is_never")
|
||||
{
|
||||
TypeId f1 = mkFunction(stringTy, numberTy);
|
||||
TypeId f2 = mkFunction(numberTy, stringTy);
|
||||
TypeId intersection = arena->addType(IntersectionType{{f1, f2}});
|
||||
TypeId unionT = arena->addType(UnionType{{errorTy, functionTy}});
|
||||
TypeId negationT = mkNegation(unionT);
|
||||
// The intersection of string -> number & number -> string, ~(error | function)
|
||||
CHECK(neverTy == intersect(intersection, negationT));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SimplifyFixture, "unknown_and_other_tops_and_bottom_types")
|
||||
{
|
||||
CHECK(unknownTy == intersect(unknownTy, unknownTy));
|
||||
|
|
|
@ -59,7 +59,7 @@ TEST_CASE("BenchmarkLevenshteinDistance")
|
|||
auto end = std::chrono::steady_clock::now();
|
||||
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
|
||||
std::cout << "Running levenshtein distance " << count << " times took " << time.count() << "ms" << std::endl;
|
||||
MESSAGE("Running levenshtein distance ", count, " times took ", time.count(), "ms");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
TEST_SUITE_BEGIN("ToString");
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferAnyError");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_returns_any")
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
using namespace Luau;
|
||||
using std::nullopt;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferClasses");
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "call_method_of_a_class")
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferFunctions");
|
||||
|
||||
|
@ -2094,6 +2095,25 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "attempt_to_call_an_intersection_of_tables_wi
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_packs_are_not_variadic")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function apply<a, b..., c...>(f: (a, b...) -> c..., x: a)
|
||||
return f(x)
|
||||
end
|
||||
|
||||
local function add(x: number, y: number)
|
||||
return x + y
|
||||
end
|
||||
|
||||
apply(add, 5)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_before_num_or_str")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
TEST_SUITE_BEGIN("IntersectionTypes");
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
TEST_SUITE_BEGIN("ProvisionalTests");
|
||||
|
||||
// These tests check for behavior that differs from the final behavior we'd
|
||||
|
@ -502,6 +504,9 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
|
|||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||
Unifier u{NotNull{&normalizer}, NotNull{scope.get()}, Location{}, Variance::Covariant};
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
u.enableNewSolver();
|
||||
|
||||
u.tryUnify(option1, option2);
|
||||
|
||||
CHECK(!u.failure);
|
||||
|
@ -565,7 +570,7 @@ return wrapStrictTable(Constants, "Constants")
|
|||
std::optional<TypeId> result = first(m->returnType);
|
||||
REQUIRE(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("(any & ~table)?", toString(*result));
|
||||
CHECK_EQ("(any & ~(*error-type* | table))?", toString(*result));
|
||||
else
|
||||
CHECK_MESSAGE(get<AnyType>(*result), *result);
|
||||
}
|
||||
|
@ -905,6 +910,9 @@ TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together")
|
|||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||
Unifier u{NotNull{&normalizer}, NotNull{scope.get()}, Location{}, Variance::Covariant};
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
u.enableNewSolver();
|
||||
|
||||
u.tryUnify(option1, option2);
|
||||
|
||||
CHECK(!u.failure);
|
||||
|
|
|
@ -1820,4 +1820,46 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refinements_should_preserve_error_suppressio
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "many_refinements_on_val")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function is_nan(val: any): boolean
|
||||
return type(val) == "number" and val ~= val
|
||||
end
|
||||
|
||||
local function is_js_boolean(val: any): boolean
|
||||
return not not val and val ~= 0 and val ~= "" and not is_nan(val)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("(any) -> boolean", toString(requireType("is_nan")));
|
||||
CHECK_EQ("(any) -> boolean", toString(requireType("is_js_boolean")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table")
|
||||
{
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
// this test is DCR-only as an instance of DCR fixing a bug in the old solver
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a : unknown = nil
|
||||
|
||||
local idx, val
|
||||
|
||||
if typeof(a) == "table" then
|
||||
for i, v in a do
|
||||
idx = i
|
||||
val = v
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("unknown", toString(requireType("idx")));
|
||||
CHECK_EQ("unknown", toString(requireType("val")));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
TEST_SUITE_BEGIN("TypeSingletons");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "function_args_infer_singletons")
|
||||
|
|
|
@ -132,6 +132,24 @@ TEST_CASE_FIXTURE(Fixture, "cannot_change_type_of_table_prop")
|
|||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "report_sensible_error_when_adding_a_value_to_a_nonexistent_prop")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t = {}
|
||||
t.foo[1] = 'one'
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
INFO(result.errors[0]);
|
||||
|
||||
UnknownProperty* err = get<UnknownProperty>(result.errors[0]);
|
||||
REQUIRE(err);
|
||||
|
||||
CHECK("t" == toString(err->table));
|
||||
CHECK("foo" == err->key);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "function_calls_can_produce_tables")
|
||||
{
|
||||
CheckResult result = check("function get_table() return {prop=999} end get_table().prop = 0");
|
||||
|
@ -439,8 +457,6 @@ TEST_CASE_FIXTURE(Fixture, "table_param_row_polymorphism_2")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
for (const auto& e : result.errors)
|
||||
std::cout << "Error: " << e << std::endl;
|
||||
|
||||
TypeId qType = requireType("q");
|
||||
const TableType* qTable = get<TableType>(qType);
|
||||
|
@ -3642,4 +3658,75 @@ end
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "certain_properties_of_table_literal_arguments_can_be_covariant")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f(a: {[string]: string | {any} | nil })
|
||||
return a
|
||||
end
|
||||
|
||||
local x = f({
|
||||
title = "Feature.VirtualEvents.EnableNotificationsModalTitle",
|
||||
body = "Feature.VirtualEvents.EnableNotificationsModalBody",
|
||||
notNow = "Feature.VirtualEvents.NotNowButton",
|
||||
getNotified = "Feature.VirtualEvents.GetNotifiedButton",
|
||||
})
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "subproperties_can_also_be_covariantly_tested")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type T = {
|
||||
[string]: {[string]: (string | number)?}
|
||||
}
|
||||
|
||||
function f(t: T)
|
||||
return t
|
||||
end
|
||||
|
||||
local x = f({
|
||||
subprop={x="hello"}
|
||||
})
|
||||
|
||||
local y = f({
|
||||
subprop={x=41}
|
||||
})
|
||||
|
||||
local z = f({
|
||||
subprop={}
|
||||
})
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cyclic_shifted_tables")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
end
|
||||
|
||||
-- Remove name from cyclic table
|
||||
local foo = id({})
|
||||
foo.foo = id({})
|
||||
foo.foo.foo = id({})
|
||||
foo.foo.foo.foo = id({})
|
||||
foo.foo.foo.foo.foo = foo
|
||||
|
||||
local almostFoo = id({})
|
||||
almostFoo.foo = id({})
|
||||
almostFoo.foo.foo = id({})
|
||||
almostFoo.foo.foo.foo = id({})
|
||||
almostFoo.foo.foo.foo.foo = almostFoo
|
||||
-- Shift
|
||||
almostFoo = almostFoo.foo.foo
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -224,7 +224,7 @@ TEST_CASE_FIXTURE(Fixture, "crazy_complexity")
|
|||
A:A():A():A():A():A():A():A():A():A():A():A()
|
||||
)");
|
||||
|
||||
std::cout << "OK! Allocated " << typeChecker.types.size() << " types" << std::endl;
|
||||
MESSAGE("OK! Allocated ", typeChecker.types.size(), " types");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -1332,4 +1332,43 @@ TEST_CASE_FIXTURE(Fixture, "handle_self_referential_HasProp_constraints")
|
|||
)");
|
||||
}
|
||||
|
||||
/* We had an issue where we were unifying two type packs
|
||||
*
|
||||
* free-2-0... and (string, free-4-0...)
|
||||
*
|
||||
* The correct thing to do here is to promote everything on the right side to
|
||||
* level 2-0 before binding the left pack to the right. If we fail to do this,
|
||||
* then the code fragment here fails to typecheck because the argument and
|
||||
* return types of C are generalized before we ever get to checking the body of
|
||||
* C.
|
||||
*/
|
||||
TEST_CASE_FIXTURE(Fixture, "promote_tail_type_packs")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
||||
local A: any = nil
|
||||
|
||||
local C
|
||||
local D = A(
|
||||
A({}, {
|
||||
__call = function(a): string
|
||||
local E: string = C(a)
|
||||
return E
|
||||
end
|
||||
}),
|
||||
{
|
||||
F = function(s: typeof(C))
|
||||
end
|
||||
}
|
||||
)
|
||||
|
||||
function C(b: any): string
|
||||
return ''
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -345,7 +345,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauTransitiveSubtyping", true},
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
};
|
||||
|
||||
TableType::Props freeProps{
|
||||
|
@ -369,6 +368,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table
|
|||
TypeId target = arena.addType(TableType{TableState::Unsealed, TypeLevel{}});
|
||||
TypeId metatable = arena.addType(MetatableType{target, mt});
|
||||
|
||||
state.enableNewSolver();
|
||||
state.tryUnify(metatable, free);
|
||||
state.log.commit();
|
||||
|
||||
|
@ -439,11 +439,10 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "unifying_two_unions_under_dcr_does_not_creat
|
|||
const TypeId innerType = arena.freshType(nestedScope.get());
|
||||
|
||||
ScopedFastFlag sffs[]{
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
{"LuauAlwaysCommitInferencesOfFunctionCalls", true},
|
||||
};
|
||||
|
||||
state.enableScopeTests();
|
||||
state.enableNewSolver();
|
||||
|
||||
SUBCASE("equal_scopes")
|
||||
{
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
TEST_SUITE_BEGIN("TypePackTests");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_multi_return")
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
TEST_SUITE_BEGIN("UnionTypes");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "return_types_can_be_disjoint")
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferUnknownNever");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "string_subtype_and_unknown_supertype")
|
||||
|
|
|
@ -944,6 +944,11 @@ end)(true) == 5050)
|
|||
assert(pcall(typeof) == false)
|
||||
assert(pcall(type) == false)
|
||||
|
||||
function nothing() end
|
||||
|
||||
assert(pcall(function() return typeof(nothing()) end) == false)
|
||||
assert(pcall(function() return type(nothing()) end) == false)
|
||||
|
||||
-- typeof == type in absence of custom userdata
|
||||
assert(concat(typeof(5), typeof(nil), typeof({}), typeof(newproxy())) == "number,nil,table,userdata")
|
||||
|
||||
|
|
|
@ -235,6 +235,12 @@ assert(flag);
|
|||
|
||||
assert(select(2, pcall(math.random, 1, 2, 3)):match("wrong number of arguments"))
|
||||
|
||||
-- argument count
|
||||
function nothing() end
|
||||
|
||||
assert(pcall(math.abs) == false)
|
||||
assert(pcall(function() return math.abs(nothing()) end) == false)
|
||||
|
||||
-- min/max
|
||||
assert(math.min(1) == 1)
|
||||
assert(math.min(1, 2) == 1)
|
||||
|
@ -249,6 +255,7 @@ assert(math.max(1, -1, 2) == 2)
|
|||
assert(math.noise(0.5) == 0)
|
||||
assert(math.noise(0.5, 0.5) == -0.25)
|
||||
assert(math.noise(0.5, 0.5, -0.5) == 0.125)
|
||||
assert(math.noise(455.7204209769105, 340.80410508750134, 121.80087666537628) == 0.5010709762573242)
|
||||
|
||||
local inf = math.huge * 2
|
||||
local nan = 0 / 0
|
||||
|
|
|
@ -107,6 +107,12 @@ assert(tostring(1234567890123) == '1234567890123')
|
|||
assert(#tostring('\0') == 1)
|
||||
assert(tostring(true) == "true")
|
||||
assert(tostring(false) == "false")
|
||||
|
||||
function nothing() end
|
||||
|
||||
assert(pcall(tostring) == false)
|
||||
assert(pcall(function() return tostring(nothing()) end) == false)
|
||||
|
||||
print('+')
|
||||
|
||||
x = '"ílo"\n\\'
|
||||
|
|
|
@ -201,7 +201,7 @@ static FValueResult<bool> parseFFlag(std::string_view view)
|
|||
auto [name, value] = parseFValueHelper(view);
|
||||
bool state = value ? *value == "true" : true;
|
||||
if (value && value != "true" && value != "false")
|
||||
std::cerr << "Ignored '" << name << "' because '" << *value << "' is not a valid FFlag state." << std::endl;
|
||||
fprintf(stderr, "Ignored '%s' because '%s' is not a valid flag state\n", name.c_str(), value->c_str());
|
||||
|
||||
return {name, state};
|
||||
}
|
||||
|
@ -264,9 +264,7 @@ int main(int argc, char** argv)
|
|||
if (skipFastFlag(flag->name))
|
||||
continue;
|
||||
|
||||
if (flag->dynamic)
|
||||
std::cout << 'D';
|
||||
std::cout << "FFlag" << flag->name << std::endl;
|
||||
printf("%sFFlag%s\n", flag->dynamic ? "D" : "", flag->name);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -286,7 +284,7 @@ int main(int argc, char** argv)
|
|||
if (doctest::parseIntOption(argc, argv, "-O", doctest::option_int, level))
|
||||
{
|
||||
if (level < 0 || level > 2)
|
||||
std::cerr << "Optimization level must be between 0 and 2 inclusive." << std::endl;
|
||||
fprintf(stderr, "Optimization level must be between 0 and 2 inclusive\n");
|
||||
else
|
||||
optimizationLevel = level;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
AstQuery.last_argument_function_call_type
|
||||
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
|
||||
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
|
||||
BuiltinTests.aliased_string_format
|
||||
BuiltinTests.assert_removes_falsy_types
|
||||
BuiltinTests.assert_removes_falsy_types2
|
||||
|
@ -55,7 +57,6 @@ ProvisionalTests.typeguard_inference_incomplete
|
|||
RefinementTest.discriminate_from_truthiness_of_x
|
||||
RefinementTest.not_t_or_some_prop_of_t
|
||||
RefinementTest.refine_a_property_of_some_global
|
||||
RefinementTest.refinements_should_preserve_error_suppression
|
||||
RefinementTest.truthy_constraint_on_properties
|
||||
RefinementTest.type_narrow_to_vector
|
||||
RefinementTest.typeguard_cast_free_table_to_vector
|
||||
|
@ -69,9 +70,6 @@ TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
|
|||
TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
|
||||
TableTests.dont_suggest_exact_match_keys
|
||||
TableTests.error_detailed_metatable_prop
|
||||
TableTests.expected_indexer_from_table_union
|
||||
TableTests.expected_indexer_value_type_extra
|
||||
TableTests.expected_indexer_value_type_extra_2
|
||||
TableTests.explicitly_typed_table
|
||||
TableTests.explicitly_typed_table_with_indexer
|
||||
TableTests.fuzz_table_unify_instantiated_table
|
||||
|
@ -128,6 +126,7 @@ TypeInfer.follow_on_new_types_in_substitution
|
|||
TypeInfer.fuzz_free_table_type_change_during_index_check
|
||||
TypeInfer.infer_assignment_value_types_mutable_lval
|
||||
TypeInfer.no_stack_overflow_from_isoptional
|
||||
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2
|
||||
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
||||
TypeInfer.type_infer_recursion_limit_no_ice
|
||||
TypeInfer.type_infer_recursion_limit_normalizer
|
||||
|
|
Loading…
Reference in a new issue